//! Local calendar time for the body, weather and the prompt clock. //! //! The host converts with the save's `hosting.timeZone`. The browser build //! reads the device's own clock (`Date`), so a local pet follows its owner's //! current zone; moving to a host records that zone first. /// Calendar fields at one instant; `month0` counts from 0 as JavaScript does. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Local { pub year: i32, pub month0: u32, pub day: u32, pub hour: u32, pub minute: u32, } impl Local { pub fn date_key(&self) -> String { format!("{:04}-{:02}-{:02}", self.year, self.month0 + 1, self.day) } } #[cfg(not(target_arch = "wasm32"))] pub fn local(time_zone: &str, t: f64) -> Local { use chrono::{Datelike, TimeZone, Timelike}; let tz = time_zone.parse::().unwrap_or(chrono_tz::UTC); let d = tz .timestamp_millis_opt(t as i64) .single() .unwrap_or_else(|| tz.timestamp_millis_opt(0).unwrap()); Local { year: d.year(), month0: d.month0(), day: d.day(), hour: d.hour(), minute: d.minute(), } } #[cfg(not(target_arch = "wasm32"))] pub fn valid_zone(time_zone: &str) -> bool { time_zone.parse::().is_ok() } #[cfg(target_arch = "wasm32")] pub fn local(_time_zone: &str, t: f64) -> Local { let d = js_sys::Date::new(&t.into()); Local { year: d.get_full_year() as i32, month0: d.get_month(), day: d.get_date(), hour: d.get_hours(), minute: d.get_minutes(), } } /// The browser supplied this zone name itself; check only its shape. #[cfg(target_arch = "wasm32")] pub fn valid_zone(time_zone: &str) -> bool { !time_zone.is_empty() && time_zone.len() <= 64 && time_zone .bytes() .all(|b| b.is_ascii_alphanumeric() || b"/_+-".contains(&b)) }