UNPKG

@js-joda/core

Version:

a date and time library for javascript

218 lines (196 loc) 7.33 kB
/** * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos * @license BSD-3-Clause (see LICENSE in the root directory of this source tree) */ import { ZoneOffset } from '../../ZoneOffset'; import { ZoneId } from '../../ZoneId'; import { ZoneRegion } from '../../ZoneRegion'; import { ChronoField } from '../../temporal/ChronoField'; import { ZoneRulesProvider } from '../../zone/ZoneRulesProvider'; import { OffsetIdPrinterParser } from './OffsetIdPrinterParser'; /** * Prints or parses a zone ID. * @private */ export class ZoneIdPrinterParser { /** * * @param {TemporalQuery} query * @param {string} description */ constructor(query, description) { this.query = query; this.description = description; } //----------------------------------------------------------------------- /** * * @param {DateTimePrintContext } context * @param {StringBuilder} buf * @returns {boolean} */ print(context, buf) { const zone = context.getValueQuery(this.query); if (zone == null) { return false; } buf.append(zone.id()); return true; } //----------------------------------------------------------------------- /** * This implementation looks for the longest matching string. * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just * Etc/GMC although both are valid. * * This implementation uses a tree to search for valid time-zone names in * the parseText. The top level node of the tree has a length equal to the * length of the shortest time-zone as well as the beginning characters of * all other time-zones. * * @param {DateTimeParseContext} context * @param {String} text * @param {number} position * @return {number} */ parse(context, text, position) { const length = text.length; if (position > length) { return ~position; } if (position === length) { return ~position; } // handle fixed time-zone IDs const nextChar = text.charAt(position); if (nextChar === '+' || nextChar === '-') { const newContext = context.copy(); const endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); if (endPos < 0) { return endPos; } const offset = newContext.getParsed(ChronoField.OFFSET_SECONDS); const zone = ZoneOffset.ofTotalSeconds(offset); context.setParsedZone(zone); return endPos; } else if (length >= position + 2) { const nextNextChar = text.charAt(position + 1); if (context.charEquals(nextChar, 'U') && context.charEquals(nextNextChar, 'T')) { if (length >= position + 3 && context.charEquals(text.charAt(position + 2), 'C')) { return this._parsePrefixedOffset(context, text, position, position + 3); } return this._parsePrefixedOffset(context, text, position, position + 2); } else if (context.charEquals(nextChar, 'G') && length >= position + 3 && context.charEquals(nextNextChar, 'M') && context.charEquals(text.charAt(position + 2), 'T')) { return this._parsePrefixedOffset(context, text, position, position + 3); } } // javascript special case if(text.substr(position, 6) === 'SYSTEM'){ context.setParsedZone(ZoneId.systemDefault()); return position + 6; } // ... if (context.charEquals(nextChar, 'Z')) { context.setParsedZone(ZoneOffset.UTC); return position + 1; } const availableZoneIds = ZoneRulesProvider.getAvailableZoneIds(); if (zoneIdTree.size !== availableZoneIds.length) { zoneIdTree = ZoneIdTree.createTreeMap(availableZoneIds); } const maxParseLength = length - position; let treeMap = zoneIdTree.treeMap; let parsedZoneId = null; let parseLength = 0; while(treeMap != null) { const parsedSubZoneId = text.substr(position, Math.min(treeMap.length, maxParseLength)); treeMap = treeMap.get(parsedSubZoneId); if (treeMap != null && treeMap.isLeaf) { parsedZoneId = parsedSubZoneId; parseLength = treeMap.length; } } if (parsedZoneId != null) { context.setParsedZone(ZoneRegion.ofId(parsedZoneId)); return position + parseLength; } return ~position; } /** * * @param {DateTimeParseContext} context * @param {String} text * @param {number} prefixPos * @param {number} position * @return {number} */ _parsePrefixedOffset(context, text, prefixPos, position) { const prefix = text.substring(prefixPos, position).toUpperCase(); const newContext = context.copy(); if (position < text.length && context.charEquals(text.charAt(position), 'Z')) { context.setParsedZone(ZoneId.ofOffset(prefix, ZoneOffset.UTC)); return position; } const endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); if (endPos < 0) { context.setParsedZone(ZoneId.ofOffset(prefix, ZoneOffset.UTC)); return position; } const offsetSecs = newContext.getParsed(ChronoField.OFFSET_SECONDS); const offset = ZoneOffset.ofTotalSeconds(offsetSecs); context.setParsedZone(ZoneId.ofOffset(prefix, offset)); return endPos; } /** * * @returns {string} */ toString() { return this.description; } } class ZoneIdTree { static createTreeMap(availableZoneIds) { const sortedZoneIds = availableZoneIds.sort((a, b) => a.length - b.length); const treeMap = new ZoneIdTreeMap(sortedZoneIds[0].length, false); for (let i=0; i<sortedZoneIds.length; i++){ treeMap.add(sortedZoneIds[i]); } return new ZoneIdTree(sortedZoneIds.length, treeMap); } constructor(size, treeMap) { this.size = size; this.treeMap = treeMap; } } class ZoneIdTreeMap { constructor(length = 0, isLeaf = false){ this.length = length; this.isLeaf = isLeaf; this._treeMap = {}; } add(zoneId){ const idLength = zoneId.length; if(idLength === this.length) { this._treeMap[zoneId] = new ZoneIdTreeMap(idLength, true); } else if (idLength > this.length) { const subZoneId = zoneId.substr(0, this.length); let subTreeMap = this._treeMap[subZoneId]; if (subTreeMap == null) { subTreeMap = new ZoneIdTreeMap(idLength, false); this._treeMap[subZoneId] = subTreeMap; } subTreeMap.add(zoneId); } } get(zoneId){ return this._treeMap[zoneId]; } } let zoneIdTree = new ZoneIdTree([]);