JS Date() to calendar components

A timezone is needed to transform a timestamp to calendar date and time. The JS Date() object doesn't store a timezone.

Note: date and time have a precise meaning:

  • date refers to components from year to day.
  • time refers to components from hours to milliseconds.

As such, we provide the timezone to a timezone-aware helper

The Luxon's DateTime is timezone aware.

get individual calendar components as numbers or strings (Luxon)

we first instantiate a zoneDateTime

const zdt = DateTime.fromJSDate(bedtimeDate, { zone: "Europe/Paris" })

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

// Or get them all at once from a plain object

const { year, month, day, hour, minute, second } = zdt.toObject()

// Get strings
zdt.weekdayLong  // 'Monday', 'Tuesday', etc.
zdt.monthLong   // 'January', 'February', etc.

pre-formatted strings (Luxon)

zdt.toISODate()		// "2026-02-13"
dt.toISO(); 		// "2026-02-13T19:50:15.123+01:00" <-- Includes offset

custom-format strings (Luxon)

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"

locale aware custom-format strings (Intl)

We provide the locale in the BCP 47 format, which includes a Language subtag and a Region subtag, e.g. en-GB or fr-FR.

If we don't provide a locale, it default to the host's locale (which is separate from the host's timezone). The following examples were made with a en-US host locale.

with a dedicated 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"

with an inline option object:

const options = {
    timeZone: "America/Los_Angeles",
    year: "numeric", // "2025"
    month: "long", // "November"
    day: "numeric", // "8"
}

twoBillionDate.toLocaleDateString("fr-FR", options)
// '17 mai 2033'

Further customize the format (Intl)

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 = {

 dateStyle: 	'full' | 'long' | 'medium' | 'short';
 timeStyle: 	'full' | 'long' | 'medium' | 'short';

 // granular method

 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 (do not use)

Those helpers ignore the timezone in which the event happened. They only care about what was the time in the current timezone when the event happened.

For example, a Paris midnight-registered bedtime reads as a 7AM bedtime when the device switches to China timezone, but the bedtime should read as midnight instead.

we should avoid unless we 100% want the host timezone and we want to rely on the implicit timezone behavior.

read the calendar components

birth.getFullYear() // 2025
birth.getMonth() // 0 for January, 11 for December
birth.getDate() // 18 for 18th
birth.getHours()
birth.getMinutes()

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")
earlymorning logo

© Antoine Weber 2026 - All rights reserved

JS Date() to calendar components

A timezone is needed to transform a timestamp to calendar date and time. The JS Date() object doesn't store a timezone.

Note: date and time have a precise meaning:

  • date refers to components from year to day.
  • time refers to components from hours to milliseconds.

As such, we provide the timezone to a timezone-aware helper

The Luxon's DateTime is timezone aware.

get individual calendar components as numbers or strings (Luxon)

we first instantiate a zoneDateTime

const zdt = DateTime.fromJSDate(bedtimeDate, { zone: "Europe/Paris" })

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

// Or get them all at once from a plain object

const { year, month, day, hour, minute, second } = zdt.toObject()

// Get strings
zdt.weekdayLong  // 'Monday', 'Tuesday', etc.
zdt.monthLong   // 'January', 'February', etc.

pre-formatted strings (Luxon)

zdt.toISODate()		// "2026-02-13"
dt.toISO(); 		// "2026-02-13T19:50:15.123+01:00" <-- Includes offset

custom-format strings (Luxon)

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"

locale aware custom-format strings (Intl)

We provide the locale in the BCP 47 format, which includes a Language subtag and a Region subtag, e.g. en-GB or fr-FR.

If we don't provide a locale, it default to the host's locale (which is separate from the host's timezone). The following examples were made with a en-US host locale.

with a dedicated 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"

with an inline option object:

const options = {
    timeZone: "America/Los_Angeles",
    year: "numeric", // "2025"
    month: "long", // "November"
    day: "numeric", // "8"
}

twoBillionDate.toLocaleDateString("fr-FR", options)
// '17 mai 2033'

Further customize the format (Intl)

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 = {

 dateStyle: 	'full' | 'long' | 'medium' | 'short';
 timeStyle: 	'full' | 'long' | 'medium' | 'short';

 // granular method

 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 (do not use)

Those helpers ignore the timezone in which the event happened. They only care about what was the time in the current timezone when the event happened.

For example, a Paris midnight-registered bedtime reads as a 7AM bedtime when the device switches to China timezone, but the bedtime should read as midnight instead.

we should avoid unless we 100% want the host timezone and we want to rely on the implicit timezone behavior.

read the calendar components

birth.getFullYear() // 2025
birth.getMonth() // 0 for January, 11 for December
birth.getDate() // 18 for 18th
birth.getHours()
birth.getMinutes()

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")