Skip to content

Latest commit

 

History

History
171 lines (138 loc) · 3.82 KB

File metadata and controls

171 lines (138 loc) · 3.82 KB

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.