UNPKG

timezonecomplete

Version:

DateTime, TimeZone, Duration and Period library aimed at providing a consistent and complete date-time interface, away from the original JavaScript Date class.

1,207 lines 95 kB
/** * Copyright(c) 2014 ABB Switzerland Ltd. * * Olsen Timezone Database container * * DO NOT USE THIS CLASS DIRECTLY, USE TimeZone */ "use strict"; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TzDatabase = exports.NormalizeOption = exports.Transition = exports.isValidOffsetString = exports.ZoneInfo = exports.RuleType = exports.RuleInfo = exports.AtType = exports.OnType = exports.ToType = void 0; var assert_1 = require("./assert"); var basics_1 = require("./basics"); var basics = require("./basics"); var duration_1 = require("./duration"); var error_1 = require("./error"); var math = require("./math"); /** * Type of rule TO column value */ var ToType; (function (ToType) { /** * Either a year number or "only" */ ToType[ToType["Year"] = 0] = "Year"; /** * "max" */ ToType[ToType["Max"] = 1] = "Max"; })(ToType || (exports.ToType = ToType = {})); /** * Type of rule ON column value */ var OnType; (function (OnType) { /** * Day-of-month number */ OnType[OnType["DayNum"] = 0] = "DayNum"; /** * "lastSun" or "lastWed" etc */ OnType[OnType["LastX"] = 1] = "LastX"; /** * e.g. "Sun>=8" */ OnType[OnType["GreqX"] = 2] = "GreqX"; /** * e.g. "Sun<=8" */ OnType[OnType["LeqX"] = 3] = "LeqX"; })(OnType || (exports.OnType = OnType = {})); var AtType; (function (AtType) { /** * Local time (no DST) */ AtType[AtType["Standard"] = 0] = "Standard"; /** * Wall clock time (local time with DST) */ AtType[AtType["Wall"] = 1] = "Wall"; /** * Utc time */ AtType[AtType["Utc"] = 2] = "Utc"; })(AtType || (exports.AtType = AtType = {})); /** * DO NOT USE THIS CLASS DIRECTLY, USE TimeZone * * See http://www.cstdbill.com/tzdb/tz-how-to.html */ var RuleInfo = /** @class */ (function () { /** * Constructor * @param from * @param toType * @param toYear * @param type * @param inMonth * @param onType * @param onDay * @param onWeekDay * @param atHour * @param atMinute * @param atSecond * @param atType * @param save * @param letter * @throws nothing */ function RuleInfo( /** * FROM column year number. */ from, /** * TO column type: Year for year numbers and "only" values, Max for "max" value. */ toType, /** * If TO column is a year, the year number. If TO column is "only", the FROM year. */ toYear, /** * TYPE column, not used so far */ type, /** * IN column month number 1-12 */ inMonth, /** * ON column type */ onType, /** * If onType is DayNum, the day number */ onDay, /** * If onType is not DayNum, the weekday */ onWeekDay, /** * AT column hour */ atHour, /** * AT column minute */ atMinute, /** * AT column second */ atSecond, /** * AT column type */ atType, /** * DST offset from local standard time (NOT from UTC!) */ save, /** * Character to insert in %s for time zone abbreviation * Note if TZ database indicates "-" this is the empty string */ letter) { this.from = from; this.toType = toType; this.toYear = toYear; this.type = type; this.inMonth = inMonth; this.onType = onType; this.onDay = onDay; this.onWeekDay = onWeekDay; this.atHour = atHour; this.atMinute = atMinute; this.atSecond = atSecond; this.atType = atType; this.save = save; this.letter = letter; if (this.save) { this.save = this.save.convert(basics_1.TimeUnit.Hour); } } /** * Returns true iff this rule is applicable in the year * @throws nothing */ RuleInfo.prototype.applicable = function (year) { if (year < this.from) { return false; } switch (this.toType) { case ToType.Max: return true; case ToType.Year: return (year <= this.toYear); } }; /** * Sort comparison * @return (first effective date is less than other's first effective date) * @throws timezonecomplete.InvalidTimeZoneData if this rule depends on a weekday and the weekday in question doesn't exist */ RuleInfo.prototype.effectiveLess = function (other) { if (this.from < other.from) { return true; } if (this.from > other.from) { return false; } if (this.inMonth < other.inMonth) { return true; } if (this.inMonth > other.inMonth) { return false; } if (this.effectiveDate(this.from) < other.effectiveDate(this.from)) { return true; } return false; }; /** * Sort comparison * @return (first effective date is equal to other's first effective date) * @throws timezonecomplete.InvalidTimeZoneData for invalid internal structure of the database */ RuleInfo.prototype.effectiveEqual = function (other) { if (this.from !== other.from) { return false; } if (this.inMonth !== other.inMonth) { return false; } if (!this.effectiveDate(this.from).equals(other.effectiveDate(this.from))) { return false; } return true; }; /** * Returns the year-relative date that the rule takes effect. Depending on the rule this can be a UTC time, a wall clock time, or a * time in standard offset (i.e. you still need to compensate for this.atType) * @throws timezonecomplete.NotApplicable if this rule is not applicable in the given year */ RuleInfo.prototype.effectiveDate = function (year) { (0, assert_1.default)(this.applicable(year), "timezonecomplete.NotApplicable", "Rule is not applicable in %d", year); // year and month are given var y = year; var m = this.inMonth; var d = 0; // calculate day switch (this.onType) { case OnType.DayNum: { d = this.onDay; } break; case OnType.GreqX: { try { d = basics.weekDayOnOrAfter(y, m, this.onDay, this.onWeekDay); } catch (e) { if ((0, error_1.errorIs)(e, "NotFound")) { // Apr Sun>=27 actually means any sunday after April 27, i.e. it does not have to be in April. Try next month. if (m + 1 <= 12) { m = m + 1; } else { m = 1; y = y + 1; } d = basics.firstWeekDayOfMonth(y, m, this.onWeekDay); } } } break; case OnType.LeqX: { try { d = basics.weekDayOnOrBefore(y, m, this.onDay, this.onWeekDay); } catch (e) { if ((0, error_1.errorIs)(e, "NotFound")) { if (m > 1) { m = m - 1; } else { m = 12; y = y - 1; } d = basics.lastWeekDayOfMonth(y, m, this.onWeekDay); } } } break; case OnType.LastX: { d = basics.lastWeekDayOfMonth(y, m, this.onWeekDay); } break; } return basics_1.TimeStruct.fromComponents(y, m, d, this.atHour, this.atMinute, this.atSecond); }; /** * Effective date in UTC in the given year, in a specific time zone * @param year * @param standardOffset the standard offset from UT of the time zone * @param dstOffset the DST offset before the rule */ RuleInfo.prototype.effectiveDateUtc = function (year, standardOffset, dstOffset) { var d = this.effectiveDate(year); switch (this.atType) { case AtType.Utc: return d; case AtType.Standard: { // transition time is in zone local time without DST var millis = d.unixMillis; millis -= standardOffset.milliseconds(); return new basics_1.TimeStruct(millis); } case AtType.Wall: { // transition time is in zone local time with DST var millis = d.unixMillis; millis -= standardOffset.milliseconds(); if (dstOffset) { millis -= dstOffset.milliseconds(); } return new basics_1.TimeStruct(millis); } } }; return RuleInfo; }()); exports.RuleInfo = RuleInfo; /** * Type of reference from zone to rule */ var RuleType; (function (RuleType) { /** * No rule applies */ RuleType[RuleType["None"] = 0] = "None"; /** * Fixed given offset */ RuleType[RuleType["Offset"] = 1] = "Offset"; /** * Reference to a named set of rules */ RuleType[RuleType["RuleName"] = 2] = "RuleName"; })(RuleType || (exports.RuleType = RuleType = {})); /** * DO NOT USE THIS CLASS DIRECTLY, USE TimeZone * * See http://www.cstdbill.com/tzdb/tz-how-to.html * First, and somewhat trivially, whereas Rules are considered to contain one or more records, a Zone is considered to * be a single record with zero or more continuation lines. Thus, the keyword, “Zone,” and the zone name are not repeated. * The last line is the one without anything in the [UNTIL] column. * Second, and more fundamentally, each line of a Zone represents a steady state, not a transition between states. * The state exists from the date and time in the previous line’s [UNTIL] column up to the date and time in the current line’s * [UNTIL] column. In other words, the date and time in the [UNTIL] column is the instant that separates this state from the next. * Where that would be ambiguous because we’re setting our clocks back, the [UNTIL] column specifies the first occurrence of the instant. * The state specified by the last line, the one without anything in the [UNTIL] column, continues to the present. * The first line typically specifies the mean solar time observed before the introduction of standard time. Since there’s no line before * that, it has no beginning. 8-) For some places near the International Date Line, the first two lines will show solar times differing by * 24 hours; this corresponds to a movement of the Date Line. For example: * # Zone NAME GMTOFF RULES FORMAT [UNTIL] * Zone America/Juneau 15:02:19 - LMT 1867 Oct 18 * -8:57:41 - LMT ... * When Alaska was purchased from Russia in 1867, the Date Line moved from the Alaska/Canada border to the Bering Strait; and the time in * Alaska was then 24 hours earlier than it had been. <aside>(6 October in the Julian calendar, which Russia was still using then for * religious reasons, was followed by a second instance of the same day with a different name, 18 October in the Gregorian calendar. * Isn’t civil time wonderful? 8-))</aside> * The abbreviation, “LMT,” stands for “local mean time,” which is an invention of the tz database and was probably never actually * used during the period. Furthermore, the value is almost certainly wrong except in the archetypal place after which the zone is named. * (The tz database usually doesn’t provide a separate Zone record for places where nothing significant happened after 1970.) */ var ZoneInfo = /** @class */ (function () { /** * Constructor * @param gmtoff * @param ruleType * @param ruleOffset * @param ruleName * @param format * @param until * @throws nothing */ function ZoneInfo( /** * GMT offset in fractional minutes, POSITIVE to UTC (note JavaScript.Date gives offsets * contrary to what you might expect). E.g. Europe/Amsterdam has +60 minutes in this field because * it is one hour ahead of UTC */ gmtoff, /** * The RULES column tells us whether daylight saving time is being observed: * A hyphen, a kind of null value, means that we have not set our clocks ahead of standard time. * An amount of time (usually but not necessarily “1:00” meaning one hour) means that we have set our clocks ahead by that amount. * Some alphabetic string means that we might have set our clocks ahead; and we need to check the rule * the name of which is the given alphabetic string. */ ruleType, /** * If the rule column is an offset, this is the offset */ ruleOffset, /** * If the rule column is a rule name, this is the rule name */ ruleName, /** * The FORMAT column specifies the usual abbreviation of the time zone name. It can have one of four forms: * the string, “zzz,” which is a kind of null value (don’t ask) * a single alphabetic string other than “zzz,” in which case that’s the abbreviation * a pair of strings separated by a slash (‘/’), in which case the first string is the abbreviation * for the standard time name and the second string is the abbreviation for the daylight saving time name * a string containing “%s,” in which case the “%s” will be replaced by the text in the appropriate Rule’s LETTER column */ format, /** * Until timestamp in unix utc millis. The zone info is valid up to * and excluding this timestamp. * Note this value can be undefined (for the first rule) */ until) { this.gmtoff = gmtoff; this.ruleType = ruleType; this.ruleOffset = ruleOffset; this.ruleName = ruleName; this.format = format; this.until = until; if (this.ruleOffset) { this.ruleOffset = this.ruleOffset.convert(basics.TimeUnit.Hour); } } return ZoneInfo; }()); exports.ZoneInfo = ZoneInfo; var TzMonthNames; (function (TzMonthNames) { TzMonthNames[TzMonthNames["Jan"] = 1] = "Jan"; TzMonthNames[TzMonthNames["Feb"] = 2] = "Feb"; TzMonthNames[TzMonthNames["Mar"] = 3] = "Mar"; TzMonthNames[TzMonthNames["Apr"] = 4] = "Apr"; TzMonthNames[TzMonthNames["May"] = 5] = "May"; TzMonthNames[TzMonthNames["Jun"] = 6] = "Jun"; TzMonthNames[TzMonthNames["Jul"] = 7] = "Jul"; TzMonthNames[TzMonthNames["Aug"] = 8] = "Aug"; TzMonthNames[TzMonthNames["Sep"] = 9] = "Sep"; TzMonthNames[TzMonthNames["Oct"] = 10] = "Oct"; TzMonthNames[TzMonthNames["Nov"] = 11] = "Nov"; TzMonthNames[TzMonthNames["Dec"] = 12] = "Dec"; })(TzMonthNames || (TzMonthNames = {})); /** * Turns a month name from the TZ database into a number 1-12 * @param name * @throws timezonecomplete.InvalidTimeZoneData for invalid month name */ function monthNameToNumber(name) { for (var i = 1; i <= 12; ++i) { if (TzMonthNames[i] === name) { return i; } } return (0, error_1.throwError)("InvalidTimeZoneData", "Invalid month name '%s'", name); } var TzDayNames; (function (TzDayNames) { TzDayNames[TzDayNames["Sun"] = 0] = "Sun"; TzDayNames[TzDayNames["Mon"] = 1] = "Mon"; TzDayNames[TzDayNames["Tue"] = 2] = "Tue"; TzDayNames[TzDayNames["Wed"] = 3] = "Wed"; TzDayNames[TzDayNames["Thu"] = 4] = "Thu"; TzDayNames[TzDayNames["Fri"] = 5] = "Fri"; TzDayNames[TzDayNames["Sat"] = 6] = "Sat"; })(TzDayNames || (TzDayNames = {})); /** * Returns true if the given string is a valid offset string i.e. * 1, -1, +1, 01, 1:00, 1:23:25.143 * @throws nothing */ function isValidOffsetString(s) { return /^(\-|\+)?([0-9]+((\:[0-9]+)?(\:[0-9]+(\.[0-9]+)?)?))$/.test(s); } exports.isValidOffsetString = isValidOffsetString; /** * Defines a moment at which the given rule becomes valid */ var Transition = /** @class */ (function () { /** * Constructor * @param at * @param offset * @param letter * @throws nothing */ function Transition( /** * Transition time in UTC millis */ at, /** * New offset (type of offset depends on the function) */ offset, /** * New timzone abbreviation letter */ letter) { this.at = at; this.offset = offset; this.letter = letter; if (this.offset) { this.offset = this.offset.convert(basics.TimeUnit.Hour); } } return Transition; }()); exports.Transition = Transition; /** * Option for TzDatabase#normalizeLocal() */ var NormalizeOption; (function (NormalizeOption) { /** * Normalize non-existing times by ADDING the DST offset */ NormalizeOption[NormalizeOption["Up"] = 0] = "Up"; /** * Normalize non-existing times by SUBTRACTING the DST offset */ NormalizeOption[NormalizeOption["Down"] = 1] = "Down"; })(NormalizeOption || (exports.NormalizeOption = NormalizeOption = {})); /** * This class is a wrapper around time zone data JSON object from the tzdata NPM module. * You usually do not need to use this directly, use TimeZone and DateTime instead. */ var TzDatabase = /** @class */ (function () { /** * Constructor - do not use, this is a singleton class. Use TzDatabase.instance() instead * @throws AlreadyCreated if an instance already exists * @throws timezonecomplete.InvalidTimeZoneData if `data` is empty or invalid */ function TzDatabase(data) { var _this = this; /** * Performance improvement: zone info cache */ this._zoneInfoCache = {}; /** * Performance improvement: rule info cache */ this._ruleInfoCache = {}; /** * pre-calculated transitions per zone */ this._zoneTransitionsCache = new Map(); /** * pre-calculated transitions per ruleset */ this._ruleTransitionsCache = new Map(); (0, assert_1.default)(!TzDatabase._instance, "AlreadyCreated", "You should not create an instance of the TzDatabase class yourself. Use TzDatabase.instance()"); (0, assert_1.default)(data.length > 0, "InvalidTimeZoneData", "Timezonecomplete needs time zone data. You need to install one of the tzdata NPM modules before using timezonecomplete."); if (data.length === 1) { this._data = data[0]; } else { this._data = { zones: {}, rules: {} }; data.forEach(function (d) { if (d && d.rules && d.zones) { for (var _i = 0, _a = Object.keys(d.rules); _i < _a.length; _i++) { var key = _a[_i]; _this._data.rules[key] = d.rules[key]; } for (var _b = 0, _c = Object.keys(d.zones); _b < _c.length; _b++) { var key = _c[_b]; _this._data.zones[key] = d.zones[key]; } } }); } this._minmax = validateData(this._data); } /** * (re-) initialize timezonecomplete with time zone data * * @param data TZ data as JSON object (from one of the tzdata NPM modules). * If not given, Timezonecomplete will search for installed modules. * @throws timezonecomplete.InvalidTimeZoneData if `data` or the global time zone data is invalid */ TzDatabase.init = function (data) { TzDatabase._instance = undefined; // needed for assert in constructor if (data) { TzDatabase._instance = new TzDatabase(Array.isArray(data) ? data : [data]); } else { var data_1 = []; // try to find TZ data in global variables var g = void 0; if (typeof window !== "undefined") { g = window; } else if (typeof global !== "undefined") { g = global; } else if (typeof self !== "undefined") { g = self; } else { g = {}; } if (g) { for (var _i = 0, _a = Object.keys(g); _i < _a.length; _i++) { var key = _a[_i]; if (key.startsWith("tzdata")) { if (typeof g[key] === "object" && g[key].rules && g[key].zones) { data_1.push(g[key]); } } } } // try to find TZ data as installed NPM modules var findNodeModules = function (require) { try { // first try tzdata which contains all data var tzDataName = "tzdata"; var d = require(tzDataName); // use variable to avoid browserify acting up data_1.push(d); } catch (e) { // then try subsets var moduleNames = [ "tzdata-africa", "tzdata-antarctica", "tzdata-asia", "tzdata-australasia", "tzdata-backward", "tzdata-backward-utc", "tzdata-etcetera", "tzdata-europe", "tzdata-northamerica", "tzdata-pacificnew", "tzdata-southamerica", "tzdata-systemv" ]; moduleNames.forEach(function (moduleName) { try { var d = require(moduleName); data_1.push(d); } catch (e) { // nothing } }); } }; if (data_1.length === 0) { if (typeof module === "object" && typeof module.exports === "object") { findNodeModules(require); // need to put require into a function to make webpack happy } } TzDatabase._instance = new TzDatabase(data_1); } }; /** * Single instance of this database * @throws timezonecomplete.InvalidTimeZoneData if the global time zone data is invalid */ TzDatabase.instance = function () { if (!TzDatabase._instance) { TzDatabase.init(); } return TzDatabase._instance; }; /** * Returns a sorted list of all zone names * @throws nothing */ TzDatabase.prototype.zoneNames = function () { if (!this._zoneNames) { this._zoneNames = Object.keys(this._data.zones); this._zoneNames.sort(); } return this._zoneNames; }; /** * Returns true iff the given zone name exists * @param zoneName * @throws nothing */ TzDatabase.prototype.exists = function (zoneName) { return this._data.zones.hasOwnProperty(zoneName); }; /** * Minimum non-zero DST offset (which excludes standard offset) of all rules in the database. * Note that DST offsets need not be whole hours. * * Does return zero if a zoneName is given and there is no DST at all for the zone. * * @param zoneName (optional) if given, the result for the given zone is returned * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.minDstSave = function (zoneName) { try { if (zoneName) { var zoneInfos = this.getZoneInfos(zoneName); var result = void 0; var ruleNames = []; for (var _i = 0, zoneInfos_1 = zoneInfos; _i < zoneInfos_1.length; _i++) { var zoneInfo = zoneInfos_1[_i]; if (zoneInfo.ruleType === RuleType.Offset) { if (!result || result.greaterThan(zoneInfo.ruleOffset)) { if (zoneInfo.ruleOffset.milliseconds() !== 0) { result = zoneInfo.ruleOffset; } } } if (zoneInfo.ruleType === RuleType.RuleName && ruleNames.indexOf(zoneInfo.ruleName) === -1) { ruleNames.push(zoneInfo.ruleName); var temp = this.getRuleInfos(zoneInfo.ruleName); for (var _a = 0, temp_1 = temp; _a < temp_1.length; _a++) { var ruleInfo = temp_1[_a]; if (!result || result.greaterThan(ruleInfo.save)) { if (ruleInfo.save.milliseconds() !== 0) { result = ruleInfo.save; } } } } } if (!result) { result = duration_1.Duration.hours(0); } return result.clone(); } else { return duration_1.Duration.minutes(this._minmax.minDstSave); } } catch (e) { if ((0, error_1.errorIs)(e, ["NotFound.Rule", "Argument.N"])) { e = (0, error_1.error)("InvalidTimeZoneData", e.message); } throw e; } }; /** * Maximum DST offset (which excludes standard offset) of all rules in the database. * Note that DST offsets need not be whole hours. * * Returns 0 if zoneName given and no DST observed. * * @param zoneName (optional) if given, the result for the given zone is returned * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.maxDstSave = function (zoneName) { try { if (zoneName) { var zoneInfos = this.getZoneInfos(zoneName); var result = void 0; var ruleNames = []; for (var _i = 0, zoneInfos_2 = zoneInfos; _i < zoneInfos_2.length; _i++) { var zoneInfo = zoneInfos_2[_i]; if (zoneInfo.ruleType === RuleType.Offset) { if (!result || result.lessThan(zoneInfo.ruleOffset)) { result = zoneInfo.ruleOffset; } } if (zoneInfo.ruleType === RuleType.RuleName && ruleNames.indexOf(zoneInfo.ruleName) === -1) { ruleNames.push(zoneInfo.ruleName); var temp = this.getRuleInfos(zoneInfo.ruleName); for (var _a = 0, temp_2 = temp; _a < temp_2.length; _a++) { var ruleInfo = temp_2[_a]; if (!result || result.lessThan(ruleInfo.save)) { result = ruleInfo.save; } } } } if (!result) { result = duration_1.Duration.hours(0); } return result.clone(); } else { return duration_1.Duration.minutes(this._minmax.maxDstSave); } } catch (e) { if ((0, error_1.errorIs)(e, ["NotFound.Rule", "Argument.N"])) { e = (0, error_1.error)("InvalidTimeZoneData", e.message); } throw e; } }; /** * Checks whether the zone has DST at all * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.hasDst = function (zoneName) { return (this.maxDstSave(zoneName).milliseconds() !== 0); }; TzDatabase.prototype.nextDstChange = function (zoneName, a) { var utcTime = (typeof a === "number" ? new basics_1.TimeStruct(a) : a); var zone = this._getZoneTransitions(zoneName); var iterator = zone.findFirst(); if (iterator && iterator.transition.atUtc > utcTime) { return iterator.transition.atUtc.unixMillis; } while (iterator) { iterator = zone.findNext(iterator); if (iterator && iterator.transition.atUtc > utcTime) { return iterator.transition.atUtc.unixMillis; } } return undefined; }; /** * Returns true iff the given zone name eventually links to * "Etc/UTC", "Etc/GMT" or "Etc/UCT" in the TZ database. This is true e.g. for * "UTC", "GMT", "Etc/GMT" etc. * * @param zoneName IANA time zone name. * @throws nothing */ TzDatabase.prototype.zoneIsUtc = function (zoneName) { var actualZoneName = zoneName; var zoneEntries = this._data.zones[zoneName]; // follow links while (typeof (zoneEntries) === "string") { /* istanbul ignore if */ if (!this._data.zones.hasOwnProperty(zoneEntries)) { throw new Error("Zone \"" + zoneEntries + "\" not found (referred to in link from \"" + zoneName + "\" via \"" + actualZoneName + "\""); } actualZoneName = zoneEntries; zoneEntries = this._data.zones[actualZoneName]; } return (actualZoneName === "Etc/UTC" || actualZoneName === "Etc/GMT" || actualZoneName === "Etc/UCT"); }; TzDatabase.prototype.normalizeLocal = function (zoneName, a, opt) { if (opt === void 0) { opt = NormalizeOption.Up; } if (this.hasDst(zoneName)) { var localTime = (typeof a === "number" ? new basics_1.TimeStruct(a) : a); // local times behave like this during DST changes: // forward change (1h): 0 1 3 4 5 // forward change (2h): 0 1 4 5 6 // backward change (1h): 1 2 2 3 4 // backward change (2h): 1 2 1 2 3 // Therefore, binary searching is not possible. // Instead, we should check the DST forward transitions within a window around the local time // get all transitions (note this includes fake transition rules for zone offset changes) var zone = this._getZoneTransitions(zoneName); var transitions = zone.transitionsInYears(localTime.components.year - 1, localTime.components.year + 1); // find the DST forward transitions var prev = duration_1.Duration.hours(0); for (var _i = 0, transitions_1 = transitions; _i < transitions_1.length; _i++) { var transition = transitions_1[_i]; var offset = transition.newState.dstOffset.add(transition.newState.standardOffset); // forward transition? if (offset.greaterThan(prev)) { var localBefore = transition.atUtc.unixMillis + prev.milliseconds(); var localAfter = transition.atUtc.unixMillis + offset.milliseconds(); if (localTime.unixMillis >= localBefore && localTime.unixMillis < localAfter) { var forwardChange = offset.sub(prev); // non-existing time var factor = (opt === NormalizeOption.Up ? 1 : -1); var resultMillis = localTime.unixMillis + factor * forwardChange.milliseconds(); return (typeof a === "number" ? resultMillis : new basics_1.TimeStruct(resultMillis)); } } prev = offset; } // no non-existing time } return (typeof a === "number" ? a : a.clone()); }; /** * Returns the standard time zone offset from UTC, without DST. * Throws if info not found. * @param zoneName IANA time zone name * @param utcTime Timestamp in UTC, either as TimeStruct or as Unix millisecond value * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.standardOffset = function (zoneName, utcTime) { var zoneInfo = this.getZoneInfo(zoneName, utcTime); return zoneInfo.gmtoff.clone(); }; /** * Returns the total time zone offset from UTC, including DST, at * the given UTC timestamp. * Throws if zone info not found. * * @param zoneName IANA time zone name * @param utcTime Timestamp in UTC, either as TimeStruct or as Unix millisecond value * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.totalOffset = function (zoneName, utcTime) { var u = typeof utcTime === "number" ? new basics_1.TimeStruct(utcTime) : utcTime; var zone = this._getZoneTransitions(zoneName); var state = zone.stateAt(u); return state.dstOffset.add(state.standardOffset); }; /** * The time zone rule abbreviation, e.g. CEST for Central European Summer Time. * Note this is dependent on the time, because with time different rules are in effect * and therefore different abbreviations. They also change with DST: e.g. CEST or CET. * * @param zoneName IANA zone name * @param utcTime Timestamp in UTC unix milliseconds * @param dstDependent (default true) set to false for a DST-agnostic abbreviation * @return The abbreviation of the rule that is in effect * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.abbreviation = function (zoneName, utcTime, dstDependent) { if (dstDependent === void 0) { dstDependent = true; } var u = typeof utcTime === "number" ? new basics_1.TimeStruct(utcTime) : utcTime; var zone = this._getZoneTransitions(zoneName); if (dstDependent) { var state = zone.stateAt(u); return state.abbreviation; } else { var lastNonDst = zone.initialState.dstOffset.milliseconds() === 0 ? zone.initialState.abbreviation : ""; var iterator = zone.findFirst(); if ((iterator === null || iterator === void 0 ? void 0 : iterator.transition.newState.dstOffset.milliseconds()) === 0) { lastNonDst = iterator.transition.newState.abbreviation; } while (iterator && iterator.transition.atUtc <= u) { iterator = zone.findNext(iterator); if ((iterator === null || iterator === void 0 ? void 0 : iterator.transition.newState.dstOffset.milliseconds()) === 0) { lastNonDst = iterator.transition.newState.abbreviation; } } return lastNonDst; } }; /** * Returns the standard time zone offset from UTC, excluding DST, at * the given LOCAL timestamp, again excluding DST. * * If the local timestamp exists twice (as can occur very rarely due to zone changes) * then the first occurrence is returned. * * Throws if zone info not found. * * @param zoneName IANA time zone name * @param localTime Timestamp in time zone time * @throws timezonecomplete.NotFound.Zone if zoneName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.standardOffsetLocal = function (zoneName, localTime) { var unixMillis = (typeof localTime === "number" ? localTime : localTime.unixMillis); var zoneInfos = this.getZoneInfos(zoneName); for (var _i = 0, zoneInfos_3 = zoneInfos; _i < zoneInfos_3.length; _i++) { var zoneInfo = zoneInfos_3[_i]; if (zoneInfo.until === undefined || zoneInfo.until + zoneInfo.gmtoff.milliseconds() > unixMillis) { return zoneInfo.gmtoff.clone(); } } /* istanbul ignore if */ /* istanbul ignore next */ if (true) { return (0, error_1.throwError)("InvalidTimeZoneData", "No zone info found"); } }; /** * Returns the total time zone offset from UTC, including DST, at * the given LOCAL timestamp. Non-existing local time is normalized out. * There can be multiple UTC times and therefore multiple offsets for a local time * namely during a backward DST change. This returns the FIRST such offset. * Throws if zone info not found. * * @param zoneName IANA time zone name * @param localTime Timestamp in time zone time * @throws timezonecomplete.NotFound.Zone if zoneName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.totalOffsetLocal = function (zoneName, localTime) { var ts = (typeof localTime === "number" ? new basics_1.TimeStruct(localTime) : localTime); var normalizedTm = this.normalizeLocal(zoneName, ts); /// Note: during offset changes, local time can behave like: // forward change (1h): 0 1 3 4 5 // forward change (2h): 0 1 4 5 6 // backward change (1h): 1 2 2 3 4 // backward change (2h): 1 2 1 2 3 <-- note time going BACKWARD // Therefore binary search does not apply. Linear search through transitions // and return the first offset that matches var zone = this._getZoneTransitions(zoneName); var transitions = zone.transitionsInYears(normalizedTm.components.year - 1, normalizedTm.components.year + 2); var prev; var prevPrev; for (var _i = 0, transitions_2 = transitions; _i < transitions_2.length; _i++) { var transition = transitions_2[_i]; var offset = transition.newState.dstOffset.add(transition.newState.standardOffset); if (transition.atUtc.unixMillis + offset.milliseconds() > normalizedTm.unixMillis) { // found offset: prev.offset applies break; } prevPrev = prev; prev = transition; } /* istanbul ignore else */ if (prev) { // special care during backward change: take first occurrence of local time var prevOffset = prev.newState.dstOffset.add(prev.newState.standardOffset); var prevPrevOffset = prevPrev ? prevPrev.newState.dstOffset.add(prevPrev.newState.standardOffset) : undefined; if (prevPrev && prevPrevOffset !== undefined && prevPrevOffset.greaterThan(prevOffset)) { // backward change var diff = prevPrevOffset.sub(prevOffset); if (normalizedTm.unixMillis >= prev.atUtc.unixMillis + prevOffset.milliseconds() && normalizedTm.unixMillis < prev.atUtc.unixMillis + prevOffset.milliseconds() + diff.milliseconds()) { // within duplicate range return prevPrevOffset.clone(); } else { return prevOffset.clone(); } } else { return prevOffset.clone(); } } else { var state = zone.stateAt(normalizedTm); return state.dstOffset.add(state.standardOffset); } }; /** * DEPRECATED because DST offset depends on the zone too, not just on the ruleset * Returns the DST offset (WITHOUT the standard zone offset) for the given ruleset and the given UTC timestamp * * @deprecated * @param ruleName name of ruleset * @param utcTime UTC timestamp * @param standardOffset Standard offset without DST for the time zone * @throws timezonecomplete.NotFound.Rule if ruleName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.dstOffsetForRule = function (ruleName, utcTime, standardOffset) { var ts = (typeof utcTime === "number" ? new basics_1.TimeStruct(utcTime) : utcTime); // find applicable transition moments var transitions = this.getTransitionsDstOffsets(ruleName, ts.components.year - 1, ts.components.year, standardOffset); // find the last prior to given date var offset; for (var i = transitions.length - 1; i >= 0; i--) { var transition = transitions[i]; if (transition.at <= ts.unixMillis) { offset = transition.offset.clone(); break; } } /* istanbul ignore if */ if (!offset) { // apparently no longer DST, as e.g. for Asia/Tokyo offset = duration_1.Duration.minutes(0); } return offset; }; /** * Returns the time zone letter for the given * ruleset and the given UTC timestamp * * @deprecated * @param ruleName name of ruleset * @param utcTime UTC timestamp as TimeStruct or unix millis * @param standardOffset Standard offset without DST for the time zone * @throws timezonecomplete.NotFound.Rule if ruleName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.letterForRule = function (ruleName, utcTime, standardOffset) { var ts = (typeof utcTime === "number" ? new basics_1.TimeStruct(utcTime) : utcTime); // find applicable transition moments var transitions = this.getTransitionsDstOffsets(ruleName, ts.components.year - 1, ts.components.year, standardOffset); // find the last prior to given date var letter; for (var i = transitions.length - 1; i >= 0; i--) { var transition = transitions[i]; if (transition.at <= ts.unixMillis) { letter = transition.letter; break; } } /* istanbul ignore if */ if (!letter) { // apparently no longer DST, as e.g. for Asia/Tokyo letter = ""; } return letter; }; /** * DEPRECATED because DST offset depends on the zone too, not just on the ruleset * Return a list of all transitions in [fromYear..toYear] sorted by effective date * * @deprecated * @param ruleName Name of the rule set * @param fromYear first year to return transitions for * @param toYear Last year to return transitions for * @param standardOffset Standard offset without DST for the time zone * * @return Transitions, with DST offsets (no standard offset included) * @throws timezonecomplete.Argument.FromYear if fromYear > toYear * @throws timezonecomplete.NotFound.Rule if ruleName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.getTransitionsDstOffsets = function (ruleName, fromYear, toYear, standardOffset) { (0, assert_1.default)(fromYear <= toYear, "Argument.FromYear", "fromYear must be <= toYear"); var rules = this._getRuleTransitions(ruleName); var result = []; var prevDst = (0, duration_1.hours)(0); // wrong, but that's why the function is deprecated var iterator = rules.findFirst(); while (iterator && iterator.transition.at.year <= toYear) { if (iterator.transition.at.year >= fromYear && iterator.transition.at.year <= toYear) { result.push({ at: ruleTransitionUtc(iterator.transition, standardOffset, prevDst).unixMillis, letter: iterator.transition.newState.letter || "", offset: iterator.transition.newState.dstOffset }); } prevDst = iterator.transition.newState.dstOffset; iterator = rules.findNext(iterator); } result.sort(function (a, b) { return a.at - b.at; }); return result; }; /** * Return both zone and rule changes as total (std + dst) offsets. * Adds an initial transition if there is none within the range. * * @param zoneName IANA zone name * @param fromYear First year to include * @param toYear Last year to include * @throws timezonecomplete.Argument.FromYear if fromYear > toYear * @throws timezonecomplete.NotFound.Zone if zoneName not found * @throws timezonecomplete.InvalidTimeZoneData if an error is discovered in the time zone database */ TzDatabase.prototype.getTransitionsTotalOffsets = function (zoneName, fromYear, toYear) { (0, assert_1.default)(fromYear <= toYear, "Argument.FromYear", "fromYear must be <= toYear"); var zone = this._getZoneTransitions(zoneName); var result = []; var startState = zone.stateAt(new basics_1.TimeStruct({ year: fromYear, month: 1, day: 1 })); result.push({ at: new basics_1.TimeStruct({ year: fromYear }).unixMillis, letter: startState.letter, offset: startState.dstOffset.add(startState.standardOffset) }); var iterator = zone.findFirst(); while (iterator && iterator.transition.atUtc.year <= toYear) { if (iterator.transition.atUtc.year >= fromYear) { result.push({ at: iterator.transition.atUtc.unixMillis, letter: iterator.transition.newState.letter || "", offset: iterator.transition.newState.dstOffset.add(iterator.transition.newState.standardOffset) }); } iterator = zone.findNext(iterator); } result.sort(function (a, b) { return a.at - b.at; }); return result; }; /** * Get the zone info for the given UTC timestamp. Throws if not found. * @param zoneName IANA time zone name * @param utcTime UTC time stamp as unix milliseconds or as a TimeStruct * @returns ZoneInfo object. Do not change, we cache this object. * @throws timezonecomplete.NotFound.Zone if zone name not found or a linked zone not found * @throws timezonecomplete.InvalidTimeZoneData if values in the time zone database are invalid */ TzDatabase.prototype.getZoneInfo = function (zoneName, utcTime) { var unixMillis = (typeof utcTime === "number" ? utcTime : utcTime.unixMillis); var zoneInfos = this.getZoneInfos(zoneName); for (var _i = 0, zoneInfos_4 = zoneInfos; _i < zoneInfos_4.length; _i++) { var zoneInfo = zoneInfos_4[_i]; if (zoneInfo.until === undefined || zoneInfo.until > unixMillis) { return zoneInfo; } } return (0, error_1.throwError)("NotFound.Zone", "no zone info found for zone '%s'", zoneName); }; /** * Return the zone records for a given zone name sorted by UNTIL, after * following any links. * * @param zoneName IANA zone name like "Pacific/Efate" * @return Array of zone infos. Do not change, this is a cached value. * @throws timezonecomplete.NotFound.Zone if zone does not exist or a linked zone does not exit */ TzDatabase.prototype.getZoneInfos = function (zoneName) { // FIRST validate zone name before searching cache /* istanbul ignore if */ (0, assert_1.default)(this._data.zones.hasOwnProperty(zoneName), "NotFound.Zone", "zone not found: '%s'", zoneName); // Take from cache if (this._zoneInfoCache.hasOwnProperty(zoneName)) { return this._zoneInfoCache[zoneName]; } var result = []; var actualZoneName = zoneName; var zoneEntries = this._data.zones[zoneName]; // follow links while (typeof (zoneEntries) === "string") { /* istanbul ignore if */ if (!this._data.zones.hasOwnProperty(zoneEntries)) { return (0, error_1.throwError)("NotFound.Zone", "Zone \"" + zoneEntries + "\" not found (referred to in link from \"" + zoneName + "\" via