src / timeHelpers.ts
export type DateFormat = "iso" | "human" | "unix";
export interface TimeDescription {
local_datetime: string;
weekday: string;
timezone: string;
locale: string;
iso_utc: string;
unix_seconds: number;
formatted: string;
}
export function describeTime(
date: Date,
timezone: string,
locale: string,
format: DateFormat,
): TimeDescription {
const tz = timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
const loc = locale || Intl.DateTimeFormat().resolvedOptions().locale;
const machineParts = new Intl.DateTimeFormat("en-CA", {
timeZone: tz,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
weekday: "long",
timeZoneName: "longOffset",
}).formatToParts(date);
const get = (t: string) => machineParts.find((p) => p.type === t)?.value ?? "";
const local_datetime = `${get("year")}-${get("month")}-${get("day")} ${get("hour")}:${get("minute")}:${get("second")}`;
const weekday = get("weekday");
const offset = (get("timeZoneName") || "").replace(/^GMT/, "") || "Z";
let formatted: string;
switch (format) {
case "iso":
formatted = `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}${offset}`;
break;
case "human":
formatted = new Intl.DateTimeFormat(loc, {
timeZone: tz,
dateStyle: "full",
timeStyle: "long",
}).format(date);
break;
case "unix":
formatted = String(Math.floor(date.getTime() / 1000));
break;
}
return {
local_datetime,
weekday,
timezone: tz,
locale: loc,
iso_utc: date.toISOString(),
unix_seconds: Math.floor(date.getTime() / 1000),
formatted,
};
}
export function isValidTimezone(tz: string): boolean {
try {
new Intl.DateTimeFormat("en-US", { timeZone: tz }).format(new Date());
return true;
} catch {
return false;
}
}