use std::cell::Cell;
use crate::interpreter::RuntimeValue;
thread_local! {
static FIXED_CLOCK: Cell<Option<(i32, i64)>> = const { Cell::new(None) };
}
pub fn set_fixed_clock(days_since_epoch: i32, nanos_since_epoch: i64) {
FIXED_CLOCK.with(|c| c.set(Some((days_since_epoch, nanos_since_epoch))));
}
pub fn clear_fixed_clock() {
FIXED_CLOCK.with(|c| c.set(None));
}
pub fn today() -> RuntimeValue {
if let Some((days, _)) = FIXED_CLOCK.with(|c| c.get()) {
return RuntimeValue::Date(days);
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
RuntimeValue::Date((duration.as_secs() / 86400) as i32)
}
#[cfg(target_arch = "wasm32")]
{
RuntimeValue::Date(0) }
}
pub fn now() -> RuntimeValue {
if let Some((_, nanos)) = FIXED_CLOCK.with(|c| c.get()) {
return RuntimeValue::Moment(nanos);
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
RuntimeValue::Moment(duration.as_nanos() as i64)
}
#[cfg(target_arch = "wasm32")]
{
RuntimeValue::Moment(0) }
}
pub fn date_add_span(days_since_epoch: i32, months: i32, days: i32) -> i32 {
let z = days_since_epoch + 719468;
let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i32 + 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 mut year = y + if m <= 2 { 1 } else { 0 };
let mut month = m as i32;
let mut day = d as i32;
let total_months = year.wrapping_mul(12).wrapping_add(month - 1).wrapping_add(months);
year = total_months / 12;
month = total_months % 12 + 1;
if month <= 0 {
month += 12;
year -= 1;
}
let dim = days_in_month(year, month);
if day > dim {
day = dim;
}
let yp = year - if month <= 2 { 1 } else { 0 };
let era2 = if yp >= 0 { yp / 400 } else { (yp - 399) / 400 };
let yoe2 = (yp - era2 * 400) as u32;
let mp2 = if month > 2 { month as u32 - 3 } else { month as u32 + 9 };
let doy2 = (153 * mp2 + 2) / 5 + day as u32 - 1;
let doe2 = yoe2 * 365 + yoe2 / 4 - yoe2 / 100 + doy2;
let result = era2 * 146097 + doe2 as i32 - 719468;
result.wrapping_add(days)
}
pub fn moment_add_span(nanos_since_epoch: i64, months: i32, days: i32) -> i64 {
let dt = logicaffeine_base::temporal::civil_from_unix_nanos(nanos_since_epoch);
let shifted = logicaffeine_base::temporal::add_span(dt, months as i64, days as i64);
logicaffeine_base::temporal::unix_nanos_from_civil(shifted)
}
pub fn days_in_month(year: i32, month: i32) -> i32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if is_leap {
29
} else {
28
}
}
_ => 30, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn month_end_clamps() {
let jan31_2024 = 19753;
let feb29_2024 = date_add_span(jan31_2024, 1, 0);
assert_eq!(feb29_2024 - jan31_2024, 29);
}
#[test]
fn days_in_month_handles_leap_years() {
assert_eq!(days_in_month(2024, 2), 29);
assert_eq!(days_in_month(2023, 2), 28);
assert_eq!(days_in_month(2000, 2), 29);
assert_eq!(days_in_month(1900, 2), 28);
assert_eq!(days_in_month(2024, 4), 30);
assert_eq!(days_in_month(2024, 12), 31);
}
}