@silane/datetime
Version:
Date and time library similar to Python's "datetime" package.
1,462 lines (1,433 loc) • 80.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.datetime = {}));
})(this, (function (exports) { 'use strict';
/**
* The base class of the other exceptions in this module.
* It is a subclass of Error.
*/
class DateTimeError extends Error {
/**
* @param {string} message Some error message.
*/
constructor(message) {
super(message);
Object.defineProperty(this, 'name', {
configurable: true,
enumerable: false,
value: this.constructor.name,
writable: true
});
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
/**
* Raised when derived classes should override the method.
*/
class NotImplementedDateTimeError extends DateTimeError {
constructor() {
super("Not implemented.");
}
}
/**
* Raised when an operation or function is applied to an object of
* inappropriate type.
*/
class TypeDateTimeError extends DateTimeError {
constructor() {
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Type error.';
super(message);
}
}
/**
* Raised when an operation or function receives an argument that has the
* right type but an inappropriate value.
*/
class ValueDateTimeError extends DateTimeError {
constructor() {
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Value error.';
super(message);
}
}
/**
* The smallest year number allowed in a date or datetime object.
*/
const MINYEAR = 1;
/**
* The largest year number allowed in a date or datetime object.
*/
const MAXYEAR = 9999;
/** @typedef {globalThis.Date} stdDate */
const stdDate = globalThis.Date;
/**
* "stdDate.UTC" converts years between 0 and 99 to a year in the 20th century.
* Usually it can be avoided just adding setUTCFullYear(year)
* after constructing the "stdDate" instance.
* Buf if the parameters from month to milliseconds are outside of their
* range, year can be updated to accommodate these values.
* In this case, this function must be used.
* @param {number} year
* @param {number} month
* @param {number} day
* @param {number} hour
* @param {number} minute
* @param {number} second
* @param {number} millisecond
*/
function safeStdDateUTC(year, month, day, hour, minute, second, millisecond) {
const d = new stdDate(2000, 0, 1);
d.setUTCFullYear(year, month - 1, day);
d.setUTCHours(hour, minute, second, millisecond);
return d;
}
const daysPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const leapedDaysPerMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const totalDaysPerMonth = function () {
let sum = 0;
const ret = daysPerMonth.map(x => sum += x);
ret.unshift(0);
return ret;
}();
const totalLeapedDaysPerMonth = function () {
let sum = 0;
const ret = leapedDaysPerMonth.map(x => sum += x);
ret.unshift(0);
return ret;
}();
/**
* Try set property value and returns true if successful or false if fails for some reason like object is freezed.
* @param {object} obj
* @param {string | symbol} prop
* @param {unknown} value
* @returns {boolean}
*/
function trySetProperty(obj, prop, value) {
try {
obj[prop] = value;
} catch (e) {
if (e instanceof TypeError) {
return false;
}
throw e;
}
return Object.is(obj[prop], value);
}
/**
* Calculate quotient and remainder.
* @param {number} a - Dividend
* @param {number} b - Divisor
* @returns {[number, number]} Quotient and remainder respectively
*/
function divmod(a, b) {
const quotient = Math.floor(a / b);
return [quotient, a - quotient * b];
}
/**
* Number to "0" padded string with given length.
* @param {number} integer
* @param {number} length
* @returns {string}
*/
function zeroPad(integer, length) {
return integer.toString().padStart(length, '0');
}
/**
* TimeDelta to offset string.
* @param {TimeDelta} timeDelta - Must be between timedelta({hours: -24}) and
* timedelta({hours: 24}) both exclusive.
* @returns {string}
*/
function toOffsetString(timeDelta) {
let offset = timeDelta;
const minus = offset.days < 0;
if (minus) {
offset = neg(offset);
}
const seconds = offset.seconds % 60;
const totalMinutes = Math.floor(offset.seconds / 60);
const minutes = zeroPad(totalMinutes % 60, 2);
const hours = zeroPad(Math.floor(totalMinutes / 60), 2);
let ret = `${minus ? '-' : '+'}${hours}:${minutes}`;
if (offset.microseconds) {
ret += `:${zeroPad(seconds, 2)}.${zeroPad(offset.microseconds, 6)}`;
} else if (seconds) {
ret += `:${zeroPad(seconds, 2)}`;
}
return ret;
}
/**
* Returns if it's leap year.
* @param {number} year
* @returns {boolean}
*/
function isLeapYear(year) {
if (year % 4 !== 0) return false;
if (year % 100 === 0 && year % 400 !== 0) return false;
return true;
}
/**
* DateTime to formatted string.
* @param {DateTime} dt
* @param {string} format
* @returns {string}
*/
function strftime(dt, format) {
const a = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const A = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
const b = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const B = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
let ret = '';
for (let i = 0; i < format.length; ++i) {
if (format[i] !== '%' || i + 1 >= format.length) {
ret += format[i];
continue;
}
let s;
switch (format[i + 1]) {
case 'a':
s = a[dt.weekday()];
break;
case 'A':
s = A[dt.weekday()];
break;
case 'w':
s = ((dt.weekday() + 1) % 7).toString();
break;
case 'd':
s = zeroPad(dt.day, 2);
break;
case 'b':
s = b[dt.month - 1];
break;
case 'B':
s = B[dt.month - 1];
break;
case 'm':
s = zeroPad(dt.month, 2);
break;
case 'y':
s = zeroPad(dt.year % 100, 2);
break;
case 'Y':
s = zeroPad(dt.year, 4);
break;
case 'H':
s = zeroPad(dt.hour, 2);
break;
case 'I':
s = zeroPad(dt.hour % 12, 2);
break;
case 'p':
s = dt.hour < 12 ? 'AM' : 'PM';
break;
case 'M':
s = zeroPad(dt.minute, 2);
break;
case 'S':
s = zeroPad(dt.second, 2);
break;
case 'f':
s = zeroPad(dt.microsecond, 6);
break;
case 'z':
const offset = dt.utcOffset();
if (offset == null) s = '';else s = toOffsetString(offset).replace(':', '');
break;
case 'Z':
const tzName = dt.tzName();
if (tzName == null) s = '';else s = tzName;
break;
case '%':
s = '%';
break;
}
ret += s;
++i;
}
return ret;
}
/** @type {?TimeDelta} */
let timeDeltaMin = null;
/** @type {?TimeDelta} */
let timeDeltaMax = null;
/** @type {?TimeDelta} */
let timeDeltaResolution = null;
/**
* Represents a duration, the difference between two dates or times.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.timedelta.
*/
class TimeDelta {
/**
* @param {Object} duration An object consisting of duration values.
* @param {number} [duration.days]
* @param {number} [duration.seconds]
* @param {number} [duration.microseconds]
* @param {number} [duration.milliseconds]
* @param {number} [duration.minutes]
* @param {number} [duration.hours]
* @param {number} [duration.weeks]
*/
constructor() {
let {
days = 0,
seconds = 0,
microseconds = 0,
milliseconds = 0,
minutes = 0,
hours = 0,
weeks = 0
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
microseconds += milliseconds * 1000;
seconds += minutes * 60;
seconds += hours * 3600;
days += weeks * 7;
let frac;
[days, frac] = divmod(days, 1);
seconds += frac * 3600 * 24;
[seconds, frac] = divmod(seconds, 1);
microseconds += frac * 1000 ** 2;
microseconds = Math.round(microseconds);
let div, mod;
[div, mod] = divmod(microseconds, 1000 ** 2);
microseconds = mod;
seconds += div;
[div, mod] = divmod(seconds, 3600 * 24);
seconds = mod;
days += div;
if (!(-999999999 <= days && days <= 999999999)) {
throw new ValueDateTimeError('Cannot handle duration greater than "TimeDelta.max" or ' + 'lesser than "TimeDelta.min".');
}
/**
* @private
* @readonly
*/
this._days = days;
/**
* @private
* @readonly
*/
this._seconds = seconds;
/**
* @private
* @readonly
*/
this._microseconds = microseconds;
}
/**
* Between -999999999 and 999999999 inclusive.
* @type {number}
*/
get days() {
return this._days;
}
/**
* Between 0 and 86399 inclusive
* @type {number}
*/
get seconds() {
return this._seconds;
}
/**
* Between 0 and 999999 inclusive
* @type {number}
*/
get microseconds() {
return this._microseconds;
}
/**
* Return the total number of seconds contained in the duration.
* @returns {number}
*/
totalSeconds() {
if (this['_totalSeconds'] != null) return this['_totalSeconds'];
const ret = this.days * 3600 * 24 + this.seconds + this.microseconds / 1000000;
trySetProperty(this, '_totalSeconds', ret);
return ret;
}
/**
* Return the human-readable string respresentation.
* @returns {string}
*/
toString() {
if (this['_string'] != null) return this['_string'];
let ret = '';
if (this.days) {
ret += `${this.days} day(s), `;
}
const totalMinutes = Math.floor(this.seconds / 60);
ret += `${Math.floor(totalMinutes / 60)}:${zeroPad(totalMinutes % 60, 2)}:` + `${zeroPad(this.seconds % 60, 2)}`;
if (this.microseconds) {
ret += `.${zeroPad(this.microseconds, 6)}`;
}
trySetProperty(this, '_string', ret);
return ret;
}
/**
* Same as totalSeconds().
* @returns {number}
*/
valueOf() {
return this.totalSeconds();
}
/**
* The most negative timedelta object, new TimeDelta(\{days: -999999999\}).
* @type {!TimeDelta}
*/
static get min() {
if (!timeDeltaMin) {
timeDeltaMin = new TimeDelta({
days: -999999999
});
}
return timeDeltaMin;
}
/**
* The most positive timedelta object, new TimeDelta(\{days: 999999999,
* hours: 23, minutes: 59, seconds: 59, microseconds: 999999\}).
* @type {!TimeDelta}
*/
static get max() {
if (!timeDeltaMax) {
timeDeltaMax = new TimeDelta({
days: 999999999,
hours: 23,
minutes: 59,
seconds: 59,
microseconds: 999999
});
}
return timeDeltaMax;
}
/**
* The smallest possible difference between non-equal timedelta objects,
* new TimeDelta(\{microseconds: 1\}).
* @type {!TimeDelta}
*/
static get resolution() {
if (!timeDeltaResolution) {
timeDeltaResolution = new TimeDelta({
microseconds: 1
});
}
return timeDeltaResolution;
}
}
/** @type {?Date} */
let dateMin = null;
/** @type {?Date} */
let dateMax = null;
/** @type {?TimeDelta} */
let dateResolution = null;
/**
* Represents a date (year, month and day) in an idealized calendar.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.date.
*/
class Date {
/**
* @param {number} year Between MINYEAR and MAXYEAR.
* @param {number} month Between 1 and 12.
* @param {number} day Between 1 and the number of days in the given month
* and year.
*/
constructor(year, month, day) {
if (!(MINYEAR <= year && year <= MAXYEAR)) throw new ValueDateTimeError('"year" must be between "MINYEAR" and "MAXYEAR".');
if (!(1 <= month && month <= 12)) throw new ValueDateTimeError('"month" must be between 1 and 12.');
if (!(1 <= day && day <= (isLeapYear(year) ? leapedDaysPerMonth[month - 1] : daysPerMonth[month - 1]))) throw new ValueDateTimeError('Invalid day for the year and month.');
/**
* @private
* @readonly
*/
this._year = year;
/**
* @private
* @readonly
*/
this._month = month;
/**
* @private
* @readonly
*/
this._day = day;
}
/**
* Between MINYEAR and MAXYEAR.
* @type {number}
*/
get year() {
return this._year;
}
/**
* Between 1 and 12.
* @type {number}
*/
get month() {
return this._month;
}
/**
* Between 1 and the number of days in the given month and year.
* @type {number}
*/
get day() {
return this._day;
}
/**
* Return the Date corresponding to the given standard library Date object.
* @param {!stdDate} d The standard library Date object.
* @param {boolean} utc If true, use getUTC***() instead of get***()
* to construct Date.
* @returns {!Date}
*/
static fromStdDate(d) {
let utc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!utc) return new Date(d.getFullYear(), d.getMonth() + 1, d.getDate());else return new Date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
}
/**
* Return the current local date.
* @returns {!Date}
*/
static today() {
const today = new stdDate();
return Date.fromStdDate(today);
}
/**
* Return the Date corresponding to the proleptic Gregorian ordinal, where
* January 1 of year 1 has ordinal 1.
* @param {number} ordinal The proleptic Gregorian ordinal.
* @returns {!Date}
*/
static fromOrdinal(ordinal) {
let q, r;
let year = 1,
month = 1,
day = 1;
[q, r] = divmod(ordinal - 1, 365 * 303 + 366 * 97);
year += q * 400;
[q, r] = divmod(r, 365 * 76 + 366 * 24);
year += q * 100;
[q, r] = divmod(r, 365 * 3 + 366 * 1);
year += q * 4;
[q, r] = divmod(r, 365);
if (q <= 2) {
// not a leap year
year += q;
for (month = 1; month <= 12 && r >= totalDaysPerMonth[month]; ++month);
day += r - totalDaysPerMonth[month - 1];
} else {
// leap year
year += 3;
if (q === 4) r += 365;
for (month = 1; month <= 12 && r >= totalLeapedDaysPerMonth[month]; ++month);
day += r - totalLeapedDaysPerMonth[month - 1];
}
return new Date(year, month, day);
}
/**
* Return a Date corresponding to a dateString given in the format
* `YYYY-MM-DD` or `YYYYMMDD`.
* @param {string} dateString The date string.
* @returns {!Date}
*/
static fromISOFormat(dateString) {
let match = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec(dateString) || /^(\d\d\d\d)(\d\d)(\d\d)$/.exec(dateString);
if (match == null) {
throw new ValueDateTimeError('Invalid format.');
}
const [year, month, day] = match.slice(1).map(Number);
return new Date(year, month, day);
}
/**
* Return a standard library Date object corresponding to this Date.
* @param {boolean} utc If true, the value of getUTC***(), instead of
* get***(), will correspond to this Date.
* @returns {!stdDate}
*/
toStdDate() {
let utc = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let ret;
if (!utc) {
ret = new stdDate(this.year, this.month - 1, this.day);
ret.setFullYear(this.year);
} else {
ret = new stdDate(stdDate.UTC(this.year, this.month - 1, this.day));
ret.setUTCFullYear(this.year);
}
return ret;
}
/**
* Return a Date with the same value, except for those parameters given new
* values by whichever keyword arguments are specified.
* @param {Object} newValues The object consisting of new values.
* @param {number} [newValues.year]
* @param {number} [newValues.month]
* @param {number} [newValues.day]
* @returns {!Date}
*/
replace(_ref) {
let {
year = this.year,
month = this.month,
day = this.day
} = _ref;
return new Date(year, month, day);
}
/**
* Return the proleptic Gregorian ordinal of the Date,
* where January 1 of year 1 has ordinal 1.
* For any Date object d, Date.fromordinal(d.toordinal()) == d.
* @returns {number}
*/
toOrdinal() {
if (this['_ordinal'] != null) return this['_ordinal'];
let totalDays = 0;
const lastYear = this.year - 1;
const nLeapYear = Math.floor(lastYear / 4) - Math.floor(lastYear / 100) + Math.floor(lastYear / 400);
totalDays += nLeapYear * 366 + (lastYear - nLeapYear) * 365;
if (isLeapYear(this.year)) {
totalDays += totalLeapedDaysPerMonth[this.month - 1];
} else {
totalDays += totalDaysPerMonth[this.month - 1];
}
totalDays += this.day;
trySetProperty(this, '_ordinal', totalDays);
return totalDays;
}
/**
* Return the day of the week as an integer, where Monday is 0 and Sunday
* is 6. For example, date(2002, 12, 4).weekday() == 2, a Wednesday.
* @returns {number}
*/
weekday() {
return (this.toStdDate().getDay() + 6) % 7;
}
/**
* Return a string representing the date in ISO 8601 format, YYYY-MM-DD.
* @returns {string}
*/
isoFormat() {
if (this['_isoFormat'] != null) return this['_isoFormat'];
const ret = `${zeroPad(this.year, 4)}-${zeroPad(this.month, 2)}-${zeroPad(this.day, 2)}`;
trySetProperty(this, '_isoFormat', ret);
return ret;
}
/**
* Return a string representing the date, controlled by an explicit format
* string. Format codes referring to hours, minutes or seconds will see 0
* values.
* @param {string} format The format string.
* @returns {string}
*/
strftime(format) {
const dt = DateTime.combine(this, new Time());
return strftime(dt, format);
}
/**
* Same as isoFormat().
* @returns {string}
*/
toString() {
return this.isoFormat();
}
/**
* Same as toOrdinal().
* @returns {number}
*/
valueOf() {
return this.toOrdinal();
}
/**
* The earliest representable date, new Date(MINYEAR, 1, 1).
* @type {!Date}
*/
static get min() {
if (!dateMin) {
dateMin = new Date(MINYEAR, 1, 1);
}
return dateMin;
}
/**
* The latest representable date, new Date(MAXYEAR, 12, 31).
* @type {!Date}
*/
static get max() {
if (!dateMax) {
dateMax = new Date(MAXYEAR, 12, 31);
}
return dateMax;
}
/**
* The smallest possible difference between non-equal date objects,
* new TimeDelta(\{days: 1\}).
* @type {!TimeDelta}
*/
static get resolution() {
if (!dateResolution) {
dateResolution = new TimeDelta({
days: 1
});
}
return dateResolution;
}
}
/**
* This is an abstract base class, meaning that this class should not be
* instantiated directly. Define a subclass of tzinfo to capture information
* about a particular time zone.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.tzinfo.
*/
class TZInfo {
/**
* Return offset of local time from UTC, as a TimeDelta object that is
* positive east of UTC. If local time is west of UTC, this should be
* negative.
* @param {?DateTime} dt The DateTime object.
* @returns {?TimeDelta}
*/
utcOffset(dt) {
throw new NotImplementedDateTimeError();
}
/**
* Return the daylight saving time (DST) adjustment, as a TimeDelta object
* or null if DST information isn’t known.
* @param {?DateTime} dt The DateTime object.
* @returns {?TimeDelta}
*/
dst(dt) {
throw new NotImplementedDateTimeError();
}
/**
* Return the time zone name corresponding to the datetime object dt, as a
* string.
* @param {?DateTime} dt The DateTime object.
* @returns {?string}
*/
tzName(dt) {
throw new NotImplementedDateTimeError();
}
/**
* This is called from the default datetime.astimezone() implementation.
* When called from that, dt.tzinfo is self, and dt’s date and time data are
* to be viewed as expressing a UTC time. The purpose of fromutc() is to
* adjust the date and time data, returning an equivalent datetime in self’s
* local time.
* @param {!DateTime} dt The DateTime object.
* @returns {!DateTime}
*/
fromUTC(dt) {
if (dt.tzInfo !== this) {
throw new ValueDateTimeError('"dt.tzInfo" must be same instance as "this".');
}
let dtoff = dt.utcOffset();
let dtdst = dt.dst();
if (dtoff == null || dtdst == null) {
throw new ValueDateTimeError('"dt.utcOffset()" and "dt.dst()" must not return null.');
}
const delta = sub(dtoff, dtdst);
if (cmp(delta, new TimeDelta()) !== 0) {
dt = add(dt, delta);
dtdst = dt.dst();
}
if (dtdst == null) return dt;else return add(dt, dtdst);
}
}
/** @type {?TimeZone} */
let timeZoneUTC = null;
/**
* The TimeZone class is a subclass of TZInfo, each instance of which represents
* a timezone defined by a fixed offset from UTC.
* Objects of this class cannot be used to represent timezone information in the
* locations where different offsets are used in different days of the year or
* where historical changes have been made to civil time.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.timezone.
*/
class TimeZone extends TZInfo {
/**
*
* @param {!TimeDelta} offset Represents the difference between the local
* time and UTC. It must be strictly between
* -TimeDelta(\{hours: 24\}) and
* TimeDelta(\{hours: 24\}), otherwise
* ValueDateTimeError is raised.
* @param {?string} name If specified, it must be a string that will be used
* as the value returned by the DateTime.tzname()
* method.
*/
constructor(offset) {
let name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
super();
if (!(cmp(new TimeDelta({
hours: -24
}), offset) < 0 && cmp(offset, new TimeDelta({
hours: 24
})) < 0)) throw new ValueDateTimeError('"offset" must be "TimeDelta({hours: -24}) < offset < ' + 'TimeDelta({hours: 24})".');
if (name == null) {
name = 'UTC';
if (cmp(offset, new TimeDelta()) != 0) {
name += toOffsetString(offset);
}
}
/**
* @private
* @readonly
*/
this._offset = offset;
/**
* @private
* @readonly
*/
this._name = name;
}
/**
* Return the fixed value specified when the TimeZone instance is
* constructed.
* @param {?DateTime} dt This argument is ignored.
* @returns {!TimeDelta}
*/
utcOffset(dt) {
return this._offset;
}
/**
* Return the fixed value specified when the timezone instance is
* constructed.
* If name is not provided in the constructor, the name returned by
* tzName(dt) is generated from the value of the offset as follows.
* If offset is TimeDelta(\{\}), the name is “UTC”, otherwise it is a string
* in the format UTC±HH:MM, where ± is the sign of offset, HH and MM are two
* digits of offset.hours and offset.minutes respectively.
* @param {?DateTime} dt This argument is ignored.
* @returns {string}
*/
tzName(dt) {
return this._name;
}
/**
* Always returns null.
* @param {?DateTime} dt This argument is ignored.
* @returns {null}
*/
dst(dt) {
return null;
}
/**
* Return add(dt, offset). The dt argument must be an aware datetime
* instance, with tzInfo set to this.
* @param {!DateTime} dt The DateTime object.
* @returns {!DateTime}
*/
fromUTC(dt) {
if (dt.tzInfo !== this) {
throw new ValueDateTimeError('"dt.tzInfo" must be same instance as "this".');
}
return add(dt, this._offset);
}
/**
* The UTC timezone, new TimeZone(new TimeDelta(\{\})).
* @type {!TimeZone}
*/
static get utc() {
if (!timeZoneUTC) {
timeZoneUTC = new TimeZone(new TimeDelta({}));
}
return timeZoneUTC;
}
}
/**
* A subclass of TZInfo representing local timezone of execution environment.
*/
class LocalTZInfo extends TZInfo {
constructor() {
super();
// Offset without DST
const stdOffset = -new stdDate(2000, 0, 1).getTimezoneOffset();
/**
* @private
* @readonly
*/
this._stdOffset = new TimeDelta({
minutes: stdOffset
});
}
/**
* Return offset of local time from UTC, as a TimeDelta object that is
* positive east of UTC. If local time is west of UTC, this is negative.
* @param {?DateTime} dt The DateTime object.
* @returns {!TimeDelta}
*/
utcOffset(dt) {
if (dt == null) return this._stdOffset;
const offset = -dt.toStdDate(false).getTimezoneOffset();
return new TimeDelta({
minutes: offset
});
}
/**
* Return the daylight saving time (DST) adjustment as a TimeDelta object.
* @param {?DateTime} dt The DateTime object.
* @returns {!TimeDelta}
*/
dst(dt) {
if (dt == null) return new TimeDelta();
const offsetMinutes = -dt.toStdDate(false).getTimezoneOffset();
const offset = new TimeDelta({
minutes: offsetMinutes
});
return sub(offset, this._stdOffset);
}
/**
* Return the time zone name corresponding to the datetime object dt, as a
* string.
* @param {?DateTime} dt The DateTime object.
* @returns {string}
*/
tzName(dt) {
const offset = this.utcOffset(dt);
return toOffsetString(offset);
}
/**
* This is called from the default datetime.astimezone() implementation.
* When called from that, dt.tzinfo is self, and dt’s date and time data are
* to be viewed as expressing a UTC time. The purpose of fromutc() is to
* adjust the date and time data, returning an equivalent datetime in self’s
* local time.
* @param {!DateTime} dt The DateTime object.
* @returns {!DateTime}
*/
fromUTC(dt) {
if (dt.tzInfo !== this) throw new ValueDateTimeError('"dt.tzInfo" must be same instance as "this".');
const local = DateTime.fromStdDate(dt.toStdDate(true), false).replace({
microsecond: dt.microsecond,
tzInfo: this,
fold: 0
});
return local;
}
}
/**
* An instance of a class which is a subclass of TZInfo representing local
* timezone of execution environment.
*/
const LOCALTZINFO = new LocalTZInfo();
/** @type {?Time} */
let timeMin = null;
/** @type {?Time} */
let timeMax = null;
/** @type {?TimeDelta} */
let timeResolution = null;
/**
* A time object represents a (local) time of day, independent of any particular
* day, and subject to adjustment via a tzinfo object.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.time.
*/
class Time {
/**
* @param {number} hour Between 0 and 23.
* @param {number} minute Between 0 and 59.
* @param {number} second Between 0 and 59.
* @param {number} microsecond Between 0 and 999999.
* @param {?TZInfo} tzInfo The timezone information.
* @param {number} fold 0 or 1.
*/
constructor() {
let hour = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
let minute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
let second = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
let microsecond = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
let tzInfo = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
let fold = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
if (!(0 <= hour && hour <= 23)) throw new ValueDateTimeError('"hour" must be between 0 and 23.');
if (!(0 <= minute && minute <= 59)) throw new ValueDateTimeError('"minute" must be between 0 and 59.');
if (!(0 <= second && second <= 59)) throw new ValueDateTimeError('"second" must be between 0 and 59.');
if (!(0 <= microsecond && microsecond <= 999999)) throw new ValueDateTimeError('"microsecond" must be between 0 and 999999.');
if (!(fold === 0 || fold === 1)) throw new ValueDateTimeError('"fold" must be 0 or 1.');
/**
* @private
* @readonly
*/
this._hour = hour;
/**
* @private
* @readonly
*/
this._minute = minute;
/**
* @private
* @readonly
*/
this._second = second;
/**
* @private
* @readonly
*/
this._microsecond = microsecond;
/**
* @private
* @readonly
*/
this._tzInfo = tzInfo;
/**
* @private
* @readonly
*/
this._fold = fold;
}
/**
* Between 0 and 23.
* @type {number}
*/
get hour() {
return this._hour;
}
/**
* Between 0 and 59.
* @type {number}
*/
get minute() {
return this._minute;
}
/**
* Between 0 and 59.
* @type {number}
*/
get second() {
return this._second;
}
/**
* Between 0 and 999999.
* @type {number}
*/
get microsecond() {
return this._microsecond;
}
/**
* The object passed as the tzInfo argument to the Time constructor, or null
* if none was passed.
* @type {?TZInfo}
*/
get tzInfo() {
return this._tzInfo;
}
/**
* 0 or 1. Used to disambiguate wall times during a repeated interval.
* (A repeated interval occurs when clocks are rolled back at the end of
* daylight saving time or when the UTC offset for the current zone is
* decreased for political reasons.) The value 0 (1) represents the earlier
* (later) of the two moments with the same wall time representation.
* @type {number}
*/
get fold() {
return this._fold;
}
/**
* Return a Time corresponding to a dateString given in the format
* `HH[:MM[:SS[.fff[fff]]]][Z|((+|-)HH[:MM[:SS[.fff[fff]]]])]` or
* `HH[MM[SS[.fff[fff]]]][Z|((+|-)HH[MM[SS[.fff[fff]]]])]`.
* @param {string} timeString The time string.
* @returns {!Time}
*/
static fromISOFormat(timeString) {
function parseTimeString(str) {
const match = /^(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3})(\d{3})?)?)?)?$/.exec(str) || /^(\d\d)(?:(\d\d)(?:(\d\d)(?:\.(\d{3})(\d{3})?)?)?)?$/.exec(str);
if (match == null) return null;
match.splice(0, 1);
const ret = match.map(x => x == null ? 0 : parseInt(x, 10));
ret[3] = ret[3] * 1000 + ret[4];
ret.splice(4, 1);
return ret;
}
let sepIdx = timeString.search(/[Z+-]/);
if (sepIdx === -1) sepIdx = timeString.length;
const timeStr = timeString.slice(0, sepIdx);
const offsetStr = timeString.slice(sepIdx);
const timeArray = parseTimeString(timeStr);
if (timeArray == null) throw new ValueDateTimeError('Invalid format.');
let tzInfo = null;
if (offsetStr === 'Z') {
tzInfo = new TimeZone(new TimeDelta({}));
} else if (offsetStr !== '') {
const offsetArray = parseTimeString(offsetStr.slice(1));
if (offsetArray == null) {
throw new ValueDateTimeError('Invalid format.');
}
let offset = new TimeDelta({
hours: offsetArray[0],
minutes: offsetArray[1],
seconds: offsetArray[2],
microseconds: offsetArray[3]
});
if (offsetStr[0] === '-') offset = neg(offset);
tzInfo = new TimeZone(offset);
}
return new Time(timeArray[0], timeArray[1], timeArray[2], timeArray[3], tzInfo);
}
/**
* Return a time with the same value, except for those attributes given new
* values by whichever keyword arguments are specified. Note that
* \{tzinfo: null\} can be specified to create a naive time from an aware
* time, without conversion of the time data.
* @param {Object} newValues The object consisting of new values.
* @param {number} [newValues.hour]
* @param {number} [newValues.minute]
* @param {number} [newValues.second]
* @param {number} [newValues.microsecond]
* @param {?TZInfo} [newValues.tzInfo]
* @param {number} [newValues.fold]
* @returns {!Time}
*/
replace(_ref2) {
let {
hour = this.hour,
minute = this.minute,
second = this.second,
microsecond = this.microsecond,
tzInfo = this.tzInfo,
fold = this.fold
} = _ref2;
return new Time(hour, minute, second, microsecond, tzInfo, fold);
}
/**
* Return a string representing the time in ISO 8601 format.
* @param {"auto"|"microseconds"|"milliseconds"|"seconds"|"minutes"|"hours"
* } timeSpec Specifies the number of additional components of the time to
* include.
* @returns {string}
*/
isoFormat() {
let timeSpec = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'auto';
if (timeSpec === 'auto') {
timeSpec = this.microsecond ? 'microseconds' : 'seconds';
}
if (this['_isoFormat']?.[timeSpec] != null) {
return this['_isoFormat'][timeSpec];
}
let ret = '';
switch (timeSpec) {
case 'microseconds':
case 'milliseconds':
if (timeSpec === 'microseconds') ret = zeroPad(this.microsecond, 6) + ret;else ret = zeroPad(Math.floor(this.microsecond / 1000), 3) + ret;
ret = '.' + ret;
case 'seconds':
ret = ':' + zeroPad(this.second, 2) + ret;
case 'minutes':
ret = ':' + zeroPad(this.minute, 2) + ret;
case 'hours':
ret = zeroPad(this.hour, 2) + ret;
break;
default:
throw new ValueDateTimeError('"timeSpec" must be either "auto", "microseconds", "milliseconds", ' + '"seconds", "minutes" or "hours".');
}
const offset = this.utcOffset();
if (offset != null) {
ret += toOffsetString(offset);
}
if (!this['_isoFormat']) {
trySetProperty(this, '_isoFormat', {});
}
// Suppose the object is freezed by is
if (this['_isoFormat']) {
trySetProperty(this['_isoFormat'], timeSpec, ret);
}
return ret;
}
/**
* If tzInfo is null, returns null, else returns this.tzInfo.utcOffset(null).
* @returns {?TimeDelta}
*/
utcOffset() {
return this.tzInfo == null ? null : this.tzInfo.utcOffset(null);
}
/**
* If tzInfo is null, returns null, else returns this.tzInfo.dst(null).
* @returns {?TimeDelta}
*/
dst() {
return this.tzInfo == null ? null : this.tzInfo.dst(null);
}
/**
* If tzInfo is null, returns null, else returns this.tzInfo.tzName(null).
* @returns {?string}
*/
tzName() {
return this.tzInfo == null ? null : this.tzInfo.tzName(null);
}
/**
* Return a string representing the time, controlled by an explicit format
* string.
* @param {string} format The format string.
* @returns {string}
*/
strftime(format) {
const dt = DateTime.combine(new Date(1900, 1, 1), this);
return strftime(dt, format);
}
/**
* Same as isoFormat().
* @returns {string}
*/
toString() {
return this.isoFormat();
}
/**
* The earliest representable time, new Time(0, 0, 0, 0).
* @type {!Time}
*/
static get min() {
if (!timeMin) {
timeMin = new Time(0, 0, 0, 0);
}
return timeMin;
}
/**
* The latest representable time, new Time(23, 59, 59, 999999).
* @type {!Time}
*/
static get max() {
if (!timeMax) {
timeMax = new Time(23, 59, 59, 999999);
}
return timeMax;
}
/**
* The smallest possible difference between non-equal time objects,
* new TimeDelta(\{microseconds: 1\}).
* @type {!TimeDelta}
*/
static get resolution() {
if (!timeResolution) {
timeResolution = new TimeDelta({
microseconds: 1
});
}
return timeResolution;
}
}
/** @type {?DateTime} */
let dateTimeMin = null;
/** @type {?DateTime} */
let dateTimeMax = null;
/** @type {?TimeDelta} */
let dateTimeResolution = null;
/**
* A DateTime object is a single object containing all the information from a
* Date object and a Time object.
* Javascript version of
* https://docs.python.org/3/library/datetime.html#datetime.datetime.
*/
class DateTime extends Date {
/**
* @param {number} year Between MINYEAR and MAXYEAR.
* @param {number} month Between 1 and 12.
* @param {number} day Between 1 and the number of days in the given month
* and year.
* @param {number} hour Between 0 and 23.
* @param {number} minute Between 0 and 59.
* @param {number} second Between 0 and 59.
* @param {number} microsecond Between 0 and 999999.
* @param {?TZInfo} tzInfo The timezone information.
* @param {number} fold 0 or 1.
*/
constructor(year, month, day) {
let hour = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
let minute = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
let second = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
let microsecond = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0;
let tzInfo = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
let fold = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 0;
super(year, month, day);
if (!(0 <= hour && hour <= 23)) throw new ValueDateTimeError('"hour" must be between 0 and 23.');
if (!(0 <= minute && minute <= 59)) throw new ValueDateTimeError('"minute" must be between 0 and 59.');
if (!(0 <= second && second <= 59)) throw new ValueDateTimeError('"second" must be between 0 and 59.');
if (!(0 <= microsecond && microsecond <= 999999)) throw new ValueDateTimeError('"microsecond" must be between 0 and 999999.');
if (!(fold === 0 || fold === 1)) throw new ValueDateTimeError('"fold" must be 0 or 1.');
/**
* @private
* @readonly
*/
this._hour = hour;
/**
* @private
* @readonly
*/
this._minute = minute;
/**
* @private
* @readonly
*/
this._second = second;
/**
* @private
* @readonly
*/
this._microsecond = microsecond;
/**
* @private
* @readonly
*/
this._tzInfo = tzInfo;
/**
* @private
* @readonly
*/
this._fold = fold;
}
/**
* Between 0 and 23.
* @type {number}
*/
get hour() {
return this._hour;
}
/**
* Between 0 and 59.
* @type {number}
*/
get minute() {
return this._minute;
}
/**
* Between 0 and 59.
* @type {number}
*/
get second() {
return this._second;
}
/**
* Between 0 and 999999.
* @type {number}
*/
get microsecond() {
return this._microsecond;
}
/**
* The object passed as the tzInfo argument to the Time constructor, or null
* if none was passed.
* @type {?TZInfo}
*/
get tzInfo() {
return this._tzInfo;
}
/**
* 0 or 1. Used to disambiguate wall times during a repeated interval.
* (A repeated interval occurs when clocks are rolled back at the end of
* daylight saving time or when the UTC offset for the current zone is
* decreased for political reasons.) The value 0 (1) represents the earlier
* (later) of the two moments with the same wall time representation.
* @type {number}
*/
get fold() {
return this._fold;
}
/**
* Return a DateTime corresponding to the given standard library Date object.
* @param {!stdDate} d The standard library Date object.
* @param {boolean} utc If true, use getUTC***() instead of get***()
* to construct DateTime.
* @returns {!DateTime}
*/
static fromStdDate(d) {
let utc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!utc) return new DateTime(d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds() * 1000);else return new DateTime(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds() * 1000);
}
/**
* Return the current local date and time, with tzInfo null.
* @returns {!DateTime}
*/
static today() {
return DateTime.fromStdDate(new stdDate());
}
/**
* Return the current date and time.
* @param {?TZInfo} tz If specified, the current date and time are converted
* to tz's time zone, else same as today().
* @returns {!DateTime}
*/
static now() {
let tz = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (tz == null) return DateTime.today();
return tz.fromUTC(DateTime.utcNow().replace({
tzInfo: tz
}));
}
/**
* Return the current UTC date and time, with tzInfo null.
* @returns {!DateTime}
*/
static utcNow() {
return DateTime.fromStdDate(new stdDate(), true);
}
/**
* Return the local date and time corresponding to the POSIX timestamp.
* @param {number} timeStamp The POSIX timestamp.
* @param {?TZInfo} tz If null, the timestamp is converted to the platform's
* local date and time, and the returned DateTime object
* is naive. If not null, the timestamp is converted to
* tz's time zone.
* @returns {!DateTime}
*/
static fromTimeStamp(timeStamp) {
let tz = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (tz == null) return DateTime.fromStdDate(new stdDate(timeStamp * 1000));
return tz.fromUTC(DateTime.utcFromTimeStamp(timeStamp).replace({
tzInfo: tz
}));
}
/**
* Return the UTC date and time corresponding to the POSIX timestamp, with
* tzInfo null. (The resulting object is naive.)
* @param {number} timeStamp The POSIX timestamp.
* @returns {!DateTime}
*/
static utcFromTimeStamp(timeStamp) {
return DateTime.fromStdDate(new stdDate(timeStamp * 1000), true);
}
/**
* Return a new DateTime object whose date components are equal to the given
* Date object’s, and whose time components are equal to the given Time
* object’s. If the tzInfo argument is provided, its value is used to set
* the tzInfo attribute of the result, otherwise the tzInfo attribute of the
* time argument is used.
* @param {!Date} date The Date object.
* @param {!Time} time The Time object.
* @param {?TZInfo} [tzInfo] The TZInfo object.
* @returns {!DateTime}
*/
static combine(date, time) {
let tzInfo = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
if (tzInfo === undefined) tzInfo = time.tzInfo;
return new DateTime(date.year, date.month, date.day, time.hour, time.minute, time.second, time.microsecond, tzInfo, time.fold);
}
/**
* Return a DateTime corresponding to a dateString in one of the formats
* emitted by Date.isoFormat() and DateTime.isoFormat().
* @param {string} dateString The date string.
* @returns {!DateTime}
*/
static fromISOFormat(dateString) {
let sepIdx = dateString.search(/[^\d\-]/);
if (sepIdx === -1) sepIdx = dateString.length;
const dateStr = dateString.slice(0, sepIdx);
const timeStr = dateString.slice(sepIdx + 1);
return DateTime.combine(Date.fromISOFormat(dateStr), timeStr ? Time.fromISOFormat(timeStr) : new Time());
}
/**
* Return a standard library Date object corresponding to this DateTime.
* Since standard library Date object has only millisecond resolution, the
* microsecond value is truncated.
* @param {boolean} utc If true, the value of getUTC***(), instead of
* get***(), will correspond to this Date.
* @returns {!stdDate}
*/
toStdDate() {
let utc = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let ret;
if (!utc) {
ret = new stdDate(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.microsecond / 1000);
ret.setFullYear(this.year);
} else {
ret = new stdDate(stdDate.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.m