tinymist_std/
time.rs

1//! Cross platform time utilities.
2
3pub use std::time::SystemTime as Time;
4pub use time::UtcDateTime;
5
6#[cfg(not(feature = "web"))]
7pub use std::time::{Duration, Instant};
8#[cfg(feature = "web")]
9pub use web_time::{Duration, Instant};
10
11/// Returns the current datetime in utc (UTC+0).
12pub fn utc_now() -> UtcDateTime {
13    now().into()
14}
15
16/// A local datetime and its available timezone information.
17#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub struct LocalDatetime {
19    /// The local wall-clock datetime.
20    pub datetime: time::PrimitiveDateTime,
21    /// The local offset from UTC in whole minutes.
22    ///
23    /// `None` means that the environment does not provide local timezone
24    /// information and `datetime` is in UTC.
25    pub local_offset_minutes: Option<i32>,
26}
27
28impl LocalDatetime {
29    /// Creates a local datetime from calendar and clock components.
30    pub fn from_ymd_hms(
31        year: i32,
32        month: u8,
33        day: u8,
34        hour: u8,
35        minute: u8,
36        second: u8,
37        local_offset_minutes: Option<i32>,
38    ) -> Option<Self> {
39        let date =
40            time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day).ok()?;
41        let time = time::Time::from_hms(hour, minute, second).ok()?;
42        Some(Self {
43            datetime: time::PrimitiveDateTime::new(date, time),
44            local_offset_minutes,
45        })
46    }
47}
48
49/// Returns the current local datetime when the environment provides it.
50///
51/// Environments without the `system` or `web` capability return the existing
52/// UTC epoch fallback without accessing a host clock or timezone database.
53#[cfg(any(feature = "system", feature = "web"))]
54pub fn local_now() -> Option<LocalDatetime> {
55    use chrono::{Datelike, Timelike};
56
57    let now: chrono::DateTime<chrono::Local> = now().into();
58    LocalDatetime::from_ymd_hms(
59        now.year(),
60        now.month().try_into().ok()?,
61        now.day().try_into().ok()?,
62        now.hour().try_into().ok()?,
63        now.minute().try_into().ok()?,
64        now.second().try_into().ok()?,
65        Some(now.offset().local_minus_utc() / 60),
66    )
67}
68
69/// Returns the UTC fallback in environments without host time capabilities.
70#[cfg(not(any(feature = "system", feature = "web")))]
71pub fn local_now() -> Option<LocalDatetime> {
72    let now = utc_now();
73    Some(LocalDatetime {
74        datetime: time::PrimitiveDateTime::new(now.date(), now.time()),
75        local_offset_minutes: None,
76    })
77}
78
79/// Returns the current system time (UTC+0).
80#[cfg(any(feature = "system", feature = "web"))]
81pub fn now() -> Time {
82    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
83    {
84        Time::now()
85    }
86    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
87    {
88        use web_time::web::SystemTimeExt;
89        web_time::SystemTime::now().to_std()
90    }
91}
92
93/// Returns a dummy time on environments that do not support time.
94#[cfg(not(any(feature = "system", feature = "web")))]
95pub fn now() -> Time {
96    Time::UNIX_EPOCH
97}
98
99pub use time::format_description::well_known::Rfc3339;
100
101/// The trait helping convert to a [`UtcDateTime`].
102pub trait ToUtcDateTime {
103    /// Converts to a [`UtcDateTime`].
104    fn to_utc_datetime(self) -> Option<UtcDateTime>;
105}
106
107impl ToUtcDateTime for i64 {
108    /// Converts a UNIX timestamp to a [`UtcDateTime`].
109    fn to_utc_datetime(self) -> Option<UtcDateTime> {
110        UtcDateTime::from_unix_timestamp(self).ok()
111    }
112}
113
114impl ToUtcDateTime for Time {
115    /// Converts a system time to a [`UtcDateTime`].
116    fn to_utc_datetime(self) -> Option<UtcDateTime> {
117        Some(UtcDateTime::from(self))
118    }
119}
120
121/// Converts a [`UtcDateTime`] to typst's datetime.
122#[cfg(feature = "typst")]
123pub fn to_typst_time(timestamp: UtcDateTime) -> typst::foundations::Datetime {
124    let datetime = ::time::PrimitiveDateTime::new(timestamp.date(), timestamp.time());
125    typst::foundations::Datetime::Datetime(datetime)
126}
127
128/// Creates a format description for yyyy-mm-dd.
129pub fn yyyy_mm_dd() -> Vec<::time::format_description::BorrowedFormatItem<'static>> {
130    ::time::format_description::parse_borrowed::<2>("[year]-[month]-[day]").unwrap()
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[cfg(not(any(feature = "system", feature = "web")))]
138    #[test]
139    fn local_now_uses_utc_epoch_fallback() {
140        assert_eq!(
141            local_now(),
142            LocalDatetime::from_ymd_hms(1970, 1, 1, 0, 0, 0, None)
143        );
144    }
145
146    #[test]
147    fn test_yyyy_mm_dd() {
148        let format = yyyy_mm_dd();
149        assert!(!format.is_empty(), "format should not be empty");
150    }
151}