import { DateTime } from "luxon"; // A "date-only" value here always means UTC midnight — Prisma's `@db.Date` // columns (`Planning.startDate`/`finishDate`, see apps/api's schema.prisma) // carry no time-of-day, so every comparison/computation on them needs to be // anchored the same way to stay meaningful. `DateTime` (Luxon) is the // in-memory representation everywhere in this package; plain `Date`/ISO // `string` only ever appear at the two boundaries that require them — // Prisma (`Date`) and URLs/query strings (`string`). /** * Parses a strict `YYYY-MM-DD` string into a UTC-midnight {@link DateTime}. * Returns `null` for anything that isn't a real calendar date — including a * value that's merely shaped right but impossible (e.g. `2026-02-30`), * unlike native `Date` which would silently roll it over to March 2nd. */ export function parseDateOnly(iso: string): DateTime | null { const parsed = DateTime.fromISO(iso, { zone: "utc" }); return parsed.isValid ? parsed.startOf("day") : null; } /** * Formats a {@link DateTime} back to `YYYY-MM-DD`, the inverse of * {@link parseDateOnly}. * * @throws if `date` is an invalid `DateTime` — every `DateTime` produced by * this package's own functions is always valid, so this only fires if a * caller constructs one by hand incorrectly. */ export function formatDateOnly(date: DateTime): string { const iso = date.toISODate(); if (iso === null) { throw new Error("Cannot format an invalid DateTime as a date-only string"); } return iso; } /** * Normalizes a `Date` (e.g. a value read back from Prisma) or a * `DateTime` in any zone/with any time-of-day to a UTC-midnight * {@link DateTime} — the common representation every other function in * this package expects and returns. */ export function toDateOnly(date: Date | DateTime): DateTime { const dateTime = date instanceof DateTime ? date : DateTime.fromJSDate(date, { zone: "utc" }); return dateTime.toUTC().startOf("day"); }