Skip to main content

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}