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