Skip to content

brickfrog/tempo

Repository files navigation

tempo

UTC date/time library for MoonBit. RFC 3339 parsing, Unix timestamp conversion, basic 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

Type Description
Date year, month (1–12), day (1–31)
Time hour, minute, second, nanosecond
DateTime Combined UTC date and time
Duration Signed 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")
}
///|
test {
  let dt = @tempo.DateTime::from_unix_seconds(0L)
  inspect(dt.format(), content="1970-01-01T00:00:00Z")

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

Parsing

Accepts RFC 3339 / ISO 8601. Only UTC offsets (Z, +00:00, -00:00) are accepted — others raise TempoError.

///|
test {
  let dt = @tempo.DateTime::parse("2026-03-28T14:31:43.125Z")
  inspect(dt.time.nanosecond, content="125000000")
}
///|
test {
  let result = try {
    @tempo.DateTime::parse("2026-03-28T14:31:43+09:00") |> ignore
    "ok"
  } catch {
    @tempo.TempoError(_) => "error"
  }
  assert_eq(result, "error")
}

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::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")
}

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)
}

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

See ROADMAP.md.

About

MoonBit date/time library — UTC-first, RFC 3339

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors