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}