## The adapter pattern

The `Adapter.IDateAdapter

***

## Built-in: NativeAdapter

Zero dependencies. Uses `Date` + `Intl.DateTimeFormat`.

```tsx showLineNumbers
import { NativeAdapter } from '@gentleduck/calendar'

const adapter = new NativeAdapter()
```

The `NativeAdapter` implements every adapter method with `Date` plus `Intl.DateTimeFormat` for locale-aware formatting. No polyfills required.

***

## Shipped adapters

The package includes 7 ready-to-use adapters. Adapters marked with a peer dependency require you to install the underlying date library yourself.

| Adapter | Import | Description | Peer dependency |
| :--- | :--- | :--- | :--- |
| `NativeAdapter` | `@gentleduck/calendar` | Zero-dep adapter for the built-in `Date` object | None |
| `DateFnsAdapter` | `@gentleduck/calendar/adapter` | Adapter for date-fns | `date-fns` |
| `DayjsAdapter` | `@gentleduck/calendar/adapter` | Adapter for dayjs | `dayjs` |
| `LuxonAdapter` | `@gentleduck/calendar/adapter` | Adapter for Luxon | `luxon` |
| `PersianAdapter` | `@gentleduck/calendar/adapter` | Persian / Jalali calendar | None |
| `IslamicAdapter` | `@gentleduck/calendar/adapter` | Islamic / Hijri calendar | None |
| `HebrewAdapter` | `@gentleduck/calendar/adapter` | Hebrew calendar | None |

***

## Writing a custom adapter

Implement the `Adapter.IDateAdapter<TDate>` interface for your preferred date library.

### dayjs example

```tsx showLineNumbers
import type { Adapter } from '@gentleduck/calendar'
import dayjs, { type Dayjs } from 'dayjs'

const dayjsAdapter: Adapter.IDateAdapter<Dayjs> = {
  today: () => dayjs().startOf('day'),
  create: (y, m, d) => dayjs().year(y).month(m).date(d).startOf('day'),
  isValid: (d) => d.isValid(),
  isSameDay: (a, b) => a.isSame(b, 'day'),
  isSameMonth: (a, b) => a.isSame(b, 'month'),
  isBefore: (a, b) => a.isBefore(b),
  isAfter: (a, b) => a.isAfter(b),
  startOfMonth: (d) => d.startOf('month'),
  endOfMonth: (d) => d.endOf('month'),
  startOfWeek: (d, weekStart) => {
    const diff = (d.day() - weekStart + 7) % 7
    return d.subtract(diff, 'day')
  },
  addDays: (d, n) => d.add(n, 'day'),
  addMonths: (d, n) => d.add(n, 'month'),
  addYears: (d, n) => d.add(n, 'year'),
  getYear: (d) => d.year(),
  getMonth: (d) => d.month(),
  getDate: (d) => d.date(),
  getDayOfWeek: (d) => d.day() as any,
  toDate: (d) => d.toDate(),
  fromDate: (d) => dayjs(d).startOf('day'),
  format: (d, opts, locale) => new Intl.DateTimeFormat(locale, opts).format(d.toDate()),
  getHours: (d) => d.hour(),
  getMinutes: (d) => d.minute(),
  getSeconds: (d) => d.second(),
  setTime: (d, h, m, s) => d.hour(h).minute(m).second(s ?? 0),
}
```

***

### Adapter interface

Every adapter must implement these methods:

| Method | Signature | Description |
| :--- | :--- | :--- |
| `today` | `() => TDate` | Return today at midnight |
| `create` | `(y, m, d) => TDate` | Create a date from year, month (0-indexed), day |
| `isValid` | `(d) => boolean` | Check if a date is valid |
| `isSameDay` | `(a, b) => boolean` | Compare two dates ignoring time |
| `isSameMonth` | `(a, b) => boolean` | Compare month and year |
| `isBefore` | `(a, b) => boolean` | a is before b |
| `isAfter` | `(a, b) => boolean` | a is after b |
| `startOfMonth` | `(d) => TDate` | First day of the month |
| `endOfMonth` | `(d) => TDate` | Last day of the month |
| `startOfWeek` | `(d, weekStart) => TDate` | First day of the week |
| `addDays` | `(d, n) => TDate` | Add n days |
| `addMonths` | `(d, n) => TDate` | Add n months |
| `addYears` | `(d, n) => TDate` | Add n years |
| `getYear` | `(d) => number` | Extract year |
| `getMonth` | `(d) => number` | Extract month (0-indexed) |
| `getDate` | `(d) => number` | Extract day of month |
| `getDayOfWeek` | `(d) => Adapter.WeekStartDay` | Day of week (0=Sun) |
| `toDate` | `(d) => Date` | Convert to native Date |
| `fromDate` | `(d: Date) => TDate` | Convert from native Date |
| `format` | `(d, opts, locale?) => string` | Format for display |
| `getHours` | `(d) => number` | Extract hours |
| `getMinutes` | `(d) => number` | Extract minutes |
| `getSeconds` | `(d) => number` | Extract seconds |
| `setTime` | `(d, h, m, s?) => TDate` | Set time on a date |
| `getMonthsInYear?` | `(d) => number` | Optional. Month count for the year (defaults to 12). Hebrew leap years return 13. |

} tone="warning">
  All date operations must be immutable - never mutate the input date. Return a new instance instead.