---
id: "packages/active-format/errors-and-runtime"
title: "Errors and runtime"
description: "What the package throws, what it returns as empty, and which runtime capabilities two of its functions need."
url: "/packages/active-format/errors-and-runtime"
locale: "en"
revision: "5e7b62f2d25e1dec"
---

# Errors and runtime

## Three kinds of outcome

| Situation | Outcome |
| --- | --- |
| an empty or unreadable **value** in a human formatter | `''`: your interface decides what to show |
| a language tag the runtime cannot format | `UnsupportedLocaleError` |
| a programming or configuration error | a `RangeError` (or `Error`) naming the problem |

The package never falls back silently: there is no English floor and no host default.

## Unsupported language tags

Any BCP 47 tag your runtime's `Intl` formats is accepted. Every other tag throws an
`UnsupportedLocaleError`, a `RangeError` that carries the `locale` as passed and a `reason`:
`malformed`, `unsupported`, `calendar` (only the Gregorian calendar is supported) or
`numbering-system`.

To test a tag before it becomes a presentation, for example a tag from a profile or a request
header, use `isSupportedLocale(tag)`. It gives the same verdict without throwing.

**Example: Unsupported language tags**

Client: Runs entirely in your browser. Source: `examples/format/locale-errors.tsx`.

```tsx
// apps/docs/examples/format/locale-errors.tsx
'use client';

import {
  formatNumber,
  isSupportedLocale,
  UnsupportedLocaleError,
} from '@workspace/active-format';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// Tags a host might receive from a profile, a header or a setting.
const TAGS = ['en-GB', 'nl', 'pt-BR', 'zz-ZZ', 'en_US', 'ja-JP-u-ca-japanese'];

function tryFormat(tag: string): string {
  try {
    return formatNumber(1234.5, 'decimal-1', tag);
  } catch (err) {
    if (err instanceof UnsupportedLocaleError) {
      return `UnsupportedLocaleError (${err.reason})`;
    }
    throw err;
  }
}

/**
 * A refused tag throws: there is no silent English or host default. A host
 * that wants to test a tag first asks `isSupportedLocale`.
 */
export default function LocaleErrors() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Tag</TableHead>
          <TableHead>isSupportedLocale</TableHead>
          <TableHead>formatNumber(1234.5)</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {TAGS.map((tag) => (
          <TableRow key={tag}>
            <TableCell className="font-mono text-xs">{tag}</TableCell>
            <TableCell className="font-mono text-xs" suppressHydrationWarning>
              {String(isSupportedLocale(tag))}
            </TableCell>
            <TableCell suppressHydrationWarning>{tryFormat(tag)}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}
```

## Configuration errors

These throw, because they are mistakes in code or configuration, not in data:

- a malformed pattern in `formatDateWithPattern`;
- a malformed currency code, or an invalid `decimals` or `sign`;
- an unknown unit in `formatUnit`;
- an inconsistent `NumberSpec`, or one out of range;
- `smallestUnit` larger than `largestUnit` in `formatElapsedDuration`;
- an untyped call of `todayIsoDate` or `calendarDayOf` without a zone.

## Runtime requirements

The package installs no polyfill: your application owns its runtime. Two functions need an `Intl`
capability beyond ES2022, in Node.js and in the browser alike:

| Function | Needs | Without it |
| --- | --- | --- |
| `formatCurrency` with a `DecimalString` | `Intl.NumberFormat` reading a string as an exact decimal (ES2023) | **silent precision loss**: the engine converts the string to a number itself |
| `formatElapsedDuration` | `Intl.DurationFormat` (ES2025) | **throws** on every call |

First versions with both, according to MDN's compatibility data: Chrome and Edge 129, Firefox 136,
Safari 16.4, Node.js 23. Decide your supported runtimes from that, or check once at start-up:

```ts
const exactDecimal =
  new Intl.NumberFormat('en', {useGrouping: false}).format(
    '9007199254740993' as unknown as number,
  ) === '9007199254740993';
const elapsedDuration =
  typeof (Intl as {DurationFormat?: unknown}).DurationFormat === 'function';
```

Without the exact-decimal capability, keep amounts with more digits than a double carries away from
`formatCurrency`, or install a polyfill. Without `Intl.DurationFormat`, do not call
`formatElapsedDuration`, or install a polyfill before the first call.
