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
yyearyyyy → 2024
MmonthM → 1, MM → 01, MMM → Jan, MMMM → January
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().