Skip to content

Migrate from Luxon to Temporal with GMT

Checked against Luxon 3.7.2 on 2026-10-27.

Migrate to Temporal with GMT explains the one change that applies to every call below: GMT takes strings in and gives strings out.

LuxonGMTNote
DateTime.fromISO(s)
isValidDate@northguild/gmt/plain/validate
isValidDateTime@northguild/gmt/plain/validate
isValidZonedDateTime@northguild/gmt/zoned/validate
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/MM/yyyy")
parseDateWithPattern@northguild/gmt/plain/parse
Measured in M9d.
DateTime.fromFormat(s, "yyyy MMMM")
parseYearMonthWithPattern@northguild/gmt/plain/parse
A year and a month with no day, returned as YYYY-MM.
dt.isValid, dt.invalidReason
isValidDate@northguild/gmt/plain/validate
GMT gives no reason string.
dt.toLocaleString(DateTime.DATE_FULL), dt.toFormat(f)
formatDate@northguild/gmt/plain/format
GMT has no token formatter; pass a locale and dateStyle.
dt.toISO(), dt.toISODate()noneNone; the value is already the string. toISO() omits the [zone]. Measured in M10b, M14.
DateTime.now().setZone(z)
getZonedNow@northguild/gmt/zoned/get
DateTime.now().setZone(z).toISODate()
getZonedToday@northguild/gmt/zoned/get
dt.plus({ days: 1 })
addDate@northguild/gmt/plain/calculate
addZoned@northguild/gmt/zoned/calculate
The result across the Berlin gap is the same. Measured in M1, M2.
dt.plus({ months: 1 })
addDate@northguild/gmt/plain/calculate
The day is clamped to the month length. Measured in M7.
dt.minus({ … })
subtractDate@northguild/gmt/plain/calculate
subtractZoned@northguild/gmt/zoned/calculate
dt.plus({ hours: 1 })
addZoned@northguild/gmt/zoned/calculate
b.diff(a, "days").days
diffDate@northguild/gmt/plain/calculate
diffZoned@northguild/gmt/zoned/calculate
The arguments are in the other order: earlier first.
b.diff(a, ["months", "days"])
diffDate@northguild/gmt/plain/calculate
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/gmt/plain/calculate
dt.endOf("month")
endOfDate@northguild/gmt/plain/calculate
dt.setZone(z).startOf("day")
startOfZoned@northguild/gmt/zoned/calculate
The Santiago result is the same. Measured in M6.
a < b
isBeforeDate@northguild/gmt/plain/compare
isBeforeZoned@northguild/gmt/zoned/compare
a.equals(b)
areZonedEqual@northguild/gmt/zoned/compare
areZonedEqual follows ZonedDateTime.prototype.equals. Luxon treats UTC and Etc/UTC as different zones. Measured in M13b, M13c.
a.hasSame(b, "day")
areZonedEqualBy@northguild/gmt/zoned/compare
areDatesEqualBy@northguild/gmt/plain/compare
Interval.fromDateTimes(s, e).contains(d)
isBetweenDate@northguild/gmt/plain/compare
Luxon intervals are half-open. GMT includes both ends by default; pass { inclusiveEnd: false }.
i.overlaps(j)
intervalsOverlapDate@northguild/gmt/plain/interval
intervalsOverlapZoned@northguild/gmt/zoned/interval
DateTime.min(…), DateTime.max(…)
minDate@northguild/gmt/plain/calculate
maxDate@northguild/gmt/plain/calculate
minZoned@northguild/gmt/zoned/calculate
dt.daysInMonth, dt.isInLeapYear
getDaysInMonth@northguild/gmt/plain/calculate
getDaysInYearMonth@northguild/gmt/plain/calculate
isLeapYear@northguild/gmt/plain/validate
dt.weekYear, dt.weekNumber, dt.weeksInWeekYear
getIsoWeekDate@northguild/gmt/calendar/calculate
getWeeksInYear@northguild/gmt/plain/calculate
localWeekNumber is a locale week. Measured in M12.
dt.isWeekend
isWeekend@northguild/gmt/plain/compare
Both read the weekend from the locale.
dt.setZone(z)
convertZonedToZoned@northguild/gmt/zoned/convert
convertUtcToZoned@northguild/gmt/utc/convert
Measured in M16.
DateTime.fromISO(local, { zone: z })
convertPlainDateTimeToZoned@northguild/gmt/zoned/convert
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/gmt/zoned/format
formatDuration@northguild/gmt/duration/format
  • Luxon values are immutable DateTime objects. 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 DateTime with isValid set to false, and toISO() returns null. GMT returns "", null, false or [].
  • Luxon intervals are half-open. GMT includes both ends in isBetweenDate unless you pass inclusiveEnd: false.
import { diffDate } from "@northguild/gmt/plain/calculate";
diffDate("2024-03-01", "2024-03-15", "days"); // 14

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.

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
LuxonGMTNote
dt.quarter
getQuarterForDate@northguild/gmt/plain/calculate
dt.set({ month })
setDate@northguild/gmt/plain/calculate
Months are 1 to 12 in both.
DateTime.fromSeconds(n), dt.toUnixInteger()
convertUnixToUtc@northguild/gmt/unix/convert
convertUtcToUnix@northguild/gmt/utc/convert
Pass { epochUnit: "seconds" }.
dt.offset
getZonedOffsetAs@northguild/gmt/zoned/parse
Pass the unit minutes.
dt.isInDST
isInDaylightSaving@northguild/gmt/zoned/compare
Info.isValidIANAZone(z)
isValidTimeZone@northguild/gmt/zoned/validate
dt.ordinal
getDayOfYear@northguild/gmt/plain/calculate

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.