Skip to content

Commit

Permalink
Share Windows and Unix DST implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Apr 17, 2023
1 parent 1a5edfe commit 3356a22
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 201 deletions.
97 changes: 97 additions & 0 deletions src/offset/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

//! The local (system) time zone.

use std::cmp;
use std::cmp::Ordering;

#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};

Expand Down Expand Up @@ -186,6 +189,100 @@ impl TimeZone for Local {
}
}

pub(crate) struct TzInfo {
std_offset: FixedOffset,
dst_offset: FixedOffset,
std_transition: Option<NaiveDateTime>,
dst_transition: Option<NaiveDateTime>,
}

impl TzInfo {
fn lookup_with_dst_transitions(&self, dt: &NaiveDateTime) -> LocalResult<FixedOffset> {
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;
Expand Down
122 changes: 16 additions & 106 deletions src/offset/local/tz_info/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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))
}
}

Expand Down
96 changes: 1 addition & 95 deletions src/offset/local/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -50,13 +48,6 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<Date
// - There are either zero or two DST transitions.
// - As of Vista(?) only years from 2004 until a few years into the future are supported.
// - All other years get the base settings, which seem to be that of the current year.
struct TzInfo {
std_offset: FixedOffset,
dst_offset: FixedOffset,
std_transition: Option<NaiveDateTime>,
dst_transition: Option<NaiveDateTime>,
}

impl TzInfo {
fn get_current_for_year(year: i32) -> Option<Self> {
// The API limits years to 1601..=30827.
Expand Down Expand Up @@ -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<FixedOffset> {
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
Expand Down

0 comments on commit 3356a22

Please sign in to comment.