Dates and timestamps
overview
JS Dates are represented by millisecond timestamps: the number of milliseconds since Jan 1st 1970, 00:00 UTC (Unix Epoch).
read the timestamp
d_2021.getTime() // 1_609_459_200_000 (ms)
// getTimestamp() would be a better name
get the current timestamp with a helper:
Date.now() // 1_771_004_506_434 (ms)
new Date().getTime() // equivalent
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)
For debugging, the Node.js REPL logs the date in its ISO 8601 format and in the UTC timezone, while the Chrome console logs a more verbose representation.
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. TypeScript allows comparison between objects.
d2021 > d2020
note on timestamp difference between Date objects (do not use)
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
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")
Calendar components to Dates
A timezone is required to disambiguate (locate) the calendar components, get the UTC date and time, and compute the timestamp from it.
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(_iso_string_)
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
A duration is a distance between two instants, two timestamps.
We initialize durations with a quantity or as a difference between two date objects.
We describe a duration with date and time components.
format duration with Luxon
We use the Luxon library to format durations. We focus on the human-friendly durations.
from milliseconds
const dur = Duration.fromMillis(1_000_000)
dur.toHuman() // '1000000 milliseconds'
dur.rescale().toHuman() // '16 minutes, 40 seconds'
from calendar components
const dur = Duration.fromObject({ hour: 36 })
dur.toHuman() // '36 hours'
dur.rescale().toHuman() // '1 day, 12 hours'
dur.shiftTo("minutes").toHuman() // '2160 minutes'
from difference
const dur = zdt2.diff(zdt1, ["years", "months", "days"])
dur.toHuman() // "3 years, 2 months, 14 days"
Relative time
A relative time describes how far away are we, as of now, to an event that happens in the past or in the future. It is a duration relative to now.
When describing the duration, we can focus on the calendar date components, with low precision, such as today.
We can also emphasis the duration, regardless of the date components, such as 3 hours ago, or in 2 minutes.
Duration centric (Luxon)
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 distance centric (Luxon)
zdt.toRelativeCalendar() // "today"
zdt.toRelativeCalendar() // "yesterday"
zdt.toRelativeCalendar() // "tomorrow"
zdt.toRelativeCalendar() // "2 days ago"
zdt.toRelativeCalendar({ unit: "months" }) // "last month"