uefi_raw/
time.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Date and time types.
4
5use bitflags::bitflags;
6use core::fmt::{self, Display, Formatter};
7
8/// Date and time representation.
9#[derive(Debug, Default, Copy, Clone, Eq)]
10#[repr(C)]
11pub struct Time {
12    /// Year. Valid range: `1900..=9999`.
13    pub year: u16,
14
15    /// Month. Valid range: `1..=12`.
16    pub month: u8,
17
18    /// Day of the month. Valid range: `1..=31`.
19    pub day: u8,
20
21    /// Hour. Valid range: `0..=23`.
22    pub hour: u8,
23
24    /// Minute. Valid range: `0..=59`.
25    pub minute: u8,
26
27    /// Second. Valid range: `0..=59`.
28    pub second: u8,
29
30    /// Unused padding.
31    pub pad1: u8,
32
33    /// Nanosececond. Valid range: `0..=999_999_999`.
34    pub nanosecond: u32,
35
36    /// Offset in minutes from UTC. Valid range: `-1440..=1440`, or
37    /// [`Time::UNSPECIFIED_TIMEZONE`].
38    pub time_zone: i16,
39
40    /// Daylight savings time information.
41    pub daylight: Daylight,
42
43    /// Unused padding.
44    pub pad2: u8,
45}
46
47impl Time {
48    /// Indicates the time should be interpreted as local time.
49    pub const UNSPECIFIED_TIMEZONE: i16 = 0x07ff;
50
51    /// Create an invalid `Time` with all fields set to zero.
52    #[must_use]
53    pub const fn invalid() -> Self {
54        Self {
55            year: 0,
56            month: 0,
57            day: 0,
58            hour: 0,
59            minute: 0,
60            second: 0,
61            pad1: 0,
62            nanosecond: 0,
63            time_zone: 0,
64            daylight: Daylight::empty(),
65            pad2: 0,
66        }
67    }
68
69    /// True if all fields are within valid ranges, false otherwise.
70    #[must_use]
71    pub fn is_valid(&self) -> bool {
72        (1900..=9999).contains(&self.year)
73            && (1..=12).contains(&self.month)
74            && (1..=31).contains(&self.day)
75            && self.hour <= 23
76            && self.minute <= 59
77            && self.second <= 59
78            && self.nanosecond <= 999_999_999
79            && ((-1440..=1440).contains(&self.time_zone)
80                || self.time_zone == Self::UNSPECIFIED_TIMEZONE)
81    }
82}
83
84impl Display for Time {
85    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86        write!(f, "{:04}-{:02}-{:02} ", self.year, self.month, self.day)?;
87        write!(
88            f,
89            "{:02}:{:02}:{:02}.{:09}",
90            self.hour, self.minute, self.second, self.nanosecond
91        )?;
92
93        if self.time_zone == Self::UNSPECIFIED_TIMEZONE {
94            write!(f, " (local)")?;
95        } else {
96            let offset_in_hours = self.time_zone as f32 / 60.0;
97            let integer_part = offset_in_hours as i16;
98            // We can't use "offset_in_hours.fract()" because it is part of `std`.
99            let fraction_part = offset_in_hours - (integer_part as f32);
100            // most time zones
101            if fraction_part == 0.0 {
102                write!(f, "UTC+{offset_in_hours}")?;
103            }
104            // time zones with 30min offset (and perhaps other special time zones)
105            else {
106                write!(f, "UTC+{offset_in_hours:.1}")?;
107            }
108        }
109
110        Ok(())
111    }
112}
113
114/// The padding fields of `Time` are ignored for comparison.
115impl PartialEq for Time {
116    fn eq(&self, other: &Self) -> bool {
117        self.year == other.year
118            && self.month == other.month
119            && self.day == other.day
120            && self.hour == other.hour
121            && self.minute == other.minute
122            && self.second == other.second
123            && self.nanosecond == other.nanosecond
124            && self.time_zone == other.time_zone
125            && self.daylight == other.daylight
126    }
127}
128
129bitflags! {
130    /// A bitmask containing daylight savings time information.
131    #[repr(transparent)]
132    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
133    pub struct Daylight: u8 {
134        /// Time is affected by daylight savings time.
135        const ADJUST_DAYLIGHT = 0x01;
136
137        /// Time has been adjusted for daylight savings time.
138        const IN_DAYLIGHT = 0x02;
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    extern crate alloc;
145
146    use super::*;
147    use alloc::string::ToString;
148
149    #[test]
150    fn test_time_display() {
151        let mut time = Time {
152            year: 2023,
153            month: 5,
154            day: 18,
155            hour: 11,
156            minute: 29,
157            second: 57,
158            nanosecond: 123_456_789,
159            time_zone: Time::UNSPECIFIED_TIMEZONE,
160            daylight: Daylight::empty(),
161            pad1: 0,
162            pad2: 0,
163        };
164        assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789 (local)");
165
166        time.time_zone = 120;
167        assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789UTC+2");
168
169        time.time_zone = 150;
170        assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789UTC+2.5");
171    }
172}