#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
#[inline]
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn now() -> u64 {
let datetime = crate::wasi_http::wasi::clocks::wall_clock::now();
datetime.seconds
}
#[inline]
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[inline]
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn now_millis() -> u64 {
let datetime = crate::wasi_http::wasi::clocks::wall_clock::now();
to_millis(datetime.seconds, datetime.nanoseconds)
}
#[inline]
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[inline]
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn now_iso() -> String {
let datetime = crate::wasi_http::wasi::clocks::wall_clock::now();
to_iso(datetime.seconds, datetime.nanoseconds)
}
#[inline]
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn now_iso() -> String {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
to_iso(duration.as_secs(), duration.subsec_nanos())
}
#[inline]
#[must_use]
pub fn to_millis(seconds: u64, nanoseconds: u32) -> u64 {
seconds
.saturating_mul(1000)
.saturating_add(u64::from(nanoseconds / 1_000_000))
}
#[must_use]
#[allow(clippy::similar_names)] pub fn to_iso(seconds: u64, nanoseconds: u32) -> String {
use crate::constants::{SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE};
let days = seconds / SECONDS_PER_DAY;
let remaining = seconds % SECONDS_PER_DAY;
let hours = remaining / SECONDS_PER_HOUR;
let remaining = remaining % SECONDS_PER_HOUR;
let minutes = remaining / SECONDS_PER_MINUTE;
let secs = remaining % SECONDS_PER_MINUTE;
let z = days + 719468;
let era = z / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
if nanoseconds == 0 {
format!("{year:04}-{m:02}-{d:02}T{hours:02}:{minutes:02}:{secs:02}Z")
} else {
let millis = nanoseconds / 1_000_000;
format!("{year:04}-{m:02}-{d:02}T{hours:02}:{minutes:02}:{secs:02}.{millis:03}Z")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_millis() {
assert_eq!(to_millis(1737024600, 500_000_000), 1737024600500);
}
#[test]
fn test_to_millis_truncates() {
assert_eq!(to_millis(1000, 123_456_789), 1000123);
}
#[test]
fn test_to_iso_epoch() {
assert_eq!(to_iso(0, 0), "1970-01-01T00:00:00Z");
}
#[test]
fn test_to_iso_known_date() {
assert_eq!(to_iso(1737024600, 0), "2025-01-16T10:50:00Z");
}
#[test]
fn test_to_iso_leap_year() {
assert_eq!(to_iso(1709208000, 0), "2024-02-29T12:00:00Z");
}
#[test]
fn test_to_iso_end_of_year() {
assert_eq!(to_iso(1735689599, 0), "2024-12-31T23:59:59Z");
}
#[test]
fn test_to_iso_start_of_year() {
assert_eq!(to_iso(1735689600, 0), "2025-01-01T00:00:00Z");
}
#[test]
fn test_to_iso_y2k() {
assert_eq!(to_iso(946684800, 0), "2000-01-01T00:00:00Z");
}
#[test]
fn test_to_iso_far_future() {
assert_eq!(to_iso(4133980799, 0), "2100-12-31T23:59:59Z");
}
#[test]
fn test_to_iso_month_boundaries() {
assert_eq!(to_iso(1677628800, 0), "2023-03-01T00:00:00Z");
assert_eq!(to_iso(1719792000, 0), "2024-07-01T00:00:00Z");
assert_eq!(to_iso(1730419200, 0), "2024-11-01T00:00:00Z");
}
#[test]
fn test_to_iso_day_boundary() {
assert_eq!(to_iso(1719791999, 0), "2024-06-30T23:59:59Z");
assert_eq!(to_iso(1719792000, 0), "2024-07-01T00:00:00Z");
}
#[test]
fn test_to_iso_century_boundary() {
assert_eq!(to_iso(946684799, 0), "1999-12-31T23:59:59Z");
assert_eq!(to_iso(946684800, 0), "2000-01-01T00:00:00Z");
}
#[test]
fn test_to_iso_multi_decade() {
let cases = [
(0, "1970-01-01T00:00:00Z"),
(315532800, "1980-01-01T00:00:00Z"),
(631152000, "1990-01-01T00:00:00Z"),
(946684800, "2000-01-01T00:00:00Z"),
(1262304000, "2010-01-01T00:00:00Z"),
(1577836800, "2020-01-01T00:00:00Z"),
(1893456000, "2030-01-01T00:00:00Z"),
];
for (secs, expected) in cases {
assert_eq!(to_iso(secs, 0), expected);
}
}
#[test]
fn test_to_iso_with_milliseconds() {
assert_eq!(to_iso(1737024600, 500_000_000), "2025-01-16T10:50:00.500Z");
assert_eq!(to_iso(1737024600, 1_000_000), "2025-01-16T10:50:00.001Z");
assert_eq!(to_iso(1737024600, 999_000_000), "2025-01-16T10:50:00.999Z");
assert_eq!(to_iso(1737024600, 123_000_000), "2025-01-16T10:50:00.123Z");
}
#[test]
fn test_to_iso_subsecond_truncation() {
assert_eq!(to_iso(1737024600, 123_456_789), "2025-01-16T10:50:00.123Z");
assert_eq!(to_iso(1737024600, 999_999_999), "2025-01-16T10:50:00.999Z");
}
#[test]
fn test_to_iso_zero_nanoseconds_no_decimal() {
assert_eq!(to_iso(1737024600, 0), "2025-01-16T10:50:00Z");
assert!(!to_iso(1737024600, 0).contains('.'));
}
#[test]
fn test_now_returns_reasonable_value() {
let timestamp = now();
assert!(timestamp > 1577836800, "Timestamp should be after 2020");
assert!(timestamp < 4102444800, "Timestamp should be before 2100");
}
#[test]
fn test_now_millis_returns_reasonable_value() {
let timestamp_ms = now_millis();
assert!(
timestamp_ms > 1577836800000,
"Timestamp should be after 2020"
);
assert!(
timestamp_ms < 4102444800000,
"Timestamp should be before 2100"
);
}
#[test]
fn test_now_millis_is_greater_than_now() {
let secs = now();
let ms = now_millis();
assert!(ms >= secs * 1000);
assert!(ms < (secs + 2) * 1000);
}
#[test]
fn test_now_iso_format() {
let iso = now_iso();
assert!(iso.ends_with('Z'), "Should end with Z");
assert!(iso.contains('T'), "Should contain T separator");
assert!(
iso.len() == 20 || iso.len() == 24,
"Should be 20 or 24 chars, got {}",
iso.len()
);
assert!(iso.chars().nth(4) == Some('-'), "Year separator");
assert!(iso.chars().nth(7) == Some('-'), "Month separator");
assert!(iso.chars().nth(13) == Some(':'), "Hour separator");
assert!(iso.chars().nth(16) == Some(':'), "Minute separator");
}
#[test]
fn test_now_consistency() {
let secs = now();
let iso = now_iso();
let year: u64 = iso[0..4].parse().expect("valid ISO year");
assert!((2020..=2100).contains(&year));
let secs2 = now();
assert!(secs2 - secs <= 1, "now() calls should be within 1 second");
}
#[test]
fn test_now_iso_accuracy() {
let before = now();
let iso = now_iso();
let after = now();
let year: u64 = iso[0..4].parse().expect("valid ISO year");
let month: u64 = iso[5..7].parse().expect("valid ISO month");
let day: u64 = iso[8..10].parse().expect("valid ISO day");
let hour: u64 = iso[11..13].parse().expect("valid ISO hour");
let minute: u64 = iso[14..16].parse().expect("valid ISO minute");
let second: u64 = iso[17..19].parse().expect("valid ISO second");
let y = if month <= 2 { year - 1 } else { year };
let m = if month <= 2 { month + 12 } else { month };
let era = y / 400;
let yoe = y - era * 400;
let doy = (153 * (m - 3) + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146097 + doe - 719468;
let reconstructed_secs = days * 86400 + hour * 3600 + minute * 60 + second;
assert!(
reconstructed_secs >= before && reconstructed_secs <= after + 1,
"ISO '{iso}' -> {reconstructed_secs} should be between {before} and {after}"
);
}
#[test]
fn test_timestamp_changes_over_time() {
let t1 = now_millis();
std::thread::sleep(std::time::Duration::from_millis(10));
let t2 = now_millis();
assert!(t2 > t1, "Time should advance: {t2} should be > {t1}");
}
}