{
  "slug": "format",
  "name": "@gj-kit/format",
  "version": "0.1.2",
  "description": "Explicit TypeScript formatting for dates, time zones, numbers, bytes, durations, percentages, and Korean won.",
  "homepage": "https://gj-kit.github.io/gj-kit/packages/format/",
  "repository": "git+https://github.com/gj-kit/gj-kit.git",
  "license": "MIT",
  "engines": {
    "node": ">=20"
  },
  "peerDependencies": {},
  "peerDependenciesMeta": {},
  "entries": [
    {
      "subpath": ".",
      "id": "root",
      "declarationTarget": "./dist/index.d.cts",
      "symbols": [
        {
          "name": "canFormatTimeZone",
          "slug": "can-format-time-zone",
          "kind": "function",
          "declaration": "/**\n * Non-throwing probe: can this runtime render the given zone?\n *\n * `'UTC'` and `'device'` are always true (no Intl involved). For an IANA name it\n * runs the same checks the formatters run and returns false instead of throwing,\n * so an app can decide its own policy once at boot — e.g.\n * `const zone = canFormatTimeZone('Asia/Seoul') ? 'Asia/Seoul' : 'UTC'`.\n *\n * Results are cached: the runtime-wide Intl self-test runs at most once, and each\n * zone is checked at most once. Repeated calls are a map lookup.\n */\ndeclare function canFormatTimeZone(timeZone: FormatTimeZone): boolean;",
          "sourceDocumentation": "Non-throwing probe: can this runtime render the given zone?\n\n`'UTC'` and `'device'` are always true (no Intl involved). For an IANA name it\nruns the same checks the formatters run and returns false instead of throwing,\nso an app can decide its own policy once at boot — e.g.\n`const zone = canFormatTimeZone('Asia/Seoul') ? 'Asia/Seoul' : 'UTC'`.\n\nResults are cached: the runtime-wide Intl self-test runs at most once, and each\nzone is checked at most once. Repeated calls are a map lookup."
        },
        {
          "name": "FormatBinaryByteUnit",
          "slug": "format-binary-byte-unit",
          "kind": "type",
          "declaration": "type FormatBinaryByteUnit = 'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB' | 'PiB';"
        },
        {
          "name": "formatBytes",
          "slug": "format-bytes",
          "kind": "function",
          "declaration": "/**\n * Byte quantity with an explicit unit system. `system: 'decimal'` divides by\n * 1000 and labels KB/MB; `system: 'binary'` divides by 1024 and labels\n * KiB/MiB — the label always tells the truth about the divisor.\n */\ndeclare function formatBytes<TFallback = string>(value: number | null | undefined, options: FormatBytesOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "Byte quantity with an explicit unit system. `system: 'decimal'` divides by\n1000 and labels KB/MB; `system: 'binary'` divides by 1024 and labels\nKiB/MiB — the label always tells the truth about the divisor."
        },
        {
          "name": "FormatBytesOptions",
          "slug": "format-bytes-options",
          "kind": "type",
          "declaration": "type FormatBytesOptions<TFallback = string> = {\n    /** Required: `'1.5 GB'` vs `'1.5GB'` — the source apps disagreed. */\n    readonly unitSpace: boolean;\n    /**\n     * Required policy for zero and negative input — the source apps disagreed and\n     * the difference is visible: admin renders `'0 B'`/`'-5 B'`, mobile treats\n     * anything `<= 0` as \"size unknown\" and hides the chip.\n     * `'render'` formats the value; `'fallback'` returns `fallback`.\n     */\n    readonly nonPositive: 'render' | 'fallback';\n    /**\n     * How trailing zeros are handled. Default `'keep'`.\n     * - `'keep'`       renders `'1.0 GB'` — fixed column width (`toFixed`).\n     * - `'trim'`       renders `'1GB'` — drops trailing zeros after rounding.\n     * - `'trim-exact'` drops the fraction only when the value was an exact\n     *                  integer *before* rounding, so `1.04 GB` still renders\n     *                  `'1.0GB'`. This is not the same as `'trim'`; it is what\n     *                  `Number.isInteger(v) ? v : v.toFixed(1)` does.\n     */\n    readonly trailingZeros?: 'keep' | 'trim' | 'trim-exact' | undefined;\n    /** Values >= this (in the chosen unit) render as integers (e.g. 10 gives `'12MB'`). */\n    readonly wholeNumberFrom?: number | undefined;\n    /** Rendered for null/undefined/non-finite input, and for non-positive input\n     *  when `nonPositive` is `'fallback'`. Default `'-'`. */\n    readonly fallback?: TFallback | undefined;\n} & ({\n    /** Decimal SI: 1 KB = 1000 B. Unit labels KB/MB/.../PB. */\n    readonly system: 'decimal';\n    /**\n     * Exact fraction digits above the B unit — a single value, or a per-unit\n     * map when the policy differs by unit. Default 1.\n     * The per-unit form exists because a real source app rounds MB to whole\n     * numbers while giving GB/TB one decimal, and no numeric threshold can\n     * separate those two ranges (both span 1-999 in their own unit).\n     */\n    readonly fractionDigits?: ByteFractionDigits | Partial<Record<FormatDecimalByteUnit, ByteFractionDigits>> | undefined;\n    readonly minUnit?: FormatDecimalByteUnit | undefined;\n    readonly maxUnit?: FormatDecimalByteUnit | undefined;\n} | {\n    /** Binary: 1 KiB = 1024 B. Unit labels KiB/MiB/.../PiB. */\n    readonly system: 'binary';\n    readonly fractionDigits?: ByteFractionDigits | Partial<Record<FormatBinaryByteUnit, ByteFractionDigits>> | undefined;\n    readonly minUnit?: FormatBinaryByteUnit | undefined;\n    readonly maxUnit?: FormatBinaryByteUnit | undefined;\n});"
        },
        {
          "name": "FormatDateInput",
          "slug": "format-date-input",
          "kind": "type",
          "declaration": "/**\n * Accepted date inputs: an instant, never a wall-clock string.\n *\n * Strings are deliberately absent. `new Date('2026-06-08T09:05:00')` resolves\n * an offset-less string against the *device* zone, so the same API payload\n * becomes a different instant on different phones — and no `timeZone` option\n * can undo that, because it happened before the formatter saw the value.\n * Parse strings with {@link parseIsoInstant}, which makes that choice explicit.\n *\n * A number is epoch milliseconds.\n */\ntype FormatDateInput = Date | number;",
          "sourceDocumentation": "Accepted date inputs: an instant, never a wall-clock string.\n\nStrings are deliberately absent. `new Date('2026-06-08T09:05:00')` resolves\nan offset-less string against the *device* zone, so the same API payload\nbecomes a different instant on different phones — and no `timeZone` option\ncan undo that, because it happened before the formatter saw the value.\nParse strings with {@link parseIsoInstant}, which makes that choice explicit.\n\nA number is epoch milliseconds."
        },
        {
          "name": "formatDateOnly",
          "slug": "format-date-only",
          "kind": "function",
          "declaration": "/** `YYYY-MM-DD` (or `YYYY.MM.DD`) — date without the time-of-day. */\ndeclare function formatDateOnly<TFallback = string>(value: FormatDateInput | null | undefined, options: FormatDateOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "`YYYY-MM-DD` (or `YYYY.MM.DD`) — date without the time-of-day."
        },
        {
          "name": "FormatDateOptions",
          "slug": "format-date-options",
          "kind": "interface",
          "declaration": "interface FormatDateOptions<TFallback = string> {\n    /** Required. See {@link FormatTimeZone} — omission is a compile error. */\n    readonly timeZone: FormatTimeZone;\n    /**\n     * Required separator between year/month/day segments.\n     * The source apps disagreed (`2026-06-08` vs `2026.06.08`), so neither\n     * spelling is a silent default.\n     */\n    readonly separator: '-' | '.';\n    /**\n     * Rendered for null/undefined/invalid input and for instants outside the\n     * supported window `0001-01-01T14:00:00Z … 9999-12-31T09:59:59.999Z`.\n     *\n     * That window — not \"years 1–9999\" — is the actual guard: it is the set of\n     * instants every zone this package accepts (-12:00…+14:00) renders as a year\n     * between 1 and 9999, so the IANA path and the `'UTC'`/`'device'` paths agree\n     * byte for byte on where the range ends. `2026-06-08` in any zone is far\n     * inside it; only the two end years are narrowed by a day.\n     *\n     * Default `'-'`.\n     */\n    readonly fallback?: TFallback | undefined;\n}"
        },
        {
          "name": "formatDateTime",
          "slug": "format-date-time",
          "kind": "function",
          "declaration": "/**\n * `YYYY-MM-DD HH:mm` (or `YYYY.MM.DD HH:mm`) in the given zone. 24-hour clock,\n * no seconds. Month, day, hour and minute are always two digits; the year is\n * rendered unpadded (`999-06-08`), matching the source apps and keeping the\n * IANA path byte-identical to the `'UTC'`/`'device'` paths. Column width is\n * therefore fixed for years 1000–9999, which is the range real data occupies.\n *\n * Instants outside `0001-01-01T14:00:00Z … 9999-12-31T09:59:59.999Z` render\n * `fallback` — see {@link FormatDateOptions.fallback} for why the window is\n * stated as instants rather than as years.\n */\ndeclare function formatDateTime<TFallback = string>(value: FormatDateInput | null | undefined, options: FormatDateOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "`YYYY-MM-DD HH:mm` (or `YYYY.MM.DD HH:mm`) in the given zone. 24-hour clock,\nno seconds. Month, day, hour and minute are always two digits; the year is\nrendered unpadded (`999-06-08`), matching the source apps and keeping the\nIANA path byte-identical to the `'UTC'`/`'device'` paths. Column width is\ntherefore fixed for years 1000–9999, which is the range real data occupies.\n\nInstants outside `0001-01-01T14:00:00Z … 9999-12-31T09:59:59.999Z` render\n`fallback` — see {@link FormatDateOptions.fallback} for why the window is\nstated as instants rather than as years."
        },
        {
          "name": "FormatDecimalByteUnit",
          "slug": "format-decimal-byte-unit",
          "kind": "type",
          "declaration": "type FormatDecimalByteUnit = 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB';"
        },
        {
          "name": "formatDurationKo",
          "slug": "format-duration-ko",
          "kind": "function",
          "declaration": "/**\n * Elapsed milliseconds as Korean text: `'0.8초'`, `'5분'`, `'1.2시간'`.\n * Takes a duration, not two timestamps — clock-free like the rest of the family.\n */\ndeclare function formatDurationKo<TFallback = string>(milliseconds: number, options?: FormatDurationKoOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "Elapsed milliseconds as Korean text: `'0.8초'`, `'5분'`, `'1.2시간'`.\nTakes a duration, not two timestamps — clock-free like the rest of the family."
        },
        {
          "name": "FormatDurationKoOptions",
          "slug": "format-duration-ko-options",
          "kind": "interface",
          "declaration": "/**\n * Elapsed-time copy. The admin source took two timestamps and parsed them; this\n * takes the duration itself, so the family stays clock-free and the subtraction\n * stays at the call site where the two instants already are.\n */\ninterface FormatDurationKoOptions<TFallback = string> {\n    /** Rendered for NaN/negative/non-finite input. Default `'-'`. */\n    readonly fallback?: TFallback | undefined;\n}",
          "sourceDocumentation": "Elapsed-time copy. The admin source took two timestamps and parsed them; this\ntakes the duration itself, so the family stays clock-free and the subtraction\nstays at the call site where the two instants already are."
        },
        {
          "name": "FormatError",
          "slug": "format-error",
          "kind": "class",
          "declaration": "/**\n * Thrown for configuration and environment errors only. Data problems — null,\n * invalid dates, NaN, unparsable strings — never throw; they render `fallback`.\n */\ndeclare class FormatError extends Error {\n    readonly code: FormatErrorCode;\n    constructor(code: FormatErrorCode, message: string);\n}",
          "sourceDocumentation": "Thrown for configuration and environment errors only. Data problems — null,\ninvalid dates, NaN, unparsable strings — never throw; they render `fallback`."
        },
        {
          "name": "FormatErrorCode",
          "slug": "format-error-code",
          "kind": "type",
          "declaration": "/** Stable machine-readable codes. Never emitted for data problems. */\ntype FormatErrorCode = \n/** Configuration error: `timeZone` is not 'UTC' | 'device' | a zone name the\n *  runtime's Intl accepts. A programmer can fix this. */\n'ERR_TIMEZONE_INVALID'\n/** Configuration error: `locale` is not a tag the runtime's Intl accepts\n *  (`'ko_KR'`, `'en US'`, `'ko-KR-'`). `FormatLocale` accepts any string, so\n *  this is the runtime half of that axis. A programmer can fix this. */\n | 'ERR_LOCALE_INVALID'\n/** Configuration error: `minimumFractionDigits`/`maximumFractionDigits` is not\n *  an integer in 0–100, or the minimum exceeds the maximum. A programmer can\n *  fix this. */\n | 'ERR_FRACTION_DIGITS_INVALID'\n/** Environment error: the runtime's Intl failed this package's self-test —\n *  it ignores the `timeZone` option, or ignores `hourCycle`/`hour12`.\n *  A programmer cannot fix this; ask {@link canFormatTimeZone} up front. */\n | 'ERR_INTL_UNUSABLE'\n/** Environment error: a single-field formatter produced a non-numeric or\n *  out-of-range string for this specific zone. */\n | 'ERR_INTL_FIELD_OUTPUT';",
          "sourceDocumentation": "Stable machine-readable codes. Never emitted for data problems."
        },
        {
          "name": "formatKrw",
          "slug": "format-krw",
          "kind": "function",
          "declaration": "/**\n * Korean won. Fractions are never shown — KRW has no minor unit, so the value is\n * rounded to whole won first (`1000.5` becomes `'₩1,001'`). Negative values put\n * the sign before the symbol (`'-₩1,000'`), and a value that rounds to zero\n * never renders a negative zero.\n */\ndeclare function formatKrw<TFallback = string>(value: number | null | undefined, options: FormatKrwOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "Korean won. Fractions are never shown — KRW has no minor unit, so the value is\nrounded to whole won first (`1000.5` becomes `'₩1,001'`). Negative values put\nthe sign before the symbol (`'-₩1,000'`), and a value that rounds to zero\nnever renders a negative zero."
        },
        {
          "name": "FormatKrwOptions",
          "slug": "format-krw-options",
          "kind": "interface",
          "declaration": "interface FormatKrwOptions<TFallback = string> {\n    /**\n     * Required rendering style — the source apps disagreed:\n     * `'symbol'` renders `'₩1,000'`, `'suffix-ko'` renders `'1,000원'`.\n     */\n    readonly style: 'symbol' | 'suffix-ko';\n    /**\n     * Required **grouping** locale; `'device'` is the explicit opt-in to the\n     * runtime default. It selects grouping and the decimal separator only — the\n     * `₩` glyph, its position and the `원` suffix are fixed by this package and\n     * never vary with the locale.\n     */\n    readonly locale: FormatLocale;\n    /** Rendered for null/undefined/non-finite input. Default `'-'`. */\n    readonly fallback?: TFallback | undefined;\n}"
        },
        {
          "name": "FormatLocale",
          "slug": "format-locale",
          "kind": "type",
          "declaration": "/**\n * Explicit locale selector for the Intl-backed formatters.\n *\n * It selects **digit grouping and the decimal separator only**. Currency\n * symbols, symbol position and the percent sign are pinned by this package and\n * do not vary with the locale — see {@link formatKrw} and {@link formatPercent}.\n * `'device'` opts into the runtime default locale (grouping then varies by\n * device settings — an explicit, visible choice).\n */\ntype FormatLocale = 'device' | (string & {}) | readonly string[];",
          "sourceDocumentation": "Explicit locale selector for the Intl-backed formatters.\n\nIt selects **digit grouping and the decimal separator only**. Currency\nsymbols, symbol position and the percent sign are pinned by this package and\ndo not vary with the locale — see {@link formatKrw} and {@link formatPercent}.\n`'device'` opts into the runtime default locale (grouping then varies by\ndevice settings — an explicit, visible choice)."
        },
        {
          "name": "formatMonthDayTime",
          "slug": "format-month-day-time",
          "kind": "function",
          "declaration": "/** `MM-DD HH:mm` (or `MM.DD HH:mm`) — dense tables covering a short span. */\ndeclare function formatMonthDayTime<TFallback = string>(value: FormatDateInput | null | undefined, options: FormatDateOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "`MM-DD HH:mm` (or `MM.DD HH:mm`) — dense tables covering a short span."
        },
        {
          "name": "formatNumber",
          "slug": "format-number",
          "kind": "function",
          "declaration": "/**\n * Locale-grouped plain number (`12,345`). Deliberately NOT a passthrough to\n * Intl's own option bag — only Hermes-safe options are accepted.\n *\n * A locale the runtime's Intl rejects throws `FormatError('ERR_LOCALE_INVALID')`\n * and out-of-range fraction digits throw `FormatError('ERR_FRACTION_DIGITS_INVALID')`\n * — configuration errors, never data errors (§1-3).\n */\ndeclare function formatNumber<TFallback = string>(value: number | null | undefined, options: FormatNumberOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "Locale-grouped plain number (`12,345`). Deliberately NOT a passthrough to\nIntl's own option bag — only Hermes-safe options are accepted.\n\nA locale the runtime's Intl rejects throws `FormatError('ERR_LOCALE_INVALID')`\nand out-of-range fraction digits throw `FormatError('ERR_FRACTION_DIGITS_INVALID')`\n— configuration errors, never data errors (§1-3)."
        },
        {
          "name": "FormatNumberOptions",
          "slug": "format-number-options",
          "kind": "interface",
          "declaration": "interface FormatNumberOptions<TFallback = string> {\n    /** Required grouping locale; `'device'` opts into the runtime default. */\n    readonly locale: FormatLocale;\n    /** Default: Intl's own default (max 3). */\n    readonly maximumFractionDigits?: number | undefined;\n    readonly minimumFractionDigits?: number | undefined;\n    /** Rendered for null/undefined/non-finite input. Default `'-'`. */\n    readonly fallback?: TFallback | undefined;\n}"
        },
        {
          "name": "formatPercent",
          "slug": "format-percent",
          "kind": "function",
          "declaration": "/**\n * A 0-1 fraction as a percentage: `0.63` becomes `'63%'`. Closes the\n * {@link storageRatio} to screen pipe inside this package.\n *\n * The `%` sign is a literal suffix with no space, pinned like the `₩` glyph in\n * {@link formatKrw}. Intl's own percentage rendering moves the sign and inserts\n * a no-break space in some locales (French renders `63` then a space then `%`),\n * which is exactly the drift this package refuses to inherit.\n *\n * A ratio that rounds to zero renders `'0%'` — never `'-0%'`, whichever side of\n * zero it came from.\n */\ndeclare function formatPercent<TFallback = string>(ratio: number | null | undefined, options: FormatPercentOptions<TFallback>): string | TFallback;",
          "sourceDocumentation": "A 0-1 fraction as a percentage: `0.63` becomes `'63%'`. Closes the\n{@link storageRatio} to screen pipe inside this package.\n\nThe `%` sign is a literal suffix with no space, pinned like the `₩` glyph in\n{@link formatKrw}. Intl's own percentage rendering moves the sign and inserts\na no-break space in some locales (French renders `63` then a space then `%`),\nwhich is exactly the drift this package refuses to inherit.\n\nA ratio that rounds to zero renders `'0%'` — never `'-0%'`, whichever side of\nzero it came from."
        },
        {
          "name": "FormatPercentOptions",
          "slug": "format-percent-options",
          "kind": "interface",
          "declaration": "interface FormatPercentOptions<TFallback = string> {\n    /** Required grouping locale — grouping and decimal separator only. */\n    readonly locale: FormatLocale;\n    /** Exact fraction digits. Default 0 (`'63%'`). */\n    readonly fractionDigits?: 0 | 1 | 2 | undefined;\n    /** Rendered for null/undefined/non-finite input. Default `'-'`. */\n    readonly fallback?: TFallback | undefined;\n}"
        },
        {
          "name": "FormatRelativeBucket",
          "slug": "format-relative-bucket",
          "kind": "type",
          "declaration": "/** Structured relative-time classification; render copy yourself or via formatRelativeKo. */\ntype FormatRelativeBucket = \n/** `ms` counts milliseconds *until* the value, always positive. */\n{\n    readonly kind: 'future';\n    readonly ms: number;\n} | {\n    readonly kind: 'just-now';\n    readonly seconds: number;\n} | {\n    readonly kind: 'minutes';\n    readonly count: number;\n} | {\n    readonly kind: 'hours';\n    readonly count: number;\n} | {\n    readonly kind: 'days';\n    readonly count: number;\n} | {\n    readonly kind: 'months';\n    readonly count: number;\n} | {\n    readonly kind: 'years';\n    readonly count: number;\n};",
          "sourceDocumentation": "Structured relative-time classification; render copy yourself or via formatRelativeKo."
        },
        {
          "name": "formatRelativeKo",
          "slug": "format-relative-ko",
          "kind": "function",
          "declaration": "/** Korean relative time. Both app renderings are expressible; neither is a default. */\ndeclare function formatRelativeKo(value: FormatDateInput | null | undefined, options: FormatRelativeKoOptions): string;",
          "sourceDocumentation": "Korean relative time. Both app renderings are expressible; neither is a default."
        },
        {
          "name": "FormatRelativeKoOptions",
          "slug": "format-relative-ko-options",
          "kind": "type",
          "declaration": "type FormatRelativeKoOptions = {\n    /** Required explicit clock — this family never reads the system clock itself. */\n    readonly now: Date;\n    /** Required: `true` → `'3분 전'`, `false` → `'3분전'`. The apps disagreed. */\n    readonly suffixSpace: boolean;\n    /** Required: rendered for null/invalid input. The apps disagreed (`''` vs `'-'`). */\n    readonly fallback: string;\n    /**\n     * Required policy for timestamps after `now`:\n     * `'empty'` returns `''`; a function renders an absolute form instead.\n     */\n    readonly onFuture: 'empty' | ((date: Date) => string);\n    /** Label for the <60s bucket. Default `'방금'`. */\n    readonly justNowLabel?: string | undefined;\n    /** When set, the 1-day bucket renders this literal (e.g. `'어제'`) instead of `'1일 전'`. */\n    readonly yesterdayLabel?: string | undefined;\n} & ({\n    readonly maxDays?: undefined;\n    readonly onOverflow?: undefined;\n} | {\n    /** Elapsed days >= maxDays switch to onOverflow (e.g. 7 → absolute date). */\n    readonly maxDays: number;\n    /** Required together with maxDays — there is no built-in absolute rendering. */\n    readonly onOverflow: (date: Date) => string;\n});"
        },
        {
          "name": "formatText",
          "slug": "format-text",
          "kind": "function",
          "declaration": "/**\n * The single non-numeric formatter: an empty-cell placeholder.\n *\n * The input type is narrowed from the source's `unknown` on purpose. In the\n * source that `unknown` travelled with a `Number(value || 0)` coercion in the\n * neighbouring formatters, which rendered \"unknown\" as a hard zero.\n */\ndeclare function formatText(value: string | number | null | undefined, fallback?: string): string;",
          "sourceDocumentation": "The single non-numeric formatter: an empty-cell placeholder.\n\nThe input type is narrowed from the source's `unknown` on purpose. In the\nsource that `unknown` travelled with a `Number(value || 0)` coercion in the\nneighbouring formatters, which rendered \"unknown\" as a hard zero."
        },
        {
          "name": "FormatTimeZone",
          "slug": "format-time-zone",
          "kind": "type",
          "declaration": "/**\n * Explicit time zone selector. There is deliberately no default:\n * - `'UTC'`        — UTC wall clock (no Intl involved).\n * - `'device'`     — the runtime's local time (no Intl involved). This is an\n *                    explicit opt-in, not a silent fallback: the dependency on\n *                    device state is visible at every call site.\n * - IANA zone name — e.g. `'Asia/Seoul'`; resolved via `Intl.DateTimeFormat`.\n *                    An unknown name throws `FormatError('ERR_TIMEZONE_INVALID')`.\n *                    Ask {@link canFormatTimeZone} first if the runtime's Intl\n *                    is not known to be healthy.\n */\ntype FormatTimeZone = 'UTC' | 'device' | (string & {});",
          "sourceDocumentation": "Explicit time zone selector. There is deliberately no default:\n- `'UTC'`        — UTC wall clock (no Intl involved).\n- `'device'`     — the runtime's local time (no Intl involved). This is an\n                   explicit opt-in, not a silent fallback: the dependency on\n                   device state is visible at every call site.\n- IANA zone name — e.g. `'Asia/Seoul'`; resolved via `Intl.DateTimeFormat`.\n                   An unknown name throws `FormatError('ERR_TIMEZONE_INVALID')`.\n                   Ask {@link canFormatTimeZone} first if the runtime's Intl\n                   is not known to be healthy."
        },
        {
          "name": "isFormatError",
          "slug": "is-format-error",
          "kind": "function",
          "declaration": "/** Type guard usable across realms/bundles. */\ndeclare function isFormatError(value: unknown): value is FormatError;",
          "sourceDocumentation": "Type guard usable across realms/bundles."
        },
        {
          "name": "IsoParseOptions",
          "slug": "iso-parse-options",
          "kind": "interface",
          "declaration": "interface IsoParseOptions {\n    /**\n     * Required policy for ISO strings that carry no UTC offset, e.g.\n     * `'2026-06-08T09:05:00'`. There is no safe default: the `Date` constructor\n     * resolves these against the device zone, so the same string is a different\n     * instant on a Seoul phone (`00:05Z`) and a New York phone (`13:05Z`).\n     * - `'utc'`    — read the wall clock as UTC.\n     * - `'device'` — read it as device-local time. Same behaviour the source apps\n     *                had, now spelled out at the call site.\n     * - `'reject'` — return null; the caller renders its fallback.\n     *\n     * Date-only strings (`'2026-06-08'`) are always UTC midnight — that reading is\n     * unambiguous per ECMA-262 and this option does not affect them.\n     */\n    readonly assumeNoOffset: 'utc' | 'device' | 'reject';\n}"
        },
        {
          "name": "parseIsoInstant",
          "slug": "parse-iso-instant",
          "kind": "function",
          "declaration": "/**\n * Strict ISO 8601 → instant. Returns null for null/undefined/empty input and\n * for anything outside the accepted grammar — parsing failure is a data error,\n * never a throw.\n *\n * Accepted: `YYYY-MM-DD` | `YYYY-MM-DD(T| )HH:mm[:ss[.fff]][Z|±HH:MM|±HHMM]`,\n * year 1–9999, calendar-valid components (no rollover: `'2026-02-30'` is null).\n *\n * Implemented with a regular expression and `Date.UTC` arithmetic — the engine's\n * own string parser is never used, so the result does not vary between V8,\n * JavaScriptCore and Hermes the way parsing a string with `Date` does.\n */\ndeclare function parseIsoInstant(value: string | null | undefined, options: IsoParseOptions): Date | null;",
          "sourceDocumentation": "Strict ISO 8601 → instant. Returns null for null/undefined/empty input and\nfor anything outside the accepted grammar — parsing failure is a data error,\nnever a throw.\n\nAccepted: `YYYY-MM-DD` | `YYYY-MM-DD(T| )HH:mm[:ss[.fff]][Z|±HH:MM|±HHMM]`,\nyear 1–9999, calendar-valid components (no rollover: `'2026-02-30'` is null).\n\nImplemented with a regular expression and `Date.UTC` arithmetic — the engine's\nown string parser is never used, so the result does not vary between V8,\nJavaScriptCore and Hermes the way parsing a string with `Date` does."
        },
        {
          "name": "relativeBucket",
          "slug": "relative-bucket",
          "kind": "function",
          "declaration": "/**\n * Pure bucket selection against an explicit clock. Returns null for\n * null/undefined/invalid input. Thresholds: <60s just-now, <60m minutes,\n * <24h hours, <30d days, <12mo months, then years.\n *\n * Calendar-unaware by construction: a month is exactly 30 days and a year is\n * exactly 12 such months — 360 days, not 365. This reproduces both source apps\n * bit for bit; the drift is a systematic early promotion of about five days per\n * year, so `days = 360` already renders as one year.\n */\ndeclare function relativeBucket(value: FormatDateInput | null | undefined, now: Date): FormatRelativeBucket | null;",
          "sourceDocumentation": "Pure bucket selection against an explicit clock. Returns null for\nnull/undefined/invalid input. Thresholds: <60s just-now, <60m minutes,\n<24h hours, <30d days, <12mo months, then years.\n\nCalendar-unaware by construction: a month is exactly 30 days and a year is\nexactly 12 such months — 360 days, not 365. This reproduces both source apps\nbit for bit; the drift is a systematic early promotion of about five days per\nyear, so `days = 360` already renders as one year."
        },
        {
          "name": "storageRatio",
          "slug": "storage-ratio",
          "kind": "function",
          "declaration": "/** Usage as a 0-1 fraction, or null when the limit is missing/zero/invalid.\n *  Arithmetic, not rendering — pair it with {@link formatPercent}. */\ndeclare function storageRatio(used: number | null | undefined, limit: number | null | undefined): number | null;",
          "sourceDocumentation": "Usage as a 0-1 fraction, or null when the limit is missing/zero/invalid.\nArithmetic, not rendering — pair it with {@link formatPercent}."
        }
      ]
    }
  ]
}
