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
JavaScript
/**
* 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