Skip to content

Commit

Permalink
Pass around Offsets in Unix Local implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Apr 17, 2023
1 parent db5d24f commit 1a5edfe
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 84 deletions.
4 changes: 2 additions & 2 deletions src/offset/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl FixedOffset {
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
/// ```
#[must_use]
pub fn east_opt(secs: i32) -> Option<FixedOffset> {
pub const fn east_opt(secs: i32) -> Option<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: secs })
} else {
Expand Down Expand Up @@ -87,7 +87,7 @@ impl FixedOffset {
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
/// ```
#[must_use]
pub fn west_opt(secs: i32) -> Option<FixedOffset> {
pub const fn west_opt(secs: i32) -> Option<FixedOffset> {
if -86_400 < secs && secs < 86_400 {
Some(FixedOffset { local_minus_utc: -secs })
} else {
Expand Down
77 changes: 39 additions & 38 deletions src/offset/local/tz_info/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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;

/// Transition rule
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -80,17 +81,17 @@ impl TransitionRule {
}

/// Find the local time type associated to the transition rule at the specified Unix time in seconds
pub(super) fn find_local_time_type_from_local(
pub(super) fn find_local_offset_from_local(
&self,
local_time: i64,
year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
) -> Result<crate::LocalResult<FixedOffset>, Error> {
match self {
TransitionRule::Fixed(local_time_type) => {
Ok(crate::LocalResult::Single(*local_time_type))
local_time_type.offset().map(crate::LocalResult::Single)
}
TransitionRule::Alternate(alternate_time) => {
alternate_time.find_local_time_type_from_local(local_time, year)
alternate_time.find_local_offset_from_local(local_time, year)
}
}
}
Expand Down Expand Up @@ -228,11 +229,11 @@ impl AlternateTime {
}
}

fn find_local_time_type_from_local(
fn find_local_offset_from_local(
&self,
local_time: i64,
current_year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
) -> Result<crate::LocalResult<FixedOffset>, Error> {
// Check if the current year is valid for the following computations
if !(i32::min_value() + 2 <= current_year && current_year <= i32::max_value() - 2) {
return Err(Error::OutOfRange("out of range date time"));
Expand All @@ -253,49 +254,49 @@ impl AlternateTime {
- i64::from(self.dst.ut_offset);

match self.std.ut_offset.cmp(&self.dst.ut_offset) {
Ordering::Equal => Ok(crate::LocalResult::Single(self.std)),
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))
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))
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, self.dst))
Ok(crate::LocalResult::Ambiguous(self.std.offset()?, self.dst.offset()?))
} else {
Ok(crate::LocalResult::Single(self.std))
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))
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, self.dst))
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))
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))
Ok(crate::LocalResult::Single(self.dst.offset()?))
}
}
}
Expand All @@ -306,41 +307,41 @@ impl AlternateTime {
// 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))
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, self.std))
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))
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))
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))
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))
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, self.std))
Ok(crate::LocalResult::Ambiguous(self.dst.offset()?, self.std.offset()?))
} else {
Ok(crate::LocalResult::Single(self.dst))
Ok(crate::LocalResult::Single(self.dst.offset()?))
}
}
}
Expand Down Expand Up @@ -924,7 +925,7 @@ mod tests {
#[test]
fn test_transition_rule() -> Result<(), Error> {
let transition_rule_fixed = TransitionRule::from(LocalTimeType::new(-36000, false, None)?);
assert_eq!(transition_rule_fixed.find_local_time_type(0)?.offset(), -36000);
assert_eq!(transition_rule_fixed.find_local_time_type(0)?.ut_offset, -36000);

let transition_rule_dst = TransitionRule::from(AlternateTime::new(
LocalTimeType::new(43200, false, Some(b"NZST"))?,
Expand All @@ -935,10 +936,10 @@ mod tests {
7200,
)?);

assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.offset(), 46800);
assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.offset(), 43200);
assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.offset(), 43200);
assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.offset(), 46800);
assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.ut_offset, 46800);
assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.ut_offset, 43200);
assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.ut_offset, 43200);
assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.ut_offset, 46800);

let transition_rule_negative_dst = TransitionRule::from(AlternateTime::new(
LocalTimeType::new(3600, false, Some(b"IST"))?,
Expand All @@ -949,10 +950,10 @@ mod tests {
3600,
)?);

assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.offset(), 0);
assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.offset(), 3600);
assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.offset(), 3600);
assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.offset(), 0);
assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.ut_offset, 0);
assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.ut_offset, 3600);
assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.ut_offset, 3600);
assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.ut_offset, 0);

let transition_rule_negative_time_1 = TransitionRule::from(AlternateTime::new(
LocalTimeType::new(0, false, None)?,
Expand All @@ -978,19 +979,19 @@ mod tests {
)?);

assert_eq!(
transition_rule_negative_time_2.find_local_time_type(954032399)?.offset(),
transition_rule_negative_time_2.find_local_time_type(954032399)?.ut_offset,
-10800
);
assert_eq!(
transition_rule_negative_time_2.find_local_time_type(954032400)?.offset(),
transition_rule_negative_time_2.find_local_time_type(954032400)?.ut_offset,
-7200
);
assert_eq!(
transition_rule_negative_time_2.find_local_time_type(972781199)?.offset(),
transition_rule_negative_time_2.find_local_time_type(972781199)?.ut_offset,
-7200
);
assert_eq!(
transition_rule_negative_time_2.find_local_time_type(972781200)?.offset(),
transition_rule_negative_time_2.find_local_time_type(972781200)?.ut_offset,
-10800
);

Expand All @@ -1003,8 +1004,8 @@ mod tests {
90000,
)?);

assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.offset(), -14400);
assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.offset(), -14400);
assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.ut_offset, -14400);
assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.ut_offset, -14400);

Ok(())
}
Expand Down
57 changes: 36 additions & 21 deletions src/offset/local/tz_info/timezone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{cmp::Ordering, fmt, str};

use super::rule::{AlternateTime, TransitionRule};
use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
use crate::FixedOffset;

/// Time zone
#[derive(Debug, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -118,12 +119,12 @@ impl TimeZone {
}

// should we pass NaiveDateTime all the way through to this fn?
pub(crate) fn find_local_time_type_from_local(
pub(crate) fn find_local_offset_from_local(
&self,
local_time: i64,
year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
self.as_ref().find_local_time_type_from_local(local_time, year)
) -> Result<crate::LocalResult<FixedOffset>, Error> {
self.as_ref().find_local_offset_from_local(local_time, year)
}

/// Returns a reference to the time zone
Expand Down Expand Up @@ -200,11 +201,11 @@ impl<'a> TimeZoneRef<'a> {
}
}

pub(crate) fn find_local_time_type_from_local(
pub(crate) fn find_local_offset_from_local(
&self,
local_time: i64,
year: i32,
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
) -> Result<crate::LocalResult<FixedOffset>, Error> {
// #TODO: this is wrong as we need 'local_time_to_local_leap_time ?
// but ... does the local time even include leap seconds ??
// let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) {
Expand All @@ -225,46 +226,57 @@ impl<'a> TimeZoneRef<'a> {
// the end and start here refers to where the time starts prior to the transition
// and where it ends up after. not the temporal relationship.
let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset);
let transition_start =
transition.unix_leap_time + i64::from(prev.ut_offset);
let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset);

match transition_start.cmp(&transition_end) {
Ordering::Greater => {
// bakwards transition, eg from DST to regular
// this means a given local time could have one of two possible offsets
if local_leap_time < transition_end {
return Ok(crate::LocalResult::Single(prev));
return Ok(crate::LocalResult::Single(prev.offset()?));
} else if local_leap_time >= transition_end
&& local_leap_time <= transition_start
{
if prev.ut_offset < after_ltt.ut_offset {
return Ok(crate::LocalResult::Ambiguous(prev, after_ltt));
return Ok(crate::LocalResult::Ambiguous(
prev.offset()?,
after_ltt.offset()?,
));
} else {
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev));
return Ok(crate::LocalResult::Ambiguous(
after_ltt.offset()?,
prev.offset()?,
));
}
}
}
Ordering::Equal => {
// should this ever happen? presumably we have to handle it anyway.
if local_leap_time < transition_start {
return Ok(crate::LocalResult::Single(prev));
return Ok(crate::LocalResult::Single(prev.offset()?));
} else if local_leap_time == transition_end {
if prev.ut_offset < after_ltt.ut_offset {
return Ok(crate::LocalResult::Ambiguous(prev, after_ltt));
return Ok(crate::LocalResult::Ambiguous(
prev.offset()?,
after_ltt.offset()?,
));
} else {
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev));
return Ok(crate::LocalResult::Ambiguous(
after_ltt.offset()?,
prev.offset()?,
));
}
}
}
Ordering::Less => {
// forwards transition, eg from regular to DST
// this means that times that are skipped are invalid local times
if local_leap_time <= transition_start {
return Ok(crate::LocalResult::Single(prev));
return Ok(crate::LocalResult::Single(prev.offset()?));
} else if local_leap_time < transition_end {
return Ok(crate::LocalResult::None);
} else if local_leap_time == transition_end {
return Ok(crate::LocalResult::Single(after_ltt));
return Ok(crate::LocalResult::Single(after_ltt.offset()?));
}
}
}
Expand All @@ -275,13 +287,13 @@ impl<'a> TimeZoneRef<'a> {
};

if let Some(extra_rule) = self.extra_rule {
match extra_rule.find_local_time_type_from_local(local_time, year) {
Ok(local_time_type) => Ok(local_time_type),
match extra_rule.find_local_offset_from_local(local_time, year) {
Ok(offset) => Ok(offset),
Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
err => err,
}
} else {
Ok(crate::LocalResult::Single(self.local_time_types[0]))
Ok(crate::LocalResult::Single(self.local_time_types[0].offset()?))
}
}

Expand Down Expand Up @@ -572,8 +584,11 @@ impl LocalTimeType {
}

/// Returns offset from UTC in seconds
pub(crate) const fn offset(&self) -> i32 {
self.ut_offset
pub(crate) const fn offset(&self) -> Result<FixedOffset, Error> {
match FixedOffset::east_opt(self.ut_offset) {
Some(offset) => Ok(offset),
None => Err(Error::OutOfRange("offset out of bounds")),
}
}

/// Returns daylight saving time indicator
Expand Down Expand Up @@ -818,7 +833,7 @@ mod tests {
}

let time_zone_utc = TimeZone::from_posix_tz("UTC")?;
assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
assert_eq!(time_zone_utc.find_local_time_type(0)?.ut_offset, 0);
}

assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
Expand Down
Loading

0 comments on commit 1a5edfe

Please sign in to comment.