datetime_global
Version:
my Datetime untility, it mixes the Advanced capabilities of Temporal polyfill with the simplicity of Date
1,204 lines (1,203 loc) • 78.2 kB
JavaScript
import { Temporal } from 'temporal-polyfill';
import { ZDTDuration } from "./ZDTDuration.js";
/**
* this class `Datetime_global` should behave exactly like `Date` if a name matches that on `Date`.
*/
// npm: https://www.npmjs.com/package/datetime_global
// github: https://github.com/DNSCond/Datetime-local
"use strict"; // [^\x00-\x7F]
const [writable, enumerable, configurable] = [true, true, true];
/**
* Constructs a Datetime_global instance or returns a string representation.
* @param from - Input to initialize the date-time. Supports:
* - Temporal.ZonedDateTime: Used directly.
* - Temporal.Instant: Converted from epoch nanoseconds.
* - Date | Datetime_global: Converted from epoch milliseconds.
* - bigint: Nanoseconds since epoch.
* - number: Milliseconds since epoch.
* - string: Parsed using `Date.parse` (EcmaScript format or browser-dependent formats).
* - undefined: Uses current time (Datetime_global.now()).
* @param timezoneId - A Temporal.TimeZoneLike or IANA timezone string (e.g., "UTC"). Defaults to local timezone (Temporal.Now.timeZoneId()).
* @returns A Datetime_global instance if called with `new`, or a string representation if called as a function.
* @throws TypeError if timezoneId is invalid or if BigInt conversion fails.
* @throws RangeError if the input string cannot be parsed or results in an invalid date.
* @example
* // Current time in local timezone (assuming its America/New_York)
* const now = new Datetime_global();
* console.log(now.toString()); // e.g., "Fri Apr 18 2025 12:00:00 UTC-0400 (America/New_York)"
*
* // From Date in UTC
* const fromDate = new Datetime_global(new Date("2025-04-18T00:00:00Z"), "UTC");
* console.log(fromDate.toISOString()); // "2025-04-18T00:00:00.000Z"
*
* // From nanoseconds (bigint)
* const fromBigInt = new Datetime_global(1745193600000000000n, "UTC"); // April 18, 2025
* console.log(fromBigInt.toISOString()); // "2025-04-18T00:00:00.000Z"
*
* // From ISO string
* const fromString = new Datetime_global("2025-04-18T00:00:00Z", "Asia/Tokyo");
* console.log(fromString.toString()); // e.g., "Fri Apr 18 2025 09:00:00 UTC+0900 (Asia/Tokyo)"
*
* // As a function (returns string)
* const asString = Datetime_global(new Date("2025-04-18T00:00:00Z"), "UTC");
* console.log(asString); // "Fri Apr 18 2025 00:00:00 UTC+0000 (UTC)"
* @constructor
* @function
*/
export const Datetime_global = function (from = undefined, timezoneId = Temporal.Now.timeZoneId()) {
let timestamp, isBigInt = false;
if (arguments.length === 0 || from === undefined) {
from = Datetime_global.now();
}
if (from instanceof Temporal.ZonedDateTime) {
timestamp = 0n;
}
else if (from instanceof Temporal.Instant) {
timestamp = BigInt(from.epochNanoseconds);
isBigInt = true;
}
else if (from instanceof Datetime_global) {
timestamp = from?.time?.epochNanoseconds ?? null;
isBigInt = true;
}
else if (typeof from === 'bigint') {
timestamp = from;
isBigInt = true;
}
else if (from === null) {
timestamp = null;
}
else if (typeof from === 'string') {
timestamp = Date.parse(from);
}
else {
timestamp = Math.trunc(from);
}
if (typeof timestamp === 'number' && new.target === undefined) {
if (!Number.isSafeInteger(timestamp)) {
return "Invalid Date";
}
}
else if (timestamp === null && new.target === undefined)
return "Invalid Date";
let value = null;
if (timestamp !== null)
value = from instanceof Temporal.ZonedDateTime ? from : new Temporal.ZonedDateTime(BigInt(timestamp) * (isBigInt ? 1n : 1000000n), timezoneId);
const self = new.target ? this : Object.create(Datetime_global.prototype);
Object.defineProperties(self, {
time: { value, writable, enumerable, configurable, },
year: {
get() {
return this.getFullYear();
}, enumerable, configurable,
}, month: {
get() {
return this.time?.month ?? NaN;
}, enumerable, configurable,
}, day: {
get() {
return this.getDate();
}, enumerable, configurable,
}, dayOfWeek: {
get() {
return this.time?.dayOfWeek ?? NaN;
}, enumerable, configurable,
}, hour: {
get() {
return this.getHours();
}, enumerable, configurable,
}, minute: {
get() {
return this.getMinutes();
}, enumerable, configurable,
}, second: {
get() {
return this.getSeconds();
}, enumerable, configurable,
}, millisecond: {
get() {
return this.time?.millisecond ?? NaN;
}, enumerable, configurable,
}, microsecond: {
get() {
return this.time?.microsecond ?? NaN;
}, enumerable, configurable,
}, nanosecond: {
get() {
return this.time?.nanosecond ?? NaN;
}, enumerable, configurable,
}, epochMilliseconds: {
get() {
return this.time?.epochMilliseconds ?? NaN;
}, enumerable, configurable,
}, epochNanoseconds: {
get() {
return this.time?.epochNanoseconds ?? null;
}, set(value) {
this.time = new Temporal.ZonedDateTime(BigInt(value), this.timezoneId);
}, enumerable, configurable,
},
});
if (!new.target)
return self.toString();
};
Object.defineProperties(Datetime_global.prototype, {
minutesAfterMidnight: {
get() {
const { time } = this;
if (time === null)
return NaN;
return time.startOfDay().until(time, {
smallestUnit: 'minutes',
largestUnit: 'minutes',
}).minutes;
}, set(value) {
this.setHours(0, Math.trunc(value));
}, enumerable, configurable,
}, timezoneId: {
get() {
const time = this.validate();
if (time === null)
throw new RangeError('Datetime_global timezoneId requires to be valid');
return time.timeZoneId;
}, set(value) {
this.time = this.toTimezone(value).toTemporalZonedDateTime();
}, enumerable, configurable,
}, date: {
get() {
return this.toDate();
}, set(value) {
if (value instanceof Date) {
const instant = Temporal.Instant.fromEpochMilliseconds(value);
this.time = new Temporal.ZonedDateTime(instant.epochNanoseconds, this.getTimezoneId());
}
else
throw new TypeError('date must be set using a Date.');
}, enumerable, configurable,
},
});
Datetime_global.compare = function (zonedDatetime1, zonedDatetime2) {
zonedDatetime1 = new Datetime_global(zonedDatetime1, 'UTC');
zonedDatetime2 = new Datetime_global(zonedDatetime2, 'UTC');
const nano1 = zonedDatetime1.getTimestamp(), nano2 = zonedDatetime2.getTimestamp();
if (nano1 > nano2) {
return +1;
}
else if (nano1 < nano2) {
return -1;
}
else {
return +0;
}
};
/**
* Creates a UTC timestamp from date-time components, returning nanoseconds since the epoch.
* @param year - The year (0-99 maps to 1900-1999; otherwise used as-is). if it's a string the `Date.parse` is used
* @param month - The month (0-11; default 0, January).
* @param date - The day of the month (1-31; default 1).
* @param hour - The hour (0-23; default 0).
* @param minute - The minute (0-59; default 0).
* @param second - The second (0-59; default 0).
* @param millisecond - The millisecond (0-999; default 0).
* @param nanosecond - The nanosecond (default 0).
* @returns The nanoseconds since the epoch (January 1, 1970, 00:00:00 UTC).
* @throws RangeError if the components form an invalid date.
* @example
* // April 18, 2025, 00:00:00 UTC
* console.log(Datetime_global.fromComponentsUTC(2025, 3, 18));
* // 1745193600000000000n
*
* // With nanoseconds
* console.log(Datetime_global.fromComponentsUTC(2025, 3, 18, 0, 0, 0, 0, 500));
* // 1745193600000000500n
*/
Datetime_global.fromComponentsUTC = function (year, month = 0, date = 1, hour = 0, minute = 0, second = 0, millisecond = 0, nanosecond = 0n) {
const date_time = new Date;
if (arguments.length === 1) {
if (typeof year === 'string') {
year = Date.parse(year);
}
date_time.setTime(year);
}
else if (arguments.length > 1) {
date_time.setTime(Date.UTC(year, month, date, hour, minute, second, millisecond));
}
return (BigInt(date_time.getTime()) * 1000000n) + BigInt(nanosecond);
};
/**
* Parses a string into a Temporal.ZonedDateTime using strict ISO 8601 parsing.
* @param string - An ISO 8601 string (e.g., "2025-04-18T00:00:00Z") or object with date-time fields.
* @returns A Temporal.ZonedDateTime instance.
* @throws RangeError if the string is invalid or cannot be parsed.
* @example
* const zdt = Datetime_global.parse_strict("2025-04-18T00:00:00Z");
* console.log(zdt.toString()); // "2025-04-18T00:00:00+00:00[UTC]"
*/
Datetime_global.parse_strict = function (string) {
return Temporal.ZonedDateTime.from(string);
};
/**
* Returns the current timestamp as nanoseconds since the epoch (January 1, 1970, 00:00:00 UTC).
* @returns The nanoseconds since the epoch.
* @example
* console.log(Datetime_global.now()); // e.g., 1745193600000000000n
*/
Datetime_global.now = function () {
return Temporal.Now.instant().epochNanoseconds;
};
/**
* Returns the current timestamp as nanoseconds since the epoch (January 1, 1970, 00:00:00 UTC).
* @returns The nanoseconds since the epoch.
* @example
* console.log(Datetime_global.now()); // e.g., 1745193600000000000n
*/
Datetime_global.nowInTimezone = function (timezone) {
if (timezone)
return new Datetime_global(Datetime_global.now(), timezone);
return new Datetime_global;
};
/**
* Returns the current timestamp in milliseconds since the epoch, with sub-second components (milliseconds, microseconds, nanoseconds) set to 0.
* @returns The milliseconds since the epoch.
* @example
* console.log(Datetime_global.zeroms()); // e.g., 1745193600000
*/
Datetime_global.zeroms = function () {
return (new Date).setMilliseconds(0);
};
/**
* Returns the current timestamp in nanoseconds since the epoch, with sub-second components (milliseconds, microseconds, nanoseconds) set to 0.
* @returns The nanoseconds since the epoch.
* @example
* console.log(Datetime_global.zerons()); // e.g., 1745193600000000000n
*/
Datetime_global.zerons = function () {
return BigInt(Datetime_global.zeroms()) * 1000000n;
};
/**
* Parses a date string into milliseconds since the epoch, using `Date.parse`.
* @param dateString - The date string to parse (e.g., "2025-04-18", "2025-04-18T00:00:00Z").
* @returns The milliseconds since the epoch, or NaN if invalid.
* @example
* console.log(Datetime_global.parse("2025-04-18T00:00:00Z")); // 1745193600000
*/
Datetime_global.parse = Date.parse;
Datetime_global.prototype[Symbol.toStringTag] = 'Datetime_global';
Datetime_global[Symbol.toStringTag] = 'Datetime_global_constructor';
Datetime_global.prototype.validate = function () {
if (typeof this.time === 'object') {
if (!+(this.time instanceof Temporal.ZonedDateTime) || this.time === null)
throw new TypeError('Datetime_global.prototype.time must be null or a Temporal.ZonedDateTime');
return this.time;
}
else {
throw new TypeError('Datetime_global.prototype.time must be null or a Temporal.ZonedDateTime');
}
};
/**
* converts this Datetime_global to Date
* @returns {Date} the Date with millisecond precision. sub millisecond precision is lost
*/
Datetime_global.prototype.toDate = function () {
return new Date(this.time?.epochMilliseconds ?? NaN);
};
/**
* Creates a new Datetime_global in the specified timezone, preserving the instant in time.
* @param timezoneId - A Temporal.TimeZoneLike or IANA timezone string (e.g., "UTC", "America/New_York").
* @returns A new Datetime_global instance.
* @throws TypeError if timezoneId is invalid.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toTimezone("Asia/Tokyo").toString());
* // "Fri Apr 18 2025 09:00:00 UTC+0900 (Asia/Tokyo)"
*/
Datetime_global.prototype.toTimezone = Datetime_global.prototype.withTimezone = function (timezoneId) {
const time = this.validate();
if (time === null)
throw new RangeError('Datetime_global.prototype.toTimezone requires to be valid');
return new Datetime_global(time.epochNanoseconds, timezoneId);
};
/**
* Retrieves the timezone identifier for this Datetime_global instance.
*
* @returns {string} The timezone identifier (e.g., "America/New_York") associated with the Temporal.ZonedDateTime instance.
*/
Datetime_global.prototype.getTimezoneId = function () {
const time = this.validate();
if (time === null)
throw new RangeError('Datetime_global.prototype.getTimezoneId requires to be valid');
return time.timeZoneId;
};
/**
* Returns the timestamp in milliseconds since the epoch (January 1, 1970, 00:00:00 UTC).
* @returns The milliseconds since the epoch.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.valueOf()); // 1745193600000
*/
Datetime_global.prototype.valueOf = function () {
const time = this.validate();
return time?.epochMilliseconds ?? NaN;
};
/**
*
* Returns the timestamp in milliseconds since the epoch (January 1, 1970, 00:00:00 UTC).
* @returns The milliseconds since the epoch.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.valueOf()); // 1745193600000
* @alias valueOf returning milliseconds since the epoch.
*/
Datetime_global.prototype.getTime = function () {
return this.valueOf();
};
/**
* Sets the timestamp, modifying the instance in place. Use the `Datetime_global` constructor instead.
* @param timestamp - Nanoseconds (bigint) or milliseconds (number) since the epoch.
* @returns The new timestamp in milliseconds since the epoch.
* @example
* const dt = new Datetime_global();
* dt.setTime(1745193600000);
* console.log(dt.toISOString()); // "2025-04-18T00:00:00.000Z"
*/
Datetime_global.prototype.setTime = function (timestamp) {
const time = this.validate();
timestamp = toDateValue(timestamp);
if (!Number.isFinite(timestamp) || time === null) {
this.time = null;
return NaN;
}
this.time = new Temporal.ZonedDateTime(BigInt(timestamp) * 1000000n, time.timeZoneId);
return this.time.epochMilliseconds;
};
/**
* Returns a string representation of the date-time, including timezone offset and ID.
* Format (php): "D M d Y H:i:s \U\T\CO (e)" (e.g., "Fri Apr 18 2025 00:00:00 UTC+0000 (UTC)").
* @returns The formatted string.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toString()); // "Fri Apr 18 2025 00:00:00 UTC+0000 (UTC)"
*/
Datetime_global.prototype.toString = function () {
const time = this.validate();
if (time === null)
return "Invalid Date";
const pad = (n, z = 2) => padNumber(n, z).replace(/^\+/, ''), fullYear = pad(time.withCalendar('iso8601').year, 4);
const offset = Datetime_global.getUTCOffset(this.getTimezoneOffset()), string = `${this.getDayName()} ${this.getMonthName()} ${pad(this.getDate())}`, timeStr = `${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
return `${string} ${fullYear} ${timeStr} ${offset} (${this.timezoneId})`;
};
/**
* Returns an HTML <time> element with the date-time in the browser's local timezone.
* The datetime attribute is in ISO 8601 format (UTC).
* @returns The HTML string (e.g., "<time datetime='2025-04-18T00:00:00.000Z'>Fri Apr 18 2025 00:00:00 UTC</time>").
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toHTML()); // Depends on local timezone
*/
Datetime_global.prototype.toHTML = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return `<time datetime="${date.toISOString()}">${date}</time>`;
};
export function htmlencode(string) {
return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
}
/**
* @deprecated
*/
Datetime_global.prototype.toHTMLFormatted = function (dtg, format) {
return `<time datetime="${dtg.toISOString()}">${htmlencode(dtg.format(format))}</time>`;
};
/*
function htmlencode(string) {
return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
}
const toHTMLFormatted = function(dtg, format) {
return `<time datetime="${dtg.toISOString()}">${htmlencode(dtg.format(format))}</time>`;
};
*/
/**
* Returns an HTML `<time>` element string representing the current datetime in GMT (UTC) format.
*
* The `datetime` attribute is set using the ISO 8601 string (`Date.prototype.toISOString()`),
* and the inner text of the element is the UTC string (`Date.prototype.toUTCString()`).
*
* This is useful for embedding machine-readable and human-readable timestamps in HTML,
* ensuring proper formatting and time zone clarity.
*
* @this {Datetime_global} The `Datetime_global` instance containing the time value.
* @returns {string} A string containing the `<time>` HTML element with GMT time.
*/
Datetime_global.prototype.toHTML_GMT = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
if (!isValid(date))
return `<span>Invalid Date</span>`;
return `<time datetime="${date.toISOString()}">${date.toUTCString()}</time>`;
};
Datetime_global.prototype.toHTML_UTC = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
if (!isValid(date))
return `<span>Invalid Date</span>`;
return `<time datetime="${this.toISOString()}">${this.toUTCString()}</time>`;
};
Datetime_global.prototype.toHTMLHistoryString = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
if (!isValid(date))
return `<span>Invalid Date</span>`;
const options = { smallestUnit: 'seconds', largestUnit: 'years' }, historyString = this.until(new Date, options).toHumanString(2);
return `<time datetime="${this.toISOString()}">${historyString}</time>`;
};
/**
* Returns an HTML <time> element with the date-time formatted in the instance's timezone.
* The datetime attribute is in ISO 8601 format (UTC).
* @returns The HTML string.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toHTMLString());
* // "<time datetime='2025-04-18T00:00:00.000Z'>Fri Apr 18 2025 00:00:00 UTC+0000 (UTC)</time>"
*/
Datetime_global.prototype.toHTMLString = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
if (!isValid(date))
return `<span>Invalid Date</span>`;
return `<time datetime="${date.toISOString()}">${this.toString()}</time>`;
};
/**
* Returns the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday).
*
* will convert to the iso8601 calendar
* @returns The day of the week (0-6).
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC"); // Friday
* console.log(dt.getDay()); // 5
*/
Datetime_global.prototype.getDay = function () {
const this_time = this.validate();
if (this_time === null)
return NaN;
const time = this_time.withCalendar('iso8601');
return time.dayOfWeek % time.daysInWeek;
};
/**
* returns the true day of week arcording to the specified calendar
*/
Datetime_global.prototype.getDayOfWeek = function () {
return this.validate()?.dayOfWeek ?? NaN;
};
/**
* Returns the year minus 1900 (e.g., 2025 -> 125) only iso8601 calendar.
* @returns The year minus 1900.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getYear()); // 125
*/
Datetime_global.prototype.getYear = function () {
return (this.validate()?.withCalendar('iso8601').year ?? NaN) - 1900;
};
/**
* Returns the full four-digit year (e.g., 2025).
* @returns The year.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getFullYear()); // 2025
*/
Datetime_global.prototype.getFullYear = function () {
return this.validate()?.year ?? NaN;
};
/**
* Returns the month (0 = January, 1 = February, ..., 11 = December).
* @returns The month (0-11).
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getMonth()); // 3
*/
Datetime_global.prototype.getMonth = function () {
return (this.validate()?.month ?? NaN) - 1;
};
/**
* Returns the day of the month (1-31).
* @returns The day of the month.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getDate()); // 18
*/
Datetime_global.prototype.getDate = function () {
return this.validate()?.day ?? NaN;
};
/**
* Returns the hour (0-23).
* @returns The hour.
* @example
* const dt = new Datetime_global("2025-04-18T15:00:00Z", "UTC");
* console.log(dt.getHours()); // 15
*/
Datetime_global.prototype.getHours = function () {
return this.validate()?.hour ?? NaN;
};
/**
* Returns the minute (0-59).
* @returns The minute.
* @example
* const dt = new Datetime_global("2025-04-18T15:30:00Z", "UTC");
* console.log(dt.getMinutes()); // 30
*/
Datetime_global.prototype.getMinutes = function () {
return this.validate()?.minute ?? NaN;
};
/**
* Returns the second (0-59).
* @returns The second.
* @example
* const dt = new Datetime_global("2025-04-18T15:30:45Z", "UTC");
* console.log(dt.getSeconds()); // 45
*/
Datetime_global.prototype.getSeconds = function () {
return this.validate()?.second ?? NaN;
};
/**
* Returns the millisecond (0-999).
* @returns The millisecond.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00.123Z", "UTC");
* console.log(dt.getMilliseconds()); // 123
*/
Datetime_global.prototype.getMilliseconds = function () {
return this.validate()?.millisecond ?? NaN;
};
// builtin-proxy-UTC
/**
* Returns the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday) in UTC.
* @returns The day of the week (0-6).
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC"); // Friday
* console.log(dt.getUTCDay()); // 5
*/
Datetime_global.prototype.getUTCDay = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCDay();
};
/**
* Returns the year minus 1900 (e.g., 2025 -> 125) in UTC.
*
* will convert to Date's calendar
* @returns The year minus 1900. in UTC
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getYear()); // 125
*/
Datetime_global.prototype.getUTCYear = function () {
return this.getUTCFullYear() - 1900;
};
/**
* Returns the year in UTC.
*
* will convert to Date's calendar
* @returns The year minus in UTC
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getUTCFullYear()); // 2025
*/
Datetime_global.prototype.getUTCFullYear = function () {
return this.toDate().getUTCFullYear();
};
/**
* Returns the month (0 = January, 1 = February, ..., 11 = December) in utc.
*
* will convert to Date's calendar
* @returns The month (0-11) in utc.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getMonth()); // 3
*/
Datetime_global.prototype.getUTCMonth = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCMonth();
};
/**
* Returns the day of the month (1-31) in utc.
*
* will convert to Date's calendar
* @returns The day of the month in utc.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getDate()); // 18
*/
Datetime_global.prototype.getUTCDate = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCDate();
};
/**
* Returns the hour (0-23) in utc.
*
* will convert to Date's calendar
* @returns The hour in utc.
* @example
* const dt = new Datetime_global("2025-04-18T15:00:00Z", "UTC");
* console.log(dt.getHours()); // 15
*/
Datetime_global.prototype.getUTCHours = function () {
return new Date(this.validate()?.epochMilliseconds ?? NaN).getUTCHours();
};
/**
* Returns the minute (0-59) in utc.
*
* will convert to Date's calendar
* @returns The minute in utc.
* @example
* const dt = new Datetime_global("2025-04-18T15:30:00Z", "UTC");
* console.log(dt.getMinutes()); // 30
*/
Datetime_global.prototype.getUTCMinutes = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCMinutes();
};
/**
* Returns the second (0-59) in utc.
*
* will convert to Date's calendar
* @returns The second in utc.
* @example
* const dt = new Datetime_global("2025-04-18T15:30:45Z", "UTC");
* console.log(dt.getSeconds()); // 45
*/
Datetime_global.prototype.getUTCSeconds = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCSeconds();
};
/**
* Returns the millisecond (0-999) in utc.
*
* will convert to Date's calendar
* @returns The millisecond in utc.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00.123Z", "UTC");
* console.log(dt.getMilliseconds()); // 123
*/
Datetime_global.prototype.getUTCMilliseconds = function () {
const date = new Date(this.validate()?.epochMilliseconds ?? NaN);
return date.getUTCMilliseconds();
};
/**
* Returns the timezone offset in minutes (positive for west of UTC, negative for east).
* @returns The offset in minutes.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "America/New_York");
* console.log(dt.getTimezoneOffset()); // 240 (4 hours west)
*/
Datetime_global.prototype.getTimezoneOffset = function () {
const this_time = this.validate();
if (this_time === null)
return NaN;
return -Math.trunc((Number(this_time.offsetNanoseconds) / 1e9) / 60);
};
/**
* Returns the date-time as an ISO 8601 string in UTC (e.g., "2025-04-18T12:34:56.789Z").
* Matches Date.prototype.toISOString, with millisecond precision.
* @returns A string in ISO 8601 format.
*/
Datetime_global.prototype.toISOString = function () {
return this.toDate().toISOString();
};
/**
* Returns the date-time as an ISO 8601 string with timezone (e.g., "2025-04-18T00:00:00+00:00[UTC]").
* @returns The ISO 8601 string.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toJSON()); // "2025-04-18T00:00:00+00:00[UTC]"
*/
Datetime_global.prototype.toJSON = function () {
return this.toISOString();
};
/**
* Sets the year, optionally month and day, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent months/years.
* @param fullYear - The year (e.g., 2025).
* @param month - The month (0-11; optional, defaults to current month).
* @param date - The day of the month (1-31; optional, defaults to current day).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setFullYear(2026);
* console.log(dt.toISOString()); // "2026-04-18T00:00:00.000Z"
*/
Datetime_global.prototype.setFullYear = function (fullYear, month, date) {
const this_time = this.validate();
date = (((arguments.length > 1 ? date : this_time?.day))) ?? NaN;
month = (arguments.length > 2 ? month : this_time?.month) ?? NaN;
if (NaNInArray([toDateValue(this_time?.epochMilliseconds), date, month]) || this_time === null) {
this.time = null;
return NaN;
}
const year = fullYear, try_date = {
year: toNumber(year), day: toNumber(date), month: toNumber(month) + 1,
}, self_time = overflowDatetime_global(this, try_date);
return (this.time = self_time).epochMilliseconds;
};
function toNumber(mixed) {
return toDateValue(mixed);
}
/**
* Sets the month, optionally day, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent months/years.
* @param month - The month (0-11).
* @param date - The day of the month (1-31; optional, defaults to current day).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setMonth(5); // June
* console.log(dt.toISOString()); // "2025-06-18T00:00:00.000Z"
*/
Datetime_global.prototype.setMonth = function (month, date) {
date = arguments.length > 1 ? date : this.getDate();
return this.setFullYear(this.getFullYear(), month, date);
};
/**
* Sets the day of the month, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent months.
* @param date - The day of the month (1-31).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setDate(19);
* console.log(dt.toISOString()); // "2025-04-19T00:00:00.000Z"
*/
Datetime_global.prototype.setDate = function (date) {
return this.setFullYear(this.getFullYear(), this.getMonth(), date);
};
/**
* Sets the hours, minutes, seconds, and milliseconds, modifying the object in place.
* Handles overflow/underflow by rolling over to the next or previous day/month/year.
* Sub-millisecond precision (microseconds, nanoseconds) is preserved.
* @param hours - The hours to set (e.g., 0-23; 25 rolls over to next day's 1:00).
* @param minutes - The minutes to set (0-59; optional, defaults to current minutes).
* @param seconds - The seconds to set (0-59; optional, defaults to current seconds).
* @param milliseconds - The milliseconds to set (0-999; optional, defaults to current milliseconds).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes forever loop to be reached.
* @throws TypeError if inputs are non-numeric.
*/
Datetime_global.prototype.setHours = function (hours, minutes, seconds, milliseconds) {
const this_time = this.validate();
if (!Number.isFinite(toDateValue(this_time?.epochMilliseconds)) || this_time === null) {
this.time = null;
return NaN;
}
minutes = arguments.length > 1 ? minutes : this_time.minute;
seconds = arguments.length > 2 ? seconds : this_time.second;
milliseconds = arguments.length > 3 ? milliseconds : this_time.millisecond;
if (NaNInArray([hours, minutes, seconds, milliseconds]) || this_time === null) {
this.time = null;
return NaN;
}
const try_time = {
hour: toNumber(hours),
minute: toNumber(minutes),
second: toNumber(seconds),
millisecond: toNumber(milliseconds),
}, time = overflowDatetime_global(this, try_time);
return (this.time = time).epochMilliseconds;
};
/**
* Sets the minutes, optionally seconds and milliseconds, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent hours/days.
* @param minutes - The minutes (0-59).
* @param seconds - The seconds (0-59; optional, defaults to current seconds).
* @param milliseconds - The milliseconds (0-999; optional, defaults to current milliseconds).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setMinutes(45);
* console.log(dt.toISOString()); // "2025-04-18T00:45:00.000Z"
*/
Datetime_global.prototype.setMinutes = function (minutes, seconds, milliseconds) {
seconds = arguments.length > 1 ? seconds : this.getSeconds();
milliseconds = arguments.length > 2 ? milliseconds : this.getMilliseconds();
return this.setHours(this.getHours(), minutes, seconds, milliseconds);
};
/**
* Sets the seconds, optionally milliseconds, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent minutes/hours.
* @param seconds - The seconds (0-59).
* @param milliseconds - The milliseconds (0-999; optional, defaults to current milliseconds).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setSeconds(45);
* console.log(dt.toISOString()); // "2025-04-18T00:00:45.000Z"
*/
Datetime_global.prototype.setSeconds = function (seconds, milliseconds) {
milliseconds = arguments.length > 1 ? milliseconds : this.getMilliseconds();
return this.setHours(this.getHours(), this.getMinutes(), seconds, milliseconds);
};
/**
* Sets the milliseconds, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent seconds.
* @param milliseconds - The milliseconds (0-999).
* @returns The new timestamp in milliseconds since the epoch.
* @throws Error if excessive overflow causes recursion limit to be reached.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setMilliseconds(500);
* console.log(dt.toISOString()); // "2025-04-18T00:00:00.500Z"
*/
Datetime_global.prototype.setMilliseconds = function (milliseconds) {
return this.setHours(this.getHours(), this.getMinutes(), this.getSeconds(), milliseconds);
};
// UTC
/**
* Sets the year, optionally month and day, in UTC, modifying the instance in place.
*
* will convert to Date's calendar
* @param fullYear - The year (e.g., 2025).
* @param month - The month (0-11; optional, defaults to current UTC month).
* @param date - The day of the month (1-31; optional, defaults to current UTC day).
* @returns The new timestamp in milliseconds since the epoch.
* @throws RangeError if the components form an invalid date.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setUTCFullYear(2026);
* console.log(dt.toISOString()); // "2026-04-18T00:00:00.000Z"
*/
Datetime_global.prototype.setUTCFullYear = function (fullYear, month, date) {
const this_time = this.validate();
if (!Number.isFinite(toDateValue(this_time?.epochMilliseconds)) || this_time === null) {
this.time = null;
return NaN;
}
const nanosecond = BigInt(this_time.nanosecond), microsecond = BigInt(this_time.microsecond) * 1000n, datetime = new Date(this_time.epochMilliseconds);
month = arguments.length > 1 ? month : datetime.getUTCMonth();
date = arguments.length > 2 ? date : datetime.getUTCDate();
if (NaNInArray([month, date])) {
this.time = null;
return NaN;
}
const rtV = toDateValue(datetime.setUTCFullYear(fullYear, month, date));
if (isNaN(rtV)) {
this.time = null;
return NaN;
}
const returnValue = BigInt(rtV);
this.time = new Temporal.ZonedDateTime(((returnValue * 1000000n) + microsecond + nanosecond), this.timezoneId);
return Number(returnValue);
};
/**
* Sets the hours, optionally minutes, seconds, and milliseconds, in UTC, modifying the instance in place.
* Handles overflow/underflow by rolling over to adjacent days.
*
* will convert to Date's calendar
* @param hours - The hours (e.g., 0-23; 25 rolls over to next day's 1:00).
* @param minutes - The minutes (0-59; optional, defaults to current UTC minutes).
* @param seconds - The seconds (0-59; optional, defaults to current UTC seconds).
* @param milliseconds - The milliseconds (0-999; optional, defaults to current UTC milliseconds).
* @returns The new timestamp in milliseconds since the epoch.
* @throws RangeError if the components form an invalid date.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setUTCHours(15);
* console.log(dt.toISOString()); // "2025-04-18T15:00:00.000Z"
*/
Datetime_global.prototype.setUTCHours = function (hours, minutes, seconds, milliseconds) {
const this_time = this.validate();
if (!Number.isFinite(toDateValue(this_time?.epochMilliseconds)) || this_time === null) {
this.time = null;
return NaN;
}
const nanosecond = BigInt(this_time.nanosecond), microsecond = BigInt(this_time.microsecond) * 1000n, date = new Date(this_time.epochMilliseconds);
minutes = arguments.length > 1 ? minutes : date.getUTCMinutes();
seconds = arguments.length > 2 ? seconds : date.getUTCSeconds();
milliseconds = arguments.length > 3 ? milliseconds : date.getUTCMilliseconds();
if (NaNInArray([minutes, seconds, seconds])) {
this.time = null;
return NaN;
}
const rtV = toDateValue(date.setUTCHours(hours, minutes, seconds, milliseconds));
if (isNaN(rtV)) {
this.time = null;
return NaN;
}
const returnValue = BigInt(rtV);
this.time = new Temporal.ZonedDateTime(((returnValue * 1000000n) + microsecond + nanosecond), this.timezoneId);
return Number(returnValue);
};
/**
* sets the month based on `Date.prototype.setUTCFullYear`
* @param month the zero indexed month (0 to 11)
* @param date the day of the month.
*/
Datetime_global.prototype.setUTCMonth = function (month, date) {
date = arguments.length > 1 ? date : this.getUTCDate();
return this.setUTCFullYear(this.getUTCFullYear(), month, date);
};
/**
* sets the day based on `Date.prototype.setUTCFullYear`
* @param date the day of the month.
*/
Datetime_global.prototype.setUTCDate = function (date) {
return this.setUTCFullYear(this.getUTCFullYear(), this.getUTCMonth(), date);
};
/**
* sets the minutes based on `Date.prototype.setUTCHours`
* @param minutes the minutes (0 to 59)
* @param seconds the seconds (0 to 59)
* @param milliseconds the milliseconds (0 to 999)
*/
Datetime_global.prototype.setUTCMinutes = function (minutes, seconds, milliseconds) {
seconds = arguments.length > 1 ? seconds : this.getUTCSeconds();
milliseconds = arguments.length > 2 ? milliseconds : this.getUTCMilliseconds();
return this.setUTCHours(this.getUTCHours(), minutes, seconds, milliseconds);
};
/**
* sets the seconds based on `Date.prototype.setUTCHours`
* @param seconds the seconds (0 to 59)
* @param milliseconds the milliseconds (0 to 999)
*/
Datetime_global.prototype.setUTCSeconds = function (seconds, milliseconds) {
milliseconds = arguments.length > 1 ? milliseconds : this.getUTCMilliseconds();
return this.setUTCHours(this.getUTCHours(), this.getUTCMinutes(), seconds, milliseconds);
};
/**
* Returns the internal Temporal.ZonedDateTime instance.
* @returns The Temporal.ZonedDateTime.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.toTemporalZonedDateTime().toString());
* // "2025-04-18T00:00:00+00:00[UTC]"
*/
Datetime_global.prototype.toTemporalZonedDateTime = function () {
const zdt = this.validate();
if (zdt === null)
throw new RangeError('Datetime_global.prototype.toTemporalZonedDateTime requires the datetime to be valid');
return zdt;
};
// custom
/**
* Sets the nanoseconds and optionally microseconds, modifying the instance in place.
* Preserves other date-time components.
* @param nanoseconds - The nanoseconds to set (default 0).
* @param microseconds - The microseconds to set (optional, defaults to current microseconds).
* @returns The new timestamp in nanoseconds since the epoch.
* @throws TypeError if inputs are not bigints.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* dt.setNanoseconds(500n);
* console.log(dt.getNanoseconds()); // 500n
*/
Datetime_global.prototype.setNanoseconds = function (nanoseconds = 0n, microseconds) {
const self_time = this.validate(), did_specify_microseconds = arguments.length > 1;
if (self_time === null)
throw new TypeError('cannot set nanoseconds on invalid object');
microseconds = BigInt(did_specify_microseconds ? (microseconds ?? 0) : self_time.microsecond);
return BigInt((this.time = new Temporal.ZonedDateTime(self_time.with({
microsecond: did_specify_microseconds ? 0 : self_time.microsecond,
nanosecond: 0, millisecond: self_time.millisecond,
}).epochNanoseconds + ((microseconds * 1000n) + BigInt(nanoseconds)), self_time.timeZoneId)).epochNanoseconds);
};
/**
* Returns the nanosecond and microseconds component of the date-time (0-999).
* @returns The nanoseconds and microseconds as a bigint.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00.000000500Z", "UTC");
* console.log(dt.getNanoseconds()); // 500n
*/
Datetime_global.prototype.getUTCNanoseconds = Datetime_global.prototype.getNanoseconds = function () {
const this_time = this.validate();
if (this_time === null)
throw new TypeError('cannot get nanoseconds on invalid object');
return (BigInt(this_time.microsecond) * 1000n) + BigInt(this_time.nanosecond);
};
/**
* Formats a timezone offset in minutes as a UTC string (e.g., "UTC+0000").
* @param offset - The offset in minutes (positive for west of UTC, negative for east).
* @returns The formatted offset string.
* @example
* console.log(Datetime_global.getUTCOffset(240)); // "UTC-0400"
*/
Datetime_global.getUTCOffset = function (offset) {
if (isNaN(offset))
return 'UTC+0000';
const sign = offset > 0 ? "-" : "+", absOffset = Math.abs(offset);
const pad = (n) => padNumber(n).replace(/^\+/, '');
const hours = pad(Math.trunc(absOffset / 60));
const minutes = pad(absOffset % 60);
return `UTC${sign}${hours}${minutes}`;
};
/**
* Updates the innerText of HTML <time> elements with formatted date-time strings.
* Uses the `data-datetime-global-format` attribute for the format (default: "D M d Y H:i:s \U\T\CO (e)")
* and `data-iana-timezone` for the timezone (default: local timezone).
* @param timetags - A NodeList or Array of HTMLTimeElement.
* @throws RangeError if a time element's dateTime attribute is invalid.
* @example
* // HTML: <time datetime="2025-04-18T00:00:00Z" data-datetime-global-format="[offsetFromNow]"></time>
* Datetime_global.htmlToCurrentTime(document.querySelectorAll('time'));
* // Updates innerText to e.g., "in 5 days"
*/
Datetime_global.htmlToCurrentTime = function (timetags = []) {
const now = Temporal.Now.zonedDateTimeISO();
Array.from(timetags).forEach(function (each) {
const tz = each.getAttribute('data-iana-timezone') ?? Temporal.Now.timeZoneId(), d = new Datetime_global(Date.parse(each.dateTime), tz), f = each.getAttribute('data-datetime-global-format') ?? 'D M d Y H:i:s \\U\\T\\CO (e)';
switch (f) {
case "[TemporalOffset]":
{
const target = d.toTemporalZonedDateTime();
const isFuture = target.epochMilliseconds > now.epochMilliseconds, largestUnit = 'days';
const duration = isFuture ? now.until(target, { largestUnit }) : target.until(now, { largestUnit });
const parts = [];
if (duration.days)
parts.push(`${duration.days} day${duration.days !== 1 ? 's' : ''}`);
if (duration.hours)
parts.push(`${duration.hours} hour${duration.hours !== 1 ? 's' : ''}`);
if (duration.minutes)
parts.push(`${duration.minutes} minute${duration.minutes !== 1 ? 's' : ''}`);
if (duration.seconds && parts.length === 0)
parts.push(`${duration.seconds} second${duration.seconds !== 1 ? 's' : ''}`);
const join = parts.map(s => s.trim()).join(', ');
each.innerText = isFuture ? 'in ' + join : join + ' ago';
}
return;
}
each.innerText = d.format(f);
});
};
/**
* Formats the time difference between now and the given date as a human-readable string.
* @param date The timestamp (in milliseconds) to compare against the current time.
* @returns A string like "now", "in 30 seconds", or "5 minutes ago".
*/
export const formatOffsetFromNow = function (date) {
const diffMs = (+date) - Date.now();
const absDiffMs = Math.abs(diffMs);
const isFuture = diffMs > 0;
const seconds = Math.floor(absDiffMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (absDiffMs < 1000) {
return "now";
}
else if (seconds < 60) {
return `${isFuture ? "in " : ""}${seconds} second${seconds === 1 ? "" : "s"}${isFuture ? "" : " ago"}`;
}
else if (minutes < 60) {
return `${isFuture ? "in " : ""}${minutes} minute${minutes === 1 ? "" : "s"}${isFuture ? "" : " ago"}`;
}
else if (hours < 24) {
return `${isFuture ? "in " : ""}${hours} hour${hours === 1 ? "" : "s"}${isFuture ? "" : " ago"}`;
}
else {
return `${isFuture ? "in " : ""}${days} day${days === 1 ? "" : "s"}${isFuture ? "" : " ago"}`;
}
};
/**
* applies the UTC time to the `Datetime_global`
*
* @returns {Datetime_global}
*/
Datetime_global.prototype.toUTCTimezone = function () {
return this.toTimezone('UTC');
};
/**
* applies the system's LocalTime to the `Datetime_global`
*
* @returns {Datetime_global}
*/
Datetime_global.prototype.toLocalTime = function () {
return this.toTimezone(Temporal.Now.timeZoneId());
};
/**
* Returns the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday).
* @alias getDay
* @returns The day of the week (0-6).
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC"); // Friday
* console.log(dt.getDayNumberWeek()); // 5
*/
Datetime_global.prototype.getDayNumberWeek = function () {
return this.getDay();
};
/**
* Returns the day of the month (1-31).
* @alias getDate
* @returns The day of the month.
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getDayNumberMonth()); // 18
*/
Datetime_global.prototype.getDayNumberMonth = Datetime_global.prototype.getDayNumber = function () {
return this.getDate();
};
/**
* Returns the abbreviated weekday name (e.g., "Mon", "Tue") in en-US locale.
* @returns The weekday name. (oneOf "Sun" | "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" )
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC"); // Friday
* console.log(dt.getDayName()); // "Fri"
*/
Datetime_global.prototype.getDayName = function () {
return Datetime_global.daynames[this.getDay()];
};
/**
* Returns the abbreviated month name (e.g., "Jan", "Feb") in en-US locale.
* @returns The month name. (oneOf "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" )
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getMonthName()); // "Apr"
*/
Datetime_global.prototype.getMonthName = function () {
return Datetime_global.monthnames[this.withCalender('iso8601').getMonth()];
};
/**
* Returns the full weekday name (e.g., "Monday", "Tuesday") in en-US locale.
* @returns The weekday name. (OneOf "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday")
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC"); // Friday
* console.log(dt.getFullDayName()); // "Friday"
*/
Datetime_global.prototype.getFullDayName = function () {
return Datetime_global.daynamesFull[this.getDay()];
};
/**
* Returns the full month name (e.g., "January", "February") in en-US locale.
* @returns The full month name. (oneOf "January" | "February" | "March" | "April" | "May" | "June" | "July" | "August" | "September" | "October" | "November" | "December")
* @example
* const dt = new Datetime_global("2025-04-18T00:00:00Z", "UTC");
* console.log(dt.getFullMonthName()); // "April"
*/
Datetime_global.prototype.getFullMonthName = function () {
return Datetime_global.monthnamesFull[this.withCalender('iso8601').getMonth()];
};
/**
* @see Temporal.ZonedDateTime.toLocaleString
* @param locales
* @param options
*/
Datetime_global.prototype.toLocaleString = function (locales, options) {
return this.time?.toLocaleString(locales, options) ?? "Invalid Date";
};
/**
* Converts the internal datetime value to a GMT (UTC) string representation.
*
* This method provides a standardized, human-readable string of the datetime
* in Greenwich Mean Time (UTC), following the format produced by
* `Date.prototype.toUTCString()`. It is primarily intended for display purposes
* where a clear, unambiguous representation of the time in UTC is required.
*
* Internally, this method delegates to `toDate()`, which is expected to return a
* native JavaScript `Date` instance representing the same point in time as this
* `Datetime_global` object.
*
* #### Example
* ```js
* const dt = new Datetime_global(...);
* console.log(dt.toGMTString());
* // Output: "Mon, 09 Jun 2025 12:00:00 GMT"
* ```
*
* @method toGMTString
* @memberof Datetime_global
* @instance
* @returns {string} A GMT-formatted date string compliant with `Date