Dates to calendar components
note about calendar components
Note: date and time have a precise meaning:
- date refers to components from year to day.
- time refers to components from hours to milliseconds.
the need of a timezone
A timezone is needed to transform a timestamp to its matching calendar components (date and time).
JS Dates objects don't store a timezone, and JS Dates helpers are hardcoded to use the host machine timezone.
Instead, we use timezone-aware objects and helpers. We instantiate them from a JS Date, to which we add an explicit timezone.
The Luxon library and the Temporal API provide such objects and helpers.
The Luxon library provides a DateTime class to create timezone-aware instances, from which we extract calendar components.
get individual calendar components (Luxon)
We initialize a DateTime instance, then read its components, mostly as numbers:
const zdt = DateTime.fromJSDate(bedtimeDate, { zone: "Europe/Paris" })
// Getters
zdt.zoneName // 'Europe/Paris'
zdt.year
zdt.month // 1-12
zdt.day // 1-31
zdt.hour // 0-23
zdt.minute // 0-59
zdt.second // 0-59
zdt.millisecond
zdt.weekdayLong // 'Monday', 'Tuesday', etc.
zdt.monthLong // 'January', 'February', etc.
// Make plain object and destructure
const { year, month, day, hour, minute, second } = zdt.toObject()
get pre-formatted and custom strings with Luxon
ISO string
zdt.toISODate() // "2026-02-13"
zdt.toISO() // "2026-02-13T19:50:15.123+01:00" <-- Includes offset
custom-format string
zdt.toFormat("yyyy") // "2026"
zdt.toFormat("yy") // "26"
zdt.toFormat("M") // "1"
zdt.toFormat("MM") // "01"
zdt.toFormat("MMM") // "Jan"
zdt.toFormat("MMMM") // "January"
zdt.toFormat("d") // "9"
zdt.toFormat("dd") // "09"
zdt.toFormat("EEE") // "Fri"
zdt.toFormat("EEEE") // "Friday"
get custom strings with Intl (locale aware) or built-in formatters
We provide the locale in the BCP 47 format, e.g. en-GB or fr-FR. The locale is independent from the timezone. If omitted, Intl defaults to the host's locale.
Create a fully-fledged, reusable formatter:
const formatter = new Intl.DateTimeFormat("en-GB", {
timeZone: "Europe/Paris",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})
formatter.format(date)
// "3 March at 14:05"
Use a built-in formatter in addition to an options object:
const options = {
timeZone: "America/Los_Angeles",
year: "numeric", // "2025"
month: "long", // "November"
day: "numeric", // "8"
}
twoBillionDate.toLocaleDateString("fr-FR", options)
// '17 mai 2033'
options overview
The option object sets the format of each specific component that we want to include. If a specific component is not mentioned in the object or set to undefined, it will be left out from the output.
const options = {
weekday: undefined,
day: "numeric",
month: "long",
year: "numeric",
}
options:
type Intl.DateTimeFormatOptions = {
// shortcut options
dateStyle: 'full' | 'long' | 'medium' | 'short';
timeStyle: 'full' | 'long' | 'medium' | 'short';
// granular options
weekday: 'long' | 'short' | 'narrow';
era: 'long' | 'short' | 'narrow';
year: 'numeric' | '2-digit';
month: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
day: 'numeric' | '2-digit';
hour: 'numeric' | '2-digit';
minute: 'numeric' | '2-digit';
second: 'numeric' | '2-digit';
timeZoneName: 'long' | 'short' | 'shortOffset' | 'longOffset' | 'shortGeneric' | 'longGeneric';
timeZone: string;
fractionalSecondDigits: 1 | 2 | 3;
hour12: boolean | 'h11' | 'h12' | 'h23' | 'h24';
calendar: 'gregory' | 'buddhist' | 'chinese'
localeMatcher: 'best fit' | 'lookup';
formatMatcher: 'best fit' | 'basic' | 'standard';
};
JS helpers that rely on the host timezone (avoid)
Those helpers ignore the timezone in which the event happened. They display the event's time as it reads in the current timezone.
For example, a Paris midnight-registered bedtime is transformed to a 7AM bedtime because the device switched to China timezone. Instead, the bedtime should stick to midnight, unless we specifically want to read when the event happened in the current timezone.
For example, if we read the UNIX epoch's getHours() from a Paris-based machine, it reads as 1.
calendar components read as in the current timezone
birth.getFullYear() // 2025
birth.getMonth() // 0 for January, 11 for December
birth.getDate() // 18 for 18th
birth.getHours()
birth.getMinutes()
// UNIX Epoch
// Device in Europe/Paris
new Date(0).getHours() // 1
pre-formatted strings
date.toString()
// Sun Jun 12 2022 11:44:53 GMT+0200 (Central European Summer Time)
date.toDateString()
// Sun Jun 12 2022
date.toTimeString()
// 11:44:53 GMT+0200 (Central European Summer Time)
locale aware pre-formatted strings
The device's locale is picked if omitted.
date.toLocaleString() // 1/29/2022, 9:52:08 PM
date.toLocaleString("fr-FR") // 29/01/2022, 21:52:08
date.toLocaleDateString() // 1/29/2022
date.toLocaleDateString("fr-FR") // 29/01/2022
date.toLocaleTimeString() // 9:52:08 PM
date.toLocaleTimeString("fr-FR") // 21:52:08
to ISO string
ISO8601 is an international standard. It indicates both the date component and the time component. It may provide the timezone.
birthDate.toISOString() // 2015-06-18T11:13:00.000Z
Date.parse("2015-06-18T11:13:00.000Z")