universal-common
Version:
Library that provides useful missing base class library functionality.
848 lines (753 loc) • 30.4 kB
JavaScript
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);
}
}