// Convert a datetime-local string ("YYYY-MM-DDTHH:MM") to a UTC Date, // interpreting the wall-clock time in the given IANA timezone. export function zonedLocalToUTC(localStr, tz) { if (!localStr || !tz) return null; const [datePart, timePart] = String(localStr).split("T"); if (!datePart || !timePart) return null; const [y, mo, d] = datePart.split("-").map(Number); const [h, mi] = timePart.split(":").map(Number); if ([y, mo, d, h, mi].some((n) => Number.isNaN(n))) return null; // Treat the components as if they are already UTC. The result's wall-clock // in the target TZ will differ from what we want by exactly the TZ offset // for that moment, so we measure that offset and subtract it. const asUTC = new Date(Date.UTC(y, mo - 1, d, h, mi)); const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).formatToParts(asUTC); const get = (type) => Number(parts.find((p) => p.type === type)?.value); const observed = Date.UTC( get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"), ); const offsetMs = observed - asUTC.getTime(); return new Date(asUTC.getTime() - offsetMs); } // Convert a UTC Date (or ISO string) to a datetime-local string // ("YYYY-MM-DDTHH:MM") rendered in the given IANA timezone. export function utcToZonedLocal(utc, tz) { if (!utc || !tz) return ""; const d = utc instanceof Date ? utc : new Date(utc); if (Number.isNaN(d.getTime())) return ""; const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }).formatToParts(d); const get = (type) => parts.find((p) => p.type === type)?.value; const year = get("year"); const month = get("month"); const day = get("day"); let hour = get("hour"); const minute = get("minute"); if (hour === "24") hour = "00"; return `${year}-${month}-${day}T${hour}:${minute}`; } // Short timezone label (e.g., "EDT", "PDT") for a Date in a given IANA TZ. export function shortTimezoneName(date, tz) { if (!date || !tz) return ""; const d = date instanceof Date ? date : new Date(date); if (Number.isNaN(d.getTime())) return ""; try { const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short", }).formatToParts(d); return parts.find((p) => p.type === "timeZoneName")?.value || ""; } catch { return ""; } }