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