From 3356a2275f57b5fe775ef5a2dcbf14e150210ff9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 17 Apr 2023 20:20:47 +0200 Subject: [PATCH] Share Windows and Unix DST implementation --- src/offset/local/mod.rs | 97 ++++++++++++++++++++++++ src/offset/local/tz_info/rule.rs | 122 ++++--------------------------- src/offset/local/windows.rs | 96 +----------------------- 3 files changed, 114 insertions(+), 201 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index c40ed17d70..782f9fc2cf 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -3,6 +3,9 @@ //! The local (system) time zone. +use std::cmp; +use std::cmp::Ordering; + #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -186,6 +189,100 @@ impl TimeZone for Local { } } +pub(crate) struct TzInfo { + std_offset: FixedOffset, + dst_offset: FixedOffset, + std_transition: Option, + dst_transition: Option, +} + +impl TzInfo { + fn lookup_with_dst_transitions(&self, dt: &NaiveDateTime) -> LocalResult { + let std_offset = self.std_offset; + let dst_offset = self.dst_offset; + let std_transition_after = self.std_transition.unwrap() + std_offset - dst_offset; + let dst_transition_after = self.dst_transition.unwrap() + dst_offset - std_offset; + + // Depending on the dst and std offsets, *_transition_after can have a local time that is + // before or after *_transition. To remain sane we define *_min and *_max values that have + // the times in order. + let std_transition_min = + cmp::min(self.std_transition.as_ref().unwrap(), &std_transition_after); + let std_transition_max = + cmp::max(self.std_transition.as_ref().unwrap(), &std_transition_after); + let dst_transition_min = + cmp::min(self.dst_transition.as_ref().unwrap(), &dst_transition_after); + let dst_transition_max = + cmp::max(self.dst_transition.as_ref().unwrap(), &dst_transition_after); + + match std_offset.local_minus_utc().cmp(&dst_offset.local_minus_utc()) { + Ordering::Equal => LocalResult::Single(std_offset), + Ordering::Less => { + if dst_transition_min < std_transition_min { + // Northern hemisphere DST. + // - The transition to DST happens at an earlier date than that to STD. + // - At DST start the local time is adjusted forwards (creating a gap), at DST + // end the local time is adjusted backwards (creating ambiguous datetimes). + if dt > dst_transition_min && dt < dst_transition_max { + LocalResult::None + } else if dt >= dst_transition_max && dt < std_transition_min { + LocalResult::Single(dst_offset) + } else if dt >= std_transition_min && dt <= std_transition_max { + LocalResult::Ambiguous(dst_offset, std_offset) + } else { + LocalResult::Single(std_offset) + } + } else { + // Southern hemisphere DST. + // - The transition to STD happens at a earlier date than that to DST. + // - At DST start the local time is adjusted forwards (creating a gap), at DST + // end the local time is adjusted backwards (creating ambiguous datetimes). + if dt >= std_transition_min && dt <= std_transition_max { + LocalResult::Ambiguous(dst_offset, std_offset) + } else if dt > std_transition_max && dt <= dst_transition_min { + LocalResult::Single(std_offset) + } else if dt > dst_transition_min && dt < dst_transition_max { + LocalResult::None + } else { + LocalResult::Single(dst_offset) + } + } + } + Ordering::Greater => { + if dst_transition_min < std_transition_min { + // Southern hemisphere reverse DST. + // - The transition to DST happens at an earlier date than that to STD. + // - At DST start the local time is adjusted backwards (creating ambiguous + // datetimes), at DST end the local time is adjusted forwards (creating a gap) + if dt >= dst_transition_min && dt <= dst_transition_max { + LocalResult::Ambiguous(std_offset, dst_offset) + } else if dt > dst_transition_max && dt <= std_transition_min { + LocalResult::Single(dst_offset) + } else if dt > std_transition_min && dt < std_transition_max { + LocalResult::None + } else { + LocalResult::Single(std_offset) + } + } else { + // Northern hemisphere reverse DST. + // - The transition to STD happens at a earlier date than that to DST. + // - At DST start the local time is adjusted backwards (creating ambiguous + // datetimes), at DST end the local time is adjusted forwards (creating a gap) + if dt > std_transition_min && dt < std_transition_max { + LocalResult::None + } else if dt >= std_transition_max && dt < dst_transition_min { + LocalResult::Single(std_offset) + } else if dt >= dst_transition_min && dt <= dst_transition_max { + LocalResult::Ambiguous(std_offset, dst_offset) + } else { + LocalResult::Single(dst_offset) + } + } + } + } + } +} + #[cfg(test)] mod tests { use super::Local; diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index a90a3e9384..e43139c2a9 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -6,7 +6,8 @@ use super::{ rem_euclid, Error, CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, DAY_IN_MONTHS_NORMAL_YEAR, SECONDS_PER_DAY, }; -use crate::FixedOffset; +use crate::offset::local::TzInfo; +use crate::{FixedOffset, NaiveDateTime}; /// Transition rule #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -239,113 +240,22 @@ impl AlternateTime { return Err(Error::OutOfRange("out of range date time")); } - let dst_start_transition_start = + let dst_start_transition = self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time); - let dst_start_transition_end = self.dst_start.unix_time(current_year, 0) - + i64::from(self.dst_start_time) - + i64::from(self.dst.ut_offset) - - i64::from(self.std.ut_offset); - - let dst_end_transition_start = + let dst_end_transition = self.dst_end.unix_time(current_year, 0) + i64::from(self.dst_end_time); - let dst_end_transition_end = self.dst_end.unix_time(current_year, 0) - + i64::from(self.dst_end_time) - + i64::from(self.std.ut_offset) - - i64::from(self.dst.ut_offset); - - match self.std.ut_offset.cmp(&self.dst.ut_offset) { - Ordering::Equal => Ok(crate::LocalResult::Single(self.std.offset()?)), - Ordering::Less => { - if self.dst_start.transition_date(current_year).0 - < self.dst_end.transition_date(current_year).0 - { - // northern hemisphere - // For the DST END transition, the `start` happens at a later timestamp than the `end`. - if local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } else if local_time > dst_start_transition_start - && local_time < dst_start_transition_end - { - Ok(crate::LocalResult::None) - } else if local_time >= dst_start_transition_end - && local_time < dst_end_transition_end - { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } else if local_time >= dst_end_transition_end - && local_time <= dst_end_transition_start - { - Ok(crate::LocalResult::Ambiguous(self.std.offset()?, self.dst.offset()?)) - } else { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } - } else { - // southern hemisphere regular DST - // For the DST END transition, the `start` happens at a later timestamp than the `end`. - if local_time < dst_end_transition_end { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } else if local_time >= dst_end_transition_end - && local_time <= dst_end_transition_start - { - Ok(crate::LocalResult::Ambiguous(self.std.offset()?, self.dst.offset()?)) - } else if local_time > dst_end_transition_end - && local_time < dst_start_transition_start - { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } else if local_time >= dst_start_transition_start - && local_time < dst_start_transition_end - { - Ok(crate::LocalResult::None) - } else { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } - } - } - Ordering::Greater => { - if self.dst_start.transition_date(current_year).0 - < self.dst_end.transition_date(current_year).0 - { - // southern hemisphere reverse DST - // For the DST END transition, the `start` happens at a later timestamp than the `end`. - if local_time < dst_start_transition_end { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } else if local_time >= dst_start_transition_end - && local_time <= dst_start_transition_start - { - Ok(crate::LocalResult::Ambiguous(self.dst.offset()?, self.std.offset()?)) - } else if local_time > dst_start_transition_start - && local_time < dst_end_transition_start - { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } else if local_time >= dst_end_transition_start - && local_time < dst_end_transition_end - { - Ok(crate::LocalResult::None) - } else { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } - } else { - // northern hemisphere reverse DST - // For the DST END transition, the `start` happens at a later timestamp than the `end`. - if local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } else if local_time > dst_end_transition_start - && local_time < dst_end_transition_end - { - Ok(crate::LocalResult::None) - } else if local_time >= dst_end_transition_end - && local_time < dst_start_transition_end - { - Ok(crate::LocalResult::Single(self.std.offset()?)) - } else if local_time >= dst_start_transition_end - && local_time <= dst_start_transition_start - { - Ok(crate::LocalResult::Ambiguous(self.dst.offset()?, self.std.offset()?)) - } else { - Ok(crate::LocalResult::Single(self.dst.offset()?)) - } - } - } - } + + let tz_info = TzInfo { + std_offset: self.std.offset()?, + dst_offset: self.dst.offset()?, + std_transition: Some(NaiveDateTime::from_timestamp_opt(dst_end_transition, 0).unwrap()), + dst_transition: Some( + NaiveDateTime::from_timestamp_opt(dst_start_transition, 0).unwrap(), + ), + }; + + let local_datetime = NaiveDateTime::from_timestamp_opt(local_time, 0).unwrap(); + Ok(tz_info.lookup_with_dst_transitions(&local_datetime)) } } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 32f65bdbc7..c8e1de826e 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -8,8 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::cmp; -use std::cmp::Ordering; use std::mem::MaybeUninit; use std::ptr; @@ -18,7 +16,7 @@ use winapi::um::minwinbase::SYSTEMTIME; use winapi::um::sysinfoapi::GetSystemTime; use winapi::um::timezoneapi::{GetTimeZoneInformationForYear, TIME_ZONE_INFORMATION}; -use super::{FixedOffset, Local}; +use super::{FixedOffset, Local, TzInfo}; use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; // We don't use `GetLocalTime` because its results can be ambiguous, while conversion from UTC @@ -50,13 +48,6 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult, - dst_transition: Option, -} - impl TzInfo { fn get_current_for_year(year: i32) -> Option { // The API limits years to 1601..=30827. @@ -119,91 +110,6 @@ impl TzInfo { }; local_result_offset.map(|offset| DateTime::from_utc(*local_time - offset, offset)) } - - fn lookup_with_dst_transitions(&self, dt: &NaiveDateTime) -> LocalResult { - let std_offset = self.std_offset; - let dst_offset = self.dst_offset; - let std_transition_after = self.std_transition.unwrap() + std_offset - dst_offset; - let dst_transition_after = self.dst_transition.unwrap() + dst_offset - std_offset; - - // Depending on the dst and std offsets, *_transition_after can have a local time that is - // before or after *_transition. To remain sane we define *_min and *_max values that have - // the times in order. - let std_transition_min = - cmp::min(self.std_transition.as_ref().unwrap(), &std_transition_after); - let std_transition_max = - cmp::max(self.std_transition.as_ref().unwrap(), &std_transition_after); - let dst_transition_min = - cmp::min(self.dst_transition.as_ref().unwrap(), &dst_transition_after); - let dst_transition_max = - cmp::max(self.dst_transition.as_ref().unwrap(), &dst_transition_after); - - match std_offset.local_minus_utc().cmp(&dst_offset.local_minus_utc()) { - Ordering::Equal => LocalResult::Single(std_offset), - Ordering::Less => { - if dst_transition_min < std_transition_min { - // Northern hemisphere DST. - // - The transition to DST happens at an earlier date than that to STD. - // - At DST start the local time is adjusted forwards (creating a gap), at DST - // end the local time is adjusted backwards (creating ambiguous datetimes). - if dt > dst_transition_min && dt < dst_transition_max { - LocalResult::None - } else if dt >= dst_transition_max && dt < std_transition_min { - LocalResult::Single(dst_offset) - } else if dt >= std_transition_min && dt <= std_transition_max { - LocalResult::Ambiguous(dst_offset, std_offset) - } else { - LocalResult::Single(std_offset) - } - } else { - // Southern hemisphere DST. - // - The transition to STD happens at a earlier date than that to DST. - // - At DST start the local time is adjusted forwards (creating a gap), at DST - // end the local time is adjusted backwards (creating ambiguous datetimes). - if dt >= std_transition_min && dt <= std_transition_max { - LocalResult::Ambiguous(dst_offset, std_offset) - } else if dt > std_transition_max && dt <= dst_transition_min { - LocalResult::Single(std_offset) - } else if dt > dst_transition_min && dt < dst_transition_max { - LocalResult::None - } else { - LocalResult::Single(dst_offset) - } - } - } - Ordering::Greater => { - if dst_transition_min < std_transition_min { - // Southern hemisphere reverse DST. - // - The transition to DST happens at an earlier date than that to STD. - // - At DST start the local time is adjusted backwards (creating ambiguous - // datetimes), at DST end the local time is adjusted forwards (creating a gap) - if dt >= dst_transition_min && dt <= dst_transition_max { - LocalResult::Ambiguous(std_offset, dst_offset) - } else if dt > dst_transition_max && dt <= std_transition_min { - LocalResult::Single(dst_offset) - } else if dt > std_transition_min && dt < std_transition_max { - LocalResult::None - } else { - LocalResult::Single(std_offset) - } - } else { - // Northern hemisphere reverse DST. - // - The transition to STD happens at a earlier date than that to DST. - // - At DST start the local time is adjusted backwards (creating ambiguous - // datetimes), at DST end the local time is adjusted forwards (creating a gap) - if dt > std_transition_min && dt < std_transition_max { - LocalResult::None - } else if dt >= std_transition_max && dt < dst_transition_min { - LocalResult::Single(std_offset) - } else if dt >= dst_transition_min && dt <= dst_transition_max { - LocalResult::Ambiguous(std_offset, dst_offset) - } else { - LocalResult::Single(dst_offset) - } - } - } - } - } } // FIXME: use std::cmp::clamp when MSRV >= 1.50