{"version":3,"sources":["../src/date/duration.ts","../src/date/wire.ts","../src/date/range.ts"],"names":["MS_PER_HOUR","MS_PER_MINUTE","MS_PER_DAY","format","isValid","DATE_FORMAT","subMonths","subYears","subDays"],"mappings":"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","file":"chunk-LT7I4JVK.cjs","sourcesContent":["/**\n * Pure duration / timespan helpers — no external dependencies.\n *\n * Cover the two duration shapes that flow across the EthisysCore wire: a\n * `\"HH:mm:ss\"` timespan string (.NET `TimeSpan`) and a millisecond count.\n */\nimport { MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE } from \"./constants\";\n\n/** Converts a `\"HH:mm:ss\"` timespan string to milliseconds. */\nexport const timespanToMilliseconds = (timespan: string): number => {\n  if (!timespan) return 0;\n\n  const [h = \"0\", m = \"0\", s = \"0\"] = timespan.split(\":\");\n  const hours = parseInt(h || \"0\", 10);\n  const minutes = parseInt(m || \"0\", 10);\n  const seconds = parseInt(s || \"0\", 10);\n\n  return (\n    (isNaN(hours) ? 0 : hours) * 60 * 60 * 1000 +\n    (isNaN(minutes) ? 0 : minutes) * 60 * 1000 +\n    (isNaN(seconds) ? 0 : seconds) * 1000\n  );\n};\n\n/** Converts milliseconds to hours (as a float). */\nexport const millisecondsToHours = (ms: number): number => ms / MS_PER_HOUR;\n\n/** Converts hours to milliseconds. */\nexport const hoursToMilliseconds = (hours: number): number => hours * MS_PER_HOUR;\n\n/**\n * The one human-readable duration formatter. Takes a MILLISECOND count and renders\n * the largest non-zero units as `Xd Xh Xm` (e.g. `4h 15m`, `2d 3h`, `45m`). Zero is\n * a real value (`0m`); a nullish/NaN/negative input is missing data and returns the\n * em-dash. Minute granularity — seconds are not shown.\n *\n * Feed non-millisecond inputs through the converters in this module:\n *   `formatDuration(timespanToMilliseconds(\"04:15:00\"))`  // \"HH:mm:ss\" TimeSpan\n *   `formatDuration(hoursToMilliseconds(2.5))`            // fractional hours\n */\nexport const formatDuration = (ms: number | null | undefined): string => {\n  if (ms == null || Number.isNaN(ms) || ms < 0) return \"—\";\n  if (ms < MS_PER_MINUTE) return \"0m\";\n\n  const days = Math.floor(ms / MS_PER_DAY);\n  const hours = Math.floor((ms % MS_PER_DAY) / MS_PER_HOUR);\n  const minutes = Math.floor((ms % MS_PER_HOUR) / MS_PER_MINUTE);\n\n  const parts: string[] = [];\n  if (days) parts.push(`${days}d`);\n  if (hours) parts.push(`${hours}h`);\n  if (minutes) parts.push(`${minutes}m`);\n  return parts.join(\" \");\n};\n\n/**\n * Trims an API-supplied `\"HH:mm:ss\"` down to the `\"HH:mm\"` string used by\n * MUI `TimePicker`-backed forms. Returns the supplied fallback when the value\n * is missing or shorter than five characters.\n */\nexport function toHHmm(value: string | null | undefined, fallback: string): string {\n  if (!value || value.length < 5) return fallback;\n  return value.slice(0, 5);\n}\n\n/**\n * Serialises a non-empty `\"HH:mm\"` / `\"HH:mm:ss\"` value back to the canonical\n * `\"HH:mm:ss\"` API format. Returns `null` for empty input so callers can block\n * the save rather than silently persisting midnight — an empty TimePicker\n * (cleared via keyboard) is an unsaved edit, not a legitimate `00:00:00` value.\n */\nexport function toHHmmss(value: string): string | null {\n  const trimmed = value.trim();\n  if (!trimmed) return null;\n  return trimmed.length === 5 ? `${trimmed}:00` : trimmed;\n}\n","import { format, isValid } from \"date-fns\";\n\n// ---------------------------------------------------------------------------\n// Wire-boundary helpers\n// ---------------------------------------------------------------------------\n//\n// HTML's `<input type=\"date\">` and `<input type=\"datetime-local\">` emit naive\n// local strings with NO timezone suffix. Sending those raw to a .NET backend\n// that deserialises into `DateTimeOffset` parses them using the BE process's\n// local offset (UTC on most container hosts), silently misinterpreting the\n// user's wall-clock as UTC and shifting the stored timestamp by the user's\n// offset (e.g. losing the first hour of the local day during BST). These\n// helpers bridge cleanly between the input shapes and full ISO-8601 UTC.\n//\n// All are pure and timezone-aware via the runtime `Date` object.\n\n/**\n * Normalises a date-only value from `<input type=\"date\">` (`yyyy-MM-dd`) into a\n * canonical UTC ISO-8601 timestamp at midnight UTC, e.g.\n * `2026-07-14` → `2026-07-14T00:00:00.000Z`. Use at the API-payload boundary\n * for fields the BE types as `DateTimeOffset` — a bare date-only string risks\n * ambiguous/failed deserialisation. A value that is already a full ISO\n * timestamp is passed through unchanged. Empty/invalid → `undefined` so callers\n * omit the field (BE clears it).\n */\nexport function dateOnlyToIsoUtc(value: string | null | undefined): string | undefined {\n  if (!value) return undefined;\n  const parsed = value.includes(\"T\") ? new Date(value) : new Date(`${value}T00:00:00Z`);\n  return Number.isNaN(parsed.getTime()) ? undefined : parsed.toISOString();\n}\n\n/**\n * Returns the current local wall-clock as a `yyyy-MM-ddTHH:mm` string — the\n * format an `<input type=\"datetime-local\">` element expects. Minute precision;\n * seconds and timezone deliberately omitted.\n */\nexport function nowLocalDateTimeInputValue(): string {\n  return format(new Date(), \"yyyy-MM-dd'T'HH:mm\");\n}\n\n/**\n * Converts a naive local-time string from `<input type=\"datetime-local\">`\n * (shape `yyyy-MM-ddTHH:mm` or `yyyy-MM-ddTHH:mm:ss`) into a full ISO-8601 UTC\n * string with `Z` suffix that .NET `DateTimeOffset` cannot ambiguously\n * interpret. Empty / undefined inputs pass through as `undefined`.\n */\nexport function localDateTimeInputToIsoUtc(value: string | null | undefined): string | undefined {\n  if (!value) return undefined;\n  const parsed = new Date(value);\n  if (!isValid(parsed)) return undefined;\n  return parsed.toISOString();\n}\n\n/**\n * Ensures an ISO-8601 string is treated as UTC by appending a `Z` suffix when\n * no explicit offset or UTC indicator is present. The backend may emit\n * timestamps like `2026-05-26T06:21:00` (no Z), which JS would otherwise parse\n * as local time.\n */\nexport function ensureUtcIso(value: string): string {\n  return /[Zz]|[+-]\\d{2}:?\\d{2}$/.test(value) ? value : value + \"Z\";\n}\n\n/**\n * Converts an ISO-8601 string (with or without explicit offset) into the naive\n * local-time shape `yyyy-MM-ddTHH:mm` expected by `<input type=\"datetime-local\">`.\n * Returns the supplied fallback (default empty string) when the input is\n * missing or unparseable. When `value` carries no offset or `Z` suffix it is\n * treated as UTC before converting to local time, so the edit form displays the\n * correct time regardless of the user's timezone.\n */\nexport function isoUtcToLocalDateTimeInput(value: string | null | undefined, fallback = \"\"): string {\n  if (!value) return fallback;\n  const parsed = new Date(ensureUtcIso(value));\n  if (!isValid(parsed)) return fallback;\n  return format(parsed, \"yyyy-MM-dd'T'HH:mm\");\n}\n","import { format, subDays, subMonths, subYears } from \"date-fns\";\n\nimport { DATE_FORMAT } from \"./constants\";\n\nexport interface DateRange {\n  startDate: string;\n  endDate: string;\n}\n\n/**\n * Returns a `{ startDate, endDate }` date range for a named relative period,\n * formatted with `dateFormat` (defaults to the ISO date-only format). Unknown\n * periods fall back to the last week.\n * @param timePeriod - one of `week` | `month` | `quarter` | `year`\n * @param dateFormat - output format for both bounds (defaults to `DATE_FORMAT`)\n */\nexport const getDateRange = (timePeriod: string, dateFormat: string = DATE_FORMAT): DateRange => {\n  const today = new Date();\n  switch (timePeriod) {\n    case \"month\":\n      return { startDate: format(subMonths(today, 1), dateFormat), endDate: format(today, dateFormat) };\n    case \"quarter\":\n      return { startDate: format(subMonths(today, 3), dateFormat), endDate: format(today, dateFormat) };\n    case \"year\":\n      return { startDate: format(subYears(today, 1), dateFormat), endDate: format(today, dateFormat) };\n    case \"week\":\n    default:\n      return { startDate: format(subDays(today, 7), dateFormat), endDate: format(today, dateFormat) };\n  }\n};\n\n/** Computes an ISO timestamp range from a number of days back to now. */\nexport function computeDateRange(days: number): { from: string; to: string } {\n  const to = new Date().toISOString();\n  const from = new Date(Date.now() - days * 86_400_000).toISOString();\n  return { from, to };\n}\n\n/**\n * Returns the current calendar month as a half-open UTC range `[fromUtc, beforeUtc)`,\n * suitable for \"this month\" count filters. `fromUtc` is the first instant of the\n * month; `beforeUtc` is the first instant of the next month (exclusive).\n */\nexport function currentMonthRangeUtc(today: Date = new Date()): { fromUtc: string; beforeUtc: string } {\n  const year = today.getUTCFullYear();\n  const month = today.getUTCMonth();\n  return {\n    fromUtc: new Date(Date.UTC(year, month, 1)).toISOString(),\n    beforeUtc: new Date(Date.UTC(year, month + 1, 1)).toISOString(),\n  };\n}\n\n/**\n * Computes the current UTC-offset minutes for a given IANA zone name using the\n * runtime's `Intl` implementation. Returns `null` when the zone is unknown.\n *\n * Used by timezone auto-populate fallbacks so a browser reporting an IANA zone\n * that isn't in the backend seed (e.g. `Europe/London` during BST) can still\n * match against a seeded zone sharing the same current offset.\n */\nexport function getCurrentOffsetMinutes(iana: string): number | null {\n  try {\n    const parts = new Intl.DateTimeFormat(\"en\", {\n      timeZone: iana,\n      timeZoneName: \"longOffset\",\n    }).formatToParts(new Date());\n    const label = parts.find((p) => p.type === \"timeZoneName\")?.value ?? \"\";\n    if (label === \"GMT\" || label === \"UTC\") return 0;\n    const m = label.match(/GMT([+-])(\\d{2}):(\\d{2})/);\n    if (!m) return null;\n    const sign = m[1] === \"+\" ? 1 : -1;\n    return sign * (parseInt(m[2], 10) * 60 + parseInt(m[3], 10));\n  } catch {\n    return null;\n  }\n}\n"]}