# Migrate from date-fns to Temporal with GMT

Checked against date-fns 4.4.0 and @date-fns/tz 1.5.0 on 2026-10-27.

[Migrate to Temporal with GMT](/gmt/migrate/) explains the one change that applies to every call below: GMT takes strings in and gives strings out.

## Call by call

| date-fns | GMT | Note |
| --- | --- | --- |
| `parseISO(s)` | [`isValidDate`](/gmt/reference/plain/validate/isValidDate), [`isValidDateTime`](/gmt/reference/plain/validate/isValidDateTime), [`isValidZonedDateTime`](/gmt/reference/zoned/validate/isValidZonedDateTime), [`isValidUtc`](/gmt/reference/utc/validate/isValidUtc) | The string is the value, so there is nothing to parse; check it with a validator. `parseISO` drops the offset before a `[zone]` and then reads the wall time as UTC (`03:30Z`). Temporal reads the basic format `20240212`; GMT rejects it on purpose and follows the extended format of RFC 3339 §5.6. |
| `parse(s, "dd/MM/yyyy", ref)` | [`parseDateWithPattern`](/gmt/reference/plain/parse/parseDateWithPattern) | `yyyy` is four digits in GMT. date-fns reads `2-12-12` as year 2. |
| `parse(s, "yyyy MMMM", ref)` | [`parseYearMonthWithPattern`](/gmt/reference/plain/parse/parseYearMonthWithPattern) | A year and a month with no day, returned as `YYYY-MM`. |
| `isValid(d)` | [`isValidDate`](/gmt/reference/plain/validate/isValidDate) | GMT rejects 2024-02-30 and basic format. |
| `format(d, "d MMMM yyyy")` | [`formatDate`](/gmt/reference/plain/format/formatDate) | GMT has no token formatter; pass a locale and `dateStyle`. |
| `formatISO(d)` | none | None for a GMT string, which is already ISO 8601. `formatISO` writes the host offset. |
| The current time, from the built-in date object | [`getUtcNow`](/gmt/reference/utc/get/getUtcNow), [`getZonedNow`](/gmt/reference/zoned/get/getZonedNow) |  |
| `startOfToday()` | [`getToday`](/gmt/reference/plain/get/getToday), [`getZonedToday`](/gmt/reference/zoned/get/getZonedToday) |  |
| `addDays(d, n)` | [`addDate`](/gmt/reference/plain/calculate/addDate), [`addZoned`](/gmt/reference/zoned/calculate/addZoned) | date-fns works in the host zone, so a day is a calendar day of the host, and the result depends on the host zone. GMT takes the zone from the value. |
| `addMonths(d, n)` | [`addDate`](/gmt/reference/plain/calculate/addDate) | The day is clamped to the month length. `{ overflow: "reject" }` returns `""`. |
| `subDays(d, n)`, `subMonths(d, n)` | [`subtractDate`](/gmt/reference/plain/calculate/subtractDate) |  |
| `addHours(d, n)` | [`addZoned`](/gmt/reference/zoned/calculate/addZoned), [`addUtc`](/gmt/reference/utc/calculate/addUtc) | Hours are exact time. |
| `differenceInCalendarDays(later, earlier)` | [`diffDate`](/gmt/reference/plain/calculate/diffDate) | The arguments are in the other order: earlier first. |
| `differenceInMonths(later, earlier)` | [`diffDate`](/gmt/reference/plain/calculate/diffDate) | From 2024-01-31 to 2024-02-29 date-fns counts 1 month and GMT counts 0. |
| `intervalToDuration({ start, end })` | [`diffDate`](/gmt/reference/plain/calculate/diffDate) | Pass the units you want, for example `["years", "months", "days"]`. |
| `startOfMonth(d)` | [`startOfDate`](/gmt/reference/plain/calculate/startOfDate) |  |
| `endOfMonth(d)` | [`endOfDate`](/gmt/reference/plain/calculate/endOfDate) |  |
| `startOfDay(d, { in: tz(z) })` | [`startOfZoned`](/gmt/reference/zoned/calculate/startOfZoned) | The first instant of the day, which is not always 00:00. |
| `isBefore(a, b)`, `isAfter(a, b)` | [`isBeforeDate`](/gmt/reference/plain/compare/isBeforeDate), [`isAfterDate`](/gmt/reference/plain/compare/isAfterDate), [`isBeforeZoned`](/gmt/reference/zoned/compare/isBeforeZoned) |  |
| `isEqual(a, b)` | [`areDatesEqual`](/gmt/reference/plain/compare/areDatesEqual), [`areUtcEqual`](/gmt/reference/utc/compare/areUtcEqual), [`areZonedEqual`](/gmt/reference/zoned/compare/areZonedEqual) | `areZonedEqual` follows `ZonedDateTime.prototype.equals`. |
| `isSameDay(a, b)` | [`areDatesEqualBy`](/gmt/reference/plain/compare/areDatesEqualBy), [`areZonedEqualBy`](/gmt/reference/zoned/compare/areZonedEqualBy) |  |
| `isWithinInterval(d, { start, end })` | [`isBetweenDate`](/gmt/reference/plain/compare/isBetweenDate) | Both ends are inclusive by default. |
| `areIntervalsOverlapping(a, b)` | [`intervalsOverlapDate`](/gmt/reference/plain/interval/intervalsOverlapDate) |  |
| `min(ds)`, `max(ds)` | [`minDate`](/gmt/reference/plain/calculate/minDate), [`maxDate`](/gmt/reference/plain/calculate/maxDate) |  |
| `getDaysInMonth(d)`, `isLeapYear(d)` | [`getDaysInMonth`](/gmt/reference/plain/calculate/getDaysInMonth), [`getDaysInYearMonth`](/gmt/reference/plain/calculate/getDaysInYearMonth), [`isLeapYear`](/gmt/reference/plain/validate/isLeapYear) |  |
| `getISOWeek(d)`, `getISOWeekYear(d)` | [`getIsoWeekDate`](/gmt/reference/calendar/calculate/getIsoWeekDate) | One call returns year, week and weekday. `getWeek` is a locale week. |
| `getISOWeeksInYear(d)` | [`getWeeksInYear`](/gmt/reference/plain/calculate/getWeeksInYear) |  |
| `isWeekend(d)`, `addBusinessDays(d, n)`, `differenceInBusinessDays(a, b)` | [`isWeekend`](/gmt/reference/plain/compare/isWeekend), [`addBusinessDays`](/gmt/reference/plain/calculate/addBusinessDays), [`businessDaysBetween`](/gmt/reference/calendar/business/businessDaysBetween) | date-fns fixes the weekend to Saturday and Sunday. `isWeekend` in GMT takes a locale. `addBusinessDays` takes an optional calendar and counts Monday to Friday without one. `businessDaysBetween` requires a calendar and reads the weekend from `calendar.weekend`. |
| `formatDistance(d, base)`, `formatRelative(d, base)` | [`formatRelativeDate`](/gmt/reference/plain/format/formatRelativeDate), [`formatRelativeZoned`](/gmt/reference/zoned/format/formatRelativeZoned) | The wording comes from `Intl.RelativeTimeFormat`. |

## What changes

- date-fns takes and returns `Date` objects, or `TZDate` from `@date-fns/tz`. GMT takes and returns strings.
- `getMonth` counts months from 0. GMT counts them from 1 to 12, as Temporal does.
- The difference functions take the later date first, as in `differenceInCalendarDays(later, earlier)`. `diffDate` takes the earlier date first.
- Invalid input gives an `Invalid Date`, and `format` throws a `RangeError`. GMT returns `""`, `null`, `false` or `[]` instead of throwing.

```js
import { diffDate } from "@northguild/gmt/plain/calculate";

diffDate("2024-03-01", "2024-03-15", "days"); // 14
```

## Basic-format dates

`parseISO` reads `20240212`, as Temporal does. GMT's validators and parsers accept only the extended format, `2024-02-12`, which is the format of RFC 3339 §5.6, so `isValidDate("20240212")` returns `false`. This is deliberate. Convert a basic-format string before you pass it on. The behaviour matrix shows the library's result in case [M9c](/gmt/compare/behaviour/#m9c-date-fns).

## Where date-fns differs from Temporal

Each item is a case where the library's result differs from Temporal's. The [full matrix](/gmt/compare/behaviour/) also lists the cases where it matches.

- **M1. Add one calendar day across a DST gap** Input: 2024-03-30T02:30:00+01:00\[Europe/Berlin\], plus 1 day (02:30 does not exist on 2024-03-31). The library returns Asia/Kolkata host: 2024-03-31T03:30:00.000+02:00, Europe/London host: 2024-03-31T01:30:00.000+01:00, Australia/Sydney host: 2024-03-31T03:30:00.000+02:00, America/Santiago host: 2024-03-31T03:30:00.000+02:00; Temporal returns 2024-03-31T03:30:00+02:00\[Europe/Berlin\]. Specification: [AddZonedDateTime (6.5.5)](https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime). [Matrix row](/gmt/compare/behaviour/#m1-date-fns).
- **M3. Add one day into a repeated hour** Input: 2024-10-26T02:30:00+02:00\[Europe/Berlin\], plus 1 day (02:30 happens twice on 2024-10-27). The library returns Asia/Kolkata host: 2024-10-27T02:30:00.000+02:00, Europe/London host: 2024-10-27T02:30:00.000+01:00, Australia/Sydney host: 2024-10-27T02:30:00.000+02:00, America/Santiago host: 2024-10-27T02:30:00.000+01:00; Temporal returns 2024-10-27T02:30:00+02:00\[Europe/Berlin\]. Specification: [DisambiguatePossibleEpochNanoseconds (11.1.12)](https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleepochnanoseconds). [Matrix row](/gmt/compare/behaviour/#m3-date-fns).
- **M4. Resolve a wall time that does not exist** Input: 2024-03-31T02:30:00 in Europe/Berlin (the clocks skip from 02:00 to 03:00). The library returns Asia/Kolkata host: 2024-03-31T01:30:00.000+00:00, Europe/London host: 2024-03-31T00:30:00.000+00:00, Australia/Sydney host: 2024-03-31T01:30:00.000+00:00, America/Santiago host: 2024-03-31T01:30:00.000+00:00; Temporal returns 2024-03-31T01:30:00Z. Specification: [GetEpochNanosecondsFor (11.1.11)](https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor). [Matrix row](/gmt/compare/behaviour/#m4-date-fns).
- **M5. Add one day across a 30-minute DST change** Input: 2024-10-05T02:15:00+10:30\[Australia/Lord\_Howe\], plus 1 day (the clocks move 30 minutes on 2024-10-06). The library returns Asia/Kolkata host: 2024-10-06T02:45:00.000+11:00, Europe/London host: 2024-10-06T02:45:00.000+11:00, Australia/Sydney host: 2024-10-06T03:15:00.000+11:00, America/Santiago host: 2024-10-06T02:45:00.000+11:00; Temporal returns 2024-10-06T02:45:00+11:00\[Australia/Lord\_Howe\]. Specification: [AddZonedDateTime (6.5.5)](https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime). [Matrix row](/gmt/compare/behaviour/#m5-date-fns).
- **M8. Whole months between January 31 and February 29** Input: 2024-01-31 until 2024-02-29. The library returns 1; Temporal returns 0. Specification: [CompareSurpasses (3.5.5)](https://tc39.es/proposal-temporal/#sec-temporal-comparesurpasses). [Matrix row](/gmt/compare/behaviour/#m8-date-fns).
- **M9d. Read a year of one digit with a pattern** Input: "2-12-12" read with the pattern year-month-day. The library returns 0002-12-12; the stated result is rejected. Specification: [UTS #35 Date Format Patterns](https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns). Upstream: [date-fns/date-fns#942](https://github.com/date-fns/date-fns/issues/942), [date-fns/date-fns#1569](https://github.com/date-fns/date-fns/issues/1569). [Matrix row](/gmt/compare/behaviour/#m9d-date-fns).
- **M10. Read a string with a bracketed time zone** Input: 2024-03-31T03:30:00+02:00\[Europe/Berlin\]. The library returns 2024-03-31T03:30:00.000Z; Temporal returns 2024-03-31T01:30:00Z. Specification: [RFC 9557 section 4.1](https://www.rfc-editor.org/rfc/rfc9557#section-4.1). [Matrix row](/gmt/compare/behaviour/#m10-date-fns).
- **M10c. Read a string with a calendar annotation** Input: 2024-03-31T03:30:00+02:00\[Europe/Berlin\]\[u-ca=japanese\]. The library returns accepted, reads 2024-03-31T03:30:00Z; Temporal returns accepted. Specification: [RFC 9557 section 3.3](https://www.rfc-editor.org/rfc/rfc9557#section-3.3). [Matrix row](/gmt/compare/behaviour/#m10c-date-fns).
- **M10d. Read a string whose offset does not match its time zone** Input: 2022-07-08T00:14:07+01:00\[Europe/Paris\] (Paris is at +02:00 on that date). The library returns 2022-07-08T00:14:07.000Z; Temporal returns rejected. Specification: [RFC 9557 section 3.4](https://www.rfc-editor.org/rfc/rfc9557#section-3.4). [Matrix row](/gmt/compare/behaviour/#m10d-date-fns).
- **M14. Offset written for a UTC instant** Input: 2024-03-31T01:30:00Z, written back as ISO 8601 with each library's default writer. The library returns Asia/Kolkata host: 2024-03-31T07:00:00+05:30, Europe/London host: 2024-03-31T02:30:00+01:00, Australia/Sydney host: 2024-03-31T12:30:00+11:00, America/Santiago host: 2024-03-30T22:30:00-03:00; Temporal returns 2024-03-31T01:30:00Z. Specification: [Temporal.Instant.prototype.toString (8.3.11)](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring). Upstream: [date-fns/date-fns#2151](https://github.com/date-fns/date-fns/issues/2151). [Matrix row](/gmt/compare/behaviour/#m14-date-fns).

## More calls

| date-fns | GMT | Note |
| --- | --- | --- |
| `getQuarter(d)` | [`getQuarterForDate`](/gmt/reference/plain/calculate/getQuarterForDate) |  |
| `set(d, { month: 1 })` | [`setDate`](/gmt/reference/plain/calculate/setDate) | Months are 1 to 12 in GMT. |
| `fromUnixTime(sec)` | [`convertUnixToUtc`](/gmt/reference/unix/convert/convertUnixToUtc) | Pass `{ epochUnit: "seconds" }`. |
| `getUnixTime(d)` | [`convertUtcToUnix`](/gmt/reference/utc/convert/convertUtcToUnix) | Pass `{ epochUnit: "seconds" }`. |
| `eachDayOfInterval({ start, end })` | [`mapDatesInRange`](/gmt/reference/plain/map/mapDatesInRange) | The end date is included. |
| `getDayOfYear(d)` | [`getDayOfYear`](/gmt/reference/plain/calculate/getDayOfYear) |  |
| `formatDuration(d)` | [`formatDuration`](/gmt/reference/duration/format/formatDuration) |  |
| `new TZDate(…, z)` | [`convertUtcToZoned`](/gmt/reference/utc/convert/convertUtcToZoned), [`convertPlainDateTimeToZoned`](/gmt/reference/zoned/convert/convertPlainDateTimeToZoned) |  |
