2025-11-13 13:13:34 -07:00

99 lines
3.0 KiB
JavaScript

import { TZDateMini } from "./mini.js";
/**
* UTC date class. It maps getters and setters to corresponding UTC methods,
* forcing all calculations in the UTC time zone.
*
* Combined with date-fns, it allows using the class the same way as
* the original date class.
*
* This complete version provides not only getters, setters,
* and `getTimezoneOffset`, but also the formatter functions, mirroring
* all original `Date` functionality. Use this version when you need to format
* a string or in an environment you don't fully control (a library).
* For a minimal version, see `UTCDateMini`.
*/
export class TZDate extends TZDateMini {
//#region static
static tz(tz, ...args) {
return args.length ? new TZDate(...args, tz) : new TZDate(Date.now(), tz);
}
//#endregion
//#region representation
toISOString() {
const [sign, hours, minutes] = this.tzComponents();
const tz = `${sign}${hours}:${minutes}`;
return this.internal.toISOString().slice(0, -1) + tz;
}
toString() {
// "Tue Aug 13 2024 07:50:19 GMT+0800 (Singapore Standard Time)";
return `${this.toDateString()} ${this.toTimeString()}`;
}
toDateString() {
// toUTCString returns RFC 7231 ("Mon, 12 Aug 2024 23:36:08 GMT")
const [day, date, month, year] = this.internal.toUTCString().split(" ");
// "Tue Aug 13 2024"
return `${day?.slice(0, -1) /* Remove "," */} ${month} ${date} ${year}`;
}
toTimeString() {
// toUTCString returns RFC 7231 ("Mon, 12 Aug 2024 23:36:08 GMT")
const time = this.internal.toUTCString().split(" ")[4];
const [sign, hours, minutes] = this.tzComponents();
// "07:42:23 GMT+0800 (Singapore Standard Time)"
return `${time} GMT${sign}${hours}${minutes} (${tzName(this.timeZone, this)})`;
}
toLocaleString(locales, options) {
return Date.prototype.toLocaleString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
toLocaleDateString(locales, options) {
return Date.prototype.toLocaleDateString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
toLocaleTimeString(locales, options) {
return Date.prototype.toLocaleTimeString.call(this, locales, {
...options,
timeZone: options?.timeZone || this.timeZone
});
}
//#endregion
//#region private
tzComponents() {
const offset = this.getTimezoneOffset();
const sign = offset > 0 ? "-" : "+";
const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, "0");
const minutes = String(Math.abs(offset) % 60).padStart(2, "0");
return [sign, hours, minutes];
}
//#endregion
withTimeZone(timeZone) {
return new TZDate(+this, timeZone);
}
//#region date-fns integration
[Symbol.for("constructDateFrom")](date) {
return new TZDate(+new Date(date), this.timeZone);
}
//#endregion
}
function tzName(tz, date) {
return new Intl.DateTimeFormat("en-GB", {
timeZone: tz,
timeZoneName: "long"
}).format(date).slice(12);
}