Dates and timestamps
overview
JS Dates are represented by millisecond timestamps: the number of milliseconds since Jan 1st 1970, 00:00 UTC time (Unix Epoch).
read the timestamp
d_2021.getTime() // 1_609_459_200_000 (ms)
// getTimestamp(), getInstant(), getEpochMilliseconds() would be better
rely on a helper to get the current timestamp:
Date.now() // 1_771_004_506_434 (ms)
Note that the constructor with no argument creates a date with the current timestamp:
new Date().getTime()
build a Date from a timestamp:
new Date(1_627_541_982_738)
// '2021-07-29T06:59:42.738Z'
// Thu Jul 29 2021 08:59:42 GMT+0200 (Central European Summer Time)
Note : JavaScript consoles log the date in a format that is more readable than a timestamp:
- the Node.js REPL logs the date in its ISO 8601 format, in the UTC timezone
- the Chrome console uses more verbose calendar components and reads the date in the current timezone.
timestamp difference
d_1971.getTime() - d_1970.getTime() // 31 536 000 000 (ms)
compare date, check posteriority
We use the comparison operators on dates, to check posteriority. Note: TypeScript allows comparison between objects.
d2021.getTime() > d2020.getTime() // pedanctic
d2021 > d2020
(do not use) difference between Date objects do not work in TypeScript
In JS, We can subtract two Date objects, because they implement valueOf() which return their timestamp, and that is what the subtraction operates on:
d_1971 - d_1970 // 31 536 000 000 (ms)
The Typescript compiler doesn't allow it: it only sees a subtraction between objects and forbids it. It ignores the valueOf() transformation.
Dates to calendar components
date and time terminology in the context of calendar components
date and time have a specific meaning in the context of calendar components:
- date refers to calendar components from year to day.
- time refers to components from hours to milliseconds.
the need for a timezone
A timezone is needed to transform a timestamp to some calendar components (date and time).
Events are most often closely linked to a timezone, the one where they happen, and it's preferable to retain this information and display the event's date in that timezone. The only exception is when one explicitely asks to read the event in another timezone, or if the event is not linked to a specific timezone.
JS Dates objects don't store a timezone: they don't retain this information.
Most Dates helpers are hardcoded to make use of the host machine timezone, which is not desirable as we've seen.
Instead, we want to use timezone-aware helpers, ones that we initialize with the correct timezone, the one where the event happened.
The Luxon library and the Temporal API provide such helpers.
The Luxon library provides a DateTime class to create timezone-aware instances, from which we extract calendar components.
Luxon
get individual calendar components (Luxon)
We initialize a DateTime instance, then read the calendar components, as numbers or strings:
const zdt = DateTime.fromJSDate(bedtimeJSDate, { 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 single-component strings and multi-component strings (Luxon)
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"
ISO string
zdt.toISODate() // "2026-02-13"
zdt.toISO() // "2026-02-13T19:50:15.123+01:00" <-- Includes offset
Intl formatter
locale
We provide the locale in the BCP 47 format, e.g. en-GB or fr-FR. The locale is separate from the timezone. If omitted, Intl defaults to the host machine's locale.
reusable formatter
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 pre-defined 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 for each component we want to include. If a component is not mentioned in the object, or set to undefined, it is left out from the output.
const options = {
weekday: undefined,
day: "numeric",
month: "long",
year: "numeric",
}
list of 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()
// Example: UNIX Epoch instant
// Device in (Europe/Paris)
// The instant is read in the Paris timezone
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
others
to UTC timezone 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")
Calendar components to Dates
A timezone is required to disambiguate the calendar components, to get the UTC date and time, and the timestamp.
derive the JS Date from a timezone aware instance (Luxon):
const zdt = DateTime.fromObject(
{
year: 2015,
month: 6,
day: 18,
hour: 13,
minute: 13,
},
{
zone: "Europe/Paris",
}
)
const birthDate = zdt.toJSDate()
derive the JS Date from a timezone aware instance (Temporal):
const zdt = Temporal.ZonedDateTime.from({
year: 2015,
month: 6,
day: 18,
hour: 13,
minute: 13,
timeZone: "Europe/Paris",
})
const birthDate = new Date(zdt.toInstant().epochMilliseconds)
derive the JS date from an ISO string
With an UTC offset in the date time string. This is worse than providing a timezone because it requires knowledge about the offset of the location at this date.
new Date(isoString)
new Date("2015-06-18T13:13+02:00") // offset ISO, minutes granularity
new Date("2015-06-18T13:13:00+02:00") // offset ISO, seconds granularity
new Date("2015-06-18T11:13:00.000Z") // UTC ISO (Z)
from a date-only ISO string:
new Date("2015-06-18") // assumed to be UTC
with local timezone date components (brittle, do not use)
The helpers assume the local timezone:
const birth = new Date(2015, 5 /* June */, 18, 13, 13)
new Date("2015-06-18T13:13:00")
Duration
overview
A duration is a distance between two instants.
We initialize a duration with a quantity of time, or with a difference between two instants.
We use the Luxon library.
output durations (Luxon)
set the duration units
We set the duration units (if needed) so that the subsequent outputs make use of them.
rescale()determines the units to use automatically. It eagerly uses the higher magnitude units. Then it sets them as the current units:
dur.toHuman() // '1000000 milliseconds'
dur.rescale().toHuman() // '16 minutes, 40 seconds'
shiftTo()aims to set the units imperatively, and set them as the current units:
dur.toHuman() // '1 day, 12 hours'
dur.shiftTo("hours", "minutes").toHuman() // '36 hours, 0 minutes'
dur.shiftTo("minutes").toHuman() // '2160 minutes'
output the configured, human friendly duration
toHuman() outputs a human-readable description. It uses the units that were set as the current units (configuration).
dur.toHuman() // '1 day, 12 hours'
dur.toHuman({ listStyle: "long" }) // '1 day and 12 hours'
duration instantiation (Luxon)
The way we instantiate determines the defaults units being used.
milliseconds quantity
const dur = Duration.fromMillis(1_000_000) // 1000 seconds
// dur.toHuman() // '1000000 milliseconds'
Duration.fromMillis(d2.getTime() - d1.getTime())
calendar components as a quantity
const dur = Duration.fromObject({ hour: 36 })
// dur.toHuman() // '36 hours'
from difference between two DateTime instances
In this instantiation, we can set the units being used:
const dur = zdt2.diff(zdt1, ["years", "months", "days"])
// dur.toHuman() // "3 years, 2 months, 14 days"
Relative time
Duration centric (Luxon)
A relative time describes how far we are, as of now, from an event that happens in the past or in the future. It is a duration relative to now. We focus on the duration, such as 3 hours ago, or in 2 minutes.
zdt.toRelative() // "4 hours ago"
zdt.toRelative() // "in 2 days"
zdt.toRelative({ style: "short" }) // "4 hr. ago"
zdt.toRelative({ style: "narrow" }) // "4h ago"
zdt.toRelative({ unit: "seconds" }) // "300 seconds ago"
zdt.toRelative({ unit: "years" }) // "1 year ago"
zdt.toRelative({ round: false }) // "4.5 hours ago"
zdt.toRelative({ base: anchor }) // "5 years ago"
Calendar centric (Luxon)
It can also describe where the event takes place in the calendar relative to now. We can focus on calendar date components, with low precision, such as today.
zdt.toRelativeCalendar() // "today"
zdt.toRelativeCalendar() // "yesterday"
zdt.toRelativeCalendar() // "tomorrow"
zdt.toRelativeCalendar() // "2 days ago"
zdt.toRelativeCalendar() // "last month"