# Migrate from Moment.js to Temporal with GMT

Checked against Moment.js 2.31.0 and moment-timezone 0.6.5 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

| Moment.js | GMT | Note |
| --- | --- | --- |
| `moment(s, moment.ISO_8601, true)` | [`isValidDate`](/gmt/reference/plain/validate/isValidDate), [`isValidDateTime`](/gmt/reference/plain/validate/isValidDateTime), [`isValidZonedDateTime`](/gmt/reference/zoned/validate/isValidZonedDateTime) | The string is the value; check it with a validator. Strict ISO mode rejects a string with a `[zone]`. |
| `moment(s, "DD/MM/YYYY", true)` | [`parseDateWithPattern`](/gmt/reference/plain/parse/parseDateWithPattern) | Rewrite the tokens to `dd/MM/yyyy`. |
| `moment(s, "YYYY MMM", locale)` | [`parseYearMonthWithPattern`](/gmt/reference/plain/parse/parseYearMonthWithPattern) | Rewrite the tokens to `yyyy MMM`. The result is `YYYY-MM`, with no day. |
| `.isValid()` | [`isValidDate`](/gmt/reference/plain/validate/isValidDate) |  |
| `.format("LL")` | [`formatDate`](/gmt/reference/plain/format/formatDate) | GMT has no token formatter; pass a locale and `dateStyle`. |
| `.toISOString()` | none | None for a GMT UTC string; it is already ISO 8601. |
| `moment()`, `moment.tz(z)` | [`getUtcNow`](/gmt/reference/utc/get/getUtcNow), [`getZonedNow`](/gmt/reference/zoned/get/getZonedNow) |  |
| `.clone()` | none | Not needed: a string cannot change. |
| `.add(1, "day")` | [`addDate`](/gmt/reference/plain/calculate/addDate), [`addZoned`](/gmt/reference/zoned/calculate/addZoned) | Moment changes the object and returns it; GMT returns a new string. The Berlin gap result is the same. |
| `.add(1, "month")` | [`addDate`](/gmt/reference/plain/calculate/addDate) | The day is clamped to the month length. |
| `.subtract(n, unit)` | [`subtractDate`](/gmt/reference/plain/calculate/subtractDate), [`subtractZoned`](/gmt/reference/zoned/calculate/subtractZoned) |  |
| `.add(1, "hour")` | [`addZoned`](/gmt/reference/zoned/calculate/addZoned) |  |
| `b.diff(a, "days")` | [`diffDate`](/gmt/reference/plain/calculate/diffDate) | The arguments are in the other order: earlier first. |
| `b.diff(a, "months")` | [`diffDate`](/gmt/reference/plain/calculate/diffDate) | From 2024-01-31 to 2024-02-29 Moment counts 1 month and GMT counts 0. |
| `.startOf("month")` | [`startOfDate`](/gmt/reference/plain/calculate/startOfDate) |  |
| `.endOf("month")` | [`endOfDate`](/gmt/reference/plain/calculate/endOfDate) |  |
| `moment.tz(s, z).startOf("day")` | [`startOfZoned`](/gmt/reference/zoned/calculate/startOfZoned) | The Santiago result is the same. |
| `.isBefore(b)`, `.isAfter(b)` | [`isBeforeDate`](/gmt/reference/plain/compare/isBeforeDate), [`isAfterDate`](/gmt/reference/plain/compare/isAfterDate), [`isBeforeZoned`](/gmt/reference/zoned/compare/isBeforeZoned) |  |
| `.isSame(b)` | [`areDatesEqual`](/gmt/reference/plain/compare/areDatesEqual), [`areUtcEqual`](/gmt/reference/utc/compare/areUtcEqual) |  |
| `.isSame(b, "day")` | [`areDatesEqualBy`](/gmt/reference/plain/compare/areDatesEqualBy), [`areZonedEqualBy`](/gmt/reference/zoned/compare/areZonedEqualBy) |  |
| `.isBetween(a, b)` | [`isBetweenDate`](/gmt/reference/plain/compare/isBetweenDate) | Moment excludes both ends by default; pass `{ inclusiveStart: false, inclusiveEnd: false }`. |
| `moment.min(…)`, `moment.max(…)` | [`minDate`](/gmt/reference/plain/calculate/minDate), [`maxDate`](/gmt/reference/plain/calculate/maxDate) |  |
| `.daysInMonth()`, `.isLeapYear()` | [`getDaysInMonth`](/gmt/reference/plain/calculate/getDaysInMonth), [`getDaysInYearMonth`](/gmt/reference/plain/calculate/getDaysInYearMonth), [`isLeapYear`](/gmt/reference/plain/validate/isLeapYear) |  |
| `.isoWeek()`, `.isoWeekYear()`, `.isoWeeksInYear()` | [`getIsoWeekDate`](/gmt/reference/calendar/calculate/getIsoWeekDate), [`getWeeksInYear`](/gmt/reference/plain/calculate/getWeeksInYear) | `.week()` is a locale week. |
| `moment.tz(local, z)`, `.tz(z)` | [`convertPlainDateTimeToZoned`](/gmt/reference/zoned/convert/convertPlainDateTimeToZoned), [`convertZonedToZoned`](/gmt/reference/zoned/convert/convertZonedToZoned) | Moment has no disambiguation option; GMT takes `disambiguation`. |
| `.fromNow()`, `.calendar()` | [`formatRelativeZoned`](/gmt/reference/zoned/format/formatRelativeZoned), [`formatCalendar`](/gmt/reference/plain/format/formatCalendar) |  |
| `moment.duration(s).humanize()` | [`parseDuration`](/gmt/reference/duration/parse/parseDuration), [`formatDuration`](/gmt/reference/duration/format/formatDuration) |  |
| `moment.unix(sec)`, `.unix()` | [`convertUnixToUtc`](/gmt/reference/unix/convert/convertUnixToUtc), [`convertUtcToUnix`](/gmt/reference/utc/convert/convertUtcToUnix) | Pass `{ epochUnit: "seconds" }`. |
| `.isDST()`, `.utcOffset()`, `moment.tz.zone(z)` | [`isInDaylightSaving`](/gmt/reference/zoned/compare/isInDaylightSaving), [`getZonedOffsetAs`](/gmt/reference/zoned/parse/getZonedOffsetAs), [`isValidTimeZone`](/gmt/reference/zoned/validate/isValidTimeZone) |  |

## What changes

- Moment objects are mutable: `a.add(1, "day")` changes `a` and returns it. GMT returns a new string and never changes its input.
- `month()` counts months from 0. GMT counts them from 1 to 12.
- `b.diff(a, unit)` subtracts the earlier value from the later one. `diffDate(a, b, unit)` takes the earlier value first.
- Invalid input gives an object whose `format()` returns `"Invalid date"`. GMT returns `""`, `null`, `false` or `[]`.
- Time zones come from the companion package `moment-timezone`.

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

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

## Where Moment.js 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.

- **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-moment).
- **M10. Read a string with a bracketed time zone** Input: 2024-03-31T03:30:00+02:00\[Europe/Berlin\]. The library returns rejected; 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-moment).
- **M10c. Read a string with a calendar annotation** Input: 2024-03-31T03:30:00+02:00\[Europe/Berlin\]\[u-ca=japanese\]. The library returns rejected; 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-moment).
- **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). [Matrix row](/gmt/compare/behaviour/#m14-moment).

## More calls

| Moment.js | GMT | Note |
| --- | --- | --- |
| `.quarter()` | [`getQuarterForDate`](/gmt/reference/plain/calculate/getQuarterForDate) |  |
| `.month(1)` | [`setDate`](/gmt/reference/plain/calculate/setDate) | `month()` counts from 0; GMT counts from 1. |
| `.dayOfYear()` | [`getDayOfYear`](/gmt/reference/plain/calculate/getDayOfYear) |  |
