Skip to main content

max / goingson

1.5 KB · 37 lines History Blame Raw
1 //! System time-zone resolution for recurrence.
2 //!
3 //! GoingsOn stores instants in UTC and has no per-user time-zone preference, so
4 //! "the user's zone" is the operating system's zone. Recurrence advances dates in
5 //! this zone (see `goingson_core::recurrence`) so a fixed local time-of-day, "every
6 //! day at 09:00", survives daylight-saving transitions instead of drifting an hour.
7
8 use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
9 use chrono_tz::Tz;
10
11 /// The user's IANA time zone, resolved from the OS. Falls back to UTC if the OS
12 /// zone can't be read or doesn't parse as a known IANA name.
13 pub fn system_tz() -> Tz {
14 iana_time_zone::get_timezone()
15 .ok()
16 .and_then(|name| name.parse().ok())
17 .unwrap_or(Tz::UTC)
18 }
19
20 /// Interpret a civil (wall-clock) datetime as being in the user's system zone
21 /// and convert it to the corresponding UTC instant.
22 ///
23 /// Window queries (weekly/monthly review) work with civil dates like "the start
24 /// of this week"; those midnights are local, not UTC, so stamping them as UTC
25 /// misattributes edge-of-period rows by the zone offset. On a DST spring-forward
26 /// gap the civil time doesn't exist, so fall back to treating it as UTC rather
27 /// than panicking.
28 pub fn local_civil_to_utc(civil: NaiveDateTime) -> DateTime<Utc> {
29 system_tz()
30 .from_local_datetime(&civil)
31 .earliest()
32 .map_or_else(
33 || DateTime::<Utc>::from_naive_utc_and_offset(civil, Utc),
34 |dt| dt.with_timezone(&Utc),
35 )
36 }
37