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.

856 lines 49.3 kB
/** * Copyright(c) 2014 ABB Switzerland Ltd. * * Periodic interval functions */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.timestampOnWeekTimeLessThan = exports.timestampOnWeekTimeGreaterThanOrEqualTo = exports.isPeriod = exports.isValidPeriodJson = exports.Period = exports.periodDstToString = exports.PeriodDst = void 0; var assert_1 = require("./assert"); var basics_1 = require("./basics"); var basics = require("./basics"); var datetime_1 = require("./datetime"); var duration_1 = require("./duration"); var error_1 = require("./error"); var timezone_1 = require("./timezone"); /** * Specifies how the period should repeat across the day * during DST changes. */ var PeriodDst; (function (PeriodDst) { /** * Keep repeating in similar intervals measured in UTC, * unaffected by Daylight Saving Time. * E.g. a repetition of one hour will take one real hour * every time, even in a time zone with DST. * Leap seconds, leap days and month length * differences will still make the intervals different. */ PeriodDst[PeriodDst["RegularIntervals"] = 0] = "RegularIntervals"; /** * Ensure that the time at which the intervals occur stay * at the same place in the day, local time. So e.g. * a period of one day, referenceing at 8:05AM Europe/Amsterdam time * will always reference at 8:05 Europe/Amsterdam. This means that * in UTC time, some intervals will be 25 hours and some * 23 hours during DST changes. * Another example: an hourly interval will be hourly in local time, * skipping an hour in UTC for a DST backward change. */ PeriodDst[PeriodDst["RegularLocalTime"] = 1] = "RegularLocalTime"; /** * End-of-enum marker */ PeriodDst[PeriodDst["MAX"] = 2] = "MAX"; })(PeriodDst || (exports.PeriodDst = PeriodDst = {})); /** * Convert a PeriodDst to a string: "regular intervals" or "regular local time" * @throws timezonecomplete.Argument.P for invalid PeriodDst value */ function periodDstToString(p) { switch (p) { case PeriodDst.RegularIntervals: return "regular intervals"; case PeriodDst.RegularLocalTime: return "regular local time"; /* istanbul ignore next */ default: /* istanbul ignore next */ return (0, error_1.throwError)("Argument.P", "invalid PerioDst value %d", p); } } exports.periodDstToString = periodDstToString; /** * Repeating time period: consists of a reference date and * a time length. This class accounts for leap seconds and leap days. */ var Period = /** @class */ (function () { /** * Constructor implementation. See other constructors for explanation. */ function Period(a, amountOrInterval, unitOrDst, givenDst) { /** * Allow not using instanceof */ this.kind = "Period"; var reference; var interval; var dst = PeriodDst.RegularLocalTime; if ((0, datetime_1.isDateTime)(a)) { reference = a; if (typeof (amountOrInterval) === "object") { interval = amountOrInterval; dst = unitOrDst; } else { (0, assert_1.default)(typeof unitOrDst === "number" && unitOrDst >= 0 && unitOrDst < basics_1.TimeUnit.MAX, "Argument.Unit", "Invalid unit"); interval = new duration_1.Duration(amountOrInterval, unitOrDst); dst = givenDst; } if (typeof dst !== "number") { dst = PeriodDst.RegularLocalTime; } } else { try { reference = new datetime_1.DateTime(a.reference); interval = new duration_1.Duration(a.duration); dst = a.periodDst === "regular" ? PeriodDst.RegularIntervals : PeriodDst.RegularLocalTime; } catch (e) { return (0, error_1.throwError)("Argument.Json", e); } } (0, assert_1.default)(dst >= 0 && dst < PeriodDst.MAX, "Argument.Dst", "Invalid PeriodDst setting"); (0, assert_1.default)(interval.amount() > 0, "Argument.Interval", "Amount must be positive non-zero."); (0, assert_1.default)(Number.isInteger(interval.amount()), "Argument.Interval", "Amount must be a whole number"); this._reference = reference; this._interval = interval; this._dst = dst; this._calcInternalValues(); // regular local time keeping is only supported if we can reset each day // Note we use internal amounts to decide this because actually it is supported if // the input is a multiple of one day. if (this._dstRelevant() && dst === PeriodDst.RegularLocalTime) { switch (this._intInterval.unit()) { case basics_1.TimeUnit.Millisecond: (0, assert_1.default)(this._intInterval.amount() < 86400000, "Argument.Interval.NotImplemented", "When using Hour, Minute or (Milli)Second units, with Regular Local Times, " + "then the amount must be either less than a day or a multiple of the next unit."); break; case basics_1.TimeUnit.Second: (0, assert_1.default)(this._intInterval.amount() < 86400, "Argument.Interval.NotImplemented", "When using Hour, Minute or (Milli)Second units, with Regular Local Times, " + "then the amount must be either less than a day or a multiple of the next unit."); break; case basics_1.TimeUnit.Minute: (0, assert_1.default)(this._intInterval.amount() < 1440, "Argument.Interval.NotImplemented", "When using Hour, Minute or (Milli)Second units, with Regular Local Times, " + "then the amount must be either less than a day or a multiple of the next unit."); break; case basics_1.TimeUnit.Hour: (0, assert_1.default)(this._intInterval.amount() < 24, "Argument.Interval.NotImplemented", "When using Hour, Minute or (Milli)Second units, with Regular Local Times, " + "then the amount must be either less than a day or a multiple of the next unit."); break; } } } /** * Return a fresh copy of the period * @throws nothing */ Period.prototype.clone = function () { return new Period(this._reference, this._interval, this._dst); }; /** * The reference date * @throws nothing */ Period.prototype.reference = function () { return this._reference; }; /** * DEPRECATED: old name for the reference date * @throws nothing */ Period.prototype.start = function () { return this._reference; }; /** * The interval * @throws nothing */ Period.prototype.interval = function () { return this._interval.clone(); }; /** * The amount of units of the interval * @throws nothing */ Period.prototype.amount = function () { return this._interval.amount(); }; /** * The unit of the interval * @throws nothing */ Period.prototype.unit = function () { return this._interval.unit(); }; /** * The dst handling mode * @throws nothing */ Period.prototype.dst = function () { return this._dst; }; /** * The first occurrence of the period greater than * the given date. The given date need not be at a period boundary. * Pre: the fromdate and reference date must either both have timezones or not * @param fromDate: the date after which to return the next date * @return the first date matching the period after fromDate, given in the same zone as the fromDate. * @throws timezonecomplete.UnawareToAwareConversion if not both fromdate and the reference date are both aware or unaware of time zone * @throws timezonecomplete.NotFound.Zone if the UTC time zone doesn't exist in the time zone database */ Period.prototype.findFirst = function (fromDate) { (0, assert_1.default)(!!this._intReference.zone() === !!fromDate.zone(), "UnawareToAwareConversion", "The fromDate and reference date must both be aware or unaware"); var approx; var approx2; var approxMin; var periods; var diff; var newYear; var remainder; var imax; var imin; var imid; var normalFrom = this._normalizeDay(fromDate.toZone(this._intReference.zone())); if (this._intInterval.amount() === 1) { // simple cases: amount equals 1 (eliminates need for searching for referenceing point) if (this._intDst === PeriodDst.RegularIntervals) { // apply to UTC time switch (this._intInterval.unit()) { case basics_1.TimeUnit.Millisecond: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), normalFrom.utcDay(), normalFrom.utcHour(), normalFrom.utcMinute(), normalFrom.utcSecond(), normalFrom.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Second: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), normalFrom.utcDay(), normalFrom.utcHour(), normalFrom.utcMinute(), normalFrom.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Minute: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), normalFrom.utcDay(), normalFrom.utcHour(), normalFrom.utcMinute(), this._intReference.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Hour: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), normalFrom.utcDay(), normalFrom.utcHour(), this._intReference.utcMinute(), this._intReference.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Day: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), normalFrom.utcDay(), this._intReference.utcHour(), this._intReference.utcMinute(), this._intReference.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Month: approx = new datetime_1.DateTime(normalFrom.utcYear(), normalFrom.utcMonth(), this._intReference.utcDay(), this._intReference.utcHour(), this._intReference.utcMinute(), this._intReference.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; case basics_1.TimeUnit.Year: approx = new datetime_1.DateTime(normalFrom.utcYear(), this._intReference.utcMonth(), this._intReference.utcDay(), this._intReference.utcHour(), this._intReference.utcMinute(), this._intReference.utcSecond(), this._intReference.utcMillisecond(), timezone_1.TimeZone.utc()); break; /* istanbul ignore next */ default: /* istanbul ignore if */ /* istanbul ignore next */ if (true) { return (0, error_1.throwError)("Assertion", "Unknown TimeUnit"); } } while (!approx.greaterThan(fromDate)) { approx = approx.add(this._intInterval.amount(), this._intInterval.unit()); } } else { // Try to keep regular local intervals switch (this._intInterval.unit()) { case basics_1.TimeUnit.Millisecond: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), normalFrom.minute(), normalFrom.second(), normalFrom.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Second: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), normalFrom.minute(), normalFrom.second(), this._intReference.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Minute: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), normalFrom.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Hour: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Day: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Month: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), this._intReference.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; case basics_1.TimeUnit.Year: approx = new datetime_1.DateTime(normalFrom.year(), this._intReference.month(), this._intReference.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; /* istanbul ignore next */ default: /* istanbul ignore if */ /* istanbul ignore next */ if (true) { return (0, error_1.throwError)("Assertion", "Unknown TimeUnit"); } } while (!approx.greaterThan(normalFrom)) { approx = approx.addLocal(this._intInterval.amount(), this._intInterval.unit()); } } } else { // Amount is not 1, if (this._intDst === PeriodDst.RegularIntervals) { // apply to UTC time switch (this._intInterval.unit()) { case basics_1.TimeUnit.Millisecond: diff = normalFrom.diff(this._intReference).milliseconds(); periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Second: diff = normalFrom.diff(this._intReference).seconds(); periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Minute: // only 25 leap seconds have ever been added so this should still be OK. diff = normalFrom.diff(this._intReference).minutes(); periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Hour: diff = normalFrom.diff(this._intReference).hours(); periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Day: diff = normalFrom.diff(this._intReference).hours() / 24; periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Month: diff = (normalFrom.utcYear() - this._intReference.utcYear()) * 12 + (normalFrom.utcMonth() - this._intReference.utcMonth()) - 1; periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Year: // The -1 below is because the day-of-month of reference date may be after the day of the fromDate diff = normalFrom.year() - this._intReference.year() - 1; periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.add(periods * this._intInterval.amount(), basics_1.TimeUnit.Year); break; /* istanbul ignore next */ default: /* istanbul ignore if */ /* istanbul ignore next */ if (true) { return (0, error_1.throwError)("Assertion", "Unknown TimeUnit"); } } while (!approx.greaterThan(fromDate)) { approx = approx.add(this._intInterval.amount(), this._intInterval.unit()); } } else { // Try to keep regular local times. If the unit is less than a day, we reference each day anew switch (this._intInterval.unit()) { case basics_1.TimeUnit.Millisecond: if (this._intInterval.amount() < 1000 && (1000 % this._intInterval.amount()) === 0) { // optimization: same millisecond each second, so just take the fromDate // minus one second with the this._intReference milliseconds approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), normalFrom.minute(), normalFrom.second(), this._intReference.millisecond(), this._intReference.zone()) .subLocal(1, basics_1.TimeUnit.Second); } else { // per constructor assert, the seconds are less than a day, so just go the fromDate reference-of-day approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); // since we start counting from this._intReference each day, we have to // take care of the shorter interval at the boundary remainder = Math.floor((86400000) % this._intInterval.amount()); if (approx.greaterThan(normalFrom)) { // todo /* istanbul ignore if */ if (approx.subLocal(remainder, basics_1.TimeUnit.Millisecond).greaterThan(normalFrom)) { // normalFrom lies outside the boundary period before the reference date approx = approx.subLocal(1, basics_1.TimeUnit.Day); } } else { if (approx.addLocal(1, basics_1.TimeUnit.Day).subLocal(remainder, basics_1.TimeUnit.Millisecond).lessEqual(normalFrom)) { // normalFrom lies in the boundary period, move to the next day approx = approx.addLocal(1, basics_1.TimeUnit.Day); } } // optimization: binary search imax = Math.floor((86400000) / this._intInterval.amount()); imin = 0; while (imax >= imin) { // calculate the midpoint for roughly equal partition imid = Math.floor((imin + imax) / 2); approx2 = approx.addLocal(imid * this._intInterval.amount(), basics_1.TimeUnit.Millisecond); approxMin = approx2.subLocal(this._intInterval.amount(), basics_1.TimeUnit.Millisecond); if (approx2.greaterThan(normalFrom) && approxMin.lessEqual(normalFrom)) { approx = approx2; break; } else if (approx2.lessEqual(normalFrom)) { // change min index to search upper subarray imin = imid + 1; } else { // change max index to search lower subarray imax = imid - 1; } } } break; case basics_1.TimeUnit.Second: if (this._intInterval.amount() < 60 && (60 % this._intInterval.amount()) === 0) { // optimization: same second each minute, so just take the fromDate // minus one minute with the this._intReference seconds approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), normalFrom.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()) .subLocal(1, basics_1.TimeUnit.Minute); } else { // per constructor assert, the seconds are less than a day, so just go the fromDate reference-of-day approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); // since we start counting from this._intReference each day, we have to take // are of the shorter interval at the boundary remainder = Math.floor((86400) % this._intInterval.amount()); if (approx.greaterThan(normalFrom)) { if (approx.subLocal(remainder, basics_1.TimeUnit.Second).greaterThan(normalFrom)) { // normalFrom lies outside the boundary period before the reference date approx = approx.subLocal(1, basics_1.TimeUnit.Day); } } else { if (approx.addLocal(1, basics_1.TimeUnit.Day).subLocal(remainder, basics_1.TimeUnit.Second).lessEqual(normalFrom)) { // normalFrom lies in the boundary period, move to the next day approx = approx.addLocal(1, basics_1.TimeUnit.Day); } } // optimization: binary search imax = Math.floor((86400) / this._intInterval.amount()); imin = 0; while (imax >= imin) { // calculate the midpoint for roughly equal partition imid = Math.floor((imin + imax) / 2); approx2 = approx.addLocal(imid * this._intInterval.amount(), basics_1.TimeUnit.Second); approxMin = approx2.subLocal(this._intInterval.amount(), basics_1.TimeUnit.Second); if (approx2.greaterThan(normalFrom) && approxMin.lessEqual(normalFrom)) { approx = approx2; break; } else if (approx2.lessEqual(normalFrom)) { // change min index to search upper subarray imin = imid + 1; } else { // change max index to search lower subarray imax = imid - 1; } } } break; case basics_1.TimeUnit.Minute: if (this._intInterval.amount() < 60 && (60 % this._intInterval.amount()) === 0) { // optimization: same hour this._intReferenceary each time, so just take the fromDate minus one hour // with the this._intReference minutes, seconds approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), normalFrom.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()) .subLocal(1, basics_1.TimeUnit.Hour); } else { // per constructor assert, the seconds fit in a day, so just go the fromDate previous day approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); // since we start counting from this._intReference each day, // we have to take care of the shorter interval at the boundary remainder = Math.floor((24 * 60) % this._intInterval.amount()); if (approx.greaterThan(normalFrom)) { if (approx.subLocal(remainder, basics_1.TimeUnit.Minute).greaterThan(normalFrom)) { // normalFrom lies outside the boundary period before the reference date approx = approx.subLocal(1, basics_1.TimeUnit.Day); } } else { if (approx.addLocal(1, basics_1.TimeUnit.Day).subLocal(remainder, basics_1.TimeUnit.Minute).lessEqual(normalFrom)) { // normalFrom lies in the boundary period, move to the next day approx = approx.addLocal(1, basics_1.TimeUnit.Day); } } } break; case basics_1.TimeUnit.Hour: approx = new datetime_1.DateTime(normalFrom.year(), normalFrom.month(), normalFrom.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); // since we start counting from this._intReference each day, // we have to take care of the shorter interval at the boundary remainder = Math.floor(24 % this._intInterval.amount()); if (approx.greaterThan(normalFrom)) { if (approx.subLocal(remainder, basics_1.TimeUnit.Hour).greaterThan(normalFrom)) { // normalFrom lies outside the boundary period before the reference date approx = approx.subLocal(1, basics_1.TimeUnit.Day); } } else { if (approx.addLocal(1, basics_1.TimeUnit.Day).subLocal(remainder, basics_1.TimeUnit.Hour).lessEqual(normalFrom)) { // normalFrom lies in the boundary period, move to the next day approx = approx.addLocal(1, basics_1.TimeUnit.Day); } } break; case basics_1.TimeUnit.Day: // we don't have leap days, so we can approximate by calculating with UTC timestamps diff = normalFrom.diff(this._intReference).hours() / 24; periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.addLocal(periods * this._intInterval.amount(), this._intInterval.unit()); break; case basics_1.TimeUnit.Month: diff = (normalFrom.year() - this._intReference.year()) * 12 + (normalFrom.month() - this._intReference.month()); periods = Math.floor(diff / this._intInterval.amount()); approx = this._intReference.addLocal(this._interval.multiply(periods)); break; case basics_1.TimeUnit.Year: // The -1 below is because the day-of-month of reference date may be after the day of the fromDate diff = normalFrom.year() - this._intReference.year() - 1; periods = Math.floor(diff / this._intInterval.amount()); newYear = this._intReference.year() + periods * this._intInterval.amount(); approx = new datetime_1.DateTime(newYear, this._intReference.month(), this._intReference.day(), this._intReference.hour(), this._intReference.minute(), this._intReference.second(), this._intReference.millisecond(), this._intReference.zone()); break; /* istanbul ignore next */ default: /* istanbul ignore if */ /* istanbul ignore next */ if (true) { return (0, error_1.throwError)("Assertion", "Unknown TimeUnit"); } } while (!approx.greaterThan(normalFrom)) { approx = approx.addLocal(this._intInterval.amount(), this._intInterval.unit()); } } } return this._correctDay(approx).convert(fromDate.zone()); }; /** * Returns the next timestamp in the period. The given timestamp must * be at a period boundary, otherwise the answer is incorrect. * This function has MUCH better performance than findFirst. * Returns the datetime "count" times away from the given datetime. * @param prev Boundary date. Must have a time zone (any time zone) iff the period reference date has one. * @param count Number of periods to add. Optional. Must be an integer number, may be positive or negative, default 1 * @return (prev + count * period), in the same timezone as prev. * @throws timezonecomplete.Argument.Prev if prev is undefined * @throws timezonecomplete.Argument.Count if count is not an integer number */ Period.prototype.findNext = function (prev, count) { if (count === void 0) { count = 1; } (0, assert_1.default)(!!prev, "Argument.Prev", "Prev must be given"); (0, assert_1.default)(!!this._intReference.zone() === !!prev.zone(), "UnawareToAwareConversion", "The fromDate and referenceDate must both be aware or unaware"); (0, assert_1.default)(Number.isInteger(count), "Argument.Count", "Count must be an integer number"); var normalizedPrev = this._normalizeDay(prev.toZone(this._reference.zone())); if (this._intDst === PeriodDst.RegularIntervals) { return this._correctDay(normalizedPrev.add(this._intInterval.amount() * count, this._intInterval.unit())).convert(prev.zone()); } else { return this._correctDay(normalizedPrev.addLocal(this._intInterval.amount() * count, this._intInterval.unit())).convert(prev.zone()); } }; /** * The last occurrence of the period less than * the given date. The given date need not be at a period boundary. * Pre: the fromdate and the period reference date must either both have timezones or not * @param fromDate: the date before which to return the next date * @return the last date matching the period before fromDate, given * in the same zone as the fromDate. * @throws timezonecomplete.UnawareToAwareConversion if not both `from` and the reference date are both aware or unaware of time zone * @throws timezonecomplete.NotFound.Zone if the UTC time zone doesn't exist in the time zone database */ Period.prototype.findLast = function (from) { var result = this.findPrev(this.findFirst(from)); if (result.equals(from)) { result = this.findPrev(result); } return result; }; /** * Returns the previous timestamp in the period. The given timestamp must * be at a period boundary, otherwise the answer is incorrect. * @param prev Boundary date. Must have a time zone (any time zone) iff the period reference date has one. * @param count Number of periods to subtract. Optional. Must be an integer number, may be negative. * @return (next - count * period), in the same timezone as next. * @throws timezonecomplete.Argument.Next if prev is undefined * @throws timezonecomplete.Argument.Count if count is not an integer number */ Period.prototype.findPrev = function (next, count) { if (count === void 0) { count = 1; } try { return this.findNext(next, -1 * count); } catch (e) { if ((0, error_1.errorIs)(e, "Argument.Prev")) { e = (0, error_1.error)("Argument.Next", e.message); } throw e; } }; /** * Checks whether the given date is on a period boundary * (expensive!) * @throws timezonecomplete.UnawareToAwareConversion if not both `occurrence` and the reference date are both aware or unaware of time zone * @throws timezonecomplete.NotFound.Zone if the UTC time zone doesn't exist in the time zone database */ Period.prototype.isBoundary = function (occurrence) { if (!occurrence) { return false; } (0, assert_1.default)(!!this._intReference.zone() === !!occurrence.zone(), "UnawareToAwareConversion", "The occurrence and referenceDate must both be aware or unaware"); return (this.findFirst(occurrence.sub(duration_1.Duration.milliseconds(1))).equals(occurrence)); }; /** * Returns true iff this period has the same effect as the given one. * i.e. a period of 24 hours is equal to one of 1 day if they have the same UTC reference moment * and same dst. * @throws timezonecomplete.UnawareToAwareConversion if not both `other#reference()` and the reference date are both aware or unaware * of time zone * @throws timezonecomplete.NotFound.Zone if the UTC time zone doesn't exist in the time zone database */ Period.prototype.equals = function (other) { // note we take the non-normalized _reference because this has an influence on the outcome if (!this.isBoundary(other._reference) || !this._intInterval.equals(other._intInterval)) { return false; } var refZone = this._reference.zone(); var otherZone = other._reference.zone(); var thisIsRegular = (this._intDst === PeriodDst.RegularIntervals || !refZone || refZone.isUtc()); var otherIsRegular = (other._intDst === PeriodDst.RegularIntervals || !otherZone || otherZone.isUtc()); if (thisIsRegular && otherIsRegular) { return true; } if (this._intDst === other._intDst && refZone && otherZone && refZone.equals(otherZone)) { return true; } return false; }; /** * Returns true iff this period was constructed with identical arguments to the other one. * @throws nothing */ Period.prototype.identical = function (other) { return (this._reference.identical(other._reference) && this._interval.identical(other._interval) && this._dst === other._dst); }; /** * Returns an ISO duration string e.g. * 2014-01-01T12:00:00.000+01:00/P1H * 2014-01-01T12:00:00.000+01:00/PT1M (one minute) * 2014-01-01T12:00:00.000+01:00/P1M (one month) * @throws nothing */ Period.prototype.toIsoString = function () { return this._reference.toIsoString() + "/" + this._interval.toIsoString(); }; /** * A string representation e.g. * "10 years, referenceing at 2014-03-01T12:00:00 Europe/Amsterdam, keeping regular intervals". * @throws nothing */ Period.prototype.toString = function () { var result = this._interval.toString() + ", referenceing at " + this._reference.toString(); // only add the DST handling if it is relevant if (this._dstRelevant()) { result += ", keeping " + periodDstToString(this._dst); } return result; }; /** * Returns a JSON-compatible representation of this period * @throws nothing */ Period.prototype.toJson = function () { return { reference: this.reference().toString(), duration: this.interval().toString(), periodDst: this.dst() === PeriodDst.RegularIntervals ? "regular" : "local" }; }; /** * Corrects the difference between _reference and _intReference. * @throws nothing */ Period.prototype._correctDay = function (d) { if (this._reference !== this._intReference) { return new datetime_1.DateTime(d.year(), d.month(), Math.min(basics.daysInMonth(d.year(), d.month()), this._reference.day()), d.hour(), d.minute(), d.second(), d.millisecond(), d.zone()); } else { return d; } }; /** * If this._internalUnit in [Month, Year], normalizes the day-of-month * to <= 28. * @return a new date if different, otherwise the exact same object (no clone!) * @throws nothing */ Period.prototype._normalizeDay = function (d, anymonth) { if (anymonth === void 0) { anymonth = true; } if ((this._intInterval.unit() === basics_1.TimeUnit.Month && d.day() > 28) || (this._intInterval.unit() === basics_1.TimeUnit.Year && (d.month() === 2 || anymonth) && d.day() > 28)) { return new datetime_1.DateTime(d.year(), d.month(), 28, d.hour(), d.minute(), d.second(), d.millisecond(), d.zone()); } else { return d; // save on time by not returning a clone } }; /** * Returns true if DST handling is relevant for us. * (i.e. if the reference time zone has DST) * @throws nothing */ Period.prototype._dstRelevant = function () { var zone = this._reference.zone(); return !!(zone && zone.kind() === timezone_1.TimeZoneKind.Proper && zone.hasDst()); }; /** * Normalize the values where possible - not all values * are convertible into one another. Weeks are converted to days. * E.g. more than 60 minutes is transferred to hours, * but seconds cannot be transferred to minutes due to leap seconds. * Weeks are converted back to days. * @throws nothing */ Period.prototype._calcInternalValues = function () { // normalize any above-unit values var intAmount = this._interval.amount(); var intUnit = this._interval.unit(); if (intUnit === basics_1.TimeUnit.Millisecond && intAmount >= 1000 && intAmount % 1000 === 0) { // note this won't work if we account for leap seconds intAmount = intAmount / 1000; intUnit = basics_1.TimeUnit.Second; } if (intUnit === basics_1.TimeUnit.Second && intAmount >= 60 && intAmount % 60 === 0) { // note this won't work if we account for leap seconds intAmount = intAmount / 60; intUnit = basics_1.TimeUnit.Minute; } if (intUnit === basics_1.TimeUnit.Minute && intAmount >= 60 && intAmount % 60 === 0) { intAmount = intAmount / 60; intUnit = basics_1.TimeUnit.Hour; } if (intUnit === basics_1.TimeUnit.Hour && intAmount >= 24 && intAmount % 24 === 0) { intAmount = intAmount / 24; intUnit = basics_1.TimeUnit.Day; } // now remove weeks so we have one less case to worry about if (intUnit === basics_1.TimeUnit.Week) { intAmount = intAmount * 7; intUnit = basics_1.TimeUnit.Day; } if (intUnit === basics_1.TimeUnit.Month && intAmount >= 12 && intAmount % 12 === 0) { intAmount = intAmount / 12; intUnit = basics_1.TimeUnit.Year; } this._intInterval = new duration_1.Duration(intAmount, intUnit); // normalize dst handling if (this._dstRelevant()) { this._intDst = this._dst; } else { this._intDst = PeriodDst.RegularIntervals; } // normalize reference day this._intReference = this._normalizeDay(this._reference, false); }; return Period; }()); exports.Period = Period; /** * Returns true iff the given json value represents a valid period JSON * @param json * @throws nothing */ function isValidPeriodJson(json) { if (typeof json !== "object") { return false; } if (json === null) { return false; } if (typeof json.duration !== "string") { return false; } if (typeof json.periodDst !== "string") { return false; } if (typeof json.reference !== "string") { return false; } if (!["regular", "local"].includes(json.periodDst)) { return false; } try { // tslint:disable-next-line: no-unused-expression new Period(json); } catch (_a) { return false; } return true; } exports.isValidPeriodJson = isValidPeriodJson; /** * Checks if a given object is of type Period. Note that it does not work for sub classes. However, use this to be robust * against different versions of the library in one process instead of instanceof * @param value Value to check * @throws nothing */ function isPeriod(value) { return typeof value === "object" && value !== null && value.kind === "Period"; } exports.isPeriod = isPeriod; /** * Returns the first timestamp >= `opts.reference` that matches the given weekday and time. Uses the time zone and DST settings * of the given reference time. * @param opts * @throws timezonecomplete.Argument.Hour if opts.hour out of range * @throws timezonecomplete.Argument.Minute if opts.minute out of range * @throws timezonecomplete.Argument.Second if opts.second out of range * @throws timezonecomplete.Argument.Millisecond if opts.millisecond out of range * @throws timezonecomplete.Argument.Weekday if opts.weekday out of range */ function timestampOnWeekTimeGreaterThanOrEqualTo(opts) { var _a, _b, _c; // tslint:disable: max-line-length (0, assert_1.default)(opts.hour >= 0 && opts.hour < 24, "Argument.Hour", "opts.hour should be within [0..23]"); (0, assert_1.default)(opts.minute === undefined || (opts.minute >= 0 && opts.minute < 60 && Number.isInteger(opts.minute)), "Argument.Minute", "opts.minute should be within [0..59]"); (0, assert_1.default)(opts.second === undefined || (opts.second >= 0 && opts.second < 60 && Number.isInteger(opts.second)), "Argument.Second", "opts.second should be within [0..59]"); (0, assert_1.default)(opts.millisecond === undefined || (opts.millisecond >= 0 && opts.millisecond < 1000 && Number.isInteger(opts.millisecond)), "Argument.Millisecond", "opts.millisecond should be within [0.999]"); (0, assert_1.default)(opts.weekday >= 0 && opts.weekday < 7, "Argument.Weekday", "opts.weekday should be within [0..6]"); // tslint:enable: max-line-length var midnight = opts.reference.startOfDay(); while (midnight.weekDay() !== opts.weekday) { midnight = midnight.addLocal((0, duration_1.days)(1)); } var dt = new datetime_1.DateTime(midnight.year(), midnight.month(), midnight.day(), opts.hour, (_a = opts.minute) !== null && _a !== void 0 ? _a : 0, (_b = opts.second) !== null && _b !== void 0 ? _b : 0, (_c = opts.millisecond) !== null && _c !== void 0 ? _c : 0, opts.reference.zone()); if (dt < opts.reference) { // we've started out on the correct weekday and the reference timestamp was greater than the given time, need to skip a week return dt.addLocal((0, duration_1.days)(7)); } return dt; } exports.timestampOnWeekTimeGreaterThanOrEqualTo = timestampOnWeekTimeGreaterThanOrEqualTo; /** * Returns the first timestamp < `opts.reference` that matches the given weekday and time. Uses the time zone and DST settings * of the given reference time. * @param opts * @throws timezonecomplete.Argument.Hour if opts.hour out of range * @throws timezonecomplete.Argument.Minute if opts.minute out of range * @throws timezonecomplete.Argument.Second if opts.second out of range * @throws timezonecomplete.Argument.Millisecond if opts.millisecond out of range * @throws timezonecomplete.Argument.Weekday if opts.weekday out of range */ function timestampOnWeekTimeLessThan(opts) { var _a, _b, _c; // tslint:disable: max-line-length (0, assert_1.default)(opts.hour >= 0 && opts.hour < 24, "Argument.Hour", "opts.hour should be within [0..23]"); (0, assert_1.default)(opts.minute === undefined || (opts.minute >= 0 && opts.minute < 60 && Number.isInteger(opts.minute)), "Argument.Minute", "opts.minute should be within [0..59]"); (0, assert_1.default)(opts.second === undefined || (opts.second >= 0 && opts.second < 60 && Number.isInteger(opts.second)), "Argument.Second", "opts.second should be within [0..59]"); (0, assert_1.default)(opts.millisecond === undefined || (opts.millisecond >= 0 && opts.millisecond < 1000 && Number.isInteger(opts.millisecond)), "Argument.Millisecond", "opts.millisecond should be within [0.999]"); (0, assert_1.default)(opts.weekday >= 0 && opts.weekday < 7, "Argument.Weekday", "opts.weekday should be within [0..6]"); // tslint:enable: max-line-length var midnight = opts.reference.startOfDay().addLocal((0, duration_1.days)(1)); while (midnight.weekDay() !== opts.weekday) { midnight = midnight.subLocal((0, duration_1.days)(1)); } var dt = new datetime_1.DateTime(midnight.year(), midnight.month(), midnight.day(), opts.hour, (_a = opts.minute) !== null && _a !== void 0 ? _a : 0, (_b = opts.second) !== null && _b !== void 0 ? _b : 0, (_c = opts.millisecond) !== null && _c !== void 0 ? _c : 0, opts.reference.zone()); if (dt >= opts.reference) { // we've started out on the correct weekday and the reference timestamp was less than the given time, need to skip a week return dt.subLocal((0, duration_1.days)(7)); } return dt; } exports.timestampOnWeekTimeLessThan = timestampOnWeekTimeLessThan; //# sourceMappingURL=period.js.map