UNPKG

universal-common

Version:

Library that provides useful missing base class library functionality.

848 lines (753 loc) 30.4 kB
import ArgumentError from './ArgumentError.js'; import DateTimeFormat from './DateTimeFormat.js'; import DateTimeKind from './DateTimeKind.js'; import TimeSpan from './TimeSpan.js'; /** * Represents an instant in time, typically expressed as a date and time of day. * * DateTime values are stored internally as 100-nanosecond intervals (ticks) since * January 1, 0001 12:00:00 midnight, with additional flags for DateTimeKind. * * @example * // Create DateTime instances * const now = DateTime.now; * const specificDate = new DateTime(2024, 12, 25, 10, 30, 0); * const fromTicks = new DateTime(638000000000000000n); * * // Arithmetic operations * const tomorrow = now.addDays(1); * const duration = tomorrow.subtract(now); */ export default class DateTime { // Internal storage as BigInt to handle 64-bit precision #dateData; // Calendar constants static #DAYS_PER_YEAR = 365; static #DAYS_PER_4_YEARS = 1461; // 365 * 4 + 1 static #DAYS_PER_100_YEARS = 36524; // DAYS_PER_4_YEARS * 25 - 1 static #DAYS_PER_400_YEARS = 146097; // DAYS_PER_100_YEARS * 4 + 1 // Reference points (days from 1/1/0001) static #DAYS_TO_1601 = 584388; // DAYS_PER_400_YEARS * 4 static #DAYS_TO_1970 = 719162; // Unix epoch reference static #DAYS_TO_10000 = 3652059; // Maximum date // Tick limits static #MIN_TICKS = 0n; static #MAX_TICKS = BigInt(DateTime.#DAYS_TO_10000) * BigInt(TimeSpan.TICKS_PER_DAY) - 1n; // Bit masks for storage static #TICKS_MASK = 0x3FFFFFFFFFFFFFFFn; // 62 bits for ticks static #FLAGS_MASK = 0xC000000000000000n; // Top 2 bits for kind static #KIND_SHIFT = 62n; // Kind flags static #KIND_UNSPECIFIED = 0x0000000000000000n; static #KIND_UTC = 0x4000000000000000n; static #KIND_LOCAL = 0x8000000000000000n; static #KIND_LOCAL_AMBIGUOUS_DST = 0xC000000000000000n; // Days to month arrays static #DAYS_TO_MONTH_365 = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; static #DAYS_TO_MONTH_366 = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; // Special DateTime values static #minValue = null; static #maxValue = null; static #unixEpoch = null; /** * Creates a new DateTime instance. * * @constructor * @param {...(number|bigint)} args - Constructor arguments in various formats: * - (ticks) - 100-nanosecond intervals since 1/1/0001 * - (ticks, kind) - Ticks with DateTimeKind * - (year, month, day) - Date components * - (year, month, day, hour, minute, second) - Date and time components * - (year, month, day, hour, minute, second, millisecond) - With milliseconds * - (year, month, day, hour, minute, second, millisecond, kind) - With kind * * @throws {ArgumentError} If invalid arguments provided * @throws {RangeError} If date/time values are out of range */ constructor(...args) { if (args.length === 1) { // DateTime(ticks) const ticks = typeof args[0] === 'bigint' ? args[0] : BigInt(args[0]); if (ticks > DateTime.#MAX_TICKS || ticks < DateTime.#MIN_TICKS) { throw new RangeError("DateTime ticks out of range."); } this.#dateData = ticks; } else if (args.length === 2) { // DateTime(ticks, kind) const ticks = typeof args[0] === 'bigint' ? args[0] : BigInt(args[0]); const kind = args[1]; if (ticks > DateTime.#MAX_TICKS || ticks < DateTime.#MIN_TICKS) { throw new RangeError("DateTime ticks out of range."); } if (kind < DateTimeKind.UNSPECIFIED || kind > DateTimeKind.LOCAL) { throw new ArgumentError("Invalid DateTimeKind."); } this.#dateData = ticks | (BigInt(kind) << DateTime.#KIND_SHIFT); } else if (args.length === 3) { // DateTime(year, month, day) const ticks = DateTime.#dateToTicks(args[0], args[1], args[2]); this.#dateData = ticks; } else if (args.length === 6) { // DateTime(year, month, day, hour, minute, second) const dateTicks = DateTime.#dateToTicks(args[0], args[1], args[2]); const timeTicks = DateTime.#timeToTicks(args[3], args[4], args[5]); this.#dateData = dateTicks + timeTicks; } else if (args.length === 7) { // DateTime(year, month, day, hour, minute, second, millisecond) const dateTicks = DateTime.#dateToTicks(args[0], args[1], args[2]); const timeTicks = DateTime.#timeToTicks(args[3], args[4], args[5]); const millisecondTicks = BigInt(args[6]) * BigInt(TimeSpan.TICKS_PER_MILLISECOND); this.#dateData = dateTicks + timeTicks + millisecondTicks; } else if (args.length === 8) { // DateTime(year, month, day, hour, minute, second, millisecond, kind) const dateTicks = DateTime.#dateToTicks(args[0], args[1], args[2]); const timeTicks = DateTime.#timeToTicks(args[3], args[4], args[5]); const millisecondTicks = BigInt(args[6]) * BigInt(TimeSpan.TICKS_PER_MILLISECOND); const kind = args[7]; if (kind < DateTimeKind.UNSPECIFIED || kind > DateTimeKind.LOCAL) { throw new ArgumentError("Invalid DateTimeKind."); } this.#dateData = dateTicks + timeTicks + millisecondTicks + (BigInt(kind) << DateTime.#KIND_SHIFT); } else { throw new ArgumentError("Invalid DateTime constructor arguments."); } // Final validation const ticks = this.#dateData & DateTime.#TICKS_MASK; if (ticks > DateTime.#MAX_TICKS) { throw new RangeError("DateTime out of range."); } } /** * Converts date components to ticks. * * @private * @param {number} year - Year (1-9999) * @param {number} month - Month (1-12) * @param {number} day - Day (1-31) * @returns {bigint} Ticks representing the date * @throws {RangeError} If date components are invalid */ static #dateToTicks(year, month, day) { if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1) { throw new RangeError("Invalid date components."); } const isLeap = DateTime.isLeapYear(year); const daysToMonth = isLeap ? DateTime.#DAYS_TO_MONTH_366 : DateTime.#DAYS_TO_MONTH_365; if (day > daysToMonth[month] - daysToMonth[month - 1]) { throw new RangeError("Day is out of range for the specified month and year."); } const daysSinceEpoch = DateTime.#daysToYear(year) + daysToMonth[month - 1] + day - 1; return BigInt(daysSinceEpoch) * BigInt(TimeSpan.TICKS_PER_DAY); } /** * Converts time components to ticks. * * @private * @param {number} hour - Hour (0-23) * @param {number} minute - Minute (0-59) * @param {number} second - Second (0-59) * @returns {bigint} Ticks representing the time * @throws {RangeError} If time components are invalid */ static #timeToTicks(hour, minute, second) { if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) { throw new RangeError("Invalid time components."); } const totalSeconds = hour * 3600 + minute * 60 + second; return BigInt(totalSeconds) * BigInt(TimeSpan.TICKS_PER_SECOND); } /** * Calculates the number of days from 1/1/0001 to the beginning of the specified year. * * @private * @param {number} year - The year * @returns {number} Number of days */ static #daysToYear(year) { const y = year - 1; const centuries = Math.floor(y / 100); return Math.floor(y * (365 * 4 + 1) / 4) - centuries + Math.floor(centuries / 4); } /** * Gets the ticks value of this DateTime (without kind flags). * * @type {bigint} * @readonly */ get ticks() { return this.#dateData & DateTime.#TICKS_MASK; } /** * Gets the DateTimeKind of this DateTime. * * @type {number} * @readonly */ get kind() { const kindBits = (this.#dateData & DateTime.#FLAGS_MASK) >> DateTime.#KIND_SHIFT; // Map internal 4-state to external 3-state (LocalAmbiguousDst -> Local) return Number(kindBits & ~(kindBits >> 1n)); } /** * Gets the year component of this DateTime. * * @type {number} * @readonly */ get year() { const { year } = this.#getDateComponents(); return year; } /** * Gets the month component of this DateTime (1-12). * * @type {number} * @readonly */ get month() { const { month } = this.#getDateComponents(); return month; } /** * Gets the day component of this DateTime (1-31). * * @type {number} * @readonly */ get day() { const { day } = this.#getDateComponents(); return day; } /** * Gets the hour component of this DateTime (0-23). * * @type {number} * @readonly */ get hour() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number((ticks / BigInt(TimeSpan.TICKS_PER_HOUR)) % 24n); } /** * Gets the minute component of this DateTime (0-59). * * @type {number} * @readonly */ get minute() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number((ticks / BigInt(TimeSpan.TICKS_PER_MINUTE)) % 60n); } /** * Gets the second component of this DateTime (0-59). * * @type {number} * @readonly */ get second() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number((ticks / BigInt(TimeSpan.TICKS_PER_SECOND)) % 60n); } /** * Gets the millisecond component of this DateTime (0-999). * * @type {number} * @readonly */ get millisecond() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number((ticks / BigInt(TimeSpan.TICKS_PER_MILLISECOND)) % 1000n); } /** * Gets the microsecond component of this DateTime (0-999). * * @type {number} * @readonly */ get microsecond() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number((ticks / BigInt(TimeSpan.TICKS_PER_MICROSECOND)) % 1000n); } /** * Gets the nanosecond component of this DateTime (0-900). * * @type {number} * @readonly */ get nanosecond() { const ticks = this.#dateData & DateTime.#TICKS_MASK; return Number(ticks % BigInt(TimeSpan.TICKS_PER_MICROSECOND)) * 100; } /** * Gets the day of the week for this DateTime. * * @type {number} * @readonly */ get dayOfWeek() { const ticks = this.#dateData & DateTime.#TICKS_MASK; const days = Number(ticks / BigInt(TimeSpan.TICKS_PER_DAY)); return (days + 1) % 7; // Adjust for Sunday = 0 } /** * Gets the day of the year for this DateTime (1-366). * * @type {number} * @readonly */ get dayOfYear() { const ticks = this.#dateData & DateTime.#TICKS_MASK; const totalDays = Number(ticks / BigInt(TimeSpan.TICKS_PER_DAY)); // Calculate year using 400-year cycle (same as #getDateComponents but optimized for dayOfYear only) const y400 = Math.floor(totalDays / DateTime.#DAYS_PER_400_YEARS); let remainingDays = totalDays % DateTime.#DAYS_PER_400_YEARS; let y100 = Math.floor(remainingDays / DateTime.#DAYS_PER_100_YEARS); if (y100 === 4) y100 = 3; remainingDays -= y100 * DateTime.#DAYS_PER_100_YEARS; const y4 = Math.floor(remainingDays / DateTime.#DAYS_PER_4_YEARS); remainingDays -= y4 * DateTime.#DAYS_PER_4_YEARS; let y1 = Math.floor(remainingDays / DateTime.#DAYS_PER_YEAR); if (y1 === 4) y1 = 3; const dayOfYear = remainingDays - y1 * DateTime.#DAYS_PER_YEAR; return dayOfYear + 1; // Convert to 1-based } /** * Gets the date part of this DateTime. * * @type {DateTime} * @readonly */ get date() { const ticks = this.#dateData & DateTime.#TICKS_MASK; const dateTicks = (ticks / BigInt(TimeSpan.TICKS_PER_DAY)) * BigInt(TimeSpan.TICKS_PER_DAY); const kindFlags = this.#dateData & DateTime.#FLAGS_MASK; const kind = Number(kindFlags >> DateTime.#KIND_SHIFT); return new DateTime(dateTicks, kind); } /** * Gets the time of day for this DateTime. * * @type {TimeSpan} * @readonly */ get timeOfDay() { const ticks = this.#dateData & DateTime.#TICKS_MASK; const timeTicks = Number(ticks % BigInt(TimeSpan.TICKS_PER_DAY)); return new TimeSpan(timeTicks); } /** * Gets date components (year, month, day) efficiently. * * @private * @returns {{year: number, month: number, day: number}} */ #getDateComponents() { const ticks = this.#dateData & DateTime.#TICKS_MASK; const totalDays = Number(ticks / BigInt(TimeSpan.TICKS_PER_DAY)); // Calculate year using 400-year cycle const y400 = Math.floor(totalDays / DateTime.#DAYS_PER_400_YEARS); let remainingDays = totalDays % DateTime.#DAYS_PER_400_YEARS; // Calculate within 400-year period let y100 = Math.floor(remainingDays / DateTime.#DAYS_PER_100_YEARS); if (y100 === 4) y100 = 3; // Adjustment for leap year boundary remainingDays -= y100 * DateTime.#DAYS_PER_100_YEARS; const y4 = Math.floor(remainingDays / DateTime.#DAYS_PER_4_YEARS); remainingDays -= y4 * DateTime.#DAYS_PER_4_YEARS; let y1 = Math.floor(remainingDays / DateTime.#DAYS_PER_YEAR); if (y1 === 4) y1 = 3; // Adjustment for leap year boundary const year = 1 + y400 * 400 + y100 * 100 + y4 * 4 + y1; const dayOfYear = remainingDays - y1 * DateTime.#DAYS_PER_YEAR; // Find month and day within year const daysToMonth = DateTime.isLeapYear(year) ? DateTime.#DAYS_TO_MONTH_366 : DateTime.#DAYS_TO_MONTH_365; let month = 1; while (month < 12 && dayOfYear >= daysToMonth[month]) { month++; } const day = dayOfYear - daysToMonth[month - 1] + 1; return { year, month, day }; } /** * Determines if the specified year is a leap year. * * @param {number} year - The year to check * @returns {boolean} true if the year is a leap year; otherwise, false * @throws {RangeError} If year is out of range */ static isLeapYear(year) { if (year < 1 || year > 9999) { throw new RangeError("Year out of range."); } // Optimized leap year calculation if ((year & 3) !== 0) return false; if ((year & 15) === 0) return true; return (year % 25) !== 0; } /** * Returns the number of days in the specified month and year. * * @param {number} year - The year * @param {number} month - The month (1-12) * @returns {number} The number of days in the month * @throws {RangeError} If year or month is out of range */ static daysInMonth(year, month) { if (month < 1 || month > 12) { throw new RangeError("Month out of range."); } const daysToMonth = DateTime.isLeapYear(year) ? DateTime.#DAYS_TO_MONTH_366 : DateTime.#DAYS_TO_MONTH_365; return daysToMonth[month] - daysToMonth[month - 1]; } /** * Adds the specified TimeSpan to this DateTime. * * @param {TimeSpan} value - The TimeSpan to add * @returns {DateTime} A new DateTime with the added time * @throws {TypeError} If value is not a TimeSpan * @throws {RangeError} If result is out of range */ add(value) { if (!(value instanceof TimeSpan)) { throw new TypeError("Argument must be a TimeSpan."); } return this.addTicks(value.ticks); } /** * Adds the specified number of ticks to this DateTime. * * @param {number} value - The number of ticks to add * @returns {DateTime} A new DateTime with the added ticks * @throws {RangeError} If result is out of range */ addTicks(value) { const ticks = (this.#dateData & DateTime.#TICKS_MASK) + BigInt(value); if (ticks > DateTime.#MAX_TICKS || ticks < DateTime.#MIN_TICKS) { throw new RangeError("DateTime arithmetic result out of range."); } const kindFlags = this.#dateData & DateTime.#FLAGS_MASK; const kind = Number(kindFlags >> DateTime.#KIND_SHIFT); return new DateTime(ticks, kind); } /** * Adds the specified number of days to this DateTime. * * @param {number} value - The number of days to add * @returns {DateTime} A new DateTime with the added days */ addDays(value) { return this.addTicks(value * TimeSpan.TICKS_PER_DAY); } /** * Adds the specified number of hours to this DateTime. * * @param {number} value - The number of hours to add * @returns {DateTime} A new DateTime with the added hours */ addHours(value) { return this.addTicks(value * TimeSpan.TICKS_PER_HOUR); } /** * Adds the specified number of minutes to this DateTime. * * @param {number} value - The number of minutes to add * @returns {DateTime} A new DateTime with the added minutes */ addMinutes(value) { return this.addTicks(value * TimeSpan.TICKS_PER_MINUTE); } /** * Adds the specified number of seconds to this DateTime. * * @param {number} value - The number of seconds to add * @returns {DateTime} A new DateTime with the added seconds */ addSeconds(value) { return this.addTicks(value * TimeSpan.TICKS_PER_SECOND); } /** * Adds the specified number of milliseconds to this DateTime. * * @param {number} value - The number of milliseconds to add * @returns {DateTime} A new DateTime with the added milliseconds */ addMilliseconds(value) { return this.addTicks(value * TimeSpan.TICKS_PER_MILLISECOND); } /** * Adds the specified number of months to this DateTime. * * @param {number} months - The number of months to add * @returns {DateTime} A new DateTime with the added months * @throws {RangeError} If months is out of range or result is invalid */ addMonths(months) { if (months < -120000 || months > 120000) { throw new RangeError("Months out of range."); } const { year, month, day } = this.#getDateComponents(); let y = year; let d = day; let m = month + months; // Handle year rollover const q = m > 0 ? Math.floor((m - 1) / 12) : Math.floor(m / 12) - 1; y += q; m -= q * 12; if (y < 1 || y > 9999) { throw new RangeError("DateTime arithmetic result out of range."); } // Adjust day if necessary (e.g., Jan 31 + 1 month = Feb 28/29) const daysInTargetMonth = DateTime.daysInMonth(y, m); if (d > daysInTargetMonth) { d = daysInTargetMonth; } // Create new DateTime with adjusted date but same time const timeTicks = this.ticks % BigInt(TimeSpan.TICKS_PER_DAY); const dateTicks = DateTime.#dateToTicks(y, m, d); const kindFlags = this.#dateData & DateTime.#FLAGS_MASK; const kind = Number(kindFlags >> DateTime.#KIND_SHIFT); return new DateTime(dateTicks + timeTicks, kind); } /** * Adds the specified number of years to this DateTime. * * @param {number} years - The number of years to add * @returns {DateTime} A new DateTime with the added years * @throws {RangeError} If years is out of range or result is invalid */ addYears(years) { if (years < -10000 || years > 10000) { throw new RangeError("Years out of range."); } const { year, month, day } = this.#getDateComponents(); const newYear = year + years; if (newYear < 1 || newYear > 9999) { throw new RangeError("DateTime arithmetic result out of range."); } let newDay = day; // Handle leap year edge case (Feb 29 -> Feb 28 in non-leap year) if (month === 2 && day === 29 && !DateTime.isLeapYear(newYear)) { newDay = 28; } // Create new DateTime with adjusted date but same time const timeTicks = this.ticks % BigInt(TimeSpan.TICKS_PER_DAY); const dateTicks = DateTime.#dateToTicks(newYear, month, newDay); const kindFlags = this.#dateData & DateTime.#FLAGS_MASK; const kind = Number(kindFlags >> DateTime.#KIND_SHIFT); return new DateTime(dateTicks + timeTicks, kind); } /** * Subtracts the specified DateTime from this DateTime. * * @param {DateTime} value - The DateTime to subtract * @returns {TimeSpan} A TimeSpan representing the difference * @throws {TypeError} If value is not a DateTime */ subtract(value) { if (value instanceof DateTime) { const ticksDiff = Number((this.#dateData & DateTime.#TICKS_MASK) - (value.#dateData & DateTime.#TICKS_MASK)); return new TimeSpan(ticksDiff); } else if (value instanceof TimeSpan) { return this.addTicks(-value.ticks); } else { throw new TypeError("Argument must be a DateTime or TimeSpan."); } } /** * Compares this DateTime to another DateTime. * * @param {DateTime} other - The DateTime to compare to * @returns {number} -1 if this is less than other, 0 if equal, 1 if greater * @throws {TypeError} If other is not a DateTime */ compareTo(other) { if (!(other instanceof DateTime)) { throw new TypeError("Argument must be a DateTime."); } const thisTicks = this.#dateData & DateTime.#TICKS_MASK; const otherTicks = other.#dateData & DateTime.#TICKS_MASK; if (thisTicks < otherTicks) return -1; if (thisTicks > otherTicks) return 1; return 0; } /** * Determines whether this DateTime is equal to another DateTime. * * @param {DateTime} other - The DateTime to compare to * @returns {boolean} true if equal; otherwise, false */ equals(other) { return other instanceof DateTime && ((this.#dateData ^ other.#dateData) << 2n) === 0n; } /** * Converts this DateTime to a JavaScript Date object. * For UTC DateTimes, creates a Date representing the same UTC instant. * For Local/Unspecified DateTimes, creates a Date treating the components as local time. * * @returns {Date} A JavaScript Date representing the same instant */ toDate() { const year = this.year; const month = this.month - 1; // JavaScript months are 0-based const day = this.day; const hour = this.hour; const minute = this.minute; const second = this.second; const millisecond = this.millisecond; if (this.kind === DateTimeKind.UTC) { // Create Date from UTC components return new Date(Date.UTC(year, month, day, hour, minute, second, millisecond)); } else { // Create Date from local components (Local or Unspecified) return new Date(year, month, day, hour, minute, second, millisecond); } } /** * Converts the value of the current DateTime object to its equivalent string representation. * * @param {string} [format] - A standard or custom date and time format string * @returns {string} A string representation of the DateTime * * @example * const dt = new DateTime(2024, 12, 25, 14, 30, 45, 123); * console.log(dt.toString()); // Default format * console.log(dt.toString('d')); // "12/25/2024" (short date) * console.log(dt.toString('D')); // "Wednesday, December 25, 2024" (long date) * console.log(dt.toString('yyyy-MM-dd')); // "2024-12-25" (custom format) */ toString(format) { if (arguments.length === 0) { // toString() - no arguments, use default format return DateTimeFormat.format(this, null, null); } else { // toString(format) - format string provided return DateTimeFormat.format(this, format, null); } } /** * Creates a DateTime from a JavaScript Date instance. * * @param {Date} jsDate - The JavaScript Date to convert * @param {number} [kind=DateTimeKind.UNSPECIFIED] - The DateTimeKind for the result * @param {boolean} [useUtc=false] - Whether to use UTC components from the Date * @returns {DateTime} A new DateTime representing the same instant * @throws {TypeError} If jsDate is not a Date instance * @throws {ArgumentError} If kind is invalid */ static fromDate(jsDate, kind = DateTimeKind.UNSPECIFIED, useUtc = false) { if (!(jsDate instanceof Date)) { throw new TypeError("Argument must be a Date instance."); } if (kind < DateTimeKind.UNSPECIFIED || kind > DateTimeKind.LOCAL) { throw new ArgumentError("Invalid DateTimeKind."); } if (useUtc) { const year = jsDate.getUTCFullYear(); const month = jsDate.getUTCMonth() + 1; const day = jsDate.getUTCDate(); const hour = jsDate.getUTCHours(); const minute = jsDate.getUTCMinutes(); const second = jsDate.getUTCSeconds(); const millisecond = jsDate.getUTCMilliseconds(); return new DateTime(year, month, day, hour, minute, second, millisecond, kind); } else { const year = jsDate.getFullYear(); const month = jsDate.getMonth() + 1; const day = jsDate.getDate(); const hour = jsDate.getHours(); const minute = jsDate.getMinutes(); const second = jsDate.getSeconds(); const millisecond = jsDate.getMilliseconds(); return new DateTime(year, month, day, hour, minute, second, millisecond, kind); } } /** * Gets a DateTime representing the current date and time. * * @type {DateTime} * @readonly * @static */ static get now() { return DateTime.fromDate(new Date(), DateTimeKind.LOCAL); } /** * Gets a DateTime representing the current UTC date and time. * * @type {DateTime} * @readonly * @static */ static get utcNow() { return DateTime.fromDate(new Date(), DateTimeKind.UTC, true); } /** * Gets a DateTime representing the current date with time set to midnight. * * @type {DateTime} * @readonly * @static */ static get today() { return DateTime.now.date; } /** * Gets the minimum DateTime value. * * @type {DateTime} * @readonly * @static */ static get minValue() { if (!DateTime.#minValue) { DateTime.#minValue = new DateTime(DateTime.#MIN_TICKS); } return DateTime.#minValue; } /** * Gets the maximum DateTime value. * * @type {DateTime} * @readonly * @static */ static get maxValue() { if (!DateTime.#maxValue) { DateTime.#maxValue = new DateTime(DateTime.#MAX_TICKS); } return DateTime.#maxValue; } /** * Gets the Unix epoch (1970-01-01 00:00:00 UTC). * * @type {DateTime} * @readonly * @static */ static get unixEpoch() { if (!DateTime.#unixEpoch) { const epochTicks = BigInt(DateTime.#DAYS_TO_1970) * BigInt(TimeSpan.TICKS_PER_DAY); DateTime.#unixEpoch = new DateTime(epochTicks, DateTimeKind.UTC); } return DateTime.#unixEpoch; } /** * Compares two DateTime values. * * @param {DateTime} t1 - The first DateTime * @param {DateTime} t2 - The second DateTime * @returns {number} -1 if t1 < t2, 0 if equal, 1 if t1 > t2 */ static compare(t1, t2) { return t1.compareTo(t2); } /** * Determines whether two DateTime values are equal. * * @param {DateTime} t1 - The first DateTime * @param {DateTime} t2 - The second DateTime * @returns {boolean} true if equal; otherwise, false */ static equals(t1, t2) { return t1.equals(t2); } }