Migrate from Luxon to Temporal with GMT
Migrate to Temporal with GMT explains the one change that applies to every call below: GMT takes strings in and gives strings out.
Call by call
Section titled “Call by call”| Luxon | GMT | Note |
|---|---|---|
DateTime.fromISO(s) | isValidDate@northguild/isValidDateTime@northguild/isValidZonedDateTime@northguild/ | The string is the value; check it with a validator. Luxon reads a [zone] only with { setZone: true }. Temporal reads the basic format 20240212; GMT rejects it on purpose and follows the extended format of RFC 3339 §5.6. Measured in M9c, M10, M10b, M10d. |
DateTime.fromFormat(s, "dd/ | parseDateWithPattern@northguild/ | Measured in M9d. |
DateTime.fromFormat(s, "yyyy MMMM") | parseYearMonthWithPattern@northguild/ | A year and a month with no day, returned as YYYY-MM. |
dt.isValid, dt.invalidReason | isValidDate@northguild/ | GMT gives no reason string. |
dt.toLocaleString(DateTime.DATE_FULL), dt.toFormat(f) | formatDate@northguild/ | GMT has no token formatter; pass a locale and dateStyle. |
dt.toISO(), dt.toISODate() | none | None; the value is already the string. toISO() omits the [zone]. Measured in M10b, M14. |
DateTime.now().setZone(z) | getZonedNow@northguild/ | |
DateTime.now().setZone(z).toISODate() | getZonedToday@northguild/ | |
dt.plus({ days: 1 }) | addDate@northguild/addZoned@northguild/ | The result across the Berlin gap is the same. Measured in M1, M2. |
dt.plus({ months: 1 }) | addDate@northguild/ | The day is clamped to the month length. Measured in M7. |
dt.minus({ … }) | subtractDate@northguild/subtractZoned@northguild/ | |
dt.plus({ hours: 1 }) | addZoned@northguild/ | |
b.diff(a, "days").days | diffDate@northguild/diffZoned@northguild/ | The arguments are in the other order: earlier first. |
b.diff(a, ["months", "days"]) | diffDate@northguild/ | From 2024-01-31 to 2024-02-29 Luxon gives 1 month and 0 days; GMT gives 0 months and 29 days. Measured in M8. |
dt.startOf("month") | startOfDate@northguild/ | |
dt.endOf("month") | endOfDate@northguild/ | |
dt.setZone(z).startOf("day") | startOfZoned@northguild/ | The Santiago result is the same. Measured in M6. |
a < b | isBeforeDate@northguild/isBeforeZoned@northguild/ | |
a.equals(b) | areZonedEqual@northguild/ | areZonedEqual follows ZonedDateTime.prototype.equals. Luxon treats UTC and Etc/ as different zones. Measured in M13b, M13c. |
a.hasSame(b, "day") | areZonedEqualBy@northguild/areDatesEqualBy@northguild/ | |
Interval.fromDateTimes(s, e).contains(d) | isBetweenDate@northguild/ | Luxon intervals are half-open. GMT includes both ends by default; pass { inclusiveEnd: false }. |
i.overlaps(j) | intervalsOverlapDate@northguild/intervalsOverlapZoned@northguild/ | |
DateTime.min(…), DateTime.max(…) | minDate@northguild/maxDate@northguild/minZoned@northguild/ | |
dt.daysInMonth, dt.isInLeapYear | getDaysInMonth@northguild/getDaysInYearMonth@northguild/isLeapYear@northguild/ | |
dt.weekYear, dt.weekNumber, dt.weeksInWeekYear | getIsoWeekDate@northguild/getWeeksInYear@northguild/ | localWeekNumber is a locale week. Measured in M12. |
dt.isWeekend | isWeekend@northguild/ | Both read the weekend from the locale. |
dt.setZone(z) | convertZonedToZoned@northguild/convertUtcToZoned@northguild/ | Measured in M16. |
DateTime.fromISO(local, { zone: z }) | convertPlainDateTimeToZoned@northguild/ | Luxon has no disambiguation option; GMT takes disambiguation, with the four Temporal values. Measured in M4, M4b. |
dt.toRelative(), Duration.fromISO(s).toHuman() | formatRelativeZoned@northguild/formatDuration@northguild/ |
What changes
Section titled “What changes”- Luxon values are immutable
DateTimeobjects. GMT takes and returns strings. - Luxon counts months from 1 to 12, as GMT does.
b.diff(a)subtracts the earlier value from the later one.diffDate(a, b, unit)takes the earlier value first.- Invalid input gives a
DateTimewithisValidset tofalse, andtoISO()returnsnull. GMT returns"",null,falseor[]. - Luxon intervals are half-open. GMT includes both ends in
isBetweenDateunless you passinclusiveEnd: false.
import { diffDate } from "@northguild/gmt/plain/calculate";
diffDate("2024-03-01", "2024-03-15", "days"); // 14Basic-format dates
Section titled “Basic-format dates”DateTime.fromISO 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.
Where Luxon differs from Temporal
Section titled “Where Luxon differs from Temporal”Each item is a case where the library’s result differs from Temporal’s. The full matrix also lists the cases where it matches.
M8 Whole months between January 31 and February 29
- Input
- 2024-01-31 until 2024-02-29
- Library returns
1- Temporal returns
0
M10c Read a string with a calendar annotation
- Input
- 2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]
- Library returns
rejected- Temporal returns
accepted
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)
- Library returns
2022-07-07T22:14:07.000Z- Temporal returns
rejected
M13b UTC and Etc/UTC: same time zone or not
- Input
- 2024-01-01T00:00:00+00:00[UTC] and 2024-01-01T00:00:00+00:00[Etc/UTC]
- Library returns
false- Temporal returns
true
M13c Europe/Kiev and Europe/Kyiv: same time zone or not
- Input
- 2024-01-01T00:00:00+02:00[Europe/Kiev] and 2024-01-01T00:00:00+02:00[Europe/Kyiv]
- Library returns
false- Temporal returns
true
M14 Offset written for a UTC instant
- Input
- 2024-03-31T01:30:00Z, written back as ISO 8601 with each library's default writer
- Library returns
Asia/Kolkata host: 2024-03-31T07:00:00.000+05:30, Europe/London host: 2024-03-31T02:30:00.000+01:00, Australia/Sydney host: 2024-03-31T12:30:00.000+11:00, America/Santiago host: 2024-03-30T22:30:00.000-03:00- Temporal returns
2024-03-31T01:30:00Z
More calls
Section titled “More calls”| Luxon | GMT | Note |
|---|---|---|
dt.quarter | getQuarterForDate@northguild/ | |
dt.set({ month }) | setDate@northguild/ | Months are 1 to 12 in both. |
DateTime.fromSeconds(n), dt.toUnixInteger() | convertUnixToUtc@northguild/convertUtcToUnix@northguild/ | Pass { epochUnit: "seconds" }. |
dt.offset | getZonedOffsetAs@northguild/ | Pass the unit minutes. |
dt.isInDST | isInDaylightSaving@northguild/ | |
Info.isValidIANAZone(z) | isValidTimeZone@northguild/ | |
dt.ordinal | getDayOfYear@northguild/ |
Code examples: MIT-0. Packages: MIT. Third-party data and components: their own licences, credited where they appear. Everything else:© 2026 NorthGuild, all rights reserved.