//! Date and time types. use bitflags::bitflags; use core::fmt::{self, Display, Formatter}; /// Date and time representation. #[derive(Debug, Default, Copy, Clone, Eq)] #[repr(C)] pub struct Time { /// Year. Valid range: `1900..=9999`. pub year: u16, /// Month. Valid range: `1..=12`. pub month: u8, /// Day of the month. Valid range: `1..=31`. pub day: u8, /// Hour. Valid range: `0..=23`. pub hour: u8, /// Minute. Valid range: `0..=59`. pub minute: u8, /// Second. Valid range: `0..=59`. pub second: u8, /// Unused padding. pub pad1: u8, /// Nanosececond. Valid range: `0..=999_999_999`. pub nanosecond: u32, /// Offset in minutes from UTC. Valid range: `-1440..=1440`, or /// [`Time::UNSPECIFIED_TIMEZONE`]. pub time_zone: i16, /// Daylight savings time information. pub daylight: Daylight, /// Unused padding. pub pad2: u8, } impl Time { /// Indicates the time should be interpreted as local time. pub const UNSPECIFIED_TIMEZONE: i16 = 0x07ff; /// Create an invalid `Time` with all fields set to zero. #[must_use] pub const fn invalid() -> Self { Self { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, pad1: 0, nanosecond: 0, time_zone: 0, daylight: Daylight::empty(), pad2: 0, } } /// True if all fields are within valid ranges, false otherwise. #[must_use] pub fn is_valid(&self) -> bool { (1900..=9999).contains(&self.year) && (1..=12).contains(&self.month) && (1..=31).contains(&self.day) && self.hour <= 23 && self.minute <= 59 && self.second <= 59 && self.nanosecond <= 999_999_999 && ((-1440..=1440).contains(&self.time_zone) || self.time_zone == Self::UNSPECIFIED_TIMEZONE) } } impl Display for Time { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:04}-{:02}-{:02} ", self.year, self.month, self.day)?; write!( f, "{:02}:{:02}:{:02}.{:09}", self.hour, self.minute, self.second, self.nanosecond )?; if self.time_zone == Self::UNSPECIFIED_TIMEZONE { write!(f, " (local)")?; } else { let offset_in_hours = self.time_zone as f32 / 60.0; let integer_part = offset_in_hours as i16; // We can't use "offset_in_hours.fract()" because it is part of `std`. let fraction_part = offset_in_hours - (integer_part as f32); // most time zones if fraction_part == 0.0 { write!(f, "UTC+{offset_in_hours}")?; } // time zones with 30min offset (and perhaps other special time zones) else { write!(f, "UTC+{offset_in_hours:.1}")?; } } Ok(()) } } /// The padding fields of `Time` are ignored for comparison. impl PartialEq for Time { fn eq(&self, other: &Self) -> bool { self.year == other.year && self.month == other.month && self.day == other.day && self.hour == other.hour && self.minute == other.minute && self.second == other.second && self.nanosecond == other.nanosecond && self.time_zone == other.time_zone && self.daylight == other.daylight } } bitflags! { /// A bitmask containing daylight savings time information. #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct Daylight: u8 { /// Time is affected by daylight savings time. const ADJUST_DAYLIGHT = 0x01; /// Time has been adjusted for daylight savings time. const IN_DAYLIGHT = 0x02; } } #[cfg(test)] mod tests { extern crate alloc; use super::*; use alloc::string::ToString; #[test] fn test_time_display() { let mut time = Time { year: 2023, month: 5, day: 18, hour: 11, minute: 29, second: 57, nanosecond: 123_456_789, time_zone: Time::UNSPECIFIED_TIMEZONE, daylight: Daylight::empty(), pad1: 0, pad2: 0, }; assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789 (local)"); time.time_zone = 120; assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789UTC+2"); time.time_zone = 150; assert_eq!(time.to_string(), "2023-05-18 11:29:57.123456789UTC+2.5"); } }