Skip to main content

ferroui_base/utilities/
time_zone_info.rs

1//! The local time zone hook (a minimal counterpart of .NET `System.TimeZoneInfo`).
2//!
3//! The base library carries no time zone data. The offset of local time from
4//! UTC is supplied per thread by a provider that a platform layer (or a test)
5//! installs; without one, local time is UTC.
6
7use super::DateTime;
8use crate::animation::TimeSpan;
9use std::cell::RefCell;
10use std::rc::Rc;
11
12/// Gives the offset of local time from UTC at a UTC instant.
13pub type LocalUtcOffsetProvider = Rc<dyn Fn(DateTime) -> TimeSpan>;
14
15crate::ferro_thread_local! {
16    // TIMEZONE-SEAM: platform crates install the offset of the system time zone here
17    // (`TimeZoneInfo::set_local_utc_offset_provider`); the default is UTC.
18    static LOCAL_UTC_OFFSET_PROVIDER: RefCell<Option<LocalUtcOffsetProvider>> = const { RefCell::new(None) };
19}
20
21/// Access to the local time zone of the current thread.
22pub struct TimeZoneInfo;
23
24impl TimeZoneInfo {
25    /// The largest offset from UTC, 14 hours (in either direction).
26    pub const MAX_OFFSET: TimeSpan = TimeSpan::from_ticks(14 * TimeSpan::TICKS_PER_HOUR);
27
28    /// Installs (or with `None` removes) the provider of the local UTC offset
29    /// of the current thread and returns the previous one.
30    pub fn set_local_utc_offset_provider(provider: Option<LocalUtcOffsetProvider>) -> Option<LocalUtcOffsetProvider> {
31        LOCAL_UTC_OFFSET_PROVIDER.with(|current| current.replace(provider))
32    }
33
34    /// The offset of local time from UTC at the UTC instant `utc_time`
35    /// (zero when no provider is installed). The offset is truncated to whole
36    /// minutes and limited to 14 hours in either direction.
37    pub fn get_local_utc_offset(utc_time: DateTime) -> TimeSpan {
38        let provider = LOCAL_UTC_OFFSET_PROVIDER.with(|current| current.borrow().clone());
39        match provider {
40            Some(provider) => {
41                let ticks = provider(utc_time).ticks();
42                let ticks = ticks - ticks % TimeSpan::TICKS_PER_MINUTE;
43                TimeSpan::from_ticks(ticks.clamp(-Self::MAX_OFFSET.ticks(), Self::MAX_OFFSET.ticks()))
44            }
45            None => TimeSpan::ZERO,
46        }
47    }
48
49    /// The offset of local time from UTC for the local clock time
50    /// `local_time`. Around a transition of the offset the clock time can be
51    /// ambiguous or invalid; the offset in effect after the transition is
52    /// preferred.
53    pub fn get_utc_offset_of_local_time(local_time: DateTime) -> TimeSpan {
54        let first = Self::get_local_utc_offset(local_time);
55        if first == TimeSpan::ZERO && LOCAL_UTC_OFFSET_PROVIDER.with(|current| current.borrow().is_none()) {
56            return first;
57        }
58        let ticks = (local_time.ticks() - first.ticks()).clamp(DateTime::MIN_VALUE.ticks(), DateTime::MAX_VALUE.ticks());
59        Self::get_local_utc_offset(DateTime::from_ticks(ticks))
60    }
61}