logicaffeine_compile/semantics/
temporal.rs1use std::cell::Cell;
4
5use crate::interpreter::RuntimeValue;
6
7thread_local! {
8 static FIXED_CLOCK: Cell<Option<(i32, i64)>> = const { Cell::new(None) };
12}
13
14pub fn set_fixed_clock(days_since_epoch: i32, nanos_since_epoch: i64) {
16 FIXED_CLOCK.with(|c| c.set(Some((days_since_epoch, nanos_since_epoch))));
17}
18
19pub fn clear_fixed_clock() {
21 FIXED_CLOCK.with(|c| c.set(None));
22}
23
24pub fn today() -> RuntimeValue {
26 if let Some((days, _)) = FIXED_CLOCK.with(|c| c.get()) {
27 return RuntimeValue::Date(days);
28 }
29 #[cfg(not(target_arch = "wasm32"))]
30 {
31 use std::time::{SystemTime, UNIX_EPOCH};
32 let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
33 RuntimeValue::Date((duration.as_secs() / 86400) as i32)
34 }
35 #[cfg(target_arch = "wasm32")]
36 {
37 RuntimeValue::Date(0) }
39}
40
41pub fn now() -> RuntimeValue {
43 if let Some((_, nanos)) = FIXED_CLOCK.with(|c| c.get()) {
44 return RuntimeValue::Moment(nanos);
45 }
46 #[cfg(not(target_arch = "wasm32"))]
47 {
48 use std::time::{SystemTime, UNIX_EPOCH};
49 let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
50 RuntimeValue::Moment(duration.as_nanos() as i64)
51 }
52 #[cfg(target_arch = "wasm32")]
53 {
54 RuntimeValue::Moment(0) }
56}
57
58pub fn date_add_span(days_since_epoch: i32, months: i32, days: i32) -> i32 {
62 let z = days_since_epoch + 719468;
64 let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
65 let doe = (z - era * 146097) as u32;
66 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
67 let y = yoe as i32 + era * 400;
68 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
69 let mp = (5 * doy + 2) / 153;
70 let d = doy - (153 * mp + 2) / 5 + 1;
71 let m = if mp < 10 { mp + 3 } else { mp - 9 };
72 let mut year = y + if m <= 2 { 1 } else { 0 };
73 let mut month = m as i32;
74 let mut day = d as i32;
75
76 let total_months = year.wrapping_mul(12).wrapping_add(month - 1).wrapping_add(months);
78 year = total_months / 12;
79 month = total_months % 12 + 1;
80 if month <= 0 {
81 month += 12;
82 year -= 1;
83 }
84
85 let dim = days_in_month(year, month);
87 if day > dim {
88 day = dim;
89 }
90
91 let yp = year - if month <= 2 { 1 } else { 0 };
93 let era2 = if yp >= 0 { yp / 400 } else { (yp - 399) / 400 };
94 let yoe2 = (yp - era2 * 400) as u32;
95 let mp2 = if month > 2 { month as u32 - 3 } else { month as u32 + 9 };
96 let doy2 = (153 * mp2 + 2) / 5 + day as u32 - 1;
97 let doe2 = yoe2 * 365 + yoe2 / 4 - yoe2 / 100 + doy2;
98 let result = era2 * 146097 + doe2 as i32 - 719468;
99
100 result.wrapping_add(days)
102}
103
104pub fn moment_add_span(nanos_since_epoch: i64, months: i32, days: i32) -> i64 {
109 let dt = logicaffeine_base::temporal::civil_from_unix_nanos(nanos_since_epoch);
110 let shifted = logicaffeine_base::temporal::add_span(dt, months as i64, days as i64);
111 logicaffeine_base::temporal::unix_nanos_from_civil(shifted)
112}
113
114pub fn days_in_month(year: i32, month: i32) -> i32 {
116 match month {
117 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
118 4 | 6 | 9 | 11 => 30,
119 2 => {
120 let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
121 if is_leap {
122 29
123 } else {
124 28
125 }
126 }
127 _ => 30, }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn month_end_clamps() {
137 let jan31_2024 = 19753;
139 let feb29_2024 = date_add_span(jan31_2024, 1, 0);
140 assert_eq!(feb29_2024 - jan31_2024, 29);
141 }
142
143 #[test]
144 fn days_in_month_handles_leap_years() {
145 assert_eq!(days_in_month(2024, 2), 29);
146 assert_eq!(days_in_month(2023, 2), 28);
147 assert_eq!(days_in_month(2000, 2), 29);
148 assert_eq!(days_in_month(1900, 2), 28);
149 assert_eq!(days_in_month(2024, 4), 30);
150 assert_eq!(days_in_month(2024, 12), 31);
151 }
152}