website is under construction
Standard Library

Date

import "ghost:date"
import { now, format } from "ghost:date"

The date module works with instants in time.

Two things shape the whole module:

Every date is immutable. Nothing here changes a date in place. Every function takes a date and returns a new one, which is why the operations are functions in this module rather than methods on the value.

A date is an instant plus a time zone. The instant is what comparisons look at, so it never depends on where the program runs. The zone only decides what reading a calendar position back out — the year, the hour, the weekday — answers. New dates are UTC unless you say otherwise. See Time zones.

import "ghost:date"

launch = date.of(2024, 1, 31)

console.log(date.format(launch, "EEEE, MMMM d, yyyy"))
// >> Wednesday, January 31, 2024

console.log(date.format(date.addMonths(launch, 1), "yyyy-MM-dd"))
// >> 2024-02-29

A date's own toString() gives ISO-8601, which is also what printing one shows:

console.log(date.of(2024, 1, 31))
// >> 2024-01-31T00:00:00Z

Comparing dates

Dates support ==, !=, <, <=, >, and >= directly, comparing instants. Which time zone each date is attached to makes no difference — two dates naming the same moment are equal even when they read it in different zones:

if (date.now() > deadline) {
  console.log("late")
}

Arithmetic operators do not work on dates — a + b on two dates is a type error. Use addDays() and the rest below.

Building and converting

MethodWhat it does
now()The current instant.
today()Midnight UTC today.
of(year, month, day, [hour, minute, second])A date from its parts, read as UTC. The month is 1–12.
ofInZone(year, month, day, [hour, minute, second], zone)of() with a required trailing zone — the parts are read as local time in that zone.
parseISO(text)Parses RFC3339, or a bare YYYY-MM-DD. An explicit offset in the text is kept rather than normalized away.
fromUnix(seconds)A date from a Unix timestamp.
toUnix(date)Seconds since the Unix epoch.
toUnixNano(date)Nanoseconds since the Unix epoch.
format(date, pattern)The date as a string — see Formatting.

An out-of-range day, hour, minute, or second given to of() is a value error rather than silently rolling into the next period.

console.log(date.toUnix(date.of(2024, 1, 31)))
// >> 1706659200

console.log(date.parseISO("2024-03-01"))
// >> 2024-03-01T00:00:00Z

Time zones

A date carries the zone it should be read in. Moving it to another one changes nothing about the instant — only what the calendar reads answer from then on.

MethodWhat it does
inTimeZone(date, zone)The same instant, read in a different zone.
timeZone(date)The zone's IANA name, or "" for a date built from a bare numeric offset.
zoneOffset(date)The offset from UTC in seconds, east positive, at that exact instant — so it accounts for daylight saving.
import "ghost:date"

meeting = date.of(2024, 7, 15, 9, 0, 0)

console.log(meeting)          // >> 2024-07-15T09:00:00Z
console.log(date.hour(meeting)) // >> 9

local = date.inTimeZone(meeting, "America/New_York")

console.log(local)              // >> 2024-07-15T05:00:00-04:00
console.log(date.hour(local))   // >> 5
console.log(date.timeZone(local)) // >> America/New_York

Zones are always named explicitly, using IANA identifiers like America/New_York. Ghost embeds the zone database in its own binary, so the same name resolves the same way on every machine — there is deliberately no date.local() reading the host's configured zone, which would make a script's output depend on where it ran. An unrecognized name is a value error.

ofInZone() and inTimeZone() are not the same operation. ofInZone(2024, 7, 15, 9, 0, 0, "America/New_York") builds the instant that is 9am in New York. inTimeZone(of(2024, 7, 15, 9, 0, 0), "America/New_York") takes 9am UTC and reads it in New York, giving 5am. Build with the first, re-read with the second.

Arithmetic

Each takes a date and a count, and returns a new date.

Method
addDays(date, n) / subDays(date, n)
addWeeks(date, n) / subWeeks(date, n)
addMonths(date, n) / subMonths(date, n)
addYears(date, n) / subYears(date, n)
addHours(date, n) / subHours(date, n)
addMinutes(date, n) / subMinutes(date, n)
addSeconds(date, n) / subSeconds(date, n)
Adding months clamps to the target month's last day rather than rolling over. January 31 plus one month is February 28 — or the 29th in a leap year — not March 2. That is almost always what a calendar-shaped calculation means.

The calendar units — days, weeks, months, years — keep the wall-clock reading in the date's own zone across a daylight-saving change, the way a calendar app's "same time next month" does. The clock units — hours, minutes, seconds — shift by a fixed real duration regardless of zone, so "add 3 hours" always means three real hours.

Differences

Each takes two dates and returns a whole number, truncated toward zero — so differenceInDays(a, b) is always exactly -differenceInDays(b, a).

differenceInDays(a, b), differenceInHours(a, b), differenceInMinutes(a, b), differenceInSeconds(a, b).

console.log(date.differenceInDays(date.of(2024, 3, 1), date.of(2024, 1, 31)))
// >> 30

Durations

A duration is a calendar-and-clock span, kept as six separate components rather than one number of seconds — because "one month" has no fixed length in days.

MethodWhat it does
duration(years, months, days, [hours, minutes, seconds])Builds one directly.
durationBetween(a, b)The full calendar breakdown of a - b.
addDuration(date, duration)Applies one to a date.
subDuration(date, duration)Applies one backwards.

Every component you give duration() has to point the same way — all positive or all negative, with zeroes not counting either way — or it is a value error.

A duration reads its own fields with methods, which is the one place this module departs from "everything is a free function": years(), months(), days(), hours(), minutes(), seconds(), and toString().

import "ghost:date"

span = date.durationBetween(date.of(2024, 3, 15), date.of(2023, 1, 10))

console.log(span)          // >> P1Y2M5D
console.log(span.years())  // >> 1
console.log(span.months()) // >> 2
console.log(span.days())   // >> 5

toString() gives an ISO 8601 duration — PT0S for an empty one.

addDuration() and durationBetween() are exact inverses, so a span taken between two dates puts one back on the other:

start = date.of(2023, 1, 10)
end = date.of(2024, 3, 15)

console.log(date.addDuration(start, date.durationBetween(end, start)))
// >> 2024-03-15T00:00:00Z

This sits alongside the differenceInX functions rather than replacing them — "how many whole days apart" and "the full calendar breakdown" are different questions.

Two durations compare with == and !=, component by component. Ordering them with < or > is not supported: which of two spans is longer has no answer without a reference date.

Components

year(date), month(date), day(date), hour(date), minute(date), second(date), weekday(date).

weekday() counts from 0 for Sunday.

Each reads the date's own time zone, so the same instant can answer a different day, hour, or weekday depending on where it was last moved to.

Predicates

isSameDay(a, b), isWeekend(date), isLeapYear(date).

These read the date's own zone too — an instant that is Saturday in UTC but already Sunday in Tokyo answers for Tokyo once moved there.

Period boundaries

startOfDay(date), endOfDay(date), startOfWeek(date), endOfWeek(date), startOfMonth(date), endOfMonth(date), startOfYear(date), endOfYear(date).

console.log(date.endOfMonth(date.of(2024, 1, 31)))
// >> 2024-01-31T23:59:59Z

console.log(date.endOfYear(date.of(2024, 7, 15)))
// >> 2024-12-31T23:59:59Z

Each is computed in the date's own zone — midnight in Tokyo for a date moved there, not midnight UTC. startOfWeek() and endOfWeek() treat Sunday as the first day of the week, matching weekday()'s own numbering.

Formatting

format(date, pattern) builds a string from pattern letters. A run of the same letter is one token, and anything else in the pattern copies through literally.

LetterMeansLonger runs
yyearyyyy2024
MmonthM1, MM01, MMMJan, MMMMJanuary
ddaypadded at two or more
Eweekdayabbreviated below four letters, full name at four or more
Hhour, 24-hour
hhour, 12-hour
mminute
ssecond
aAM/PM
d = date.of(2024, 1, 31)

date.format(d, "yyyy-MM-dd")           // '2024-01-31'
date.format(d, "EEEE, MMMM d, yyyy")   // 'Wednesday, January 31, 2024'
date.format(d, "h:mm a")               // '12:00 AM'

Pausing a program

Sleeping is not a date operation and does not live here — it belongs to the program itself. See os.sleep().