{"version":3,"file":"datetime-B2ySVlXt.mjs","names":[],"sources":["../src/utils/datetime.ts"],"sourcesContent":["// Excel <-> JavaScript Date conversions. Mirrors\n// openpyxl/openpyxl/utils/datetime.py.\n//\n// Excel stores datetimes as fractional \"serial days\" since an epoch. Two epochs\n// are in use:\n//   * Windows  (\"1900 date system\", default): epoch 1899-12-30 with the\n//     well-known 1900 leap-year bug — Excel treats 1900-02-29 as a\n//     valid day even though it isn't. We collapse that phantom day\n//     onto 1900-02-28 (the openpyxl approach), so date math past\n//     March 1, 1900 stays consistent without leaking the bug.\n//   * Mac     (\"1904 date system\"): epoch 1904-01-01, no leap bug.\n//\n// Dates stay as numeric serials on the worksheet hot path; conversion to a JS\n// Date happens lazily only when callers ask. JS Dates are interpreted in UTC\n// throughout to avoid timezone drift between read and write.\n\nimport { OpenXmlSchemaError } from './exceptions';\n\n/** Excel epoch identifier. */\nexport type ExcelEpoch = 'windows' | 'mac';\n\n/** 1899-12-30 (UTC) — the Windows / 1900-system epoch in ms. */\nexport const WINDOWS_EPOCH_MS = Date.UTC(1899, 11, 30);\n/** 1904-01-01 (UTC) — the Mac / 1904-system epoch in ms. */\nexport const MAC_EPOCH_MS = Date.UTC(1904, 0, 1);\n\nconst MS_PER_DAY = 86_400_000;\n/** Serial day index of the phantom 1900-02-29; absorbed onto 1900-02-28. */\nconst LEAP_DUPLICATE_DAY = 60;\n\nconst epochMs = (e: ExcelEpoch | undefined): number => (e === 'mac' ? MAC_EPOCH_MS : WINDOWS_EPOCH_MS);\n\n/**\n * Convert an Excel serial date into a JS `Date` (UTC). The fractional part is\n * treated as a fraction of a day. For Windows 1900 the leap-bug compensation\n * kicks in for serials in [0, 60).\n */\nexport function excelToDate(serial: number, opts?: { epoch?: ExcelEpoch }): Date {\n  if (!Number.isFinite(serial)) {\n    throw new OpenXmlSchemaError(`excelToDate: serial \"${serial}\" is not finite`);\n  }\n  const epoch = epochMs(opts?.epoch);\n  const day = Math.floor(serial);\n  const fraction = serial - day;\n  const isWindows = epoch === WINDOWS_EPOCH_MS;\n  // Windows quirk: bump days [0, 60) by one so the day count lines up with\n  // Excel's serial numbering through the phantom Feb 29 1900.\n  const dayAdjusted = isWindows && serial >= 0 && serial < LEAP_DUPLICATE_DAY ? day + 1 : day;\n  const ms = epoch + dayAdjusted * MS_PER_DAY + Math.round(fraction * MS_PER_DAY);\n  return new Date(ms);\n}\n\n/**\n * Convert a JS `Date` into an Excel serial. The Date is read in UTC. On Windows\n * 1900, dates ≤ 1900-02-28 get a -1 day correction to account for Excel's\n * phantom leap day.\n */\nexport function dateToExcel(date: Date, opts?: { epoch?: ExcelEpoch }): number {\n  const t = date.getTime();\n  if (!Number.isFinite(t)) {\n    throw new OpenXmlSchemaError('dateToExcel: invalid Date');\n  }\n  const epoch = epochMs(opts?.epoch);\n  const dayStartMs = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());\n  const days = Math.round((dayStartMs - epoch) / MS_PER_DAY);\n  const subDay = (t - dayStartMs) / MS_PER_DAY;\n  const isWindows = epoch === WINDOWS_EPOCH_MS;\n  const daysAdjusted = isWindows && days <= LEAP_DUPLICATE_DAY ? days - 1 : days;\n  return daysAdjusted + subDay;\n}\n\n/** Excel duration serial (fraction of a day) → milliseconds. */\nexport function excelToDuration(serial: number): number {\n  if (!Number.isFinite(serial)) {\n    throw new OpenXmlSchemaError(`excelToDuration: serial \"${serial}\" is not finite`);\n  }\n  return Math.round(serial * MS_PER_DAY);\n}\n\n/** Milliseconds → Excel duration serial (fraction of a day). */\nexport function durationToExcel(ms: number): number {\n  if (!Number.isFinite(ms)) {\n    throw new OpenXmlSchemaError(`durationToExcel: ms \"${ms}\" is not finite`);\n  }\n  return ms / MS_PER_DAY;\n}\n\n// ---- ISO 8601 helpers ------------------------------------------------------\n\n/**\n * Parse an ISO-8601 / W3CDTF datetime string into a `Date`. Same grammar as\n * `new Date(string)`; the wrapper just adds typed error reporting and a\n * stricter \"must be a recognised ISO\" guard.\n */\nexport function fromIso8601(s: string): Date {\n  if (typeof s !== 'string' || s.length === 0) {\n    throw new OpenXmlSchemaError(`fromIso8601: empty input`);\n  }\n  const d = new Date(s);\n  if (Number.isNaN(d.getTime())) {\n    throw new OpenXmlSchemaError(`fromIso8601: invalid datetime \"${s}\"`);\n  }\n  return d;\n}\n\n/**\n * Format a `Date` as ISO-8601 with second precision in UTC. Trims the\n * millisecond fragment that `Date.toISOString()` always produces, so the output\n * matches Excel / openpyxl's W3CDTF style.\n */\nexport function toIso8601(d: Date): string {\n  if (Number.isNaN(d.getTime())) {\n    throw new OpenXmlSchemaError('toIso8601: invalid Date');\n  }\n  return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n"],"mappings":";;;AAsBA,MAAa,mBAAmB,KAAK,IAAI,MAAM,IAAI,EAAE;;AAErD,MAAa,eAAe,KAAK,IAAI,MAAM,GAAG,CAAC;AAE/C,MAAM,aAAa;;AAEnB,MAAM,qBAAqB;AAE3B,MAAM,WAAW,MAAuC,MAAM,QAAQ,eAAe;;;;;;AAOrF,SAAgB,YAAY,QAAgB,MAAqC;CAC/E,IAAI,CAAC,OAAO,SAAS,MAAM,GACzB,MAAM,IAAI,mBAAmB,wBAAwB,OAAO,gBAAgB;CAE9E,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAM,MAAM,KAAK,MAAM,MAAM;CAC7B,MAAM,WAAW,SAAS;CAK1B,MAAM,KAAK,SAJO,UAAU,oBAGK,UAAU,KAAK,SAAS,qBAAqB,MAAM,IAAI,OACvD,aAAa,KAAK,MAAM,WAAW,UAAU;CAC9E,OAAO,IAAI,KAAK,EAAE;AACpB;;;;;;AAOA,SAAgB,YAAY,MAAY,MAAuC;CAC7E,MAAM,IAAI,KAAK,QAAQ;CACvB,IAAI,CAAC,OAAO,SAAS,CAAC,GACpB,MAAM,IAAI,mBAAmB,2BAA2B;CAE1D,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAM,aAAa,KAAK,IAAI,KAAK,eAAe,GAAG,KAAK,YAAY,GAAG,KAAK,WAAW,CAAC;CACxF,MAAM,OAAO,KAAK,OAAO,aAAa,SAAS,UAAU;CACzD,MAAM,UAAU,IAAI,cAAc;CAGlC,QAFkB,UAAU,oBACM,QAAQ,qBAAqB,OAAO,IAAI,QACpD;AACxB;;AAGA,SAAgB,gBAAgB,QAAwB;CACtD,IAAI,CAAC,OAAO,SAAS,MAAM,GACzB,MAAM,IAAI,mBAAmB,4BAA4B,OAAO,gBAAgB;CAElF,OAAO,KAAK,MAAM,SAAS,UAAU;AACvC;;AAGA,SAAgB,gBAAgB,IAAoB;CAClD,IAAI,CAAC,OAAO,SAAS,EAAE,GACrB,MAAM,IAAI,mBAAmB,wBAAwB,GAAG,gBAAgB;CAE1E,OAAO,KAAK;AACd;;;;;;AASA,SAAgB,YAAY,GAAiB;CAC3C,IAAI,OAAO,MAAM,YAAY,EAAE,WAAW,GACxC,MAAM,IAAI,mBAAmB,0BAA0B;CAEzD,MAAM,IAAI,IAAI,KAAK,CAAC;CACpB,IAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,GAC1B,MAAM,IAAI,mBAAmB,kCAAkC,EAAE,EAAE;CAErE,OAAO;AACT;;;;;;AAOA,SAAgB,UAAU,GAAiB;CACzC,IAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,GAC1B,MAAM,IAAI,mBAAmB,yBAAyB;CAExD,OAAO,EAAE,YAAY,CAAC,CAAC,QAAQ,aAAa,GAAG;AACjD"}