1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
//! 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");
}
}