"use client"; import { useSession } from "@/lib/auth-client"; // Public-readable default (Next.js exposes NEXT_PUBLIC_* to the browser). // Operators can set this in .env.local to match the server-side // DEFAULT_TIMEZONE. If unset, both server and client default to 'UTC'. function getClientDefaultTimezone(): string { return process.env.NEXT_PUBLIC_DEFAULT_TIMEZONE || "UTC"; } function isValidIanaTimezone(tz: unknown): tz is string { if (typeof tz !== "string" || tz.length === 0 || tz.length > 64) return false; try { return Intl.supportedValuesOf("timeZone").includes(tz); } catch { return false; } } /** * useUserTimezone — TZ-04. * * Returns the calling user's IANA timezone string, sourced from the * Better Auth additionalField on `useSession()`. Falls back to * `process.env.NEXT_PUBLIC_DEFAULT_TIMEZONE || 'UTC'` while the session * is loading, when the field is missing, or when the stored value is * not a recognized IANA zone. * * This is the ONLY supported way to read the user's tz on the client. * Do NOT call `Intl.DateTimeFormat()` with a hardcoded zone or rely on * the browser's local zone — the user may have travelled or set a * preference that differs from the device. */ export function useUserTimezone(): string { const { data } = useSession(); // Better Auth additionalField is typed `string` post-Plan-01; defence // in depth: validate before returning. const raw = (data?.user as { timezone?: unknown } | undefined)?.timezone; if (isValidIanaTimezone(raw)) return raw; return getClientDefaultTimezone(); } /** * formatInUserTimezone — convenience wrapper for the common case of * "format an ISO string in the user's tz". Equivalent to * `new Date(iso).toLocaleString(locale, { ...options, timeZone: tz })`. * * Pass `tz` from `useUserTimezone()` and the same options object you'd * pass to toLocaleString / toLocaleDateString — this helper keeps * existing format strings working without rewrites. */ export function formatInUserTimezone( input: string | number | Date, tz: string, options?: Intl.DateTimeFormatOptions, locale: string = "en-US", ): string { const date = input instanceof Date ? input : new Date(input); return date.toLocaleString(locale, { ...options, timeZone: tz }); }