tempo

UTC date/time library — RFC 3339 parsing, Unix timestamps, arithmetic

datetime
time
date
rfc3339
iso8601
duration
moon add brickfrog/tempo@0.8.1
Download zip
Author
Version
0.8.1
License
Apache-2.0
Last updated
10 days ago
Downloads
183
README

#tempo

UTC date/time library for MoonBit. RFC 3339 parsing, Unix timestamp conversion, calendar and duration arithmetic. No external dependencies.

In your moon.pkg:

import { "brickfrog/tempo/src" @tempo, }

#Quick start

///|
test {
let dt = @tempo.DateTime::parse("2026-03-28T14:31:43Z")
inspect(dt.date.year, content="2026")
inspect(dt.date.month, content="3")
inspect(dt.time.hour, content="14")
inspect(dt.format(), content="2026-03-28T14:31:43Z")
}

#Types

TypeDescription
Dateyear, month (1–12), day (1–31)
Timehour, minute, second, nanosecond
DateTimeCombined UTC date and time
DurationSigned duration, stored as nanoseconds

All types implement Eq, Compare, and Show.

#Constructing values

///|
test {
let d = @tempo.Date::new(2026, 3, 28)
let t = @tempo.Time::new(14, 31, 43, 0)
let dt = @tempo.DateTime::new(d, t)
inspect(dt.format(), content="2026-03-28T14:31:43Z")
}

#Unix timestamps

///|
test {
let dt = @tempo.DateTime::from_unix_seconds(0L)
inspect(dt.format(), content="1970-01-01T00:00:00Z")
inspect(dt.to_unix_seconds(), content="0")

let dt2 = @tempo.DateTime::from_unix_nanos(1_000_000_000L)
inspect(dt2.format(), content="1970-01-01T00:00:01Z")
inspect(dt2.to_unix_nanos(), content="1000000000")

let ms = @tempo.DateTime::from_unix_millis(1500L)
inspect(ms.format(), content="1970-01-01T00:00:01.5Z")
inspect(ms.to_unix_millis(), content="1500")

let us = @tempo.DateTime::from_unix_micros(1_500_250L)
inspect(us.format(), content="1970-01-01T00:00:01.50025Z")
inspect(us.to_unix_micros(), content="1500250")
}

#Parsing

Accepts RFC 3339 / ISO 8601. UTC markers (Z, +00:00, -00:00) and fixed numeric offsets are accepted. Parsed values are stored as UTC DateTimes; the original offset is not retained. Out-of-range offsets raise TempoError.

///|
test {
let dt = @tempo.DateTime::parse("2026-03-28T14:31:43.125Z")
inspect(dt.time.nanosecond, content="125000000")
}

///|
test {
let dt = @tempo.DateTime::parse("2024-07-21T17:11:00-04:00")
inspect(dt.format(), content="2024-07-21T21:11:00Z")
}

#Formatting

DateTime::format produces RFC 3339 with a Z suffix. Fractional seconds are included only when nanosecond ≠ 0, trailing zeros trimmed.

///|
test {
let dt = @tempo.DateTime::from_unix_nanos(1_711_630_303_100_000_000L)
inspect(dt.format(), content="2024-03-28T14:31:43.1Z")
}

#Arithmetic

///|
test {
let dt = @tempo.DateTime::parse("2026-03-28T12:00:00Z")
let dt2 = dt.add(@tempo.Duration::hours(2L))
inspect(dt2.time.hour, content="14")

let dt3 = dt.sub(@tempo.Duration::minutes(30L))
inspect(dt3.time.minute, content="30")

let gap = dt2.diff(dt)
inspect(gap.as_hours(), content="2")
}

///|
test {
let a = @tempo.Duration::hours(1L)
let b = @tempo.Duration::minutes(30L)
inspect((a + b).as_minutes(), content="90")
inspect((-a).as_nanoseconds(), content="-3600000000000")
}

#Duration constructors

///|
test {
inspect(@tempo.Duration::weeks(1L).as_days(), content="7")
inspect(@tempo.Duration::days(1L).as_hours(), content="24")
inspect(@tempo.Duration::hours(1L).as_minutes(), content="60")
inspect(@tempo.Duration::minutes(1L).as_seconds(), content="60")
inspect(@tempo.Duration::seconds(1L).as_milliseconds(), content="1000")
inspect(@tempo.Duration::milliseconds(1L).as_microseconds(), content="1000")
inspect(@tempo.Duration::microseconds(1L).as_nanoseconds(), content="1000")
}

#Duration accessors

All accessors truncate toward zero (e.g. 90 minutes → 1 hour).

///|
test {
inspect(@tempo.Duration::days(3L).as_days(), content="3")
inspect(@tempo.Duration::weeks(1L).as_weeks(), content="1")
inspect(@tempo.Duration::days(7L).as_weeks(), content="1")
}

#Duration predicates

///|
test {
assert_eq(@tempo.Duration::seconds(0L).is_zero(), true)
assert_eq(@tempo.Duration::seconds(1L).is_zero(), false)
assert_eq(@tempo.Duration::seconds(-1L).is_negative(), true)
assert_eq(@tempo.Duration::seconds(1L).is_negative(), false)
assert_eq(@tempo.Duration::seconds(1L).is_positive(), true)
}

#Duration arithmetic

Duration::abs, is_positive, signum, multiply, checked_multiply, and divide cover common signed-duration operations. Duration::divide raises on division by zero and on the Int64::min_value / -1 overflow.

///|
test {
let d = @tempo.Duration::seconds(-3L)
inspect(d.abs().as_seconds(), content="3")
assert_eq(d.signum(), -1)

let doubled = @tempo.Duration::seconds(2L).multiply(3L)
inspect(doubled.as_seconds(), content="6")
assert_eq(
@tempo.Duration::seconds(2L).checked_multiply(3L),
Some(@tempo.Duration::seconds(6L)),
)

let half = @tempo.Duration::seconds(7L).divide(2L)
inspect(half.as_seconds(), content="3")
}

#Calendar arithmetic

Date::add_months and Date::add_years clamp to the end of the target month when needed. Date and DateTime both provide start_of_month, end_of_month, start_of_year, and end_of_year; the DateTime variants adjust the date and preserve the time of day.

///|
test {
let d = @tempo.Date::new(2024, 3, 15)

// Add/remove days
let d2 = d.add_days(10)
inspect(d2.day, content="25")

// ISO weekday: Monday = 1 … Sunday = 7
assert_eq(d.day_of_week(), 5) // Friday

// Day of year (1-based)
assert_eq(d.day_of_year(), 75)

// Days between two dates
let other = @tempo.Date::new(2024, 3, 1)
assert_eq(d.days_until(other), -14)
}

///|
test {
let end = @tempo.Date::new(2024, 1, 31)
inspect(end.add_months(1).format(), content="2024-02-29")

let leap = @tempo.Date::new(2024, 2, 29)
inspect(leap.add_years(1).format(), content="2025-02-28")

let mid = @tempo.Date::new(2024, 3, 15)
inspect(mid.start_of_month().format(), content="2024-03-01")
inspect(mid.end_of_month().format(), content="2024-03-31")
inspect(mid.start_of_year().format(), content="2024-01-01")
inspect(mid.end_of_year().format(), content="2024-12-31")

let dt = @tempo.DateTime::parse("2024-03-15T14:31:43Z")
inspect(dt.start_of_month().format(), content="2024-03-01T14:31:43Z")
inspect(dt.end_of_year().format(), content="2024-12-31T14:31:43Z")
}

#Field updaters

DateTime::to_date and DateTime::to_time split a timestamp into its parts. Date has with_year/with_month/with_day; Time has with_hour through with_nanosecond; DateTime has with_date/with_time plus with_year through with_nanosecond. Fallible updaters raise on invalid values rather than clamping.

///|
test {
let d = @tempo.Date::new(2024, 3, 15)
inspect(d.with_day(1).format(), content="2024-03-01")

let t = @tempo.Time::new(14, 31, 43, 125_000_000)
inspect(t.with_nanosecond(0).format(), content="14:31:43")

let dt = @tempo.DateTime::new(d, t)
inspect(dt.to_date().format(), content="2024-03-15")
inspect(dt.to_time().format(), content="14:31:43.125")
inspect(
dt.with_year(2025).with_hour(9).format(),
content="2025-03-15T09:31:43.125Z",
)
}

#Weekday and Month

Date::weekday and Date::month_enum return enums. Weekday and Month use 1-based numbers for to_int/from_int; next and previous wrap around.

///|
test {
let d = @tempo.Date::new(2024, 3, 15)
assert_eq(d.weekday(), @tempo.Friday)
assert_eq(d.month_enum(), @tempo.March)

assert_eq(@tempo.Friday.to_int(), 5)
assert_eq(@tempo.Weekday::from_int(5), Some(@tempo.Friday))
assert_eq(@tempo.Friday.next(), @tempo.Saturday)
assert_eq(@tempo.Monday.previous(), @tempo.Sunday)

assert_eq(@tempo.February.to_int(), 2)
assert_eq(@tempo.Month::from_int(2), Some(@tempo.February))
assert_eq(@tempo.February.days_in(2024), 29)
}

#Rounding and truncation

start_of_day/end_of_day, truncate_to(unit) (floor to a TimeUnit), and round_to(unit, mode) snap a DateTime to a boundary. round_to takes a RoundMode of Floor, Ceil, or HalfExpand (ties round up).

///|
test {
let dt = @tempo.DateTime::parse("2024-03-15T14:31:43.125Z")
inspect(dt.start_of_day().format(), content="2024-03-15T00:00:00Z")
inspect(dt.truncate_to(@tempo.Hour).format(), content="2024-03-15T14:00:00Z")

let half = @tempo.DateTime::parse("2024-03-15T14:30:00Z")
inspect(
half.round_to(@tempo.Hour, @tempo.HalfExpand).format(),
content="2024-03-15T15:00:00Z",
)
}

#Comparison

Named helpers over the derived ordering: is_before/is_after, min/max, and clamp for Date/Time/DateTime, plus wall-clock DateTime::since/ until (over diff).

///|
test {
let a = @tempo.Date::new(2024, 1, 1)
let b = @tempo.Date::new(2024, 2, 1)
assert_eq(a.is_before(b), true)
assert_eq(a.max(b), b)

let dt = @tempo.DateTime::parse("2024-03-15T12:00:00Z")
let later = dt.add(@tempo.Duration::hours(2L))
inspect(later.since(dt).as_hours(), content="2")
}

#Intervals

DateInterval is an inclusive [start, end] range of dates; Interval is a half-open [start, end) range of datetimes. Both provide contains, overlaps, and intersection.

///|
test {
let span = @tempo.DateInterval::{
start: @tempo.Date::new(2024, 1, 1),
end: @tempo.Date::new(2024, 1, 10),
}
assert_eq(span.contains(@tempo.Date::new(2024, 1, 5)), true)
inspect(span.length_in_days(), content="10")
}

#Weekday navigation

Date::next/previous find the nearest date on a weekday (strictly after/before); next_or_same/previous_or_same include the date itself; and nth_weekday_of_month finds the nth occurrence (a negative n counts from the end).

///|
test {
let fri = @tempo.Date::new(2024, 3, 15) // a Friday
inspect(fri.next(@tempo.Monday).format(), content="2024-03-18")
inspect(fri.previous(@tempo.Monday).format(), content="2024-03-11")

// 3rd Tuesday of March 2024
let t = @tempo.Date::nth_weekday_of_month(2024, 3, @tempo.Tuesday, 3)
inspect(t.format(), content="2024-03-19")
}

#Hashing

Date, Time, DateTime, and Duration derive Hash, so they work as keys in the stdlib hash Map/Set.

///|
test {
let counts : Map[@tempo.Date, Int] = {}
counts.set(@tempo.Date::new(2024, 3, 15), 3)
assert_eq(counts.get(@tempo.Date::new(2024, 3, 15)), Some(3))
}

#ISO 8601 durations

Duration::format_iso/parse_iso round-trip the PnDTnHnMnS subset (days, hours, minutes, seconds with fractions). Years and months are rejected as calendar units.

///|
test {
let d = @tempo.Duration::hours(2L) + @tempo.Duration::minutes(30L)
inspect(d.format_iso(), content="PT2H30M")
assert_eq(@tempo.Duration::parse_iso("PT2H30M"), d)
}

#JSON

Date, Time, DateTime, and Duration serialize to their canonical strings (RFC 3339 / ISO 8601) via moonbitlang/core/json — not field objects.

///|
test {
let dt = @tempo.DateTime::parse("2024-07-21T17:11:00Z")
inspect(dt.to_json().stringify(), content="\"2024-07-21T17:11:00Z\"")
}

#Overflow safety

to_unix_nanos and the add/sub/diff operators wrap silently outside the Int64-nanosecond range (~1677–2262). The checked variants return None instead: to_unix_nanos_checked, DateTime::checked_add/checked_sub/checked_diff, and Duration::checked_add/checked_sub. min_unix_nanos/max_unix_nanos document the bounds.

///|
test {
let far = @tempo.DateTime::parse("3000-01-01T00:00:00Z")
assert_eq(far.to_unix_nanos_checked(), None)

match @tempo.DateTime::epoch().checked_add(@tempo.Duration::hours(1L)) {
Some(dt) => inspect(dt.format(), content="1970-01-01T01:00:00Z")
None => ()
}
}

#Date and Time parsing/formatting

///|
test {
// Date-only parse/format
let d = @tempo.Date::parse("2024-03-15")
inspect(d.format(), content="2024-03-15")

// Time-only parse/format
let t = @tempo.Time::parse("14:31:43.500")
inspect(t.format(), content="14:31:43.5")
}

#Current time

///|
test {
// Millisecond precision on js/wasm-gc, whole seconds on native.
let now = @tempo.DateTime::now()
assert_eq(now > @tempo.DateTime::epoch(), true)
}

#Calendar helpers

///|
test {
assert_eq(@tempo.is_leap_year(2000), true)
assert_eq(@tempo.is_leap_year(1900), false)
assert_eq(@tempo.is_leap_year(2024), true)
assert_eq(@tempo.days_in_month(2024, 2), 29)
assert_eq(@tempo.days_in_month(2023, 2), 28)
}

#Year-month

YearMonth is a year-and-month value with no day — useful for billing periods and month-granular keys. length is leap-aware; plus_months / plus_years carry across years; Compare orders chronologically.

///|
test {
let ym = @tempo.YearMonth::new(2024, 2)
inspect(ym.format(), content="2024-02")
inspect(ym.length(), content="29") // February 2024 is a leap year
inspect(ym.at_day(15).format(), content="2024-02-15")
inspect(ym.at_end_of_month().format(), content="2024-02-29")
inspect(ym.plus_months(11).format(), content="2025-01")
inspect(@tempo.YearMonth::parse("2025-01").format(), content="2025-01")
}

#Periods

Period is a calendar-aware { years, months, days } value — the calendar counterpart to the elapsed-time Duration. It stores its fields exactly and is never auto-normalized (15 months stays 15 months), because applying a period is anchor-dependent: adding one month clamps to the end of a short month, while adding 30 days does not. normalized rolls months into years only. Date::until returns the period between two dates and round-trips with add_period. Operations that would overflow the Int fields raise.

///|
test {
let p = @tempo.Period::of(1, 2, 10)
inspect(p.format(), content="P1Y2M10D")
inspect(@tempo.Period::of(0, 15, 0).normalized().format(), content="P1Y3M")

// Anchor-dependent: months adjust (with clamping) before days.
let d = @tempo.Date::new(2024, 1, 31)
inspect(d.add_period(@tempo.Period::of(0, 1, 0)).format(), content="2024-02-29")
inspect(d.add_period(@tempo.Period::of(0, 0, 30)).format(), content="2024-03-01")

// until round-trips with add_period.
let start = @tempo.Date::new(2024, 1, 15)
let end = @tempo.Date::new(2024, 3, 20)
let span = start.until(end)
inspect(span.format(), content="P2M5D")
assert_eq(start.add_period(span), end)
}

#ISO week and ordinal dates

Date exposes ISO 8601 week dates and ordinal (day-of-year) dates. The ISO week-numbering year can differ from the calendar year for dates near January 1 and December 31.

///|
test {
let d = @tempo.Date::new(2021, 1, 1)
assert_eq(d.iso_week_year(), 2020) // belongs to ISO week-year 2020
assert_eq(d.iso_week(), 53)
inspect(d.format_iso_week(), content="2020-W53-5")
inspect(@tempo.Date::from_iso_week(2020, 53, 5).format(), content="2021-01-01")

// Ordinal (day-of-year) dates, leap-aware.
inspect(@tempo.Date::from_ordinal(2024, 60).format(), content="2024-02-29")
inspect(@tempo.Date::new(2024, 12, 31).format_ordinal(), content="2024-366")

// Parse the string forms (inverses of the format_* methods).
inspect(
@tempo.Date::parse_iso_week("2020-W53-5").format(),
content="2021-01-01",
)
inspect(@tempo.Date::parse_ordinal("2024-060").format(), content="2024-02-29")
}

#Quarters

///|
test {
let d = @tempo.Date::new(2024, 5, 15)
assert_eq(d.quarter(), 2)
inspect(d.start_of_quarter().format(), content="2024-04-01")
inspect(d.end_of_quarter().format(), content="2024-06-30")
assert_eq(d.days_in_year(), 366)
assert_eq(d.is_leap(), true)
}

#Fractional and human-readable durations

as_seconds_f64 / as_minutes_f64 / as_hours_f64 give fractional totals as Double. humanize renders an English elapsed string (days/hours/minutes/ seconds, non-zero components only, - prefix for negatives).

///|
test {
inspect(@tempo.Duration::minutes(90).as_hours_f64(), content="1.5")
inspect(@tempo.Duration::milliseconds(1500).as_seconds_f64(), content="1.5")

let d = @tempo.Duration::hours(2) + @tempo.Duration::minutes(30)
inspect(d.humanize(), content="2 hours 30 minutes")
inspect(@tempo.Duration::seconds(1).humanize(), content="1 second")
inspect(@tempo.Duration::minutes(-90).humanize(), content="-1 hour 30 minutes")
}

#Custom formatting

format_fixed produces a fixed-width timestamp (always nine fractional digits) whose byte order matches chronological order for years 0–9999 — handy for log lines and database keys. format_with is a brace-token DSL: the tokens {YYYY}, {MM}, {DD}, {HH}, {mm}, {ss}, {fff}, and {nnnnnnnnn} substitute, everything else is literal, {{ and }} emit literal braces, and an unknown token or unmatched brace raises.

///|
test {
let dt = @tempo.DateTime::parse("2024-07-21T17:11:00.5Z")
inspect(dt.format_fixed(), content="2024-07-21T17:11:00.500000000Z")
inspect(dt.format_with("{YYYY}/{MM}/{DD} {HH}:{mm}"), content="2024/07/21 17:11")
}

#Not included

  • Timezones / DST — planned as a separate tempo-tz package
  • Locale-aware formattingstrftime patterns, localized names
  • Leap seconds — POSIX ignores them, so does tempo

#
TempoError

pub(all) suberror TempoError {
TempoError(String)
}

Error type for all tempo operations.
impl Show for TempoError

#
Date

pub struct Date {
year : Int
month : Int
day : Int
} derive(Compare, Eq, Hash,
Debug
)

A calendar date in the proleptic Gregorian calendar (UTC).
impl Show for Date
impl ToJson for Date

#
Date::add_days

fn Date::add_days(self : Date, days : Int) -> Date

Add a signed number of calendar days to this date (proleptic Gregorian).

This infallible operation wraps if the resulting year falls outside the Int year envelope. Use Date::add_days_checked to get None instead.

#
Date::add_days_checked

fn Date::add_days_checked(self : Date, days : Int) -> Date?

Add a signed number of calendar days, returning None if the resulting year cannot be represented by Date's Int year field.

#
Date::add_months

fn Date::add_months(self : Date, months : Int) -> Date

Add a signed number of calendar months to this date.

If the original day does not exist in the target month, the result is clamped to that month's last day. Clamping is non-sticky: 2024-02-28 plus one month is 2024-03-28, not 2024-03-31.

This infallible operation wraps if the resulting year falls outside the Int year envelope. Use Date::add_months_checked to get None instead.

#
Date::add_months_checked

fn Date::add_months_checked(self : Date, months : Int) -> Date?

Add a signed number of calendar months, returning None if the resulting year cannot be represented by Date's Int year field.

#
Date::add_period

fn Date::add_period(self : Date, period : Period) -> Date raise TempoError

Add a calendar period to this date.

Years and months are combined into one month adjustment first, using the same end-of-month clamping as Date::add_months; days are added afterward.

#
Date::add_years

fn Date::add_years(self : Date, years : Int) -> Date

Add a signed number of calendar years to this date.

If the original day does not exist in the target year/month, the result is clamped to that month's last day, so leap day moves to February 28 in non-leap target years.

This infallible operation wraps if the resulting year falls outside the Int year envelope. Use Date::add_years_checked to get None instead.

#
Date::add_years_checked

fn Date::add_years_checked(self : Date, years : Int) -> Date?

Add a signed number of calendar years, returning None if the resulting year cannot be represented by Date's Int year field.

#
Date::clamp

fn Date::clamp(self : Date, lo : Date, hi : Date) -> Date

Clamp this date to the inclusive range [lo, hi]. Assumes lo <= hi.

#
Date::day_of_week

fn Date::day_of_week(self : Date) -> Int

ISO 8601 weekday: Monday = 1 through Sunday = 7.

#
Date::day_of_year

fn Date::day_of_year(self : Date) -> Int

Day of year in 1..366 (1 = January 1).

#
Date::days_in_year

fn Date::days_in_year(self : Date) -> Int

Number of days in this date's calendar year.

#
Date::days_until

fn Date::days_until(self : Date, other : Date) -> Int

Calendar days from self to other (other minus self). Negative if other is earlier.

#
Date::end_of_month

fn Date::end_of_month(self : Date) -> Date

Return the last day of this date's calendar month.

#
Date::end_of_quarter

fn Date::end_of_quarter(self : Date) -> Date

Return the last day of this date's calendar quarter.

#
Date::end_of_year

fn Date::end_of_year(self : Date) -> Date

Return the last day of this date's calendar year.

#
Date::format

fn Date::format(self : Date) -> String

Format this date as 'YYYY-MM-DD'.

#
Date::format_iso_week

fn Date::format_iso_week(self : Date) -> String

Format this date as an ISO 8601 week date: YYYY-Www-D.

The ISO week-year is computed with Int64 intermediates, so at the absolute Int year boundary the formatted week-year may be the expanded year just outside Date's representable calendar envelope.

#
Date::format_ordinal

fn Date::format_ordinal(self : Date) -> String

Format this date as an ordinal date using ISO 8601 expanded-year output (see pad4_year), followed by day-of-year in 001..366.

#
Date::from_iso_week

fn Date::from_iso_week(week_year : Int, week : Int, weekday : Int) -> Date raise TempoError

Create a date from an ISO 8601 week date.

week_year is the ISO week-numbering year, week is 1..52 or 1..53 depending on that year, and weekday is Monday = 1 through Sunday = 7. Raises TempoError if the ISO week fields are invalid or if the computed calendar date falls outside Date's representable Int year envelope.

#
Date::from_ordinal

fn Date::from_ordinal(year : Int, day_of_year : Int) -> Date raise TempoError

Create a Date from year and day-of-year in 1..365, or 1..366 for a leap year.

#
Date::is_after

fn Date::is_after(self : Date, other : Date) -> Bool

true if this date is later than other.

#
Date::is_before

fn Date::is_before(self : Date, other : Date) -> Bool

true if this date is earlier than other.

#
Date::is_leap

fn Date::is_leap(self : Date) -> Bool

true if this date's year is a leap year.

#
Date::iso_week

fn Date::iso_week(self : Date) -> Int

ISO 8601 week number in 1..53 for this date.

#
Date::iso_week_year

fn Date::iso_week_year(self : Date) -> Int

ISO 8601 week-numbering year for this date.

This can differ from the calendar year near New Year; for example, 2021-01-01 belongs to ISO week year 2020.

At the absolute Int year boundary, the true ISO week-year may be one year outside the representable Int range. This infallible accessor wraps in that residual case; Date::format_iso_week still formats the true expanded ISO week-year text.

#
Date::max

fn Date::max(self : Date, other : Date) -> Date

Return the later of this date and other.

#
Date::min

fn Date::min(self : Date, other : Date) -> Date

Return the earlier of this date and other.

#
Date::month_enum

fn Date::month_enum(self : Date) -> Month

Calendar month for this date.

#
Date::new

fn Date::new(year : Int, month : Int, day : Int) -> Date raise TempoError

Create a Date, validating that month is 1–12 and day fits the calendar month (accounting for leap years in February).

#
Date::next

fn Date::next(self : Date, wd : Weekday) -> Date

Nearest date strictly after this date that falls on wd.

#
Date::next_or_same

fn Date::next_or_same(self : Date, wd : Weekday) -> Date

This date if it falls on wd, otherwise the next such date.

#
Date::nth_weekday_of_month

fn Date::nth_weekday_of_month(year : Int, month : Int, wd : Weekday, n : Int) -> Date raise TempoError

The nth occurrence of wd in year/month.

Positive n counts from the start of the month; negative n counts from the end, so n == -1 is the last occurrence.

#
Date::parse

fn Date::parse(s : String) -> Date raise TempoError

Parse a calendar date in 'YYYY-MM-DD' format.

#
Date::parse_iso_week

fn Date::parse_iso_week(s : String) -> Date raise TempoError

Parse an ISO 8601 week date in 'YYYY-Www-D' format.

The leading year is the ISO week-numbering year, which can differ from the resulting calendar year near New Year. This parser is strict: it accepts only an unsigned 4-digit week-year, a literal W, a 2-digit week, a 1-digit weekday, and end-of-input. Expanded-year output from Date::format_iso_week is not accepted.

#
Date::parse_ordinal

fn Date::parse_ordinal(s : String) -> Date raise TempoError

Parse an ISO 8601 ordinal date in 'YYYY-DDD' format.

This parser is strict: it accepts only an unsigned 4-digit year, a hyphen, exactly 3 digits for the day-of-year, and end-of-input. Expanded-year output from Date::format_ordinal is not accepted.

#
Date::previous

fn Date::previous(self : Date, wd : Weekday) -> Date

Nearest date strictly before this date that falls on wd.

#
Date::previous_or_same

fn Date::previous_or_same(self : Date, wd : Weekday) -> Date

This date if it falls on wd, otherwise the previous such date.

#
Date::quarter

fn Date::quarter(self : Date) -> Int

Calendar quarter number in 1..4.

#
Date::start_of_month

fn Date::start_of_month(self : Date) -> Date

Return the first day of this date's calendar month.

#
Date::start_of_quarter

fn Date::start_of_quarter(self : Date) -> Date

Return the first day of this date's calendar quarter.

#
Date::start_of_year

fn Date::start_of_year(self : Date) -> Date

Return the first day of this date's calendar year.

#
Date::until

fn Date::until(self : Date, other : Date) -> Period raise TempoError

Calendar period from this date until other.

The result round-trips through Date::add_period, including month-end clamping and the day-borrow behavior used by java.time.LocalDate.until.

#
Date::weekday

fn Date::weekday(self : Date) -> Weekday

ISO 8601 weekday for this date.

#
Date::with_day

fn Date::with_day(self : Date, day : Int) -> Date raise TempoError

Return a copy of this date with day replaced, validating the result.

This does not clamp. Chaining with with_month can raise on an intermediate invalid date before a later update would make it valid.

#
Date::with_month

fn Date::with_month(self : Date, month : Int) -> Date raise TempoError

Return a copy of this date with month replaced, validating the result.

This does not clamp. Chaining with with_day can raise on an intermediate invalid date before a later update would make it valid.

#
Date::with_year

fn Date::with_year(self : Date, year : Int) -> Date raise TempoError

Return a copy of this date with year replaced, validating the result.

#
DateInterval

pub(all) struct DateInterval {
start : Date
end : Date
} derive(Eq,
Debug
)

A calendar date interval with an inclusive end date: [start, end].

This mirrors NodaTime's DateInterval-vs-Interval convention split: DateInterval is inclusive-end for whole calendar dates, while Interval is half-open for instants. Assumes start <= end; methods do not validate inverted intervals.

#
DateInterval::contains

fn DateInterval::contains(self : DateInterval, d : Date) -> Bool

true if d is in this inclusive-end date interval [start, end].

#
DateInterval::intersection

fn DateInterval::intersection(self : DateInterval, other : DateInterval) -> DateInterval?

Return the inclusive-end overlap between this date interval and other.

#
DateInterval::length_in_days

fn DateInterval::length_in_days(self : DateInterval) -> Int

Inclusive day count for this date interval.

#
DateInterval::overlaps

fn DateInterval::overlaps(self : DateInterval, other : DateInterval) -> Bool

true if this inclusive-end date interval shares at least one date with other.

#
DateTime

pub struct DateTime {
date : Date
time : Time
} derive(Compare, Eq, Hash,
Debug
)

A combined UTC date and time.
impl Show for DateTime
impl ToJson for DateTime

#
DateTime::add

fn DateTime::add(self : DateTime, d : Duration) -> DateTime

Add a Duration to this DateTime.

This wraps on Int64 overflow. Use checked_add when overflow or a DateTime outside the representable Unix-nanoseconds range should return None.

#
DateTime::checked_add

fn DateTime::checked_add(self : DateTime, d : Duration) -> DateTime?

Add a Duration to this DateTime, returning None on Int64 overflow or when this DateTime is outside the representable Unix-nanoseconds range.

#
DateTime::checked_diff

fn DateTime::checked_diff(self : DateTime, other : DateTime) -> Duration?

Compute self - other as a Duration, returning None on Int64 overflow or when either DateTime is outside the representable Unix-nanoseconds range.

#
DateTime::checked_sub

fn DateTime::checked_sub(self : DateTime, d : Duration) -> DateTime?

Subtract a Duration from this DateTime, returning None on Int64 overflow or when this DateTime is outside the representable Unix-nanoseconds range.

#
DateTime::clamp

fn DateTime::clamp(self : DateTime, lo : DateTime, hi : DateTime) -> DateTime

Clamp this DateTime to the inclusive range [lo, hi]. Assumes lo <= hi.

#
DateTime::diff

fn DateTime::diff(self : DateTime, other : DateTime) -> Duration

Compute self - other as a Duration (may be negative).

This wraps on Int64 overflow. Use checked_diff when overflow or a DateTime outside the representable Unix-nanoseconds range should return None.

#
DateTime::end_of_day

fn DateTime::end_of_day(self : DateTime) -> DateTime

Return this DateTime at the end of its calendar day.

#
DateTime::end_of_month

fn DateTime::end_of_month(self : DateTime) -> DateTime

Return this DateTime at the end of its calendar month. Preserves the time-of-day; rebuild with DateTime::new and a zero Time if midnight is desired.

#
DateTime::end_of_year

fn DateTime::end_of_year(self : DateTime) -> DateTime

Return this DateTime at the end of its calendar year. Preserves the time-of-day; rebuild with DateTime::new and a zero Time if midnight is desired.

#
DateTime::epoch

fn DateTime::epoch() -> DateTime

The Unix epoch: 1970-01-01T00:00:00Z.

#
DateTime::format

fn DateTime::format(self : DateTime) -> String

Format this DateTime as an RFC 3339-style string (UTC, Z suffix) when the year is in the usual four-digit range; negative years and years ≥ 10000 use ISO 8601 expanded-year conventions (see pad4_year). Sub-second precision is included only when nanoseconds ≠ 0. Note: expanded-year output (years outside 0000–9999) cannot be round-tripped through DateTime::parse, which accepts only 4-digit positive years (RFC 3339).

#
DateTime::format_fixed

fn DateTime::format_fixed(self : DateTime) -> String

Format this DateTime as a UTC timestamp with a fixed-width fractional second; for years 0..9999, the whole output is fixed-width and lexicographically sortable: YYYY-MM-DDTHH:MM:SS.nnnnnnnnnZ.

The fractional-second field is always present with exactly 9 nanosecond digits. For years 0..9999, byte-for-byte string comparison of two format_fixed outputs matches chronological order. Outside that year range, expanded or negative year text (especially a leading -) does not preserve that lexicographic ordering guarantee.

#
DateTime::format_with

fn DateTime::format_with(self : DateTime, pattern : String) -> String raise TempoError

Format this DateTime with a brace-token pattern.

Supported tokens are {YYYY}, {MM}, {DD}, {HH}, {mm}, {ss}, {fff}, and {nnnnnnnnn}. Non-brace text outside tokens is copied literally. Use {{ and }} to emit literal braces. Unknown tokens and unmatched braces raise TempoError.

#
DateTime::from_unix_micros

fn DateTime::from_unix_micros(us : Int64) -> DateTime

Convert a Unix timestamp in microseconds to a DateTime. Negative values represent dates before the Unix epoch.

#
DateTime::from_unix_millis

fn DateTime::from_unix_millis(ms : Int64) -> DateTime

Convert a Unix timestamp in milliseconds to a DateTime. Negative values represent dates before the Unix epoch.

#
DateTime::from_unix_nanos

fn DateTime::from_unix_nanos(ns : Int64) -> DateTime

Convert a Unix timestamp in nanoseconds to a DateTime. Nanosecond resolution is preserved. Negative values represent dates before the Unix epoch.

#
DateTime::from_unix_seconds

fn DateTime::from_unix_seconds(ts : Int64) -> DateTime

Convert a Unix timestamp (seconds since 1970-01-01T00:00:00Z) to a DateTime. Negative values represent dates before the Unix epoch. The year range is limited by what Int64 can represent as days (~±100 billion years).

#
DateTime::is_after

fn DateTime::is_after(self : DateTime, other : DateTime) -> Bool

true if this DateTime is later than other.

#
DateTime::is_before

fn DateTime::is_before(self : DateTime, other : DateTime) -> Bool

true if this DateTime is earlier than other.

#
DateTime::max

fn DateTime::max(self : DateTime, other : DateTime) -> DateTime

Return the later of this DateTime and other.

#
DateTime::max_unix_nanos

fn DateTime::max_unix_nanos() -> Int64

Maximum Unix timestamp in nanoseconds representable by an Int64.

#
DateTime::min

fn DateTime::min(self : DateTime, other : DateTime) -> DateTime

Return the earlier of this DateTime and other.

#
DateTime::min_unix_nanos

fn DateTime::min_unix_nanos() -> Int64

Minimum Unix timestamp in nanoseconds representable by an Int64.

#
DateTime::new

fn DateTime::new(date : Date, time : Time) -> DateTime

Create a DateTime from a validated Date and Time. Both arguments should be constructed via Date::new / Time::new (which validate). This constructor performs no additional checks.

#
DateTime::now

fn DateTime::now() -> DateTime

Returns the current UTC time. Precision: whole seconds on native, milliseconds on js/wasm-gc.

#
DateTime::parse

fn DateTime::parse(s : String) -> DateTime raise TempoError

Parse an RFC 3339 / ISO 8601 datetime string. Fixed numeric offsets are accepted and normalized to UTC.

#
DateTime::round_to

fn DateTime::round_to(self : DateTime, unit : TimeUnit, mode : RoundMode) -> DateTime

Round this DateTime to the requested unit boundary.

Floor is equivalent to truncate_to(unit). Ceil rounds to the next boundary only when there is a remainder. HalfExpand rounds to the nearest boundary, with exact half-unit ties rounded up toward the later boundary.

Rounding is anchored to the calendar day and carries with Date::add_days, avoiding Unix-nanosecond conversion for dates outside the Int64 nanosecond timestamp range.

#
DateTime::since

fn DateTime::since(self : DateTime, earlier : DateTime) -> Duration

Wall-clock duration elapsed since earlier (self - earlier).

#
DateTime::start_of_day

fn DateTime::start_of_day(self : DateTime) -> DateTime

Return this DateTime at the start of its calendar day.

#
DateTime::start_of_month

fn DateTime::start_of_month(self : DateTime) -> DateTime

Return this DateTime at the start of its calendar month. Preserves the time-of-day; rebuild with DateTime::new and a zero Time if midnight is desired.

#
DateTime::start_of_year

fn DateTime::start_of_year(self : DateTime) -> DateTime

Return this DateTime at the start of its calendar year. Preserves the time-of-day; rebuild with DateTime::new and a zero Time if midnight is desired.

#
DateTime::sub

fn DateTime::sub(self : DateTime, d : Duration) -> DateTime

Subtract a Duration from this DateTime.

This wraps on Int64 overflow. Use checked_sub when overflow or a DateTime outside the representable Unix-nanoseconds range should return None.

#
DateTime::to_date

fn DateTime::to_date(self : DateTime) -> Date

Return the date component of this DateTime.

#
DateTime::to_time

fn DateTime::to_time(self : DateTime) -> Time

Return the time component of this DateTime.

#
DateTime::to_unix_micros

fn DateTime::to_unix_micros(self : DateTime) -> Int64

Convert this DateTime to a Unix timestamp in microseconds.

#
DateTime::to_unix_millis

fn DateTime::to_unix_millis(self : DateTime) -> Int64

Convert this DateTime to a Unix timestamp in milliseconds.

#
DateTime::to_unix_nanos

fn DateTime::to_unix_nanos(self : DateTime) -> Int64

Convert this DateTime to a Unix timestamp in nanoseconds.

Overflow note: this method silently wraps on Int64 overflow. Use to_unix_nanos_checked when dates outside the representable nanosecond range should return None; use to_unix_seconds for wider ranges.

#
DateTime::to_unix_nanos_checked

fn DateTime::to_unix_nanos_checked(self : DateTime) -> Int64?

Convert this DateTime to a Unix timestamp in nanoseconds.

Returns None when the instant is outside the representable Int64 nanosecond range.

#
DateTime::to_unix_seconds

fn DateTime::to_unix_seconds(self : DateTime) -> Int64

Convert this DateTime to a Unix timestamp in seconds (nanoseconds truncated).

#
DateTime::truncate_to

fn DateTime::truncate_to(self : DateTime, unit : TimeUnit) -> DateTime

Floor this DateTime to the requested unit boundary.

Day truncation is anchored to the calendar day: it returns midnight UTC on the same date, rather than truncating a duration since the Unix epoch.

#
DateTime::until

fn DateTime::until(self : DateTime, later : DateTime) -> Duration

Wall-clock duration until later (later - self).

#
DateTime::with_date

fn DateTime::with_date(self : DateTime, date : Date) -> DateTime

Return a copy of this DateTime with date replaced.

#
DateTime::with_day

fn DateTime::with_day(self : DateTime, day : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its date day replaced.

#
DateTime::with_hour

fn DateTime::with_hour(self : DateTime, hour : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its time hour replaced.

#
DateTime::with_minute

fn DateTime::with_minute(self : DateTime, minute : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its time minute replaced.

#
DateTime::with_month

fn DateTime::with_month(self : DateTime, month : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its date month replaced.

#
DateTime::with_nanosecond

fn DateTime::with_nanosecond(self : DateTime, nanosecond : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its time nanosecond replaced.

#
DateTime::with_second

fn DateTime::with_second(self : DateTime, second : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its time second replaced.

#
DateTime::with_time

fn DateTime::with_time(self : DateTime, time : Time) -> DateTime

Return a copy of this DateTime with time replaced.

#
DateTime::with_year

fn DateTime::with_year(self : DateTime, year : Int) -> DateTime raise TempoError

Return a copy of this DateTime with its date year replaced.

#
Duration

pub struct Duration {
nanoseconds : Int64
} derive(Compare, Eq, Hash,
Debug
)

A signed duration stored as total nanoseconds.
impl Add for Duration
impl Neg for Duration
impl Show for Duration
impl Sub for Duration
impl ToJson for Duration

#
Duration::abs

fn Duration::abs(self : Duration) -> Duration

Absolute value of this duration.

Int64::min_value cannot be negated as an Int64, so that edge saturates to Int64::max_value instead of raising or wrapping.

#
Duration::as_days

fn Duration::as_days(self : Duration) -> Int64

Total whole days in this duration (truncated toward zero).

#
Duration::as_hours

fn Duration::as_hours(self : Duration) -> Int64

Total whole hours in this duration (truncated toward zero).

#
Duration::as_hours_f64

fn Duration::as_hours_f64(self : Duration) -> Double

Total hours in this duration as a Double.

Double has a ~53-bit mantissa, so magnitudes beyond ~2^53 nanoseconds (~104 days) lose sub-unit precision.

#
Duration::as_microseconds

fn Duration::as_microseconds(self : Duration) -> Int64

Total whole microseconds in this duration (truncated toward zero).

#
Duration::as_milliseconds

fn Duration::as_milliseconds(self : Duration) -> Int64

Total whole milliseconds in this duration (truncated toward zero).

#
Duration::as_minutes

fn Duration::as_minutes(self : Duration) -> Int64

Total whole minutes in this duration (truncated toward zero).

#
Duration::as_minutes_f64

fn Duration::as_minutes_f64(self : Duration) -> Double

Total minutes in this duration as a Double.

Double has a ~53-bit mantissa, so magnitudes beyond ~2^53 nanoseconds (~104 days) lose sub-unit precision.

#
Duration::as_nanoseconds

fn Duration::as_nanoseconds(self : Duration) -> Int64

Total nanoseconds in this duration.

#
Duration::as_seconds

fn Duration::as_seconds(self : Duration) -> Int64

Total whole seconds in this duration (truncated toward zero).

#
Duration::as_seconds_f64

fn Duration::as_seconds_f64(self : Duration) -> Double

Total seconds in this duration as a Double.

Double has a ~53-bit mantissa, so magnitudes beyond ~2^53 nanoseconds (~104 days) lose sub-unit precision.

#
Duration::as_weeks

fn Duration::as_weeks(self : Duration) -> Int64

#
Duration::checked_add

fn Duration::checked_add(self : Duration, other : Duration) -> Duration?

Add two durations, returning None on Int64 overflow.

#
Duration::checked_multiply

fn Duration::checked_multiply(self : Duration, n : Int64) -> Duration?

Multiply this duration by an Int64 scalar, returning None on overflow.

#
Duration::checked_sub

fn Duration::checked_sub(self : Duration, other : Duration) -> Duration?

Subtract one duration from another, returning None on Int64 overflow.

#
Duration::days

fn Duration::days(d : Int64) -> Duration

Create a Duration representing the given number of days (86 400 seconds each).

#
Duration::divide

fn Duration::divide(self : Duration, n : Int64) -> Duration raise TempoError

Divide this duration by an Int64 scalar, truncating toward zero.

Division by zero raises TempoError. The Int64::min_value / -1 overflow edge also raises TempoError instead of trapping on backends where signed division overflow is a runtime error.

#
Duration::format_iso

fn Duration::format_iso(self : Duration) -> String

Format this fixed-length duration as a canonical ISO 8601 duration string.

Calendar units (years and months) are not representable as a fixed nanosecond duration and are therefore never emitted.

#
Duration::hours

fn Duration::hours(h : Int64) -> Duration

Create a Duration representing the given number of hours.

#
Duration::humanize

fn Duration::humanize(self : Duration) -> String

Format this duration as English elapsed time using day/hour/minute/second units. Sub-second remainder is dropped.

#
Duration::is_negative

fn Duration::is_negative(self : Duration) -> Bool

true when this duration is negative.

#
Duration::is_positive

fn Duration::is_positive(self : Duration) -> Bool

true when this duration is greater than zero.

#
Duration::is_zero

fn Duration::is_zero(self : Duration) -> Bool

true when this duration is exactly zero.

#
Duration::microseconds

fn Duration::microseconds(us : Int64) -> Duration

Create a Duration representing the given number of microseconds.

#
Duration::milliseconds

fn Duration::milliseconds(ms : Int64) -> Duration

Create a Duration representing the given number of milliseconds.

#
Duration::minutes

fn Duration::minutes(m : Int64) -> Duration

Create a Duration representing the given number of minutes.

#
Duration::multiply

fn Duration::multiply(self : Duration, n : Int64) -> Duration

Multiply this duration by an Int64 scalar.

This wraps on Int64 overflow. Use checked_multiply when overflow should return None.

#
Duration::nanoseconds

fn Duration::nanoseconds(ns : Int64) -> Duration

Create a Duration from a number of nanoseconds.

#
Duration::parse_iso

fn Duration::parse_iso(s : String) -> Duration raise TempoError

Parse an ISO 8601 duration string in the fixed-nanosecond subset: PnDTnHnMnS, with optional fractional seconds and leading sign.

Calendar units (Y years or date-part M months) raise TempoError; use a calendar-period representation for those units.

#
Duration::seconds

fn Duration::seconds(s : Int64) -> Duration

Create a Duration representing the given number of seconds.

#
Duration::signum

fn Duration::signum(self : Duration) -> Int

Sign of this duration: -1 for negative, 0 for zero, 1 for positive.

#
Duration::weeks

fn Duration::weeks(w : Int64) -> Duration

Create a Duration representing the given number of weeks (7 × 86 400 seconds).

#
FixedOffsetDateTime

pub struct FixedOffsetDateTime {
// private fields
} derive(Compare, Eq, Hash,
Debug
)

A UTC instant paired with a fixed numeric display offset.

DateTime is tempo's primary timestamp type. Use FixedOffsetDateTime only for wire formats and interop surfaces where a timestamp carries an explicit numeric offset but no IANA time zone. The stored instant is normalized UTC; the offset is retained only as a display hint.

#
FixedOffsetDateTime::format

fn FixedOffsetDateTime::format(self : FixedOffsetDateTime) -> String

Format the local wall-clock time with the retained fixed offset.

The local wall-clock is computed as the stored UTC instant plus offset_seconds; the fixed offset suffix is Z for zero and +HH:MM / -HH:MM otherwise. This is for RFC 3339-style interop only; DateTime remains the primary UTC timestamp type.

Note: formatting uses the same infallible day arithmetic as Date; if the local-time projection carries past the absolute Int year envelope, the year wraps. pad4_year itself handles the full Int range and does not abort.

#
FixedOffsetDateTime::from_datetime_and_offset

fn FixedOffsetDateTime::from_datetime_and_offset(utc : DateTime, offset_seconds : Int) -> FixedOffsetDateTime raise TempoError

Bundle a UTC instant with a fixed numeric display offset in seconds.

The utc argument is the UTC instant and is stored as-is. DateTime remains tempo's primary timestamp type; this wrapper is for wire-format interop when a timestamp carries an explicit numeric offset but no IANA time zone.

Raises TempoError when offset_seconds is not whole-minute granularity or is outside the RFC 3339 fixed-offset range ±23:59.

#
FixedOffsetDateTime::offset_seconds

fn FixedOffsetDateTime::offset_seconds(self : FixedOffsetDateTime) -> Int

Return the retained fixed display offset in seconds.

#
FixedOffsetDateTime::parse

fn FixedOffsetDateTime::parse(s : String) -> FixedOffsetDateTime raise TempoError

Parse an RFC 3339 datetime string, retaining the explicit fixed offset.

The stored instant is normalized UTC. The parsed numeric offset is retained only for formatting/interoperability; it is not an IANA time zone.

#
FixedOffsetDateTime::to_utc

Return the stored UTC instant.

#
Interval

pub(all) struct Interval {
start : DateTime
end : DateTime
} derive(Eq,
Debug
)

A DateTime interval with a half-open end instant: [start, end).

This mirrors NodaTime's DateInterval-vs-Interval convention split: DateInterval is inclusive-end for calendar dates, while Interval is half-open for instants. Assumes start <= end; methods do not validate inverted intervals.

#
Interval::contains

fn Interval::contains(self : Interval, dt : DateTime) -> Bool

true if dt is in this half-open DateTime interval [start, end).

#
Interval::intersection

fn Interval::intersection(self : Interval, other : Interval) -> Interval?

Return the half-open overlap between this DateTime interval and other.

#
Interval::overlaps

fn Interval::overlaps(self : Interval, other : Interval) -> Bool

true if this half-open DateTime interval shares at least one instant with other.

#
Interval::to_duration

fn Interval::to_duration(self : Interval) -> Duration

Duration of this half-open DateTime interval (end - start).

#
Month

pub(all) enum Month {
January
February
March
April
May
June
July
August
September
October
November
December
} derive(Compare, Eq, Hash,
Debug
)

Calendar month, ordered January through December.
impl Show for Month

#
Month::days_in

fn Month::days_in(self : Month, year : Int) -> Int

Number of days in this month for year.

#
Month::from_int

fn Month::from_int(n : Int) -> Month?

Convert a calendar month number (1..12) to a Month.

#
Month::next

fn Month::next(self : Month) -> Month

Next month, wrapping December to January.

#
Month::previous

fn Month::previous(self : Month) -> Month

Previous month, wrapping January to December.

#
Month::to_int

fn Month::to_int(self : Month) -> Int

Calendar month number: January = 1 through December = 12.

#
Period

pub(all) struct Period {
years : Int
months : Int
days : Int
} derive(Eq, Hash,
Debug
)

A calendar-aware date period stored as exact year, month, and day fields.

This is a calendar-field vector, not an elapsed-time scalar. It is never auto-canonicalized: fifteen months stays fifteen months, and thirty days never becomes one month.
impl Show for Period

#
Period::days

fn Period::days(self : Period) -> Int

Day field accessor.

#
Period::format

fn Period::format(self : Period) -> String

Format this calendar period as ISO 8601 date-period components.

#
Period::is_zero

fn Period::is_zero(self : Period) -> Bool

true when all three fields are zero.

#
Period::minus

fn Period::minus(self : Period, other : Period) -> Period raise TempoError

Subtract two periods field-wise, without normalization.

#
Period::months

fn Period::months(self : Period) -> Int

Month field accessor.

#
Period::negated

fn Period::negated(self : Period) -> Period raise TempoError

Negate each period field, without normalization.

#
Period::normalized

fn Period::normalized(self : Period) -> Period raise TempoError

Normalize the month field into years. Days are left untouched.

#
Period::of

fn Period::of(years : Int, months : Int, days : Int) -> Period

Create a Period, storing the year, month, and day fields exactly.

#
Period::of_days

fn Period::of_days(days : Int) -> Period

Create a Period with only a day field.

#
Period::of_months

fn Period::of_months(months : Int) -> Period

Create a Period with only a month field.

#
Period::of_weeks

fn Period::of_weeks(weeks : Int) -> Period raise TempoError

Create a Period from whole weeks, stored as days.

#
Period::of_years

fn Period::of_years(years : Int) -> Period

Create a Period with only a year field.

#
Period::parse

fn Period::parse(s : String) -> Period raise TempoError

Parse an ISO 8601 date-period string in the PnYnMnD subset.

Week notation (PnW) is accepted and stored as days. Time components and any T separator are rejected.

#
Period::plus

fn Period::plus(self : Period, other : Period) -> Period raise TempoError

Add two periods field-wise, without normalization.

#
Period::to_total_months

fn Period::to_total_months(self : Period) -> Int64

Total months represented by the year and month fields. Days are ignored.

#
Period::years

fn Period::years(self : Period) -> Int

Year field accessor.

#
Period::zero

fn Period::zero() -> Period

The zero period.

#
RoundMode

pub(all) enum RoundMode {
Floor
Ceil
HalfExpand
} derive(Compare, Eq, Hash,
Debug
)

Rounding modes for DateTime::round_to, ordered from earlier-boundary preference to nearest-boundary behavior.
impl Show for RoundMode

#
Time

pub struct Time {
hour : Int
minute : Int
second : Int
nanosecond : Int
} derive(Compare, Eq, Hash,
Debug
)

A time of day with nanosecond precision (UTC).
impl Show for Time
impl ToJson for Time

#
Time::clamp

fn Time::clamp(self : Time, lo : Time, hi : Time) -> Time

Clamp this time to the inclusive range [lo, hi]. Assumes lo <= hi.

#
Time::format

fn Time::format(self : Time) -> String

Format this time as HH:MM:SS or HH:MM:SS.fraction (nanoseconds trimmed, consistent with DateTime::format).

#
Time::is_after

fn Time::is_after(self : Time, other : Time) -> Bool

true if this time is later than other.

#
Time::is_before

fn Time::is_before(self : Time, other : Time) -> Bool

true if this time is earlier than other.

#
Time::max

fn Time::max(self : Time, other : Time) -> Time

Return the later of this time and other.

#
Time::min

fn Time::min(self : Time, other : Time) -> Time

Return the earlier of this time and other.

#
Time::new

fn Time::new(hour : Int, minute : Int, second : Int, nanosecond : Int) -> Time raise TempoError

Create a Time, validating that each field is within its canonical range: hour 0–23, minute 0–59, second 0–59, nanosecond 0–999_999_999.

#
Time::parse

fn Time::parse(s : String) -> Time raise TempoError

Parse a time-of-day string: HH:MM:SS or HH:MM:SS.fraction.

#
Time::with_hour

fn Time::with_hour(self : Time, hour : Int) -> Time raise TempoError

Return a copy of this time with hour replaced, validating the result.

#
Time::with_minute

fn Time::with_minute(self : Time, minute : Int) -> Time raise TempoError

Return a copy of this time with minute replaced, validating the result.

#
Time::with_nanosecond

fn Time::with_nanosecond(self : Time, nanosecond : Int) -> Time raise TempoError

Return a copy of this time with nanosecond replaced, validating the result.

#
Time::with_second

fn Time::with_second(self : Time, second : Int) -> Time raise TempoError

Return a copy of this time with second replaced, validating the result.

#
TimeUnit

pub(all) enum TimeUnit {
Second
Minute
Hour
Day
} derive(Compare, Eq, Hash,
Debug
)

Units supported by DateTime::truncate_to and DateTime::round_to, ordered smallest to largest.
impl Show for TimeUnit

#
Weekday

pub(all) enum Weekday {
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
} derive(Compare, Eq, Hash,
Debug
)

ISO 8601 weekday, ordered Monday through Sunday.
impl Show for Weekday

#
Weekday::from_int

fn Weekday::from_int(n : Int) -> Weekday?

Convert an ISO weekday number (1..7) to a Weekday.

#
Weekday::next

fn Weekday::next(self : Weekday) -> Weekday

Next weekday, wrapping Sunday to Monday.

#
Weekday::number_from_monday

fn Weekday::number_from_monday(self : Weekday) -> Int

ISO 8601 weekday number: Monday = 1 through Sunday = 7.

#
Weekday::number_from_sunday

fn Weekday::number_from_sunday(self : Weekday) -> Int

Sunday-based weekday number: Sunday = 1 through Saturday = 7.

#
Weekday::previous

fn Weekday::previous(self : Weekday) -> Weekday

Previous weekday, wrapping Monday to Sunday.

#
Weekday::to_int

fn Weekday::to_int(self : Weekday) -> Int

ISO 8601 weekday number: Monday = 1 through Sunday = 7.

#
YearMonth

pub(all) struct YearMonth {
year : Int
month : Int
} derive(Compare, Eq, Hash,
Debug
)

A calendar year and month in the proleptic Gregorian calendar.
impl Show for YearMonth

#
YearMonth::at_day

fn YearMonth::at_day(self : YearMonth, day : Int) -> Date raise TempoError

Return the date in this year-month at day, validating the day.

#
YearMonth::at_end_of_month

fn YearMonth::at_end_of_month(self : YearMonth) -> Date

Return the last date in this year-month.

#
YearMonth::format

fn YearMonth::format(self : YearMonth) -> String

Format this year-month using the same year text as Date::format, followed by - and a zero-padded month. Years in 0..9999 produce YYYY-MM; negative years and years >= 10000 produce expanded-year strings that cannot be round-tripped through YearMonth::parse.

#
YearMonth::length

fn YearMonth::length(self : YearMonth) -> Int

Number of days in this year-month.

#
YearMonth::month_enum

fn YearMonth::month_enum(self : YearMonth) -> Month

Calendar month for this year-month.

#
YearMonth::new

fn YearMonth::new(year : Int, month : Int) -> YearMonth raise TempoError

Create a YearMonth, validating that month is 1–12.

#
YearMonth::parse

fn YearMonth::parse(s : String) -> YearMonth raise TempoError

Parse a calendar year-month in 'YYYY-MM' format.

#
YearMonth::plus_months

fn YearMonth::plus_months(self : YearMonth, months : Int) -> YearMonth

Add a signed number of calendar months to this year-month.

#
YearMonth::plus_years

fn YearMonth::plus_years(self : YearMonth, years : Int) -> YearMonth

Add a signed number of calendar years to this year-month.

#
days_in_month

fn days_in_month(year : Int, month : Int) -> Int

Number of days in the given month (1–12). Returns 0 for invalid month values outside 1–12.

#
is_leap_year

fn is_leap_year(year : Int) -> Bool

true if year is a leap year in the proleptic Gregorian calendar.