use crate::{DateTime, NaiveDateTime}; use core::convert::Infallible; use core::fmt::Display; /// A type that can be used to represent a TimeZone pub trait TimeZone: Sized + Eq + Display { /// The error to return in case of a failure to convert the local time to UTC type Err; /// Given the time in the UTC timezone, determine the UtcOffset fn utc_offset(&self, date_time: DateTime) -> UtcOffset; /// Given the local date and time, figure out the offset from UTC fn offset_from_local_time(&self, date_time: NaiveDateTime) -> Result; } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] /// The UTC time zone pub struct Utc; impl TimeZone for Utc { type Err = Infallible; fn utc_offset(&self, _: DateTime) -> UtcOffset { UtcOffset::UTC } fn offset_from_local_time(&self, _: NaiveDateTime) -> Result { Ok(UtcOffset::UTC) } } impl Display for Utc { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "UTC") } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] /// A timezone with a fixed offset from UTC pub struct UtcOffset { offset_seconds: isize, } impl UtcOffset { /// The UTC Timezone, represented as an offset pub const UTC: Self = Self { offset_seconds: 0 }; // TODO validation /// Makes a new `UtcOffset` timezone with the given timezone difference. /// A positive number is the Eastern hemisphere. A negative number is the /// Western hemisphere. pub const fn from_seconds(seconds: isize) -> Self { Self { offset_seconds: seconds, } } /// Makes a new `UtcOffset` timezone with the given timezone difference. /// A positive number is the Eastern hemisphere. A negative number is the /// Western hemisphere. pub const fn from_hours(hours: isize) -> Self { Self::from_seconds(hours * 3600) } /// The number of hours this timezone is ahead of UTC. THis number is /// negative if the timezone is in the Western hemisphere pub fn hours_ahead(self) -> f32 { self.offset_seconds as f32 / 3600.0 } /// The number of seconds this timezone is ahead of UTC. This number is /// negative if the timezone is in the Western hemisphere pub const fn seconds_ahead(self) -> isize { self.offset_seconds } } impl Display for UtcOffset { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let hours = self.offset_seconds / 3600; let minutes = ((self.offset_seconds % 3600) / 60).abs(); let seconds = (self.offset_seconds % 60).abs(); let sign = if self.offset_seconds.is_negative() { '-' } else { '+' }; if self.offset_seconds == 0 { write!(f, "UTC") } else if self.offset_seconds % 3600 == 0 { write!(f, "UTC{:+}", hours) } else if self.offset_seconds % 60 == 0 { write!(f, "UTC{}{:02}:{:02}", sign, hours.abs(), minutes) } else { write!( f, "UTC{}{:02}:{:02}:{:02}", sign, hours.abs(), minutes, seconds ) } } } impl TimeZone for UtcOffset { type Err = Infallible; fn utc_offset(&self, _: DateTime) -> UtcOffset { *self } fn offset_from_local_time(&self, _: NaiveDateTime) -> Result { Ok(*self) } } #[cfg(test)] mod tests { use super::*; #[test] fn utc_offset_display_no_offset() { let offset = UtcOffset::UTC; let offset_str = offset.to_string(); assert_eq!(offset_str, "UTC") } #[test] fn utc_offset_display_positive_offset() { let offset = UtcOffset::from_hours(1); let offset_str = offset.to_string(); assert_eq!(offset_str, "UTC+1") } #[test] fn utc_offset_display_minute_offset() { let offset = UtcOffset::from_seconds(60); let offset_str = offset.to_string(); assert_eq!(offset_str, "UTC+00:01") } #[test] fn utc_offset_display_second_offset() { let offset = UtcOffset::from_seconds(-32); let offset_str = offset.to_string(); assert_eq!(offset_str, "UTC-00:00:32") } }