#![cfg_attr(not(test), warn(clippy::unwrap_used, clippy::panic))]
use futures::future::BoxFuture;
use jiff::civil::Date;
use jiff::tz::TimeZone;
use jiff::{Timestamp, Zoned};
use std::sync::Arc;
use std::time::Duration;
pub use jiff;
#[cfg(feature = "native")]
pub mod native;
#[cfg(feature = "wasm")]
pub mod wasm;
#[cfg(feature = "mock")]
pub mod mock;
pub trait Clock: Send + Sync + 'static {
fn now(&self) -> Timestamp;
fn now_in(&self, tz: &TimeZone) -> Zoned {
self.now().to_zoned(tz.clone())
}
fn today_in(&self, tz: &TimeZone) -> Date {
self.now_in(tz).date()
}
fn now_unix_millis(&self) -> i64 {
self.now().as_millisecond()
}
}
pub trait Timer: Send + Sync + 'static {
fn sleep(&self, dur: Duration) -> BoxFuture<'static, ()>;
fn next_tick(&self) -> BoxFuture<'static, ()>;
}
pub type ArcClock = Arc<dyn Clock>;
pub type ArcTimer = Arc<dyn Timer>;
#[cfg(test)]
mod tests {
use super::*;
struct FixedClock(Timestamp);
impl Clock for FixedClock {
fn now(&self) -> Timestamp {
self.0
}
}
fn utc_midnight_2026_05_10() -> Timestamp {
"2026-05-10T00:00:00Z".parse().expect("valid RFC 3339")
}
#[test]
fn now_in_seoul_utc_midnight_is_hour_9() {
let clock = FixedClock(utc_midnight_2026_05_10());
let tz = TimeZone::get("Asia/Seoul").expect("tzdb has Seoul");
assert_eq!(clock.now_in(&tz).hour(), 9);
}
#[test]
fn today_in_crosses_date_line() {
let clock = FixedClock("2026-05-10T23:30:00Z".parse().expect("valid"));
let tz = TimeZone::get("Asia/Seoul").expect("tzdb has Seoul");
assert_eq!(clock.today_in(&tz), jiff::civil::date(2026, 5, 11));
assert_eq!(clock.today_in(&TimeZone::UTC), jiff::civil::date(2026, 5, 10));
}
#[test]
fn dst_transition_new_york_2024() {
let tz = TimeZone::get("America/New_York").expect("tzdb has NY");
let before = FixedClock("2024-03-10T06:59:00Z".parse().expect("valid"));
let after = FixedClock("2024-03-10T07:00:00Z".parse().expect("valid"));
assert_eq!(before.now_in(&tz).hour(), 1);
assert_eq!(after.now_in(&tz).hour(), 3);
}
#[test]
fn now_unix_millis_matches_timestamp() {
let clock = FixedClock("1970-01-01T00:00:01Z".parse().expect("valid"));
assert_eq!(clock.now_unix_millis(), 1_000);
}
#[test]
fn dow_monday0_convention_via_zoned() {
let clock = FixedClock("2026-05-10T12:00:00Z".parse().expect("valid"));
let zdt = clock.now_in(&TimeZone::UTC);
assert_eq!(zdt.weekday().to_monday_zero_offset(), 6);
}
}