bp3d_os/time/
mod.rs

1// Copyright (c) 2025, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! OS local date/time extensions for time-rs.
30
31#[cfg(unix)]
32mod unix;
33
34mod instant;
35#[cfg(windows)]
36mod windows;
37
38use std::time::Duration;
39#[cfg(unix)]
40use unix as _impl;
41
42#[cfg(windows)]
43use windows as _impl;
44
45use time::{Month, OffsetDateTime, UtcOffset};
46
47mod sealed {
48    use std::time::Duration;
49    use time::{Month, OffsetDateTime, UtcOffset};
50
51    pub trait SealUO {}
52    pub trait SealODT {}
53    pub trait SealM {}
54
55    pub trait SealD {}
56
57    impl SealUO for UtcOffset {}
58    impl SealODT for OffsetDateTime {}
59    impl SealM for Month {}
60
61    impl SealD for Duration {}
62}
63
64/// Extension trait for constructing a [Month](Month) from an index.
65pub trait MonthExt: sealed::SealM {
66    /// Constructs a month from its index. Returns None if the index is unknown.
67    ///
68    /// # Arguments
69    ///
70    /// * `index`: the month index between 1 and 12.
71    ///
72    fn from_index(index: u8) -> Option<Month>;
73}
74
75impl MonthExt for Month {
76    fn from_index(index: u8) -> Option<Month> {
77        match index {
78            1 => Some(Month::January),
79            2 => Some(Month::February),
80            3 => Some(Month::March),
81            4 => Some(Month::April),
82            5 => Some(Month::May),
83            6 => Some(Month::June),
84            7 => Some(Month::July),
85            8 => Some(Month::August),
86            9 => Some(Month::September),
87            10 => Some(Month::October),
88            11 => Some(Month::November),
89            12 => Some(Month::December),
90            _ => None,
91        }
92    }
93}
94
95/// Extension trait for a proper current_local_offset over [UtcOffset](UtcOffset).
96pub trait LocalUtcOffset: sealed::SealUO {
97    /// Attempts to obtain the system’s current UTC offset. If the offset cannot be determined, None is returned.
98    ///
99    /// # Platform specific behavior
100    ///
101    /// - On unix, this reads and decodes the /etc/localtime file.
102    /// - On windows, this calls [GetTimeZoneInformation](https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-gettimezoneinformation) and reads the **Bias** field of the structure.
103    fn current_local_offset() -> Option<UtcOffset>;
104
105    /// Attempts to obtain the system’s UTC offset for the given UTC [OffsetDateTime](OffsetDateTime). If the offset cannot be determined, None is returned.
106    ///
107    /// This searches for a matching offset in UTC time for the given input datetime.
108    ///
109    /// # Platform specific behavior
110    ///
111    /// - On unix, this reads and decodes the /etc/localtime file.
112    /// - On windows, this calls [GetTimeZoneInformation](https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-gettimezoneinformation) and reads the **Bias** field of the structure.
113    fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset>;
114}
115
116/// Extension trait for a proper now_local over [OffsetDateTime](OffsetDateTime).
117pub trait LocalOffsetDateTime: sealed::SealODT {
118    /// Attempts to create a new OffsetDateTime with the current date and time in the local offset. If the offset cannot be determined, None is returned.
119    ///
120    /// # Platform specific behavior
121    ///
122    /// - On unix, this reads and decodes the /etc/localtime file.
123    /// - On windows, this calls [GetTimeZoneInformation](https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-gettimezoneinformation) and reads the **Bias** field of the structure.
124    fn now_local() -> Option<OffsetDateTime>;
125}
126
127impl LocalUtcOffset for UtcOffset {
128    #[inline]
129    fn current_local_offset() -> Option<UtcOffset> {
130        _impl::local_offset_at(&OffsetDateTime::now_utc())
131    }
132
133    #[inline]
134    fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
135        _impl::local_offset_at(&datetime)
136    }
137}
138
139impl LocalOffsetDateTime for OffsetDateTime {
140    fn now_local() -> Option<OffsetDateTime> {
141        let tm = OffsetDateTime::now_utc();
142        let offset = _impl::local_offset_at(&tm)?;
143        Some(tm.to_offset(offset))
144    }
145}
146
147/// This trait is a hack because Rust decided to reject new_unchecked on Duration.
148pub trait DurationNewUnchecked: sealed::SealD {
149    /// Unsafely construct a [Duration] object.
150    ///
151    /// # Arguments
152    ///
153    /// * `secs`: the seconds part.
154    /// * `subsec_nanos`: the sub-nanoseconds part
155    ///
156    /// returns: Duration
157    ///
158    /// # Safety
159    ///
160    /// This is insta-UB if subsec_nanos is >= 1000000000.
161    unsafe fn new_unchecked(secs: u64, subsec_nanos: u32) -> Duration;
162}
163
164impl DurationNewUnchecked for Duration {
165    unsafe fn new_unchecked(secs: u64, subsec_nanos: u32) -> Duration {
166        const NANOS_PER_SEC: u32 = 1000000000;
167        if subsec_nanos >= NANOS_PER_SEC {
168            unsafe { std::hint::unreachable_unchecked() }
169        }
170        Duration::new(secs, subsec_nanos)
171    }
172}
173
174/// This is a replacement of [Instant](std::time::Instant) for real-time systems
175///
176/// # Platform specific behavior
177///
178/// - On all unixes (including macOS), this uses `clock_gettime` with CLOCK_MONOTONIC_RAW
179///   instead of `CLOCK_MONOTONIC` which Rust decided not to do and leave broken (see
180///   https://github.com/rust-lang/rust/issues/77807).
181/// - On windows, this falls back to [Instant](std::time::Instant) which uses the WinAPI `QueryPerformanceCounter`.
182#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
183#[repr(transparent)]
184pub struct Instant(instant::Instant);
185
186impl Instant {
187    /// Creates a new [Instant] to measure performance or time in real-time systems. This instant is
188    /// monotonic and guaranteed to not be skewed by NTP adjustments.
189    #[inline(always)]
190    pub fn now() -> Self {
191        Self(instant::Instant::now())
192    }
193
194    /// Measure the time elapsed since this [Instant] was created.
195    #[inline(always)]
196    pub fn elapsed(&self) -> Duration {
197        self.0.elapsed()
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use time::{OffsetDateTime, UtcOffset};
204
205    use crate::time::{Instant, LocalUtcOffset};
206
207    use super::LocalOffsetDateTime;
208
209    #[test]
210    fn current_offset() {
211        let offset = UtcOffset::current_local_offset();
212        println!("Offset: {:?}", offset)
213    }
214
215    #[test]
216    fn now_local() {
217        let date = OffsetDateTime::now_local();
218        println!("Date: {:?}", date)
219    }
220
221    #[test]
222    fn instant() {
223        let time = Instant::now();
224        //nanosleep is awfully broken...
225        std::thread::sleep(std::time::Duration::from_millis(8));
226        let elapsed = time.elapsed();
227        println!("{:?}", elapsed);
228        assert!(elapsed >= std::time::Duration::from_millis(8));
229    }
230}