time/util.rs
1//! Utility functions, including updating time zone information.
2
3use core::num::NonZero;
4
5pub(crate) use time_core::util::{days_in_month_leap, range_validated};
6pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year};
7
8use crate::Month;
9
10/// Which direction arithmetic overflow occurred in.
11#[derive(Debug, Clone, Copy)]
12pub(crate) enum Overflow {
13 /// The overflow was positive (i.e. towards positive infinity).
14 Positive,
15 /// The overflow was negative (i.e. towards negative infinity).
16 Negative,
17}
18
19/// Whether to adjust the date, and in which direction. Useful when implementing arithmetic.
20pub(crate) enum DateAdjustment {
21 /// The previous day should be used.
22 Previous,
23 /// The next day should be used.
24 Next,
25 /// The date should be used as-is.
26 None,
27}
28
29/// Get the number of days in the month of a given year.
30///
31/// ```rust
32/// # use time::{Month, util};
33/// assert_eq!(util::days_in_month(Month::February, 2020), 29);
34/// ```
35#[inline]
36pub const fn days_in_month(month: Month, year: i32) -> u8 {
37 time_core::util::days_in_month(month as u8, year)
38}
39
40/// Get the number of days in the month of a given year.
41///
42/// ```rust
43/// # #![expect(deprecated)]
44/// # use time::{Month, util};
45/// assert_eq!(util::days_in_year_month(2020, Month::February), 29);
46/// ```
47#[deprecated(
48 since = "0.3.37",
49 note = "use `days_in_month` or `Month::length` instead"
50)]
51#[inline]
52pub const fn days_in_year_month(year: i32, month: Month) -> u8 {
53 days_in_month(month, year)
54}
55
56/// Given whether a year is a leap year and the ordinal day, return the month and day of the month.
57#[inline]
58pub(crate) const fn leap_ordinal_to_month_day(leap: bool, ordinal: u16) -> (Month, u8) {
59 let ordinal = ordinal as u32;
60 let jan_feb_len = 59 + leap as u32;
61
62 let (month_adj, ordinal_adj) = if ordinal <= jan_feb_len {
63 (0, 0)
64 } else {
65 (2, jan_feb_len)
66 };
67
68 let ordinal = ordinal - ordinal_adj;
69 let month = (ordinal * 268 + 8031) >> 13;
70 let days_in_preceding_months = (month * 3917 - 3866) >> 7;
71 let day = ordinal - days_in_preceding_months;
72 let month = month + month_adj;
73
74 (
75 // Safety: `month` is guaranteed to be between 1 and 12 inclusive.
76 unsafe {
77 match Month::from_number(NonZero::new_unchecked(month as u8)) {
78 Ok(month) => month,
79 Err(_) => core::hint::unreachable_unchecked(),
80 }
81 },
82 day as u8,
83 )
84}
85
86/// Update time zone information from the system.
87///
88/// For a version of this function that is guaranteed to be sound, see [`refresh_tz`].
89///
90/// # Safety
91///
92/// This is a system call with specific requirements. The following is from POSIX's description of
93/// `tzset`:
94///
95/// > If a thread accesses `tzname`, `daylight`, or `timezone` directly while another thread is in a
96/// > call to `tzset()`, or to any function that is required or allowed to set timezone information
97/// > as if by calling `tzset()`, the behavior is undefined.
98///
99/// Effectively, this translates to the requirement that at least one of the following must be true:
100///
101/// - The operating system provides a thread-safe environment.
102/// - The process is single-threaded.
103/// - The process is multi-threaded **and** no other thread is mutating the environment in any way
104/// at the same time a call to a method that obtains the local UTC offset. This includes adding,
105/// removing, or modifying an environment variable.
106///
107/// ## Soundness is global
108///
109/// You must not only verify this safety conditions for your code, but for **all** code that will be
110/// included in the final binary. Notably, it applies to both direct and transitive dependencies and
111/// to both Rust and non-Rust code. **For this reason it is not possible for a library to soundly
112/// call this method.**
113///
114/// ## Forward compatibility
115///
116/// This currently only does anything on Unix-like systems. On other systems, it is a no-op. This
117/// may change in the future if necessary, expanding the safety requirements. It is expected that,
118/// at a minimum, calling this method when the process is single-threaded will remain sound.
119#[cfg(feature = "local-offset")]
120#[inline]
121pub unsafe fn refresh_tz_unchecked() {
122 // Safety: The caller must uphold the safety requirements.
123 unsafe { crate::sys::refresh_tz_unchecked() };
124}
125
126/// Attempt to update time zone information from the system.
127///
128/// Returns `None` if the call is not known to be sound.
129#[cfg(feature = "local-offset")]
130#[inline]
131pub fn refresh_tz() -> Option<()> {
132 crate::sys::refresh_tz()
133}
134
135#[doc(hidden)]
136#[cfg(feature = "local-offset")]
137#[expect(
138 clippy::missing_const_for_fn,
139 reason = "no longer used; original implementation was not const"
140)]
141#[deprecated(since = "0.3.37", note = "no longer needed; TZ is refreshed manually")]
142pub mod local_offset {
143 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
144 pub enum Soundness {
145 Sound,
146 Unsound,
147 }
148
149 #[inline]
150 pub unsafe fn set_soundness(_: Soundness) {}
151
152 #[inline]
153 pub fn get_soundness() -> Soundness {
154 Soundness::Sound
155 }
156}