date
Build, parse, format and compare dates and times, do arithmetic on them, and show them in any time zone.
import "std:date"std:date is for anything with a calendar or a clock: parsing a timestamp, adding 30 days to a deadline, working out how long ago something happened, or showing the same moment in London and Tokyo. Three types do the work:
date.DateTimeis one instant in time, held in UTC. Every date you parse or build is one.date.Durationis a length of time, such as the gap between two instants.date.ZonedDateTimeis a view of an instant in a time zone, for showing local times.
A DateTime never changes. Every method that "changes" it returns a new one.
Parsing a date#
date.parse(text) reads ISO 8601 dates and times and returns a result, because the text may not be a date:
import "std:date"
auto d = date.parse("2024-03-15T14:30:00Z").unwrap()
log d.iso()
log d.unix()iso() writes the instant back as text in UTC. unix() is whole seconds since 1 January 1970 (the Unix epoch), the form most APIs and files use.
Without a format, parse accepts these shapes:
import "std:date"
string[] inputs = [
"2024-03-01",
"2024-03-01T12:30",
"2024-03-01 12:30:05",
"2024-03-01T12:30:05.250Z",
"2024-03-01T12:30:05+02:00"
]
for text in inputs (
log date.parse(text).unwrap().iso()
)Missing parts default to the start of the period (a date with no time is midnight). An offset such as +02:00 is applied, so the last line is two hours earlier in UTC.
When parsing fails#
Dates from users and files are often wrong. A bad one fails with a ParseError, and the calendar is checked, so 30 February is rejected:
import "std:date"
string[] inputs = ["2024-03-15", "yesterday", "2024-02-30"]
for text in inputs (
date.DateTime d = date.parse(text) catch (e) (
log text ++ " -> " ++ e.kind
continue
)
log text ++ " -> ok " ++ d.format("MMM D")
)Parsing other formats#
Pass format with the same tokens that format() uses (listed below) to read a layout that isn't ISO:
import "std:date"
auto uk = date.parse("15/03/2024", {format: "DD/MM/YYYY"}).unwrap()
auto us = date.parse("March 5, 2024", {format: "MMMM D, YYYY"}).unwrap()
log uk.iso()
log us.iso()Text with no offset is read as UTC. If it was written in another zone, say which with the timezone option and it is converted:
import "std:date"
auto tokyo = date.parse("2024-06-01 09:00", {timezone: "Asia/Tokyo"}).unwrap()
log tokyo.iso()Nine in the morning in Tokyo (UTC+9) is midnight UTC, nine hours earlier on the same clock. Keep this habit: parse at the edge of your program into an instant, work with instants, and convert to a zone only to display.
Formatting#
format(layout) writes the date using tokens:
import "std:date"
auto d = date.parse("2024-03-15T14:30:05Z").unwrap()
log d.format("YYYY-MM-DD")
log d.format("D MMM YYYY")
log d.format("dddd, MMMM D, YYYY")
log d.format("HH:mm:ss")
log d.format("h:mm A")
log d.format("[Week] ddd")
log d.format("X")| Token | Meaning |
|---|---|
YYYY YY | year |
MMMM MMM MM M | month: name, short name, two digits, one or two digits |
DD D | day of the month |
dddd ddd | weekday name, short weekday name |
HH H | hour, 24-hour clock |
hh h and A a | hour, 12-hour clock, and AM/PM |
mm m, ss s | minute, second |
SSS SSSSSS | fraction of a second |
Z ZZ z | offset (+02:00, +0200) and zone name |
X | unix seconds |
Wrap literal text in square brackets, as [Week] is above, so its letters aren't read as tokens.
Building dates from parts#
There is no constructor taking year, month and day. Parse a string, or start from a unix time and move from there:
import "std:date"
log date.fromUnix(0).iso()
log date.fromUnixMillis(1700000000000).iso()
auto year = 2025
auto month = 9
auto d = date.parse(year ++ "-0" ++ month ++ "-01").unwrap()
log d.format("dddd")date.daysInMonth(year, month) and date.isLeap(year) answer calendar questions without a date at all:
import "std:date"
log date.daysInMonth(2024, 2) date.daysInMonth(2025, 2)
log date.isLeap(2024) date.isLeap(1900) date.isLeap(2000)Arithmetic#
add(unit, amount) and subtract move an instant. Units are "y", "mo", "d", "h", "m", "s", "ms" and "us", or the long names such as "days":
import "std:date"
auto d = date.parse("2024-03-15T14:30:00Z").unwrap()
log d.add("days", 20).iso()
log d.add("hours", 36).iso()
log d.subtract("m", 45).iso()
log d.add("y", 1).iso()
log d.iso()The last line shows d itself is untouched.
Months and years follow the calendar, so they can land past the end of a short month. January 31 plus a month is "February 31", which rolls over to March:
import "std:date"
auto d = date.parse("2024-01-31T10:00:00Z").unwrap()
log d.add("mo", 1).iso()If you need "the last day of next month", use date.daysInMonth and with. Days are always 24 hours, so adding a day never lands on a different time of day.
Changing one field#
with(field, value) replaces one field. truncate(unit) zeroes everything smaller than a unit:
import "std:date"
auto d = date.parse("2024-03-15T14:30:45Z").unwrap()
log d.with("year", 2000).iso()
log d.with("day", 1).iso()
log d.truncate("day").iso()
log d.truncate("hour").iso()truncate("day") is how you get the start of a day, to group events by date or compare dates ignoring the time.
Comparing#
Compare with before, after, equals or compare, which is -1, 0 or 1 and suits sorting:
import "std:date"
auto a = date.parse("2024-01-01").unwrap()
auto b = date.parse("2024-06-01").unwrap()
log a.before(b) a.after(b) a.equals(a)
log a.compare(b)
date.DateTime[] dates = [b, a, date.parse("2023-01-01").unwrap()]
log dates.toSortedBy(d -> d.unix()).map(d -> d.format("YYYY-MM-DD"))Durations#
until(other) and since(other) return a date.Duration, which you read in whatever unit you want. until is negative when the other date is earlier, and since is the opposite:
import "std:date"
auto start = date.parse("2024-07-01T12:00:00Z").unwrap()
auto end = date.parse("2024-12-25").unwrap()
auto gap = start.until(end)
log floor(gap.days())
log gap.hours()
log end.until(start).days() < 0A duration offers days(), hours(), minutes(), seconds() (numbers, with fractions) and millis() and micros() (whole numbers). To get whole days, use floor as above.
date.millis(ms) makes a duration to add, and the constants date.second, date.minute, date.hour, date.day and date.week are those lengths in milliseconds:
import "std:date"
auto d = date.parse("2024-03-15T14:30:00Z").unwrap()
log d.addDuration(date.millis(date.hour * 2)).iso()
log date.week / date.dayTime zones#
inTimezone(name) returns a ZonedDateTime for an IANA name such as Europe/London. It is the same instant, shown on that zone's clock, daylight saving included:
import "std:date"
auto launch = date.parse("2024-07-01T12:00:00Z").unwrap()
for zone in ["Europe/London", "America/Los_Angeles", "Asia/Tokyo"] (
auto z = launch.inTimezone(zone).unwrap()
log zone z.format("HH:mm z")
)
log launch.inTimezone("America/Los_Angeles").unwrap().iso()A zoned value can format and write iso() with its offset, tell you its timezone(), and give back the plain instant with toDateTime(). To do arithmetic, go back to a DateTime, do it, and convert again. An unknown zone name returns an error result, so a user-supplied zone should be checked:
import "std:date"
auto d = date.parse("2024-07-01T12:00:00Z").unwrap()
log d.inTimezone("Nowhere/Land").isErr()The current time#
date.now() is the current instant, and now.unix() is a timestamp in seconds. The value differs every run, so print facts about it, not the value:
import "std:date"
auto now = date.now()
auto earlier = now.subtract("d", 1)
log now.after(earlier)
log floor(earlier.until(now).hours())
log now.unix() > 1700000000To time how long something takes, subtract two instants: date.now().since(start).millis().
What does this print?
auto d = date.parse("2024-01-31T10:00:00Z").unwrap()
log d.add("mo", 1).iso()Days until the deadline
A project is due on 2025-03-31. Given today's date, print how many whole days are left and the weekday the deadline falls on.
45 Monday
API reference#
47 members, generated from the std:date source of OSL 9.3.0.
Functions
date.daysInMonth(int year, int month) intdaysInMonth returns how many days month (1 to 12) of year has. Throws ValueError for any other month.
date.duration(int64 value) date.DurationDeprecated. Deprecated: use date.millis(ms) instead; it takes milliseconds.
duration returns a duration of value microseconds.
date.fromUnix(int64 seconds) date.DateTimefromUnix returns the instant seconds after the Unix epoch (1970-01-01 UTC); negative values are before it.
date.fromUnixMicros(int64 us) date.DateTimefromUnixMicros returns the instant us microseconds after the Unix epoch.
date.fromUnixMillis(int64 ms) date.DateTimefromUnixMillis returns the instant ms milliseconds after the Unix epoch.
date.fromUnixMs(int64 ms) date.DateTimeDeprecated. Deprecated: use date.fromUnixMillis(ms) instead.
fromUnixMs returns the instant ms milliseconds after the Unix epoch.
date.fromUnixUs(int64 us) date.DateTimeDeprecated. Deprecated: use date.fromUnixMicros(us) instead.
fromUnixUs returns the instant us microseconds after the Unix epoch.
date.isLeap(int year) booleanisLeap reports whether year has a 29 February.
date.millis(number ms) date.Durationmillis returns a duration of ms milliseconds; fractions keep microsecond precision. Throws ValueError for NaN or an infinite or out-of-range ms.
date.now() date.DateTimenow returns the current instant.
date.nowUs() int64Deprecated. Deprecated: use date.now().unixMicros() instead.
nowUs returns the current time in microseconds since the Unix epoch.
date.parse(string text, object? options) result<date.DateTime, error>parse reads a date or date-time from text. Without a format it accepts ISO 8601 / RFC 3339 forms: "2024-03-01", "2024-03-01T12:30", "2024-03-01 12:30:05", "2024-03-01T12:30:05.250Z" and "2024-03-01T12:30:05+02:00". Options: format (a layout using the same tokens as dt.format, such as "DD/MM/YYYY HH:mm"; [text] and \x are literal) and timezone (the IANA zone for text without an offset, default "UTC"). Missing fields default to the start of the period. Fails with ParseError when text doesn't match; throws ValueError for an unknown time zone or a layout token that can't be parsed (z).
Constants
int date.dayDay is one day in milliseconds, 86400000. A calendar day across a daylight saving change can be an hour longer or shorter.
int date.hourHour is one hour in milliseconds, 3600000.
int date.minuteMinute is one minute in milliseconds, 60000.
int date.secondSecond is one second in milliseconds, 1000.
int date.weekWeek is seven days in milliseconds, 604800000.
*date.DateTime
dateTime.add(string unit, number value) date.DateTimeadd returns the instant moved forward by value units, leaving dt alone. unit is "us", "ms", "s", "m", "h", "d", "mo" or "y", or the long name ("minutes", "day", ...). Months and years move by calendar (31 January plus a month is 2 or 3 March) and need whole numbers; days are 24 hours in UTC. Throws ValueError for an unknown unit, a fractional month or year, or a result out of range.
dateTime.addDuration(date.Duration duration) date.DateTimeaddDuration returns the instant moved by duration. Throws ValueError when the result is out of range.
dateTime.after(date.DateTime other) booleanafter reports whether dt is later than other.
dateTime.before(date.DateTime other) booleanbefore reports whether dt is earlier than other.
dateTime.compare(date.DateTime other) intcompare returns -1, 0 or 1 as dt is before, equal to or after other.
dateTime.equals(date.DateTime other) booleanequals reports whether dt and other are the same instant.
dateTime.format(string layout) stringformat writes the instant in UTC using layout tokens: YYYY YY (year), MMMM MMM MM M (month), DD D (day), dddd ddd (weekday), HH H (24-hour), hh h (12-hour), A a (AM/PM), mm m (minute), ss s (second), SSS SSSSSS (fraction), Z ZZ (offset), z (zone name), X (unix seconds) and x (unix microseconds). [text] and \x are written literally.
dateTime.inTimezone(string tz) result<date.ZonedDateTime, error>inTimezone returns a view of the instant in the IANA time zone tz, such as "Europe/London", for formatting local times. Fails with ValueError for an unknown zone.
dateTime.iso() stringiso writes the instant as RFC 3339 text in UTC, such as "2024-03-01T12:30:00Z".
dateTime.round(string unit) date.DateTimeDeprecated. Deprecated: use dt.truncate(unit) instead.
round returns the start of the UTC period that dt falls in; despite the name it never rounds up.
dateTime.since(date.DateTime other) date.Durationsince returns the time from other to dt, negative when other is later.
dateTime.subtract(string unit, number value) date.DateTimesubtract returns the instant moved back by value units; see dt.add.
dateTime.truncate(string unit) date.DateTimetruncate returns the start of the UTC day, hour, minute, second or millisecond ("d", "h", "m", "s", "ms" or their long names) that dt falls in, leaving dt alone. Throws ValueError for any other unit.
dateTime.unix() int64unix returns the instant in whole seconds since the Unix epoch, rounded down.
dateTime.unixMicros() int64unixMicros returns the instant in microseconds since the Unix epoch.
dateTime.unixMillis() int64unixMillis returns the instant in whole milliseconds since the Unix epoch, rounded down.
dateTime.unixMs() int64Deprecated. Deprecated: use dt.unixMillis() instead.
unixMs returns the instant in milliseconds since the Unix epoch.
dateTime.unixUs() int64Deprecated. Deprecated: use dt.unixMicros() instead.
unixUs returns the instant in microseconds since the Unix epoch.
dateTime.until(date.DateTime other) date.Durationuntil returns the time from dt to other, negative when other is earlier.
dateTime.with(string field, int value) date.DateTimewith returns the instant with one UTC calendar field replaced, leaving dt alone. field is "year", "month" (1 to 12), "day", "hour", "minute" or "second", or the short unit name ("y", "mo", "d", "h", "m", "s"). Out-of-range values roll over, as 31 February becomes 2 or 3 March. Throws ValueError for an unknown field or a result out of range.
*date.Duration
duration.days() numberdays returns the duration in 24-hour days.
duration.hours() numberhours returns the duration in hours.
duration.micros() int64micros returns the duration in whole microseconds.
duration.millis() int64millis returns the duration in whole milliseconds, rounded toward zero.
duration.minutes() numberminutes returns the duration in minutes.
duration.seconds() numberseconds returns the duration in seconds.
*date.ZonedDateTime
zonedDateTime.format(string layout) stringformat writes the instant in the zone using the layout tokens of dt.format.
zonedDateTime.iso() stringiso writes the instant as RFC 3339 text in the zone, with its offset, such as "2024-03-01T07:30:00-05:00".
zonedDateTime.timezone() stringtimezone returns the zone's IANA name, such as "Europe/London".
zonedDateTime.toDateTime() date.DateTimetoDateTime returns the instant without its zone.