{"version":3,"sources":["../src/date/constants.ts","../src/date/iso.ts","../src/date/format.ts","../src/date/formatValidation.ts"],"names":["format","isValid","parseISO","year","month","day"],"mappings":"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","file":"chunk-YNBKRMK3.cjs","sourcesContent":["/**\n * Shared date/time format tokens (date-fns syntax).\n *\n * These are the canonical formats used across the EthisysCore monolith and\n * plugins. Change a value here for a global locale requirement rather than\n * hardcoding a format string at a call site.\n */\n\n/** ISO wire format `yyyy-MM-dd` — the standard date-only format throughout the app. */\nexport const DATE_FORMAT = \"yyyy-MM-dd\";\n\n/** Audit-log timestamp, e.g. `5 March 2026, 14:30`. */\nexport const AUDIT_DATE_FORMAT = \"d MMMM yyyy, HH:mm\";\n\n/** User-facing date display, e.g. `5 Mar 2026`. */\nexport const DISPLAY_DATE_FORMAT = \"d MMM yyyy\";\n\n/** Weekday + day + month, e.g. `Monday, 5 March`. */\nexport const DAY_DATE_FORMAT = \"EEEE, d MMMM\";\n\n/** Abbreviated day + month, e.g. `Mar 5`. */\nexport const DAY_MONTH_FORMAT = \"MMM d\";\n\n/** Abbreviated day + month + year, e.g. `Mar 5, 2026`. */\nexport const DAY_MONTH_YEAR_FORMAT = \"MMM d, yyyy\";\n\n/** 12-hour time, e.g. `2:30 PM`. */\nexport const TIME_FORMAT = \"h:mm a\";\n\n/** Short datetime, e.g. `Mar 5, 2:30 PM`. */\nexport const SHORT_DATETIME_FORMAT = \"MMM d, h:mm a\";\n\n/** Milliseconds in a day — whole-day duration / diff math (`daysBetweenIso`, axis geometry). */\nexport const MS_PER_DAY = 86_400_000;\n\n/** Milliseconds in an hour — duration math without inline `1000 * 60 * 60`. */\nexport const MS_PER_HOUR = 3_600_000;\n\n/** Milliseconds in a minute — duration / relative-time math without inline `1000 * 60`. */\nexport const MS_PER_MINUTE = 60_000;\n\n/** Fixed numeric UK date, e.g. `05/03/2026`. Deliberately NOT org-driven — form-field / compact contexts. */\nexport const DISPLAY_DATE_SLASH_FORMAT = \"dd/MM/yyyy\";\n\n/** Fixed numeric UK datetime, e.g. `05/03/2026 14:30`. Deliberately NOT org-driven. */\nexport const DISPLAY_DATETIME_SLASH_FORMAT = \"dd/MM/yyyy HH:mm\";\n\n/** Weekday + day + abbreviated month, e.g. `Wed 5 Mar`. */\nexport const DISPLAY_SHORT_DATE_FORMAT = \"EEE d MMM\";\n","import { format, isValid } from \"date-fns\";\n\nimport { DATE_FORMAT, MS_PER_DAY } from \"./constants\";\n\n/** Type guard: is `value` a bare `yyyy-MM-dd` date-only string? */\nexport const isDateOnlyString = (value: unknown): value is string => {\n  return typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value);\n};\n\n/**\n * Formats a `Date`, defaulting to the standard application ISO date format\n * (`yyyy-MM-dd`). Pass `dateFormat` to override.\n *\n * Name note: this `formatDate` takes a `Date`. The org-aware `formatDate` on the\n * `@ethisyscore/core-utils/date/org-format` sub-path takes an ISO STRING and\n * returns org-configured display text — a different contract that never collides\n * because it lives on a different entry point. For the common Date→`yyyy-MM-dd`\n * case prefer {@link toIsoDate}, whose name sidesteps the overload.\n */\nexport const formatDate = (date: Date, dateFormat: string = DATE_FORMAT): string => {\n  return format(date, dateFormat);\n};\n\n/** Today's date as a `yyyy-MM-dd` ISO string (local calendar day). */\nexport const getTodayIsoDate = (): string => {\n  return formatDate(new Date());\n};\n\n/**\n * Formats a `Date` as a `yyyy-MM-dd` ISO date-only string in local time — the\n * inverse of {@link parseIsoDateLocal}. Equivalent to `formatDate(date)`, exposed\n * under the clearer name for the date-only round-trip.\n */\nexport const toIsoDate = (date: Date): string => formatDate(date);\n\n/**\n * Returns a new local `Date` at the first day of `date`'s month, offset by `months`\n * (may be negative), normalising the day-of-month to 1 — e.g. offsetting 15 Mar 2026\n * by -1 gives 1 Feb 2026, not 15 Feb. Returns an `Invalid Date` for a null/invalid\n * input so month-axis arithmetic propagates NaN rather than throwing. Used for\n * month-granular axis/range building (e.g. a Gantt chart).\n */\nexport const startOfMonthOffset = (date: Date | null | undefined, months: number): Date => {\n  if (!date || !isValid(date)) {\n    return new Date(NaN);\n  }\n  return new Date(date.getFullYear(), date.getMonth() + months, 1);\n};\n\n/**\n * Whole-day difference between two `yyyy-MM-dd` date-only strings (`toIso - fromIso`),\n * rounded to guard against DST half-day drift when the range crosses a clock-change\n * boundary. Parses both bounds as LOCAL dates (via {@link parseIsoDateLocal}); returns\n * `NaN` when either is not a valid date-only string.\n */\nexport const daysBetweenIso = (fromIso: string, toIso: string): number => {\n  const from = parseIsoDateLocal(fromIso);\n  const to = parseIsoDateLocal(toIso);\n  if (!from || !to) return NaN;\n  return Math.round((to.getTime() - from.getTime()) / MS_PER_DAY);\n};\n\n/** First day of the given date's month, as a `yyyy-MM-dd` ISO string. */\nexport const getMonthStartIso = (date: Date): string =>\n  formatDate(new Date(date.getFullYear(), date.getMonth(), 1));\n\n/** Last day of the given date's month, as a `yyyy-MM-dd` ISO string. */\nexport const getMonthEndIso = (date: Date): string =>\n  formatDate(new Date(date.getFullYear(), date.getMonth() + 1, 0));\n\n/**\n * Parses a date-only `yyyy-MM-dd` string as a LOCAL date. Returns `null` when\n * the value is not a date-only string or is not a valid calendar date. Avoids\n * the `new Date(\"yyyy-MM-dd\")` UTC parse, which shifts the day in non-UTC zones.\n */\nexport const parseIsoDateLocal = (value: string | null | undefined): Date | null => {\n  if (!value || !isDateOnlyString(value)) return null;\n  const [year, month, day] = value.split(\"-\").map(Number);\n  const d = new Date(year, month - 1, day);\n  return isValid(d) ? d : null;\n};\n\n/**\n * Adds `days` to an ISO `yyyy-MM-dd` date and returns the result as a `yyyy-MM-dd`\n * string. Parses and formats in local time (via `parseIsoDateLocal` / `formatDate`)\n * so the arithmetic can't drift across a day boundary in non-UTC timezones/DST.\n * Returns the input unchanged when it is not a valid date-only string.\n */\nexport const addDaysToIsoDate = (isoDate: string, days: number): string => {\n  const base = parseIsoDateLocal(isoDate);\n  if (!base) return isoDate;\n  base.setDate(base.getDate() + days);\n  return formatDate(base);\n};\n\n/**\n * Adds `years` to an ISO `yyyy-MM-dd` date and returns the result as a `yyyy-MM-dd`\n * string. Parses and formats in local time (like {@link addDaysToIsoDate}) so the\n * arithmetic can't drift across a day boundary in non-UTC timezones/DST. Used to seed\n * default expiry / end-of-term dates (insurance cover, licences, fixed-term contracts).\n * A 29 Feb base in a non-leap target year rolls to 1 Mar, matching `Date.setFullYear`.\n * Returns the input unchanged when it is not a valid date-only string.\n */\nexport const addYearsToIsoDate = (isoDate: string, years: number): string => {\n  const base = parseIsoDateLocal(isoDate);\n  if (!base) return isoDate;\n  base.setFullYear(base.getFullYear() + years);\n  return formatDate(base);\n};\n\n/**\n * Extracts a date-only string (`yyyy-MM-dd`) from a date string.\n * - Date-only strings (`2024-05-15`) are returned as-is.\n * - ISO datetime strings (`2024-05-15T00:00:00+05:30`) have the date portion\n *   extracted directly from the string to preserve the original calendar date\n *   without timezone conversion.\n * - Returns empty string for null, undefined, or unparseable values.\n */\nexport const toDateOnlyString = (value: string | null | undefined): string => {\n  if (!value) return \"\";\n  // Extract the yyyy-MM-dd portion before the \"T\" separator to avoid timezone\n  // conversion that can shift the date by a day.\n  const datepart = value.split(\"T\")[0];\n  return isDateOnlyString(datepart) ? datepart : \"\";\n};\n","import { format, isValid, parseISO } from \"date-fns\";\n\nimport {\n  DATE_FORMAT,\n  DAY_MONTH_FORMAT,\n  DISPLAY_DATE_SLASH_FORMAT,\n  DISPLAY_DATETIME_SLASH_FORMAT,\n  DISPLAY_SHORT_DATE_FORMAT,\n  MS_PER_MINUTE,\n} from \"./constants\";\nimport { formatDate, isDateOnlyString, parseIsoDateLocal } from \"./iso\";\n\n/**\n * FALLBACK CONVENTION: a display formatter given falsy/unparseable input returns\n * the em-dash placeholder {@link EM_DASH} so callers can render unconditionally.\n * (Wire/parse helpers in `iso.ts` / `wire.ts` return `\"\"`/`undefined` instead so\n * the value is omitted rather than shown; `formatDuration` likewise uses the\n * em-dash for missing/invalid input, but `0m` for a real zero.) The one exception\n * is {@link formatDateWithOrdinal}, whose \"Not specified\" / \"Invalid date\" are\n * deliberate, richer domain messages.\n */\nexport const EM_DASH = \"—\";\n\n/**\n * Shared body for the fixed (not org-driven) datetime formatters below: parse an\n * ISO string with the native `Date`, and render it with `fmt` or the em-dash for\n * falsy/invalid input. Not exported — the named formatters are the public surface.\n */\nconst formatFixedDateTime = (value: string | null | undefined, fmt: string): string => {\n  if (!value) return EM_DASH;\n  const date = new Date(value);\n  return Number.isNaN(date.getTime()) ? EM_DASH : format(date, fmt);\n};\n\n/**\n * Shared relative-time ladder: maps a whole-minute age to a short phrase\n * (\"Just now\", \"5m ago\", \"3h ago\", \"2d ago\"), or `null` for anything a week or\n * older so the caller can substitute an absolute date. Backs both\n * {@link formatTimeAgo} here and `formatRelativeTime` in the org-format module.\n */\nexport const relativeTimePhrase = (diffMinutes: number): string | null => {\n  if (diffMinutes < 1) return \"Just now\";\n  if (diffMinutes < 60) return `${diffMinutes}m ago`;\n  const hours = Math.floor(diffMinutes / 60);\n  if (hours < 24) return `${hours}h ago`;\n  const days = Math.floor(hours / 24);\n  if (days < 7) return `${days}d ago`;\n  return null;\n};\n\n/**\n * Formats a date-only `yyyy-MM-dd` string in the user's LOCAL timezone as the fixed\n * numeric UK date `dd/MM/yyyy`. Parses via {@link parseIsoDateLocal} to avoid the\n * `new Date(\"yyyy-MM-dd\")` UTC-midnight parse that renders as the previous day west\n * of UTC. Returns the original string when it is not a valid date-only value.\n * Deliberately fixed (not org-driven) — for form-field / compact contexts.\n */\nexport const formatSlashDate = (dateString: string): string => {\n  const parsed = parseIsoDateLocal(dateString);\n  return parsed ? format(parsed, DISPLAY_DATE_SLASH_FORMAT) : dateString;\n};\n\n/**\n * Formats an ISO datetime string as the fixed numeric UK datetime `dd/MM/yyyy HH:mm`\n * for compact display contexts such as table columns. Returns em-dash for missing or\n * invalid values. Deliberately fixed (not org-driven) — distinct from the org-aware\n * `formatDateTime` in `@ethisyscore/core-utils/date/org-format`.\n */\nexport const formatCompactDateTime = (value?: string | null): string =>\n  formatFixedDateTime(value, DISPLAY_DATETIME_SLASH_FORMAT);\n\n/**\n * Formats an ISO date/datetime string as a short weekday date `EEE d MMM` (e.g. `Wed 5 Mar`).\n * Returns em-dash for missing or invalid values. Deliberately fixed (not org-driven).\n */\nexport const formatShortDate = (value?: string | null): string =>\n  formatFixedDateTime(value, DISPLAY_SHORT_DATE_FORMAT);\n\n/**\n * Formats an ISO date/datetime string as an abbreviated month-day `MMM d` (e.g. `Mar 5`).\n * Intended for chart axis ticks where vertical space is limited. Returns em-dash for\n * missing or invalid values. Deliberately fixed (not org-driven).\n */\nexport const formatMonthDay = (value?: string | null): string =>\n  formatFixedDateTime(value, DAY_MONTH_FORMAT);\n\n/**\n * Safely formats a date string or `Date`. Handles null, undefined, and invalid\n * dates by returning a fallback string.\n * @param date - The date to format (string, Date, null, or undefined)\n * @param formatStr - The desired output format (defaults to `DATE_FORMAT`)\n * @param fallback - Returned when the date is invalid or missing (defaults to `\"—\"`)\n */\nexport const formatDateSafe = (\n  date: string | Date | null | undefined,\n  formatStr: string = DATE_FORMAT,\n  fallback: string = \"—\",\n): string => {\n  if (!date) return fallback;\n  try {\n    const dateObj = typeof date === \"string\" ? parseISO(date) : date;\n    if (!isValid(dateObj)) return fallback;\n    return format(dateObj, formatStr);\n  } catch {\n    return fallback;\n  }\n};\n\n/** Returns the ordinal suffix for a day of month (`st`, `nd`, `rd`, `th`). */\nconst getOrdinalSuffix = (day: number): string => {\n  if (day > 3 && day < 21) return \"th\";\n  switch (day % 10) {\n    case 1:\n      return \"st\";\n    case 2:\n      return \"nd\";\n    case 3:\n      return \"rd\";\n    default:\n      return \"th\";\n  }\n};\n\n/**\n * Formats a date string or `Date` to a format like `31st Aug 2025`.\n * Handles date-only strings (`yyyy-MM-dd`) as local dates to avoid timezone shifts.\n * @param date - Date string (`yyyy-MM-dd`) or `Date`\n * @returns Formatted date string with ordinal suffix\n */\nexport const formatDateWithOrdinal = (date: string | Date | null | undefined): string => {\n  if (!date) return \"Not specified\";\n\n  let dateObj: Date;\n  if (typeof date === \"string\") {\n    if (isDateOnlyString(date)) {\n      const [year, month, day] = date.split(\"-\").map(Number);\n      dateObj = new Date(year, month - 1, day);\n    } else {\n      dateObj = parseISO(date);\n    }\n  } else {\n    dateObj = date;\n  }\n\n  if (!isValid(dateObj)) return \"Invalid date\";\n\n  const day = dateObj.getDate();\n  const month = format(dateObj, \"MMM\");\n  const year = dateObj.getFullYear();\n  return `${day}${getOrdinalSuffix(day)} ${month} ${year}`;\n};\n\n/**\n * Formats a date as a relative time string (e.g. `2h ago`, `Just now`).\n * Optimised for short labels in dropdowns; anything older than a week falls\n * back to the standard date format.\n * @param date - ISO string or `Date`\n */\nexport const formatTimeAgo = (date: string | Date | null | undefined): string => {\n  if (!date) return \"\";\n\n  const dateObj = typeof date === \"string\" ? parseISO(date) : date;\n  if (!isValid(dateObj)) return \"\";\n\n  const diffMinutes = Math.floor((new Date().getTime() - dateObj.getTime()) / MS_PER_MINUTE);\n  // A week or older falls back to the plain ISO date (this helper's fixed contract);\n  // the org-aware sibling `formatRelativeTime` falls back to the org datetime instead.\n  return relativeTimePhrase(diffMinutes) ?? formatDate(dateObj);\n};\n","/**\n * Structural (token-grammar) validator for date/time format strings — a framework-agnostic\n * mirror of the CoreConnect API's `DateFnsFormatValidator` (CoreConnect.Application.Common).\n * Accepts any combination of the supported date-fns tokens (a deliberate subset — see the token\n * sets below), separators and quoted literals (so users can define custom formats), while unknown\n * tokens — incl. .NET-style DD/YYYY/tt, which date-fns either throws on or silently renders as\n * garbage — are rejected with a targeted hint.\n *\n * PARITY CONTRACT: the canonical test vectors in `__tests__/formatValidation.test.ts` are\n * duplicated verbatim in the API's `DateFnsFormatValidatorTests.cs`. Error message strings are\n * part of the contract — change them in lockstep across both repos.\n */\n\nexport interface FormatValidationResult {\n  valid: boolean;\n  error?: string;\n}\n\nconst SEPARATOR_CHARS = \" ,./-:()\";\n\nconst DAY_TOKENS = new Set([\"d\", \"dd\", \"do\"]);\nconst WEEKDAY_TOKENS = new Set([\"EEE\", \"EEEE\"]);\nconst MONTH_TOKENS = new Set([\"M\", \"MM\", \"MMM\", \"MMMM\"]);\nconst YEAR_TOKENS = new Set([\"yy\", \"yyyy\"]);\nconst HOUR12_TOKENS = new Set([\"h\", \"hh\"]);\nconst HOUR24_TOKENS = new Set([\"H\", \"HH\"]);\nconst MINUTE_TOKENS = new Set([\"m\", \"mm\"]);\nconst SECOND_TOKENS = new Set([\"s\", \"ss\"]);\nconst MERIDIEM_TOKEN = \"a\";\n\n/** Common wrong-token → right-token hints for DATE formats (.NET / minute-vs-month mixups). */\nconst DATE_HINTS: Record<string, string> = {\n  D: \"d\",\n  DD: \"dd\",\n  Y: \"yyyy\",\n  YY: \"yy\",\n  YYYY: \"yyyy\",\n  E: \"EEE\",\n  EE: \"EEE\",\n  m: \"M\",\n  mm: \"MM\",\n};\n\n/** Common wrong-token → right-token hints for TIME formats (.NET meridiem / month-vs-minute mixups). */\nconst TIME_HINTS: Record<string, string> = {\n  t: \"a\",\n  tt: \"a\",\n  A: \"a\",\n  M: \"m\",\n  MM: \"mm\",\n};\n\nconst invalid = (error: string): FormatValidationResult => ({ valid: false, error });\n\nconst isLetter = (c: string): boolean => /[a-zA-Z]/.test(c);\n\n/**\n * Walks the format string collecting letter-run tokens. Quoted literals ('...', with '' as the\n * escaped apostrophe) and separator characters are skipped; any other character, or a letter run\n * not in `isKnownToken`, fails with the canonical error (with a hint when the mixup is known).\n */\nfunction tokenize(\n  value: string,\n  isKnownToken: (token: string) => boolean,\n  hints: Record<string, string>,\n): { result: FormatValidationResult; tokens: string[] } {\n  const tokens: string[] = [];\n  let i = 0;\n\n  while (i < value.length) {\n    const c = value[i];\n\n    if (c === \"'\") {\n      i++;\n      let closed = false;\n      while (i < value.length) {\n        if (value[i] === \"'\") {\n          if (i + 1 < value.length && value[i + 1] === \"'\") {\n            i += 2; // '' inside a literal = escaped apostrophe\n            continue;\n          }\n          closed = true;\n          i++;\n          break;\n        }\n        i++;\n      }\n      if (!closed) return { result: invalid(\"Unterminated quoted literal.\"), tokens };\n      continue;\n    }\n\n    if (isLetter(c)) {\n      const start = i;\n      while (i < value.length && value[i] === c) i++;\n      let run = value.slice(start, i);\n\n      // date-fns ordinal day-of-month is the two-letter token 'do' — the only\n      // mixed-letter token the grammar supports.\n      if (run === \"d\" && value[i] === \"o\") {\n        run = \"do\";\n        i++;\n      }\n\n      if (!isKnownToken(run)) {\n        const hint = hints[run];\n        return {\n          result: invalid(hint ? `Unknown token '${run}' — use '${hint}' instead.` : `Unknown token '${run}'.`),\n          tokens,\n        };\n      }\n\n      tokens.push(run);\n      continue;\n    }\n\n    if (SEPARATOR_CHARS.includes(c)) {\n      i++;\n      continue;\n    }\n\n    return { result: invalid(`Unsupported character '${c}'.`), tokens };\n  }\n\n  return { result: { valid: true }, tokens };\n}\n\n/**\n * Validates a DATE format string: only supported date-fns date tokens, separators and quoted\n * literals; must contain a day, a month and a year token. Nullish or empty input is valid\n * (means \"unset / use the caller's default\"), mirroring the API's `string?` overload — a\n * non-empty whitespace-only string is NOT treated as empty and fails the day-token check, matching\n * the server's `string.IsNullOrEmpty` semantics.\n */\nexport function validateDateFormat(value: string | null | undefined): FormatValidationResult {\n  if (!value) return { valid: true };\n\n  const { result, tokens } = tokenize(\n    value,\n    (t) => DAY_TOKENS.has(t) || WEEKDAY_TOKENS.has(t) || MONTH_TOKENS.has(t) || YEAR_TOKENS.has(t),\n    DATE_HINTS,\n  );\n  if (!result.valid) return result;\n\n  if (!tokens.some((t) => DAY_TOKENS.has(t))) return invalid(\"Date format must include a day token (d, dd or do).\");\n  if (!tokens.some((t) => MONTH_TOKENS.has(t))) {\n    return invalid(\"Date format must include a month token (M, MM, MMM or MMMM).\");\n  }\n  if (!tokens.some((t) => YEAR_TOKENS.has(t))) return invalid(\"Date format must include a year token (yy or yyyy).\");\n\n  return { valid: true };\n}\n\n/**\n * Validates a TIME format string: only supported date-fns time tokens, separators and quoted\n * literals; must contain an hour and a minute token; 12-hour tokens require the meridiem token\n * 'a' (and vice versa) so an ambiguous 12h-without-AM/PM can never be stored. Nullish or empty\n * input is valid (means \"unset / use the caller's default\"), mirroring the API's `string?`\n * overload; a non-empty whitespace-only string is NOT treated as empty and fails the hour-token check.\n */\nexport function validateTimeFormat(value: string | null | undefined): FormatValidationResult {\n  if (!value) return { valid: true };\n\n  const { result, tokens } = tokenize(\n    value,\n    (t) =>\n      HOUR12_TOKENS.has(t) ||\n      HOUR24_TOKENS.has(t) ||\n      MINUTE_TOKENS.has(t) ||\n      SECOND_TOKENS.has(t) ||\n      t === MERIDIEM_TOKEN,\n    TIME_HINTS,\n  );\n  if (!result.valid) return result;\n\n  const hasHour12 = tokens.some((t) => HOUR12_TOKENS.has(t));\n  const hasHour24 = tokens.some((t) => HOUR24_TOKENS.has(t));\n\n  if (!hasHour12 && !hasHour24) return invalid(\"Time format must include an hour token (h, hh, H or HH).\");\n  if (!tokens.some((t) => MINUTE_TOKENS.has(t))) return invalid(\"Time format must include a minute token (m or mm).\");\n\n  const hasMeridiem = tokens.includes(MERIDIEM_TOKEN);\n  if (hasHour12 && !hasMeridiem) return invalid(\"12-hour time format requires the meridiem token 'a'.\");\n  if (hasMeridiem && !hasHour12) return invalid(\"Meridiem token 'a' requires a 12-hour hour token (h or hh).\");\n\n  return { valid: true };\n}\n"]}