Store and Display Dates Properly in JavaScript

Worth leading with directly, since it’s the biggest recent change to this topic: JavaScript now has a native, built-in replacement for the notoriously bad `Date` object. The `Temporal` API reached TC39 Stage 4 (officially part of the language spec) in March 2026 and is already shipping in Chrome, Firefox, and Edge — immutable, timezone-aware, and a genuinely better foundation than `Date` ever was. This post covers the current, correct way to store and display dates: `Temporal` where it’s available, the `Date`-based fallback where it isn’t yet.

In this post: Storing dates · The Temporal API: the native fix for Date’s real problems · Displaying dates, the Date-based way · Converting between time zones · Libraries, and which ones are still current · Related reading


Storing dates

Store in UTC, in a standardized format — this hasn’t changed and still applies regardless of which API renders the date later. ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) or a Unix timestamp are both universally recognized and avoid baking a specific time zone’s assumptions into stored data:

const dateISO = new Date().toISOString();
// "2026-08-20T12:34:56.789Z"

const timestamp = Date.now();
// 1755693296789 (milliseconds since the Unix epoch)

Convert to a local or specific time zone only at the point of display — storing already-localized dates is the single most common source of subtle date bugs, since it bakes an assumption about the viewer’s location into data that should stay timezone-neutral until render time. This applies equally whether the eventual display code uses `Date`-based formatting or `Temporal` — the storage discipline is the same regardless of which rendering API reads it back out later.


Temporal reached Stage 4 — officially part of the JavaScript spec — in March 2026, and is already shipping in Chrome, Firefox, and Edge. It’s immutable and timezone-aware by design, fixing the two biggest structural problems with Date.

The Temporal API: the native fix for Date’s real problems

Worth understanding why this matters, not just that it exists: `Date` has structural problems that libraries like Moment.js and Luxon existed specifically to work around. It’s mutable — calling a setter changes the object in place, a common source of bugs when a date gets passed around and modified unexpectedly. It has no real concept of time zones beyond UTC and whatever zone the local machine happens to be set to. And date arithmetic silently does the wrong thing in cases that look reasonable — `new Date(2026, 1, 30)` doesn’t throw for a nonexistent February 30th, it quietly rolls over into March, a well-known footgun that’s caused real production bugs. `Temporal` fixes all three natively:

// Current date/time, in the device's time zone
const now = Temporal.Now.zonedDateTimeISO();

// Format for display -- same options shape as Date's toLocaleString
console.log(now.toLocaleString('en-US', {
  year: 'numeric', month: 'long', day: 'numeric',
  hour: '2-digit', minute: '2-digit', timeZoneName: 'short'
}));
// "August 20, 2026, 12:34 PM UTC"

// Convert to a different time zone -- returns a new object, doesn't mutate "now"
const tokyoTime = now.withTimeZone('Asia/Tokyo');
console.log(tokyoTime.toString());
// "2026-08-20T21:34:56+09:00[Asia/Tokyo]"

Worth checking current support before depending on it in production: Chrome, Firefox, and Edge ship full support as of early-to-mid 2026, but Safari has only partial support, with some pieces still behind a flag. For anything that needs to work everywhere today, that means either feature-detecting `Temporal` and falling back to `Date`/a library, or waiting on Safari specifically before adopting it as the only date-handling approach in a project. `typeof Temporal !== ‘undefined’` is a straightforward feature-detection check for exactly this purpose, worth wrapping around any Temporal-specific code path in a project that also needs to run somewhere Temporal isn’t guaranteed to exist yet.


Displaying dates, the Date-based way

For anything that still needs to work in Safari today, `toLocaleDateString`/`toLocaleString` remain the real, current, correct way to format a `Date` for a user’s locale:

const date = new Date("2026-08-20T12:34:56.789Z");

console.log(date.toLocaleDateString('en-US', {
  year: 'numeric', month: 'long', day: 'numeric'
}));
// "August 20, 2026"

console.log(date.toLocaleString('en-US', {
  year: 'numeric', month: 'long', day: 'numeric',
  hour: '2-digit', minute: '2-digit', second: '2-digit',
  timeZoneName: 'short'
}));
// "August 20, 2026, 12:34 PM GMT"

Converting between time zones

The `timeZone` option on `toLocaleString` or the `Intl.DateTimeFormat` API handles the `Date`-based case:

const date = new Date("2026-08-20T12:34:56.789Z");

console.log(date.toLocaleString('en-US', {
  timeZone: 'America/New_York',
  year: 'numeric', month: 'long', day: 'numeric',
  hour: '2-digit', minute: '2-digit'
}));
// "August 20, 2026, 08:34 AM"

Libraries, and which ones are still current

Worth a direct correction: Moment.js is not a current recommendation anymore, and hasn’t been for years — its own documentation describes it as “a legacy project in maintenance mode” and points users toward Luxon or other alternatives, not something to reach for on a new project. Luxon and date-fns remain genuinely current, actively maintained choices for anything that still needs library-level date handling in a Safari-compatible way today, ahead of `Temporal` reaching full cross-browser support:

// date-fns
import { format } from 'date-fns';
console.log(format(new Date(), 'yyyy-MM-dd HH:mm:ss'));

// Luxon
import { DateTime } from 'luxon';
const nyTime = DateTime.fromISO("2026-08-20T12:34:56.789Z", { zone: 'utc' })
  .setZone('America/New_York')
  .toLocaleString(DateTime.DATETIME_FULL);

Worth planning for rather than treating as permanent: a project adopting Luxon or date-fns today is very likely migrating toward `Temporal` once Safari support catches up, since `Temporal` solves the same underlying problems natively, without a dependency to keep updated. Choosing a library with an API that already resembles Temporal’s immutable, method-chaining style — Luxon fits this better than most — makes that eventual migration meaningfully smoother than one built around an older, more `Date`-like mutable API.



The core storage advice — UTC, standardized format, localize only at display time — hasn’t changed. What’s actually changed is that JavaScript finally has a native answer to the problems that made Moment.js and its successors necessary in the first place, and it’s worth adopting deliberately as browser support closes the remaining gap rather than reaching for a library out of habit. Checking Temporal’s current support status directly before starting a new project is worth a minute of research, given how quickly that support picture is still moving in 2026.

App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *