// RFC 3339 date-time tests: construction ranges, parsing, formatting,
// epoch arithmetic and cross-offset comparisons.
///|
test "make_utc accepts a valid date" {
let dt = unwrap_datetime(make_utc(2026, 8, 13, 14, 30, 59))
assert_int_eq(dt.year(), 2026)
assert_int_eq(dt.month(), 8)
assert_int_eq(dt.day(), 13)
assert_int_eq(dt.hour(), 14)
assert_int_eq(dt.minute(), 30)
assert_int_eq(dt.second(), 59)
assert_true(dt.nanos() == 0L)
assert_int_eq(dt.offset_minutes(), 0)
}
///|
test "make_utc rejects out-of-range components" {
assert_true(make_utc(2026, 0, 1, 0, 0, 0) is Err(_))
assert_true(make_utc(2026, 13, 1, 0, 0, 0) is Err(_))
assert_true(make_utc(2026, 1, 0, 0, 0, 0) is Err(_))
assert_true(make_utc(2026, 1, 32, 0, 0, 0) is Err(_))
assert_true(make_utc(2026, 4, 31, 0, 0, 0) is Err(_))
assert_true(make_utc(2026, 1, 1, 24, 0, 0) is Err(_))
assert_true(make_utc(2026, 1, 1, 0, 60, 0) is Err(_))
assert_true(make_utc(2026, 1, 1, 0, 0, 61) is Err(_))
assert_true(make_utc(2026, 1, 1, -1, 0, 0) is Err(_))
}
///|
test "make_utc follows the leap year rules for February 29" {
assert_true(make_utc(2024, 2, 29, 0, 0, 0) is Ok(_))
assert_true(make_utc(2023, 2, 29, 0, 0, 0) is Err(_))
assert_true(make_utc(1900, 2, 29, 0, 0, 0) is Err(_))
assert_true(make_utc(2000, 2, 29, 0, 0, 0) is Ok(_))
}
///|
test "make_utc accepts leap seconds and rejects bad nanos" {
let dt = unwrap_datetime(make_utc(2026, 8, 13, 0, 0, 60))
assert_int_eq(dt.second(), 60)
assert_true(make_utc_nanos(2026, 1, 1, 0, 0, 0, -1L) is Err(_))
assert_true(make_utc_nanos(2026, 1, 1, 0, 0, 0, 1_000_000_000L) is Err(_))
assert_true(make_utc_nanos(2026, 1, 1, 0, 0, 0, 999_999_999L) is Ok(_))
}
///|
test "is_leap_year and days_in_month cover the calendar tables" {
assert_true(is_leap_year(2024))
assert_true(is_leap_year(2000))
assert_false(is_leap_year(1900))
assert_false(is_leap_year(2023))
assert_false(is_leap_year(2100))
assert_int_eq(days_in_month(2026, 1), 31)
assert_int_eq(days_in_month(2026, 2), 28)
assert_int_eq(days_in_month(2024, 2), 29)
assert_int_eq(days_in_month(2026, 4), 30)
assert_int_eq(days_in_month(2026, 6), 30)
assert_int_eq(days_in_month(2026, 12), 31)
}
///|
test "parse_rfc3339 accepts RFC 9116 date-time separators" {
assert_true(parse_rfc3339("2026-08-13T14:30:59Z") is Ok(_))
assert_true(parse_rfc3339("2026-08-13T14:30:59z") is Ok(_))
assert_true(parse_rfc3339("2026-08-13t14:30:59Z") is Ok(_))
assert_true(parse_rfc3339("2026-08-13") is Err(_))
assert_true(parse_rfc3339("2026-08-13 14:30:59Z") is Err(_))
}
///|
test "parse_rfc3339 accepts numeric offsets and fractional seconds" {
let plus = unwrap_datetime(parse_rfc3339("2026-08-13T14:30:59+08:00"))
let minus = unwrap_datetime(parse_rfc3339("2026-08-13T14:30:59-05:00"))
assert_int_eq(plus.offset_minutes(), 480)
assert_int_eq(minus.offset_minutes(), -300)
let dt = unwrap_datetime(parse_rfc3339("2026-08-13T00:00:00.5Z"))
assert_true(dt.nanos() == 500_000_000L)
let dt2 = unwrap_datetime(parse_rfc3339("2026-08-13T00:00:00.123456789Z"))
assert_true(dt2.nanos() == 123_456_789L)
}
///|
test "parse_rfc3339 rejects invalid calendar dates" {
assert_true(parse_rfc3339("2026-13-01T00:00:00Z") is Err(_))
assert_true(parse_rfc3339("2026-02-30T00:00:00Z") is Err(_))
assert_true(parse_rfc3339("2023-02-29T00:00:00Z") is Err(_))
assert_true(parse_rfc3339("2026-08-13T24:00:00Z") is Err(_))
assert_true(parse_rfc3339("2026-08-13T00:61:00Z") is Err(_))
}
///|
test "parse_rfc3339 rejects missing offsets, trailing junk and long fractions" {
assert_true(parse_rfc3339("2026-08-13T14:30:59") is Err(_))
assert_true(parse_rfc3339("2026-08-13T14:30:59Z ") is Err(_))
assert_true(parse_rfc3339("2026-08-13X") is Err(_))
assert_true(parse_rfc3339("2026-08-13T00:00:00.1234567890Z") is Err(_))
assert_true(parse_rfc3339("2026-08-13T00:00:00.Z") is Err(_))
}
///|
test "parse_rfc3339 errors carry byte offsets" {
match parse_rfc3339("2026-13-01T00:00:00Z") {
Ok(_) => fail("expected an error for month 13")
Err(err) => {
assert_err_kind(err, InvalidDateTime)
assert_int_eq(err.byte_offset(), 5)
}
}
}
///|
test "format_rfc3339 renders canonical and offset forms" {
let dt = unwrap_datetime(make_utc_nanos(2026, 8, 13, 7, 5, 9, 125_000_000L))
assert_str_eq(dt.format_rfc3339(), "2026-08-13T07:05:09.125Z")
let whole = unwrap_datetime(make_utc(2026, 8, 13, 7, 5, 9))
assert_str_eq(whole.format_rfc3339(), "2026-08-13T07:05:09Z")
match parse_rfc3339("2026-08-13T14:30:59+08:00") {
Ok(d) => assert_str_eq(d.format_rfc3339(), "2026-08-13T14:30:59+08:00")
Err(_) => fail("unexpected parse failure")
}
}
///|
test "epoch conversion matches well-known instants across offsets" {
let epoch = unwrap_datetime(make_utc(1970, 1, 1, 0, 0, 0))
assert_true(epoch.to_epoch_nanos() == 0L)
let y2k = unwrap_datetime(make_utc(2000, 1, 1, 0, 0, 0))
assert_true(y2k.to_epoch_nanos() == 946_684_800_000_000_000L)
let y2020 = unwrap_datetime(make_utc(2020, 1, 1, 0, 0, 0))
assert_true(y2020.to_epoch_nanos() == 1_577_836_800_000_000_000L)
let utc = unwrap_datetime(parse_rfc3339("2026-08-13T00:00:00Z"))
let beijing = unwrap_datetime(parse_rfc3339("2026-08-13T08:00:00+08:00"))
assert_true(utc.equals(beijing))
assert_false(utc.before(beijing))
assert_false(utc.after(beijing))
}
///|
test "is_expired, time_until_expiry and div_floor work at the boundary" {
let expires = unwrap_datetime(make_utc(2026, 6, 1, 0, 0, 0))
let before = unwrap_datetime(make_utc(2026, 5, 31, 23, 59, 59))
let at = unwrap_datetime(make_utc(2026, 6, 1, 0, 0, 0))
let after = unwrap_datetime(make_utc(2026, 6, 1, 0, 0, 1))
assert_false(is_expired(expires, before))
assert_false(is_expired(expires, at))
assert_true(is_expired(expires, after))
assert_true(time_until_expiry(expires, before) == 1L)
assert_true(time_until_expiry(expires, at) == 0L)
assert_true(time_until_expiry(expires, after) == -1L)
assert_true(div_floor_i64(7L, 2L) == 3L)
assert_true(div_floor_i64(-7L, 2L) == -4L)
assert_true(div_floor_i64(7L, -2L) == -4L)
assert_true(div_floor_i64(-7L, -2L) == 3L)
assert_true(div_floor_i64(1_500_000_000L, 1_000_000_000L) == 1L)
assert_true(div_floor_i64(-1_500_000_000L, 1_000_000_000L) == -2L)
}