It\u2019s built into the browser.<\/strong>
\nSince Temporal is an API in the browser itself, it adds nothing to your application\u2019s bundle size.<\/li>\n<\/ul>\nIt\u2019s also important to note that the Date API isn\u2019t going away. While Temporal supersedes this API, it is not being removed or deprecated. Many applications would break if browsers suddenly removed the Date API. However, also keep in mind that Moment is now considered a legacy project in maintenance mode.<\/p>\n
In the rest of the article, we\u2019ll look at some \u201crecipes\u201d for migrating Moment-based code to the new Temporal API. Let\u2019s start refactoring!<\/p>\n
Creating Date And Time Objects<\/h2>\n
Before we can manipulate dates and times, we have to create objects representing them. To create a Moment object representing the current date and time, use the moment<\/code> function.<\/p>\nconst now = moment();\nconsole.log(now); \n\/\/ Moment<2026-02-18T21:26:29-05:00>\n<\/code><\/pre>\nThis object can now be formatted or manipulated as needed.<\/p>\n
\n
\/\/ convert to UTC\n\/\/ warning: This mutates the Moment object and puts it in UTC mode!\nconsole.log(now.utc()); \n\/\/ Moment<2026-02-19T02:26:29Z>\n\n\/\/ print a formatted string - note that it's using the UTC time now\nconsole.log(now.format('MM\/DD\/YYYY hh:mm:ss a')); \n\/\/ 02\/19\/2026 02:27:07 am\n<\/code><\/pre>\n<\/div>\nThe key thing to remember about Moment is that a Moment object always includes information about the time and<\/em> the date. If you only need to work with time information, this is usually fine, but it can cause unexpected behavior in situations like Daylight Saving Time or leap years, where the date can have an effect on time calculations.<\/p>\nTemporal is more flexible. You can create an object representing the current date and time by creating a Temporal.Instant<\/code> object. This represents a point in time defined by the time since \u201cthe epoch\u201d (midnight UTC on January 1, 1970). Temporal can reference this instant in time with nanosecond-level precision.<\/p>\nconst now = Temporal.Now.instant();\n\n\/\/ see raw nanoseconds since the epoch\nconsole.log(now.epochNanoseconds);\n\/\/ 1771466342612000000n\n\n\/\/ format for UTC\nconsole.log(now.toString());\n\/\/ 2026-02-19T01:55:27.844Z\n\n\/\/ format for a particular time zone\nconsole.log(now.toString({ timeZone: 'America\/New_York' }));\n\/\/ 2026-02-18T20:56:57.905-05:00\n<\/code><\/pre>\nTemporal.Instant<\/code> objects can also be created for a specific time and date by using the from<\/code> static method.<\/p>\n\n
const myInstant = Temporal.Instant.from('2026-02-18T21:10:00-05:00');\n\n\/\/ Format the instant in the local time zone. Note that this only controls\n\/\/ the formatting - it does not mutate the object like `moment.utc` does.\nconsole.log(myInstant.toString({ timeZone: 'America\/New_York' }));\n\/\/ 2026-02-18T21:10:00-05:00\n<\/code><\/pre>\n<\/div>\nYou can also create other types of Temporal objects, including:<\/p>\n
\nTemporal.PlainDate<\/code><\/strong>: A date with no time information.<\/li>\nTemporal.PlainTime<\/code><\/strong>: A time with no date information.<\/li>\nTemporal.ZonedDateTime<\/code><\/strong>: A date and time in a specific time zone.<\/li>\n<\/ul>\nEach of these has a from<\/code> method that can be called with an object specifying the date and\/or time, or a date string to parse.<\/p>\n\/\/ Just a date\nconst today = Temporal.PlainDate.from({\n year: 2026,\n month: 2, \/\/ note we're using 2 for February\n day: 18\n});\nconsole.log(today.toString());\n\/\/ 2026-02-18\n\n\/\/ Just a time\nconst lunchTime = Temporal.PlainTime.from({\n hour: 12\n});\nconsole.log(lunchTime.toString());\n\/\/ 12:00:00 \n\n\/\/ A date and time in the US Eastern time zone\nconst dueAt = Temporal.ZonedDateTime.from({\n timeZone: 'America\/New_York',\n year: 2026,\n month: 3,\n day: 1,\n hour: 12,\n minute: 0,\n second: 0\n});\nconsole.log(dueAt.toString());\n\/\/ 2026-03-01T12:00:00-05:00[America\/New_York]\n<\/code><\/pre>\n<\/div>\n
Parsing<\/h2>\n
We\u2019ve covered programmatic creation of date and time information. Now let\u2019s look at parsing. Parsing is one area where Moment is more flexible than the built-in Temporal API.<\/p>\n
You can parse a date string by passing it to the moment<\/code> function. With a single argument, Moment expects an ISO date string, but you can use alternative formats if you provide a second argument specifying the date format being used.<\/p>\n\n
const isoDate = moment('2026-02-21T09:00:00');\nconst formattedDate = moment('2\/21\/26 9:00:00', 'M\/D\/YY h:mm:ss');\n\nconsole.log(isoDate);\n\/\/ Moment<2026-02-21T09:00:00-05:00>\n\nconsole.log(formattedDate);\n\/\/ Moment<2026-02-21T09:00:00-05:00>\n<\/code><\/pre>\n<\/div>\nIn older versions, Moment would make a best guess to parse any arbitrarily formatted date string. This could lead to unpredictable results. For example, is 02-03-2026<\/code> February 2 or March 3? For this reason, newer versions of Moment display a prominent deprecation warning if it\u2019s called without an ISO formatted date string (unless the second argument with the desired format is also given).<\/p>\nTemporal will only parse a specifically formatted date string. The string must be compliant with the ISO 8601 format or its extension, RFC 9557. If a non-compliant date string is passed to a from<\/code> method, Temporal will throw a RangeError<\/code>.<\/p>\n\n
\/\/ Using an RFC 9557 date string\nconst myDate = Temporal.Instant.from('2026-02-21T09:00:00-05:00[America\/New_York]');\nconsole.log(myDate.toString({ timeZone: 'America\/New_York' }));\n\/\/ 2026-02-21T09:00:00-05:00\n\n\/\/ Using an unknown date string\nconst otherDate = Temporal.Instant.from('2\/21\/26 9:00:00');\n\/\/ RangeError: Temporal error: Invalid character while parsing year value.\n<\/code><\/pre>\n<\/div>\nThe exact requirements of the date string depend on which kind of Temporal object you\u2019re creating. In the above example, Temporal.Instant<\/code> requires a full ISO 8601 or RFC 9557 date string specifying the date and time with a time zone offset, but you can also create PlainDate<\/code> or PlainTime<\/code> objects using just a subset of the date format.<\/p>\nconst myDate = Temporal.PlainDate.from('2026-02-21');\nconsole.log(myDate.toString());\n\/\/ 2026-02-21\n\nconst myTime = Temporal.PlainTime.from('09:00:00');\nconsole.log(myTime.toString());\n\/\/ 09:00:00\n<\/code><\/pre>\nNote that these strings must still comply with the expected format, or an error will be thrown.<\/p>\n
\n
\/\/ Using a non-compliant time strings. These will all throw a RangeError.\nTemporal.PlainTime.from('9:00');\nTemporal.PlainTime.from('9:00:00 AM');\n<\/code><\/pre>\n<\/div>\nPro tip: Handling non-ISO strings<\/strong><\/p>\nBecause Temporal prioritizes reliability, it won\u2019t try to guess the format of a string like 02-01-2026<\/code>. If your data source uses such strings, you will need to do some string manipulation to rearrange the values into an ISO string like 2026-02-01<\/code> before attempting to use it with Temporal.<\/p><\/blockquote>\nOnce you have a Moment or Temporal object, you\u2019ll probably want to convert it to a formatted string at some point.<\/p>\n
This is an instance where Moment is a bit more terse. You call the object\u2019s format<\/code> method with a string of tokens that describe the desired date format.<\/p>\nconst date = moment();\n\nconsole.log(date.format('MM\/DD\/YYYY'));\n\/\/ 02\/22\/2026\n\nconsole.log(date.format('MMMM Do YYYY, h:mm:ss a'));\n\/\/ February 22nd 2026, 8:18:30 pm\n<\/code><\/pre>\nOn the other hand, Temporal requires you to be a bit more verbose. Temporal objects, such as Instant<\/code>, have a toLocaleString<\/code> method that accepts various formatting options specified as properties of an object.<\/p>\n\n
const date = Temporal.Now.instant();\n\n\/\/ with no arguments, we'll get the default format for the current locale\nconsole.log(date.toLocaleString());\n\/\/ 2\/22\/2026, 8:23:36 PM (assuming a locale of en-US)\n\n\/\/ pass formatting options to generate a custom format string\nconsole.log(date.toLocaleString('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n}));\n\/\/ February 22, 2026 at 8:23 PM\n\n\/\/ only pass the fields you want in the format string\nconsole.log(date.toLocaleString('en-US', {\n month: 'short',\n day: 'numeric'\n}));\n\/\/ Feb 22\n<\/code><\/pre>\n<\/div>\nTemporal date formatting actually uses the Intl.DateTimeFormat<\/code> API<\/strong> (which is already readily available in modern browsers) under the hood. That means you can create a reusable DateTimeFormat<\/code> object with your custom formatting options, then pass Temporal objects to its format<\/code> method. Because of this, it doesn\u2019t support custom date formats like Moment does. If you need something like 'Q1 2026'<\/code> or other specialized formatting, you may need some custom date formatting code or reach for a third-party library.<\/p>\nconst formatter = new Intl.DateTimeFormat('en-US', {\n month: '2-digit',\n day: '2-digit',\n year: 'numeric'\n});\n\nconst date = Temporal.Now.instant();\nconsole.log(formatter.format(date));\n\/\/ 02\/22\/2026\n<\/code><\/pre>\nMoment\u2019s formatting tokens are simpler to write, but they aren\u2019t locale-friendly. The format strings \u201chard code\u201d things like month\/day order. The advantage of using a configuration object, as Temporal does, is that it will automatically adapt to any given locale and use the correct format.<\/p>\n
const date = Temporal.Now.instant();\n\nconst formatOptions = {\n month: 'numeric',\n day: 'numeric',\n year: 'numeric'\n};\n\n\nconsole.log(date.toLocaleString('en-US', formatOptions));\n\/\/ 2\/22\/2026\n\nconsole.log(date.toLocaleString('en-GB', formatOptions));\n\/\/ 22\/02\/2026\n<\/code><\/pre>\nDate calculations<\/h2>\n
In many applications, you\u2019ll need to end up performing some calculations on a date. You may want to add or subtract units of time (days, hours, seconds, etc.). For example, if you have the current date, you may want to show the user the date 1 week from now.<\/p>\n
Moment objects have methods such as add<\/code> and subtract<\/code> that perform these operations. These functions take a value and a unit, for example: add(7, 'days')<\/code>. One very important difference between Moment and Temporal, however, is that when performing these date calculations, the underlying object is modified and its original value is lost.<\/p>\nconst now = moment();\n\nconsole.log(now);\n\/\/ Moment<2026-02-24T20:08:36-05:00>\n\nconst nextWeek = now.add(7, 'days');\nconsole.log(nextWeek);\n\/\/ Moment<2026-03-03T20:08:36-05:00>\n\n\/\/ Gotcha - the original object was mutated\nconsole.log(now);\n\/\/ Moment<2026-03-03T20:08:36-05:00>\n<\/code><\/pre>\nTo avoid losing the original date, you can call clone<\/code> on the Moment object to create a copy.<\/p>\nconst now = moment();\nconst nextWeek = now.clone().add(7, 'days');\n\nconsole.log(now);\n\/\/ Moment<2026-02-24T20:12:55-05:00>\n\nconsole.log(nextWeek);\n\/\/ Moment<2026-03-03T20:12:55-05:00>\n<\/code><\/pre>\nOn the other hand, Temporal objects are immutable<\/em>. Once you\u2019ve created an object like an Instant<\/code>, PlainDate<\/code>, and so on, the value of that object will never change. Temporal objects also have add<\/code> and subtract<\/code> methods.<\/p>\nTemporal is a little picky about which time units can be added to which object types. For example, you can\u2019t add days to an Instant<\/code>:<\/p>\n\n
const now = Temporal.Now.instant();\nconst nextWeek = now.add({ days: 7 });\n\/\/ RangeError: Temporal error: Largest unit cannot be a date unit\n<\/code><\/pre>\n<\/div>\nThis is because Instant<\/code> objects represent a specific point in time in UTC and are calendar-agnostic. Because the length of a day can change based on time zone rules such as Daylight Saving Time, this calculation isn\u2019t available on an Instant<\/code>. You can<\/em>, however, perform this operation on other types of objects, such as a PlainDateTime<\/code>:<\/p>\nconst now = Temporal.Now.plainDateTimeISO();\nconsole.log(now.toLocaleString());\n\/\/ 2\/24\/2026, 8:23:59 PM\n\nconst nextWeek = now.add({ days: 7 });\n\n\/\/ Note that the original PlainDateTime remains unchanged\nconsole.log(now.toLocaleString());\n\/\/ 2\/24\/2026, 8:23:59 PM\n\nconsole.log(nextWeek.toLocaleString());\n\/\/ 3\/3\/2026, 8:23:59 PM\n<\/code><\/pre>\nYou can also calculate how much time is between two Moment or Temporal objects.<\/p>\n
With Moment\u2019s diff<\/code> function, you need to provide a unit for granularity, otherwise it will return the difference in milliseconds.<\/p>\nconst date1 = moment('2026-02-21T09:00:00');\nconst date2 = moment('2026-02-22T10:30:00');\n\nconsole.log(date2.diff(date1));\n\/\/ 91800000\n\nconsole.log(date2.diff(date1, 'days'));\n\/\/ 1\n<\/code><\/pre>\nTo do this with a Temporal object, you can pass another Temporal object to its until<\/code> or since<\/code> methods. This returns a Temporal.Duration<\/code> object containing information about the time difference. The Duration<\/code> object has properties for each component of the difference, and also can generate an ISO 8601<\/a> duration string representing the time difference.<\/p>\n\n
const date1 = Temporal.PlainDateTime.from('2026-02-21T09:00:00');\nconst date2 = Temporal.PlainDateTime.from('2026-02-22T10:30:00');\n\n\/\/ largestUnit specifies the largest unit of time to represent\n\/\/ in the duration calculation\nconst diff = date2.since(date1, { largestUnit: 'day' });\n\nconsole.log(diff.days);\n\/\/ 1\n\nconsole.log(diff.hours);\n\/\/ 1\n\nconsole.log(diff.minutes);\n\/\/ 30\n\nconsole.log(diff.toString());\n\/\/ P1DT1H30M\n\/\/ (ISO 8601 duration string: 1 day, 1 hour, 30 minutes)\n<\/code><\/pre>\n<\/div>\nComparing Dates And Times<\/h2>\n
Moment and Temporal both let you compare dates and times to determine which comes before the other, but take different approaches with the API.<\/p>\n
Moment provides methods such as isBefore<\/code>, isAfter<\/code>, and isSame<\/code> to compare two Moment objects.<\/p>\nconst date1 = moment('2026-02-21T09:00:00');\nconst date2 = moment('2026-02-22T10:30:00');\n\nconsole.log(date1.isBefore(date2));\n\/\/ true\n<\/code><\/pre>\nTemporal uses a static compare<\/code> method to perform a comparison between two objects of the same type. It returns -1<\/code> if the first date comes before the second, 0<\/code> if they are equal, or 1<\/code> if the first date comes after the second. The following example shows how to compare two PlainDate<\/code> objects. Both arguments to Temporal.PlainDate.compare<\/code> must be PlainDate<\/code> objects.<\/p>\n\n
const date1 = Temporal.PlainDate.from({ year: 2026, month: 2, day: 24 });\nconst date2 = Temporal.PlainDate.from({ year: 2026, month: 3, day: 24 });\n\n\/\/ date1 comes before date2, so -1\nconsole.log(Temporal.PlainDate.compare(date1, date2));\n\n\/\/ Error if we try to compare two objects of different types\nconsole.log(Temporal.PlainDate.compare(date1, Temporal.Now.instant()));\n\/\/ TypeError: Temporal error: Invalid PlainDate fields provided.\n<\/code><\/pre>\n<\/div>\nIn particular, this makes it easy to sort an array of Temporal objects chronologically.<\/p>\n
\/\/ An array of Temporal.PlainDate objects\nconst dates = [ ... ];\n\n\/\/ use Temporal.PlainDate.compare as the comparator function\ndates.sort(Temporal.PlainDate.compare);\n<\/code><\/pre>\nTime Zone Conversions<\/h2>\n
The core Moment library doesn\u2019t support time zone conversions. If you need this functionality, you also need to install the moment-timezone<\/code> package. This package is not tree-shakable, and therefore can add significantly to your bundle size. Once you\u2019ve installed moment-timezone<\/code>, you can convert Moment objects to different time zones with the tz<\/code> method. As with other Moment operations, this mutates the underlying object.<\/p>\n\/\/ Assuming US Eastern time\nconst now = moment();\nconsole.log(now);\n\/\/ Moment<2026-02-28T20:08:20-05:00>\n\n\/\/ Convert to Pacific time.\n\/\/ The original Eastern time is lost.\nnow.tz('America\/Los_Angeles');\nconsole.log(now);\n\/\/ Moment<2026-02-28T17:08:20-08:00>\n<\/code><\/pre>\nTime zone functionality is built into the Temporal API when using a Temporal.ZonedDateTime<\/code> object. These objects include a withTimeZone<\/code> method that returns a new ZonedDateTime<\/code> representing the same moment in time, but in the specified time zone.<\/p>\n\/\/ Again, assuming US Eastern time\nconst now = Temporal.Now.zonedDateTimeISO();\nconsole.log(now.toLocaleString());\n\/\/ 2\/28\/2026, 8:12:02 PM EST\n\n\/\/ Convert to Pacific time\nconst nowPacific = now.withTimeZone('America\/Los_Angeles');\nconsole.log(nowPacific.toLocaleString());\n\/\/ 2\/28\/2026, 5:12:02 PM PST\n\n\/\/ Original object remains unchanged\nconsole.log(now.toLocaleString());\n\/\/ 2\/28\/2026, 8:12:02 PM EST\n<\/code><\/pre>\nNote:<\/strong> The formatted values returned by toLocaleString<\/code> are, as the name implies, locale-dependent. The sample code was developed in the en-US<\/code> locale, so the format is like this: 2\/28\/2026, 5:12:02 PM PST<\/code>. In another locale, this may be different. For example, in the en-GB<\/code> locale, you would get something like 28\/2\/2026, 17:12:02 GMT-8<\/code>.<\/em><\/p>\n<\/div>\n
A Real-world Refactoring<\/h2>\n
Suppose we\u2019re building an app for scheduling events across time zones. Part of this app is a function, getEventTimes<\/code>, which takes an ISO 8601 string representing the time and date of the event, a local time zone, and a target time zone. The function creates formatted time and date strings for the event in both time zones.<\/p>\nIf the function is given an input string that\u2019s not a valid time\/date string, it will throw an error.<\/p>\n
Here\u2019s the original implementation, using Moment (also requiring use of the moment-timezone<\/code> package).<\/p>\n\n
import moment from 'moment-timezone';\n\nfunction getEventTimes(inputString, userTimeZone, targetTimeZone) {\n const timeFormat = 'MMM D, YYYY, h:mm:ss a z';\n\n \/\/ 1. Create the initial moment in the user's time zone\n const eventTime = moment.tz(\n inputString,\n moment.ISO_8601, \/\/ Expect an ISO 8601 string\n true, \/\/ Strict parsing\n userTimeZone\n );\n \n \/\/ Throw an error if the inputString did not represent a valid date\n if (!eventTime.isValid()) {\n throw new Error('Invalid date\/time input');\n }\n\n \/\/ 2. Calculate the target time\n \/\/ CRITICAL: We must clone, or 'eventTime' changes forever!\n const targetTime = eventTime.clone().tz(targetTimeZone);\n\n return {\n local: eventTime.format(timeFormat),\n target: targetTime.format(timeFormat),\n };\n}\n\nconst schedule = getEventTimes(\n '2026-03-05T15:00-05:00',\n 'America\/New_York',\n 'Europe\/London',\n);\n\nconsole.log(schedule.local);\n\/\/ Mar 5, 2026, 3:00:00 pm EST\n\nconsole.log(schedule.target); \n\/\/ Mar 5, 2026, 8:00:00 pm GMT\n<\/code><\/pre>\n<\/div>\nIn this example, we\u2019re using an expected date format of ISO 8601, which is helpfully built into Moment. We\u2019re also using strict parsing, which means Moment won\u2019t try to guess with a date string that doesn\u2019t match the format. If a non-ISO date string is passed, it will result in an invalid date object, and we throw an error.<\/p>\n
The Temporal implementation looks similar, but has a few key differences.<\/p>\n
\n
function getEventTimes(inputString, userTimeZone, targetTimeZone) {\n \/\/ 1. Parse the input directly into an Instant, then create\n \/\/ a ZonedDateTime in the user's zone.\n const instant = Temporal.Instant.from(inputString);\n const eventTime = instant.toZonedDateTimeISO(userTimeZone);\n\n \/\/ 2. Convert to the target zone\n \/\/ This automatically returns a NEW object; 'eventTime' is safe.\n const targetTime = eventTime.withTimeZone(targetTimeZone);\n\n \/\/ 3. Format using Intl (built-in)\n const options = {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n timeZoneName: 'short'\n };\n\n return {\n local: eventTime.toLocaleString(navigator.language, options),\n target: targetTime.toLocaleString(navigator.language, options)\n };\n}\n\nconst schedule = getEventTimes(\n '2026-03-05T15:00-05:00',\n 'America\/New_York',\n 'Europe\/London',\n);\n\nconsole.log(schedule.local);\n\/\/ Mar 5, 2026, 3:00:00 PM EST\n\nconsole.log(schedule.target);\n\/\/ Mar 5, 2026, 8:00:00 PM GMT\n<\/code><\/pre>\n<\/div>\nWith Moment, we have to explicitly specify a format string for the resulting date strings. Regardless of the user\u2019s location or locale, the event times will always be formatted as Mar 5, 2026, 3:00:00 pm EST<\/code>.<\/p>\nAlso, we don\u2019t have to explicitly throw an exception. If an invalid string is passed to Temporal.Instant.from<\/code>, Temporal will throw the exception for us. One thing to note is that even with strict parsing, the Moment version is still more lenient. Temporal requires the time zone offset at the end of the string.<\/p>\nYou should also note that since we\u2019re using navigator.language<\/code>, this code will only run in a browser environment, as navigator<\/code> is not defined in a Node.js environment.<\/p>\nThe Temporal implementation uses the browser\u2019s current locale (navigator.language<\/code>), so the user will automatically get event times formatted in their local time format. In the en-US<\/code> locale, this is Mar 5, 2026, 3:00:00 pm EST<\/code>. However, if the user is in London, for example, the event times will be formatted as 5 Mar 2026, 15:00:00 GMT-5<\/code>.<\/p>\nSummary<\/h2>\n
\n\n\n| Action<\/th>\n | Moment.js<\/th>\n | Temporal<\/th>\n<\/tr>\n<\/thead>\n |
\n\n| Current time<\/td>\n | moment()<\/code><\/td>\nTemporal.Now.zonedDateTimeISO()<\/code><\/td>\n<\/tr>\n\n| Parsing ISO<\/td>\n | moment(str)<\/code><\/td>\nTemporal.Instant.from(str)<\/code><\/td>\n<\/tr>\n\n| Add time<\/td>\n | .add(7, 'days')<\/code> (mutates)<\/td>\n.add({ days: 7 })<\/code> (new object)<\/td>\n<\/tr>\n\n| Difference<\/td>\n | .diff(other, 'hours')<\/code><\/td>\n | | | | | | |