UNPKG

compote-ui

Version:

An opinionated UI component library for Svelte, built on top of [Ark UI](https://ark-ui.com) with additional components and features not available in the core Ark UI library.

112 lines (111 loc) • 4.22 kB
import { CalendarDate, CalendarDateTime, ZonedDateTime, parseDate, parseDateTime, parseAbsolute, parseAbsoluteToLocal, fromAbsolute, fromDate, getLocalTimeZone, toTimeZone, toZoned } from '@internationalized/date'; const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/; // Trailing `Z`, or a `+HH`, `+HH:MM`, `+HHMM` offset. const HAS_OFFSET = /(?:[zZ]|[+-]\d{2}(?::?\d{2})?)$/; /** Normalize a DB datetime string to ISO 8601: space → `T`, bare `+HH` offset → `+HH:00`. */ function normalizeIso(value) { return value.replace(' ', 'T').replace(/([+-]\d{2})$/, '$1:00'); } /** Report the runtime shape of a value so a change can be emitted in the same shape. */ export function dateValueShape(value) { if (value == null) return null; if (typeof value === 'string') return 'string'; if (value instanceof Date) return 'date'; return 'date-value'; } /** * Coerce a string / `Date` / `DateValue` into a `DateValue` for Ark UI. * * String auto-detection: * - `"2024-01-15"` → {@link parseDate} (date-only) * - `"2024-01-15T10:30:00"` → {@link parseDateTime} (local datetime) * - `"2024-01-15T10:30:00Z"` / `"…+02:00"` → zoned (in `timeZone`, else local) */ export function toDateValue(value, timeZone) { if (value == null) return null; if (value instanceof Date) return fromDate(value, timeZone ?? getLocalTimeZone()); if (typeof value !== 'string') return value; const raw = value.trim(); if (!raw) return null; if (DATE_ONLY.test(raw)) return parseDate(raw); const iso = normalizeIso(raw); if (HAS_OFFSET.test(iso)) { return timeZone ? parseAbsolute(iso, timeZone) : parseAbsoluteToLocal(iso); } return parseDateTime(iso); } /** Serialize a `DateValue` to an ISO string (absolute `…Z` for zoned values). */ export function dateValueToString(value) { return 'timeZone' in value ? value.toAbsoluteString() : value.toString(); } /** Convert a `DateValue` to a native `Date`. */ export function dateValueToDate(value, timeZone) { return 'timeZone' in value ? value.toDate() : value.toDate(timeZone ?? getLocalTimeZone()); } /** Emit a `DateValue` back in the original `shape` it was bound as. */ export function fromDateValue(value, shape, timeZone) { if (value == null) return null; if (shape === 'string') return dateValueToString(value); if (shape === 'date') return dateValueToDate(value, timeZone); return value; } /** * Read any date-ish value — a `DateValue`, ISO/DB string, native `Date` or epoch-millisecond * number — the way it should be shown in `timeZone`. Never throws: anything unparseable * (including an out-of-range date like `2024-02-30`) is `null`. * * - `"2024-03-15"` and `CalendarDate` stay calendar dates. They are never turned into an * instant, which would put them on the previous day in zones west of UTC. * - `Date`, epoch numbers, offset strings (`Z`, `+02:00`) and `ZonedDateTime` are instants, * converted into `timeZone`. * - A datetime without an offset (`"2024-03-15T10:30"`, `CalendarDateTime`) is a wall-clock * time in `timeZone`. */ export function readDateInZone(value, timeZone) { try { return readDate(value, timeZone); } catch { return null; } } function readDate(value, timeZone) { if (value == null) return null; if (value instanceof Date) { return Number.isNaN(value.getTime()) ? null : fromDate(value, timeZone); } if (typeof value === 'number') { return Number.isFinite(value) ? fromAbsolute(value, timeZone) : null; } if (value instanceof ZonedDateTime) return toTimeZone(value, timeZone); if (value instanceof CalendarDateTime) return toZoned(value, timeZone); if (value instanceof CalendarDate) return value; if (typeof value !== 'string') return null; const raw = value.trim(); if (!raw) return null; if (DATE_ONLY.test(raw)) return parseDate(raw); const iso = normalizeIso(raw); return HAS_OFFSET.test(iso) ? parseAbsolute(iso, timeZone) : toZoned(parseDateTime(iso), timeZone); }