#[must_use]
pub fn format_instant(unix_ms: i64) -> String {
let seconds = unix_ms.div_euclid(1_000);
let millis = unix_ms.rem_euclid(1_000);
let days = seconds.div_euclid(86_400);
let tod = seconds.rem_euclid(86_400);
let (year, month, day) = civil_from_days(days);
format!(
"{year:04}-{month:02}-{day:02} {:02}:{:02}:{:02}.{millis:03}",
tod / 3_600,
(tod % 3_600) / 60,
tod % 60
)
}
#[must_use]
pub fn parse_instant(text: &str) -> Option<i64> {
let text = text.trim();
let text = text.strip_suffix('Z').unwrap_or(text);
let (date, time) = match text.split_once(['T', ' ']) {
Some((date, time)) => (date, Some(time)),
None => (text, None),
};
let mut date_parts = date.split('-');
let year = date_parts.next()?.parse::<i64>().ok()?;
let month = parse_field(date_parts.next()?, 1, 12)?;
let day = parse_field(date_parts.next()?, 1, 31)?;
if date_parts.next().is_some() {
return None;
}
let days_in_month = match month {
2 if is_leap_year(year) => 29,
2 => 28,
4 | 6 | 9 | 11 => 30,
_ => 31,
};
if day > days_in_month {
return None;
}
let (hour, minute, second, millis) = match time {
None => (0, 0, 0, 0),
Some(time) => {
let mut parts = time.split(':');
let hour = parse_field(parts.next()?, 0, 23)?;
let minute = parse_field(parts.next()?, 0, 59)?;
let (second, millis) = match parts.next() {
None => (0, 0),
Some(seconds) => match seconds.split_once('.') {
None => (parse_field(seconds, 0, 60)?, 0),
Some((seconds, fraction)) => {
let scaled: String =
fraction.chars().chain("000".chars()).take(3).collect();
(parse_field(seconds, 0, 60)?, parse_field(&scaled, 0, 999)?)
}
},
};
if parts.next().is_some() {
return None;
}
(hour, minute, second, millis)
}
};
let days = days_from_civil(year, month, day);
let seconds = days * 86_400 + i128::from(hour * 3_600 + minute * 60 + second);
i64::try_from(seconds * 1_000 + i128::from(millis)).ok()
}
pub enum TimePoint {
T(u64),
Instant(i64),
}
pub fn parse_time_point(argument: Option<&str>, command: &str) -> Result<TimePoint, String> {
let argument = argument.ok_or_else(|| format!("usage: {command} t|timestamp"))?;
if let Ok(t) = argument.parse::<u64>() {
return Ok(TimePoint::T(t));
}
parse_instant(argument)
.map(TimePoint::Instant)
.ok_or_else(|| {
format!(
"{command} requires a transaction number or a UTC timestamp \
like 2026-07-25T09:30:00Z"
)
})
}
fn parse_field(text: &str, low: i64, high: i64) -> Option<i64> {
let value = text.parse::<i64>().ok()?;
(low..=high).contains(&value).then_some(value)
}
fn is_leap_year(year: i64) -> bool {
year.rem_euclid(4) == 0 && (year.rem_euclid(100) != 0 || year.rem_euclid(400) == 0)
}
fn civil_from_days(days: i64) -> (i64, i64, i64) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = yoe + era * 400 + i64::from(month <= 2);
(year, month, day)
}
fn days_from_civil(year: i64, month: i64, day: i64) -> i128 {
let year = i128::from(year) - i128::from(month <= 2);
let era = year.div_euclid(400);
let yoe = year - era * 400;
let month = i128::from(month);
let day = i128::from(day);
let mp = if month > 2 { month - 3 } else { month + 9 };
let doy = (153 * mp + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146_097 + doe - 719_468
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instants_format_as_utc() {
assert_eq!(format_instant(0), "1970-01-01 00:00:00.000");
assert_eq!(format_instant(1_700_000_000_123), "2023-11-14 22:13:20.123");
assert_eq!(format_instant(-1), "1969-12-31 23:59:59.999");
}
#[test]
fn parsing_round_trips_formatting() {
for millis in [0_i64, 1_700_000_000_123, -86_400_000, 4_102_444_800_000] {
let text = format_instant(millis);
assert_eq!(parse_instant(&text), Some(millis), "{text}");
}
}
#[test]
fn accepts_the_shapes_operators_type() {
assert_eq!(parse_instant("1970-01-01"), Some(0));
assert_eq!(parse_instant("1970-01-02Z"), Some(86_400_000));
assert_eq!(parse_instant("1970-01-01T00:00:01"), Some(1_000));
assert_eq!(parse_instant("1970-01-01 00:01"), Some(60_000));
assert_eq!(parse_instant("1970-01-01T00:00:00.5Z"), Some(500));
}
#[test]
fn rejects_malformed_and_out_of_range_timestamps() {
for text in [
"",
"not-a-date",
"1970-13-01",
"1970-01-32",
"1970-01-01T24:00",
"1970-01-01T00:60",
"1970-01-01T00:00:00+02:00",
"1970-01-01-01",
"1970-01",
"2026-02-29",
"2026-02-31",
"2026-04-31",
] {
assert_eq!(parse_instant(text), None, "{text}");
}
}
}