Skip to main content

msime_engine/session/
clock.rs

1//! Injectable clocks. The personal-learning windows (3 s context keep, 8 s chain pause, 10 s pick-pair gap) read the steady clock and the date/time mode reads the local wall clock; tests replace both.
2
3use std::sync::Arc;
4
5use crate::time::Instant;
6
7use crate::local::date_time::LocalDateTime;
8
9/// 本地墙钟。九宫格会话也读它(组字里的日期时间行),所以和输入会话共用一份。
10pub type LocalClock = Arc<dyn Fn() -> LocalDateTime + Send + Sync>;
11
12pub struct Clock {
13    pub steady: Box<dyn Fn() -> Instant + Send>,
14    pub local: LocalClock,
15}
16
17impl Default for Clock {
18    fn default() -> Self {
19        Self {
20            steady: Box::new(Instant::now),
21            local: Arc::new(default_local),
22        }
23    }
24}
25
26#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
27fn default_local() -> LocalDateTime {
28    crate::local::date_time::current_local_date_time()
29}
30
31/// wasm 上 `OffsetDateTime::now_local` 会 panic,所以按 UTC 读宿主注入的 Unix 毫秒;没有注入时是 Unix 纪元。网页宿主关闭了本地模式,这里只是保护。
32#[cfg(all(target_family = "wasm", target_os = "unknown"))]
33fn default_local() -> LocalDateTime {
34    utc_local_date_time(crate::time::host_wall_ms().unwrap_or(0.0))
35}
36
37#[cfg(any(test, all(target_family = "wasm", target_os = "unknown")))]
38fn utc_local_date_time(unix_ms: f64) -> LocalDateTime {
39    let nanos = if unix_ms.is_finite() {
40        // 越界的读数由 from_unix_timestamp_nanos 拒绝,落回纪元
41        (unix_ms * 1_000_000.0) as i128
42    } else {
43        0
44    };
45    let now = ::time::OffsetDateTime::from_unix_timestamp_nanos(nanos)
46        .unwrap_or(::time::OffsetDateTime::UNIX_EPOCH);
47    LocalDateTime {
48        year: now.year(),
49        month: u32::from(u8::from(now.month())),
50        day: u32::from(now.day()),
51        weekday: u32::from(now.weekday().number_days_from_sunday()),
52        hour: u32::from(now.hour()),
53        minute: u32::from(now.minute()),
54        second: u32::from(now.second()),
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn utc_reading_becomes_a_local_date_time() {
64        // 2024-02-29 13:45:30.250 UTC,星期四
65        let now = utc_local_date_time(1_709_214_330_250.0);
66        assert_eq!(
67            now,
68            LocalDateTime {
69                year: 2024,
70                month: 2,
71                day: 29,
72                weekday: 4,
73                hour: 13,
74                minute: 45,
75                second: 30,
76            }
77        );
78    }
79
80    #[test]
81    fn unusable_readings_fall_back_to_the_epoch() {
82        let epoch = utc_local_date_time(0.0);
83        assert_eq!(
84            (epoch.year, epoch.month, epoch.day, epoch.weekday),
85            (1970, 1, 1, 4)
86        );
87        assert_eq!(utc_local_date_time(f64::NAN), epoch);
88        assert_eq!(utc_local_date_time(f64::MAX), epoch);
89    }
90}