1use super::TimeSource;
2use crate::HostFailure;
3use jiff::{Timestamp, tz::TimeZone};
4use num_bigint::BigInt;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug, Clone, Copy)]
9pub struct SystemTimeSource;
10
11impl TimeSource for SystemTimeSource {
12 fn system_time(&mut self) -> Result<SystemTime, HostFailure> {
13 Ok(SystemTime::now())
14 }
15
16 fn local_offset_seconds(&mut self) -> Result<i32, HostFailure> {
17 current_offset_seconds(
18 map_system_time_zone(TimeZone::try_system()),
19 Timestamp::now(),
20 )
21 }
22}
23
24pub(super) fn split_system_time(time: SystemTime) -> (BigInt, BigInt) {
25 match time.duration_since(UNIX_EPOCH) {
26 Ok(duration) => (
27 BigInt::from(duration.as_secs()),
28 BigInt::from(duration.subsec_nanos()),
29 ),
30 Err(error) => {
31 let duration = error.duration();
32 let seconds = BigInt::from(duration.as_secs());
33 let nanoseconds = duration.subsec_nanos();
34 if nanoseconds == 0 {
35 (-seconds, BigInt::from(0))
36 } else {
37 (-seconds - 1, BigInt::from(1_000_000_000u32 - nanoseconds))
38 }
39 }
40 }
41}
42
43fn map_system_time_zone<Error>(result: Result<TimeZone, Error>) -> Result<TimeZone, HostFailure> {
44 result.map_err(|_| HostFailure::new("could not determine the current local UTC offset"))
45}
46
47fn current_offset_seconds(
48 time_zone: Result<TimeZone, HostFailure>,
49 timestamp: Timestamp,
50) -> Result<i32, HostFailure> {
51 Ok(time_zone?.to_offset(timestamp).seconds())
52}
53
54#[cfg(test)]
55mod tests {
56 use super::{
57 SystemTimeSource, current_offset_seconds, map_system_time_zone, split_system_time,
58 };
59 use crate::TimeSource;
60 use jiff::{
61 Timestamp,
62 tz::{TimeZone, offset},
63 };
64 use num_bigint::BigInt;
65 use std::time::{Duration, UNIX_EPOCH};
66
67 #[test]
68 fn splits_system_time_into_canonical_seconds_and_nanoseconds() {
69 for (time, expected) in [
70 (UNIX_EPOCH, (BigInt::from(0), BigInt::from(0))),
71 (
72 UNIX_EPOCH + Duration::from_nanos(1),
73 (BigInt::from(0), BigInt::from(1)),
74 ),
75 (
76 UNIX_EPOCH + Duration::new(100_000_000_000, 999_999_999),
77 (
78 BigInt::from(100_000_000_000u64),
79 BigInt::from(999_999_999u32),
80 ),
81 ),
82 (
83 UNIX_EPOCH - Duration::from_nanos(1),
84 (BigInt::from(-1), BigInt::from(999_999_999u32)),
85 ),
86 (
87 UNIX_EPOCH - Duration::from_secs(1),
88 (BigInt::from(-1), BigInt::from(0)),
89 ),
90 (
91 UNIX_EPOCH - Duration::new(2, 3),
92 (BigInt::from(-3), BigInt::from(999_999_997u32)),
93 ),
94 ] {
95 assert_eq!(split_system_time(time), expected);
96 }
97 }
98
99 #[test]
100 fn maps_system_time_zone_discovery_without_a_silent_fallback() {
101 let tokyo = TimeZone::fixed(offset(9));
102 let failure = map_system_time_zone::<()>(Err(()))
103 .expect_err("failed discovery should remain a host failure");
104
105 assert_eq!(
106 map_system_time_zone::<()>(Ok(tokyo.clone()))
107 .expect("known time zone should remain available"),
108 tokyo,
109 );
110 assert_eq!(
111 current_offset_seconds(Ok(tokyo), Timestamp::UNIX_EPOCH)
112 .expect("known time zone should have an offset"),
113 32_400,
114 );
115 assert_eq!(
116 current_offset_seconds(Err(failure), Timestamp::UNIX_EPOCH)
117 .expect_err("failed discovery should remain a host failure")
118 .message(),
119 "could not determine the current local UTC offset",
120 );
121 }
122
123 #[test]
124 fn system_source_reads_the_current_wall_clock_and_offset_fallibly() {
125 let mut source = SystemTimeSource;
126 source
127 .system_time()
128 .expect("the operating system wall clock should be available");
129
130 assert!(
131 source
132 .local_offset_seconds()
133 .map_or(true, |offset| (-86_400..=86_400).contains(&offset)),
134 );
135 }
136}