# Errors

Every invalid input or unsupported operation throws a standard Error carrying a machine-readable code.

## Error shape

Invalid calls fail immediately instead of returning broken date objects or `NaN` numbers that corrupt downstream math. Every thrown error is a standard JavaScript error carrying two extra properties:

- **`error.code`** holds a stable camelCase identifier such as `'unreadableDate'` or `'unknownZone'`.
- **`error.at`** echoes the offending input or operation target as a string, such as `'2026-02-30'`.

Wrong argument types and mismatched kinds throw a `TypeError`. Impossible dates, unreadable strings, unknown units or zones, and unsupported operations throw a `RangeError`.

```js title="catch.js"
import { date } from 'native-dates';

try {
  date('2026-02-30');
} catch (error) {
  error instanceof RangeError; // true
  error.code; // 'unreadableDate'
  error.at; // '2026-02-30'
}
```

During development, `error.message` includes a second line with a plain-English hint explaining how to fix the call. Production builds with `NODE_ENV` set to `'production'` strip those hint strings automatically while keeping the error code and input echo unchanged.

## Handling untrusted input

Inspect `error.code` when you want custom validation messages for specific failure modes, and rethrow unexpected errors:

```js
import { datetime } from 'native-dates';

function parseTimestamp(raw, zone) {
  try {
    return datetime(raw, zone);
  } catch (error) {
    if (error.code === 'unreadableDateTime') {
      return null;
    }
    throw error;
  }
}

parseTimestamp('not-a-date', 'UTC'); // null
```

When reading user-typed form fields, query parameters, or CSV cells where unreadable text should return `null`, import [`@native-dates/plugins/loose`](/plugins/loose/) and enable loose mode:

```js
import { date, duration } from 'native-dates';
import '@native-dates/plugins/loose';

date('2026-02-30', { loose: true }); // null
duration('whenever', { loose: true }); // null
```

:::tip[Data errors vs code errors in loose mode]
Loose mode returns `null` only when the input value itself cannot be read. Mistakes in your own call options, such as an unrecognized time zone or a backwards range, still throw so bugs stay visible.
:::

## TypeError codes

Type errors indicate that an argument has the wrong JavaScript type or belongs to a different time concept than expected.

| Code | Thrown by | Cause |
|---|---|---|
| `notADateTime` | `datetime()`, `DateTime` | Input is `null`, `undefined`, or an unsupported type such as a boolean. |
| `notADate` | `date()`, `CalendarDate` | Input is `null`, `undefined`, a bare single number, an instant string with `Z` or an offset, or an unsupported type. |
| `notATime` | `time()`, `Time` | Input is `null`, `undefined`, or an unsupported type. |
| `notADuration` | `duration()`, `Duration`, `plus()`, `minus()` | Input is `null`, `undefined`, an unsupported type, or a `Duration` paired with a second unit argument. |
| `notAPoint` | `compare()`, `earliest()`, `latest()` | Value is not a `DateTime`, `CalendarDate`, `Time`, `Date`, Temporal point, or parseable string. |
| `mixedKinds` | `compare()`, `earliest()`, `latest()` | Comparing values of different kinds, such as a `CalendarDate` with a `DateTime`. |
| `mixedRange` | `DateRange`, `DateTimeRange`, `TimeRange` | Passing a range of one kind to another range constructor or factory in `@native-dates/plugins/ranges`. |

## RangeError codes

Range errors indicate that a value cannot be parsed, falls outside valid bounds, or asks for an unsupported unit, zone, or operation.

### Parsing and values

| Code | Thrown by | Cause |
|---|---|---|
| `unreadableDateTime` | `datetime()`, `DateTime` | String cannot be parsed as a date-time, or fields contain out-of-range values like February 30. |
| `unreadableDate` | `date()`, `CalendarDate` | String cannot be parsed as a date, numbers are fractional, or calendar fields are out of range. |
| `unreadableTime` | `time()`, `Time` | String cannot be parsed as a clock time, 12-hour value is outside 1 to 12, or clock fields are out of range. |
| `unreadableDuration` | `duration()`, `Duration` | Empty or unparseable duration phrase, invalid ISO 8601 duration, or out-of-range duration fields. |
| `notFinite` | `datetime()`, `duration()`, unit helpers | Numeric input is `NaN`, `Infinity`, `-Infinity`, or an invalid `Date` instance. |
| `mixedSigns` | `duration()`, `Duration` | Duration phrase or fields mix positive and negative units, such as `'1h -30m'`. |
| `fractionalMonth` | `duration()`, `months()`, `times()` | Fractional months or nanoseconds have no smaller fixed unit to spill into. |

### Units, zones, and formats

| Code | Thrown by | Cause |
|---|---|---|
| `unknownUnit` | Arithmetic, rounding, `set()`, `duration()` | Unrecognized unit name or abbreviation. |
| `unknownWeekday` | `weekday()`, `next()`, `previous()`, `configure()` | Weekday string is not recognized or weekday number is outside 1 to 7. |
| `unknownZone` | Factories, `in()`, `configure()` | Unknown IANA time zone ID, malformed UTC offset, or non-object `zoneAliases`. |
| `unknownLocale` | `format()`, `configure()` | Invalid BCP 47 locale tag rejected by `Intl.getCanonicalLocales()`. |
| `unknownFormat` | `format()` | Unknown preset name, invalid `Intl` option, unsupported duration style, or token string used without `@native-dates/plugins/tokens`. |

### Arithmetic and measurement

| Code | Thrown by | Cause |
|---|---|---|
| `notADateUnit` | `CalendarDate` math, `startOf()`, `endOf()` | Adding, subtracting, or rounding a `CalendarDate` by a clock unit such as hours or minutes. |
| `notATimeUnit` | `Time` math | Adding or subtracting a calendar unit such as days, weeks, months, or years on a `Time`. |
| `cannotAdd` | `plus()`, `add()` | Addition overflows Temporal's representable date range. |
| `cannotSubtract` | `minus()`, `subtract()`, `remove()` | Subtraction overflows Temporal's representable date range. |
| `cannotSet` | `set()` | Field value is fractional or out of range for that calendar month or clock unit. |
| `cannotRound` | `round()`, `floor()`, `ceil()`, `Time.startOf()` | Unsupported rounding unit, or step increment does not divide evenly into the next unit. |
| `needsAnchor` | `Duration.balance()`, `total()`, `compare()` | Unanchored duration requires a calendar start date to convert or compare months or years. |
| `cannotBalance` | `Duration.balance()` | Balancing a duration overflows or fails Temporal rounding. |
| `cannotTotal` | `Duration.total()`, `until()`, `since()` | Computing a total length overflows or uses an unsupported unit. |
| `noPoints` | `earliest()`, `latest()` | Called with zero arguments or an empty array. |
| `notANumber` | `CalendarDate`, `Time`, ranges | Numeric coercion via `valueOf()`, unary `+`, `-`, `<`, or `>` on a type with no epoch number. |

### Environment and plugins

| Code | Thrown by | Cause |
|---|---|---|
| `noTemporal` | Module load | `globalThis.Temporal` is missing and `@native-dates/polyfill` has not been imported yet. |
| `missingPlugin` | Factories, `configure()` | Using `loose`, `dayFirst`, or `zoneAliases` before importing the matching plugin. |
| `backwards` | `DateRange`, `DateTimeRange` | Range `end` is earlier than `start`. |
| `emptyStep` | `points()`, `split()` | Step duration passed to a range walk is zero or negative. |
| `twoPieces` | `TimeRange.intersection()` | Two overnight time ranges overlap in two separate intervals across midnight. |
| `noZone` | `TimeRange.in()` | Calling `in()` on a `TimeRange`, which has no calendar date to place in a zone. |
| `noTokens` | Range `format()` | Passing a token string to a range's `format()` instead of a preset or `Intl` options object. |
| `noField` | `format()` with tokens | Token pattern asks for a field the value lacks, such as `HH` on a `CalendarDate` or `YYYY` on a `Time`. |
| `immutable` | `DateTime` legacy setters | Calling a mutating `Date` setter such as `setHours()` when `@native-dates/plugins/legacy-date` is loaded. |
