//! System time-zone resolution for recurrence. //! //! GoingsOn stores instants in UTC and has no per-user time-zone preference, so //! "the user's zone" is the operating system's zone. Recurrence advances dates in //! this zone (see `goingson_core::recurrence`) so a fixed local time-of-day, "every //! day at 09:00", survives daylight-saving transitions instead of drifting an hour. use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; use chrono_tz::Tz; /// The user's IANA time zone, resolved from the OS. Falls back to UTC if the OS /// zone can't be read or doesn't parse as a known IANA name. pub fn system_tz() -> Tz { iana_time_zone::get_timezone() .ok() .and_then(|name| name.parse().ok()) .unwrap_or(Tz::UTC) } /// Interpret a civil (wall-clock) datetime as being in the user's system zone /// and convert it to the corresponding UTC instant. /// /// Window queries (weekly/monthly review) work with civil dates like "the start /// of this week"; those midnights are local, not UTC, so stamping them as UTC /// misattributes edge-of-period rows by the zone offset. On a DST spring-forward /// gap the civil time doesn't exist, so fall back to treating it as UTC rather /// than panicking. pub fn local_civil_to_utc(civil: NaiveDateTime) -> DateTime { system_tz() .from_local_datetime(&civil) .earliest() .map_or_else( || DateTime::::from_naive_utc_and_offset(civil, Utc), |dt| dt.with_timezone(&Utc), ) }