UNPKG

@silane/datetime

Version:

Date and time library similar to Python's "datetime" package.

1,462 lines (1,433 loc) 80.6 kB
(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