// Offer dates are stored in UTC and edited in the shop's timezone (brief ยง3). function offsetMinutes(timeZone: string, at: Date) { try { const parts = new Intl.DateTimeFormat("en-US", { timeZone, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", }).formatToParts(at); const p = Object.fromEntries(parts.map((x) => [x.type, Number(x.value)])); const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second); return Math.round((asUtc - at.getTime()) / 60000); } catch { return 0; } } /** "2026-10-20T00:00" in the shop timezone -> UTC ISO string (null for empty). */ export function shopLocalToUtc(local: string | null | undefined, timeZone: string): string | null { if (!local) return null; const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(local); if (!m) return null; const guess = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]); let t = guess - offsetMinutes(timeZone, new Date(guess)) * 60000; t = guess - offsetMinutes(timeZone, new Date(t)) * 60000; // second pass across DST changes return new Date(t).toISOString(); } /** UTC -> "YYYY-MM-DDTHH:mm" in the shop timezone, for . */ export function utcToShopLocal(utc: Date | string | null | undefined, timeZone: string): string { if (!utc) return ""; const d = new Date(utc); const local = new Date(d.getTime() + offsetMinutes(timeZone, d) * 60000); return local.toISOString().slice(0, 16); } export function formatShopDate(utc: Date | string | null | undefined, timeZone: string, withTime = true) { if (!utc) return ""; try { return new Intl.DateTimeFormat("en", { timeZone, dateStyle: "medium", ...(withTime ? { timeStyle: "short" } : {}), }).format(new Date(utc)); } catch { return new Date(utc).toISOString(); } }