UNPKG

luxon

Version:
169 lines (151 loc) 4.18 kB
import { parseZoneInfo, isUndefined, ianaRegex, objToLocalTS } from "../impl/util.js"; import Zone from "../zone.js"; const matchingRegex = RegExp(`^${ianaRegex.source}$`); let dtfCache = {}; function makeDTF(zone) { if (!dtfCache[zone]) { dtfCache[zone] = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }); } return dtfCache[zone]; } const typeToPos = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }; function hackyOffset(dtf, date) { const formatted = dtf.format(date).replace(/\u200E/g, ""), parsed = /(\d+)\/(\d+)\/(\d+),? (\d+):(\d+):(\d+)/.exec(formatted), [, fMonth, fDay, fYear, fHour, fMinute, fSecond] = parsed; return [fYear, fMonth, fDay, fHour, fMinute, fSecond]; } function partsOffset(dtf, date) { const formatted = dtf.formatToParts(date), filled = []; for (let i = 0; i < formatted.length; i++) { const { type, value } = formatted[i], pos = typeToPos[type]; if (!isUndefined(pos)) { filled[pos] = parseInt(value, 10); } } return filled; } let ianaZoneCache = {}; /** * A zone identified by an IANA identifier, like America/New_York * @implements {Zone} */ export default class IANAZone extends Zone { /** * @param {string} name - Zone name * @return {IANAZone} */ static create(name) { if (!ianaZoneCache[name]) { ianaZoneCache[name] = new IANAZone(name); } return ianaZoneCache[name]; } /** * Reset local caches. Should only be necessary in testing scenarios. * @return {void} */ static resetCache() { ianaZoneCache = {}; dtfCache = {}; } /** * Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that. * @param {string} s - The string to check validity on * @example IANAZone.isValidSpecifier("America/New_York") //=> true * @example IANAZone.isValidSpecifier("Fantasia/Castle") //=> true * @example IANAZone.isValidSpecifier("Sport~~blorp") //=> false * @return {boolean} */ static isValidSpecifier(s) { return s && s.match(matchingRegex); } /** * Returns whether the provided string identifies a real zone * @param {string} zone - The string to check * @example IANAZone.isValidZone("America/New_York") //=> true * @example IANAZone.isValidZone("Fantasia/Castle") //=> false * @example IANAZone.isValidZone("Sport~~blorp") //=> false * @return {boolean} */ static isValidZone(zone) { try { new Intl.DateTimeFormat("en-US", { timeZone: zone }).format(); return true; } catch (e) { return false; } } // Etc/GMT+8 -> -480 /** @ignore */ static parseGMTOffset(specifier) { if (specifier) { const match = specifier.match(/^Etc\/GMT([+-]\d{1,2})$/i); if (match) { return -60 * parseInt(match[1]); } } return null; } constructor(name) { super(); /** @private **/ this.zoneName = name; /** @private **/ this.valid = IANAZone.isValidZone(name); } /** @override **/ get type() { return "iana"; } /** @override **/ get name() { return this.zoneName; } /** @override **/ get universal() { return false; } /** @override **/ offsetName(ts, { format, locale }) { return parseZoneInfo(ts, format, locale, this.name); } /** @override **/ offset(ts) { const date = new Date(ts), dtf = makeDTF(this.name), [year, month, day, hour, minute, second] = dtf.formatToParts ? partsOffset(dtf, date) : hackyOffset(dtf, date); const asUTC = objToLocalTS({ year, month, day, hour, minute, second, millisecond: 0 }); let asTS = date.valueOf(); asTS -= asTS % 1000; return (asUTC - asTS) / (60 * 1000); } /** @override **/ equals(otherZone) { return otherZone.type === "iana" && otherZone.name === this.name; } /** @override **/ get isValid() { return this.valid; } }