time_zone_util: Avoid relying on timeZoneName: "longOffset".

Safari doesn’t support it yet.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-11-28 22:34:34 -08:00 committed by Tim Abbott
parent 3eb5b20c41
commit 07bd1f28d1
2 changed files with 31 additions and 16 deletions

View File

@ -1,30 +1,41 @@
import assert from "minimalistic-assert";
const parsable_formats = new Map<string, Intl.DateTimeFormat>();
const offset_formats = new Map<string, Intl.DateTimeFormat>();
function get_offset_format(time_zone: string): Intl.DateTimeFormat {
let format = offset_formats.get(time_zone);
function get_parsable_format(time_zone: string): Intl.DateTimeFormat {
let format = parsable_formats.get(time_zone);
if (format === undefined) {
format = new Intl.DateTimeFormat("en-US", {
timeZoneName: "longOffset",
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hourCycle: "h23",
timeZone: time_zone,
});
offset_formats.set(time_zone, format);
parsable_formats.set(time_zone, format);
}
return format;
}
/** Get the given time zone's offset in milliseconds at the given date. */
export function get_offset(date: number | Date, time_zone: string): number {
const offset_string = get_offset_format(time_zone)
.formatToParts(date)
.find((part) => part.type === "timeZoneName")!.value;
if (offset_string === "GMT") {
return 0;
}
const m = /^GMT(([+-])\d\d):(\d\d)$/.exec(offset_string);
assert(m !== null, offset_string);
return (Number(m[1]) * 60 + Number(m[2] + m[3])) * 60000;
const parts = Object.fromEntries(
get_parsable_format(time_zone)
.formatToParts(date)
.map((part) => [part.type, part.value]),
);
return (
Date.UTC(
Number(parts.year),
Number(parts.month) - 1,
Number(parts.day),
Number(parts.hour),
Number(parts.minute),
Number(parts.second),
) -
(Number(date) - (Number(date) % 1000))
);
}
/** Get the start of the day for the given date in the given time zone. */

View File

@ -23,6 +23,7 @@ const chatham_dst_begin = new Date("2022-09-24T14:00Z");
const chatham_dst_end = new Date("2023-04-01T14:00Z");
const kiritimati = "Pacific/Kiritimati";
const kiritimati_date_skip = new Date("1994-12-31T10:00Z");
const fractional_date = new Date("2023-01-02T03:04:05.678Z");
run_test("get_offset", () => {
assert.equal(get_offset(ny_new_year, "UTC"), 0);
@ -37,6 +38,9 @@ run_test("get_offset", () => {
assert.equal(get_offset(chatham_dst_end, chatham), (12 * 60 + 45) * 60000);
assert.equal(get_offset(pre(kiritimati_date_skip), kiritimati), -10 * 60 * 60000);
assert.equal(get_offset(kiritimati_date_skip, kiritimati), 14 * 60 * 60000);
assert.equal(get_offset(fractional_date, "UTC"), 0);
assert.equal(get_offset(fractional_date, ny), -5 * 60 * 60000);
assert.equal(get_offset(fractional_date, kiritimati), 14 * 60 * 60000);
});
run_test("start_of_day", () => {