From 1aeaaed3435d912475069c1e80cab035c5a8c760 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 9 Mar 2024 13:01:44 +0100 Subject: [PATCH 1/2] Rename `LocalResult` to `MappedLocalTime`, add alias --- README.md | 2 +- src/datetime/rustc_serialize.rs | 12 +- src/datetime/serde.rs | 10 +- src/datetime/tests.rs | 14 +-- src/format/parsed.rs | 14 +-- src/lib.rs | 12 +- src/naive/datetime/mod.rs | 4 +- src/naive/datetime/tests.rs | 6 +- src/offset/fixed.rs | 10 +- src/offset/local/mod.rs | 164 ++++++++++++++------------- src/offset/local/tz_info/rule.rs | 48 ++++---- src/offset/local/tz_info/timezone.rs | 26 ++--- src/offset/local/unix.rs | 16 +-- src/offset/local/windows.rs | 16 +-- src/offset/mod.rs | 153 +++++++++++++------------ src/offset/utc.rs | 10 +- tests/dateutils.rs | 8 +- 17 files changed, 267 insertions(+), 258 deletions(-) diff --git a/README.md b/README.md index 3e4266fb77..bfe17fcbf9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Chrono aims to provide all functionality needed to do correct operations on date * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware by default, with separate timezone-naive types. * Operations that may produce an invalid or ambiguous date and time return `Option` or - [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). + [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). * Configurable parsing and formatting with an `strftime` inspired date and time formatting syntax. * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with the current timezone of the OS. diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index f704b2f97b..5236a52fad 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -2,7 +2,7 @@ use super::DateTime; use crate::format::SecondsFormat; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; +use crate::offset::{FixedOffset, MappedLocalTime, TimeZone, Utc}; use core::fmt; use core::ops::Deref; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -13,16 +13,16 @@ impl Encodable for DateTime { } } -// lik? function to convert a LocalResult into a serde-ish Result -fn from(me: LocalResult, d: &mut D) -> Result +// Function to convert a MappedLocalTime into a serde-ish Result +fn from(me: MappedLocalTime, d: &mut D) -> Result where D: Decoder, T: fmt::Display, { match me { - LocalResult::None => Err(d.error("value is not a legal timestamp")), - LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - LocalResult::Single(val) => Ok(val), + MappedLocalTime::None => Err(d.error("value is not a legal timestamp")), + MappedLocalTime::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), + MappedLocalTime::Single(val) => Ok(val), } } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 4ef9f95515..5e66f5786e 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1251,7 +1251,7 @@ mod tests { #[test] fn test_serde_no_offset_debug() { - use crate::{LocalResult, NaiveDate, NaiveDateTime, Offset}; + use crate::{MappedLocalTime, NaiveDate, NaiveDateTime, Offset}; use core::fmt::Debug; #[derive(Clone)] @@ -1266,14 +1266,14 @@ mod tests { fn from_offset(_state: &TestTimeZone) -> TestTimeZone { TestTimeZone } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(TestTimeZone) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) } fn offset_from_local_datetime( &self, _local: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(TestTimeZone) + ) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { TestTimeZone diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2309af11a7..126e1d34d1 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -3,7 +3,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; +use crate::{Datelike, Days, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; #[derive(Clone)] struct DstTester; @@ -31,14 +31,14 @@ impl TimeZone for DstTester { DstTester } - fn offset_from_local_date(&self, _: &NaiveDate) -> crate::LocalResult { + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { unimplemented!() } fn offset_from_local_datetime( &self, local: &NaiveDateTime, - ) -> crate::LocalResult { + ) -> crate::MappedLocalTime { let local_to_winter_transition_start = NaiveDate::from_ymd_opt( local.year(), DstTester::TO_WINTER_MONTH_DAY.0, @@ -72,19 +72,19 @@ impl TimeZone for DstTester { .and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap()); if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { - LocalResult::Single(DstTester::summer_offset()) + MappedLocalTime::Single(DstTester::summer_offset()) } else if *local >= local_to_winter_transition_start && *local < local_to_summer_transition_start { - LocalResult::Single(DstTester::winter_offset()) + MappedLocalTime::Single(DstTester::winter_offset()) } else if *local >= local_to_winter_transition_end && *local < local_to_winter_transition_start { - LocalResult::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) + MappedLocalTime::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) } else if *local >= local_to_summer_transition_start && *local < local_to_summer_transition_end { - LocalResult::None + MappedLocalTime::None } else { panic!("Unexpected local time {}", local) } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 6f7253de4c..4f74b85224 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -6,7 +6,7 @@ use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; /// A type to hold parsed fields of date and time that can check all fields are consistent. @@ -888,9 +888,9 @@ impl Parsed { let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; match offset.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => Ok(t), - LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => Ok(t), + MappedLocalTime::Ambiguous(..) => Err(NOT_ENOUGH), } } @@ -944,15 +944,15 @@ impl Parsed { // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; match tz.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => { + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => { if check_offset(&t) { Ok(t) } else { Err(IMPOSSIBLE) } } - LocalResult::Ambiguous(min, max) => { + MappedLocalTime::Ambiguous(min, max) => { // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), diff --git a/src/lib.rs b/src/lib.rs index 917b39691b..9fe35437e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware //! by default, with separate timezone-naive types. //! * Operations that may produce an invalid or ambiguous date and time return `Option` or -//! [`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html). +//! [`MappedLocalTime`](https://docs.rs/chrono/latest/chrono/offset/enum.MappedLocalTime.html). //! * Configurable parsing and formatting with a `strftime` inspired date and time formatting syntax. //! * The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with //! the current timezone of the OS. @@ -130,7 +130,7 @@ //! #![cfg_attr(not(feature = "now"), doc = "```ignore")] #![cfg_attr(feature = "now", doc = "```rust")] -//! use chrono::offset::LocalResult; +//! use chrono::offset::MappedLocalTime; //! use chrono::prelude::*; //! //! # fn doctest() -> Option<()> { @@ -174,12 +174,12 @@ //! // dynamic verification //! assert_eq!( //! Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), -//! LocalResult::Single( +//! MappedLocalTime::Single( //! NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc() //! ) //! ); -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), MappedLocalTime::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), MappedLocalTime::None); //! //! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. @@ -590,9 +590,9 @@ pub mod offset; #[cfg(feature = "clock")] #[doc(inline)] pub use offset::Local; -pub use offset::LocalResult; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; +pub use offset::{LocalResult, MappedLocalTime}; pub mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 5395e396ed..d24633542d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -21,7 +21,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; use crate::time_delta::NANOS_PER_SEC; use crate::{ - expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone, + expect, try_opt, DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, Weekday, }; #[cfg(feature = "rustc-serialize")] @@ -929,7 +929,7 @@ impl NaiveDateTime { /// assert_eq!(dt.timezone(), tz); /// ``` #[must_use] - pub fn and_local_timezone(&self, tz: Tz) -> LocalResult> { + pub fn and_local_timezone(&self, tz: Tz) -> MappedLocalTime> { tz.from_local_datetime(self) } diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 27e884c036..1b5a84133d 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; #[test] fn test_datetime_add() { @@ -385,13 +385,13 @@ fn test_and_timezone_min_max_dates() { if offset_hour >= 0 { assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); } else { - assert_eq!(local_max, LocalResult::None); + assert_eq!(local_max, MappedLocalTime::None); } let local_min = NaiveDateTime::MIN.and_local_timezone(offset); if offset_hour <= 0 { assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); } else { - assert_eq!(local_min, LocalResult::None); + assert_eq!(local_min, MappedLocalTime::None); } } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 745dad29a1..24cd983766 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -9,7 +9,7 @@ use core::str::FromStr; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use super::{LocalResult, Offset, TimeZone}; +use super::{MappedLocalTime, Offset, TimeZone}; use crate::format::{scan, ParseError, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime}; @@ -129,11 +129,11 @@ impl TimeZone for FixedOffset { *offset } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(*self) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(*self) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 3b71a9a0ab..611fe18d8d 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -10,7 +10,7 @@ use std::cmp::Ordering; use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; -use super::{LocalResult, TimeZone}; +use super::{MappedLocalTime, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::Date; @@ -38,16 +38,18 @@ mod win_bindings; )) ))] mod inner { - use crate::{FixedOffset, LocalResult, NaiveDateTime}; + use crate::{FixedOffset, MappedLocalTime, NaiveDateTime}; - pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + pub(super) fn offset_from_utc_datetime( + _utc_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) } pub(super) fn offset_from_local_datetime( _local_time: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(FixedOffset::east_opt(0).unwrap()) + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) } } @@ -57,14 +59,16 @@ mod inner { not(any(target_os = "emscripten", target_os = "wasi")) ))] mod inner { - use crate::{Datelike, FixedOffset, LocalResult, NaiveDateTime, Timelike}; + use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike}; - pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset(); - LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } - pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + pub(super) fn offset_from_local_datetime( + local: &NaiveDateTime, + ) -> MappedLocalTime { let mut year = local.year(); if year < 100 { // The API in `js_sys` does not let us create a `Date` with negative years. @@ -84,7 +88,7 @@ mod inner { ); let offset = js_date.get_timezone_offset(); // We always get a result, even if this time does not exist or is ambiguous. - LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } } @@ -166,12 +170,12 @@ impl TimeZone for Local { } #[allow(deprecated)] - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime { // Get the offset at local midnight. self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) } - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime { inner::offset_from_local_datetime(local) } @@ -229,7 +233,7 @@ impl Ord for Transition { fn lookup_with_dst_transitions( transitions: &[Transition], dt: NaiveDateTime, -) -> LocalResult { +) -> MappedLocalTime { for t in transitions.iter() { // A transition can result in the wall clock time going forward (creating a gap) or going // backward (creating a fold). We are interested in the earliest and latest wall time of the @@ -247,24 +251,24 @@ fn lookup_with_dst_transitions( let wall_latest = t.transition_utc.overflowing_add_offset(offset_max); if dt < wall_earliest { - return LocalResult::Single(t.offset_before); + return MappedLocalTime::Single(t.offset_before); } else if dt <= wall_latest { return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) { - Ordering::Equal => LocalResult::Single(t.offset_before), - Ordering::Less => LocalResult::Ambiguous(t.offset_before, t.offset_after), + Ordering::Equal => MappedLocalTime::Single(t.offset_before), + Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after), Ordering::Greater => { if dt == wall_earliest { - LocalResult::Single(t.offset_before) + MappedLocalTime::Single(t.offset_before) } else if dt == wall_latest { - LocalResult::Single(t.offset_after) + MappedLocalTime::Single(t.offset_after) } else { - LocalResult::None + MappedLocalTime::None } } }; } } - LocalResult::Single(transitions.last().unwrap().offset_after) + MappedLocalTime::Single(transitions.last().unwrap().offset_after) } #[cfg(test)] @@ -275,7 +279,7 @@ mod tests { use crate::offset::TimeZone; use crate::{Datelike, Days, Utc}; #[cfg(windows)] - use crate::{FixedOffset, LocalResult, NaiveDate, NaiveDateTime}; + use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime}; #[test] fn verify_correct_offsets() { @@ -368,7 +372,7 @@ mod tests { h: u32, n: u32, s: u32, - result: LocalResult, + result: MappedLocalTime, ) { let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); assert_eq!(lookup_with_dst_transitions(transitions, dt), result); @@ -382,17 +386,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst), Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); - - compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std)); // std transition before dst transition // dst offset > std offset @@ -402,17 +406,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std), Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst), ]; - compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Ambiguous(dst, std)); - compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, LocalResult::Single(std)); - - compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst)); // dst transition before std transition // dst offset < std offset @@ -422,17 +426,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst), Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - - compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); // std transition before dst transition // dst offset < std offset @@ -442,17 +446,17 @@ mod tests { Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std), Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst), ]; - compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, LocalResult::Single(std)); - - compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, LocalResult::Ambiguous(std, dst)); - compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); // offset stays the same let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); @@ -460,18 +464,18 @@ mod tests { Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std), Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std), ]; - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, LocalResult::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); // single transition let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)]; - compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, LocalResult::Single(std)); - compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, LocalResult::None); - compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, LocalResult::Single(dst)); - compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, LocalResult::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); } #[test] @@ -486,17 +490,17 @@ mod tests { ]; assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()), - LocalResult::Single(std) + MappedLocalTime::Single(std) ); assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()), - LocalResult::Single(dst) + MappedLocalTime::Single(dst) ); // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when // converted to UTC). assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX), - LocalResult::Ambiguous(dst, std) + MappedLocalTime::Ambiguous(dst, std) ); // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN` @@ -508,17 +512,17 @@ mod tests { ]; assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()), - LocalResult::Single(dst) + MappedLocalTime::Single(dst) ); assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()), - LocalResult::Single(std) + MappedLocalTime::Single(std) ); // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when // converted to UTC). assert_eq!( lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN), - LocalResult::Ambiguous(std, dst) + MappedLocalTime::Ambiguous(std, dst) ); } diff --git a/src/offset/local/tz_info/rule.rs b/src/offset/local/tz_info/rule.rs index 0ff4fe7bde..daaed416f1 100644 --- a/src/offset/local/tz_info/rule.rs +++ b/src/offset/local/tz_info/rule.rs @@ -83,10 +83,10 @@ impl TransitionRule { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, Error> { match self { TransitionRule::Fixed(local_time_type) => { - Ok(crate::LocalResult::Single(*local_time_type)) + Ok(crate::MappedLocalTime::Single(*local_time_type)) } TransitionRule::Alternate(alternate_time) => { alternate_time.find_local_time_type_from_local(local_time, year) @@ -231,7 +231,7 @@ impl AlternateTime { &self, local_time: i64, current_year: i32, - ) -> Result, Error> { + ) -> Result, 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")); @@ -252,7 +252,7 @@ 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::MappedLocalTime::Single(self.std)), Ordering::Less => { if self.dst_start.transition_date(current_year).0 < self.dst_end.transition_date(current_year).0 @@ -260,41 +260,41 @@ impl AlternateTime { // 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::MappedLocalTime::Single(self.std)) } else if local_time > dst_start_transition_start && local_time < dst_start_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else if local_time >= dst_start_transition_end && local_time < dst_end_transition_end { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_end && local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) } else { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } } 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::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_end && local_time <= dst_end_transition_start { - Ok(crate::LocalResult::Ambiguous(self.std, self.dst)) + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) } else if local_time > dst_end_transition_end && local_time < dst_start_transition_start { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_start && local_time < dst_start_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } } } @@ -305,41 +305,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::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_end && local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) } else if local_time > dst_start_transition_start && local_time < dst_end_transition_start { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } else if local_time >= dst_end_transition_start && local_time < dst_end_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } } 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::MappedLocalTime::Single(self.dst)) } else if local_time > dst_end_transition_start && local_time < dst_end_transition_end { - Ok(crate::LocalResult::None) + Ok(crate::MappedLocalTime::None) } else if local_time >= dst_end_transition_end && local_time < dst_start_transition_end { - Ok(crate::LocalResult::Single(self.std)) + Ok(crate::MappedLocalTime::Single(self.std)) } else if local_time >= dst_start_transition_end && local_time <= dst_start_transition_start { - Ok(crate::LocalResult::Ambiguous(self.dst, self.std)) + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) } else { - Ok(crate::LocalResult::Single(self.dst)) + Ok(crate::MappedLocalTime::Single(self.dst)) } } } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 0965a8a3e3..dbb2def931 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -129,7 +129,7 @@ impl TimeZone { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, Error> { self.as_ref().find_local_time_type_from_local(local_time, year) } @@ -218,7 +218,7 @@ impl<'a> TimeZoneRef<'a> { &self, local_time: i64, year: i32, - ) -> Result, Error> { + ) -> Result, 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) { @@ -229,7 +229,7 @@ impl<'a> TimeZoneRef<'a> { let local_leap_time = local_time; // if we have at least one transition, - // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions + // we must check _all_ of them, incase of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions let offset_after_last = if !self.transitions.is_empty() { let mut prev = self.local_time_types[0]; @@ -246,26 +246,26 @@ impl<'a> TimeZoneRef<'a> { // 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::MappedLocalTime::Single(prev)); } 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::MappedLocalTime::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); } } } 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::MappedLocalTime::Single(prev)); } 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::MappedLocalTime::Ambiguous(prev, after_ltt)); } else { - return Ok(crate::LocalResult::Ambiguous(after_ltt, prev)); + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); } } } @@ -273,11 +273,11 @@ impl<'a> TimeZoneRef<'a> { // 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::MappedLocalTime::Single(prev)); } else if local_leap_time < transition_end { - return Ok(crate::LocalResult::None); + return Ok(crate::MappedLocalTime::None); } else if local_leap_time == transition_end { - return Ok(crate::LocalResult::Single(after_ltt)); + return Ok(crate::MappedLocalTime::Single(after_ltt)); } } } @@ -298,7 +298,7 @@ impl<'a> TimeZoneRef<'a> { err => err, } } else { - Ok(crate::LocalResult::Single(offset_after_last)) + Ok(crate::MappedLocalTime::Single(offset_after_last)) } } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index a2c2724380..ad99542330 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,17 +12,17 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::Sys use super::tz_info::TimeZone; use super::{FixedOffset, NaiveDateTime}; -use crate::{Datelike, LocalResult}; +use crate::{Datelike, MappedLocalTime}; -pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { offset(utc, false) } -pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { offset(local, true) } -fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { +fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime { TZ_INFO.with(|maybe_cache| { maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) }) @@ -104,7 +104,7 @@ fn current_zone(var: Option<&str>) -> TimeZone { } impl Cache { - fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime { let now = SystemTime::now(); match now.duration_since(self.last_checked) { @@ -156,13 +156,13 @@ impl Cache { .offset(); return match FixedOffset::east_opt(offset) { - Some(offset) => LocalResult::Single(offset), - None => LocalResult::None, + Some(offset) => MappedLocalTime::Single(offset), + None => MappedLocalTime::None, }; } // we pass through the year as the year of a local point in time must either be valid in that locale, or - // the entire time was skipped in which case we will return LocalResult::None anyway. + // the entire time was skipped in which case we will return MappedLocalTime::None anyway. self.zone .find_local_time_type_from_local(d.and_utc().timestamp(), d.year()) .expect("unable to select local time type") diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 8256baea80..100b1f1eef 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -15,7 +15,7 @@ use std::ptr; use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; use crate::offset::local::{lookup_with_dst_transitions, Transition}; -use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; // We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates // as Chrono. Also it really isn't that difficult to work out the correct offset from the provided @@ -24,13 +24,13 @@ use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveT // This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC // falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is // very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`. -pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { // Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be // using the rules for the year of the corresponding local time. But this matches what // `SystemTimeToTzSpecificLocalTime` is documented to do. let tz_info = match TzInfo::for_year(utc.year()) { Some(tz_info) => tz_info, - None => return LocalResult::None, + None => return MappedLocalTime::None, }; let offset = match (tz_info.std_transition, tz_info.dst_transition) { (Some(std_transition), Some(dst_transition)) => { @@ -64,16 +64,16 @@ pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult tz_info.std_offset, }; - LocalResult::Single(offset) + MappedLocalTime::Single(offset) } // We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle // ambiguous cases (during a DST transition). Instead we get the timezone information for the // current year and compute it ourselves, like we do on Unix. -pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { let tz_info = match TzInfo::for_year(local.year()) { Some(tz_info) => tz_info, - None => return LocalResult::None, + None => return MappedLocalTime::None, }; // Create a sorted slice of transitions and use `lookup_with_dst_transitions`. match (tz_info.std_transition, tz_info.dst_transition) { @@ -87,7 +87,7 @@ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult [dst_transition, std_transition], Ordering::Equal => { // This doesn't make sense. Let's just return the standard offset. - return LocalResult::Single(tz_info.std_offset); + return MappedLocalTime::Single(tz_info.std_offset); } }; lookup_with_dst_transitions(&transitions, *local) @@ -102,7 +102,7 @@ pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult LocalResult::Single(tz_info.std_offset), + (None, None) => MappedLocalTime::Single(tz_info.std_offset), } } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 005c6bdd9a..87beaa7a21 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -39,7 +39,7 @@ pub use self::utc::Utc; /// The conversion result from the local time to the timezone-aware datetime types. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] -pub enum LocalResult { +pub enum MappedLocalTime { /// Given local time representation is invalid. /// This can occur when, for example, the positive timezone transition. None, @@ -50,12 +50,12 @@ pub enum LocalResult { Ambiguous(T /* min */, T /* max */), } -impl LocalResult { +impl MappedLocalTime { /// Returns `Some` only when the conversion result is unique, or `None` otherwise. #[must_use] pub fn single(self) -> Option { match self { - LocalResult::Single(t) => Some(t), + MappedLocalTime::Single(t) => Some(t), _ => None, } } @@ -64,7 +64,7 @@ impl LocalResult { #[must_use] pub fn earliest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t), _ => None, } } @@ -73,36 +73,39 @@ impl LocalResult { #[must_use] pub fn latest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(_, t) => Some(t), _ => None, } } - /// Maps a `LocalResult` into `LocalResult` with given function. + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. #[must_use] - pub fn map U>(self, mut f: F) -> LocalResult { + pub fn map U>(self, mut f: F) -> MappedLocalTime { match self { - LocalResult::None => LocalResult::None, - LocalResult::Single(v) => LocalResult::Single(f(v)), - LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)), + MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)), } } } +/// The conversion result from the local time to the timezone-aware datetime types. +pub type LocalResult = MappedLocalTime; + #[allow(deprecated)] -impl LocalResult> { +impl MappedLocalTime> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] - pub fn and_time(self, time: NaiveTime) -> LocalResult> { + pub fn and_time(self, time: NaiveTime) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_time(time).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_time(time).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -112,12 +115,12 @@ impl LocalResult> { /// Propagates any error. Ambiguous result would be discarded. #[inline] #[must_use] - pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { + pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_hms_opt(hour, min, sec).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -134,12 +137,12 @@ impl LocalResult> { min: u32, sec: u32, milli: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_milli_opt(hour, min, sec, milli) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -156,12 +159,12 @@ impl LocalResult> { min: u32, sec: u32, micro: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_micro_opt(hour, min, sec, micro) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -178,25 +181,25 @@ impl LocalResult> { min: u32, sec: u32, nano: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_nano_opt(hour, min, sec, nano) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } } -impl LocalResult { +impl MappedLocalTime { /// Returns the single unique conversion result, or panics accordingly. #[must_use] #[track_caller] pub fn unwrap(self) -> T { match self { - LocalResult::None => panic!("No such local time"), - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, t2) => { + MappedLocalTime::None => panic!("No such local time"), + MappedLocalTime::Single(t) => t, + MappedLocalTime::Ambiguous(t1, t2) => { panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) } } @@ -222,7 +225,7 @@ pub trait TimeZone: Sized + Clone { /// /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// - /// Returns `LocalResult::None` on invalid input data. + /// Returns `MappedLocalTime::None` on invalid input data. fn with_ymd_and_hms( &self, year: i32, @@ -231,11 +234,11 @@ pub trait TimeZone: Sized + Clone { hour: u32, min: u32, sec: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) { Some(dt) => self.from_local_datetime(&dt), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -261,10 +264,10 @@ pub trait TimeZone: Sized + Clone { /// Returns `None` on the out-of-range date, invalid month and/or day. #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] #[allow(deprecated)] - fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { + fn ymd_opt(&self, year: i32, month: u32, day: u32) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -296,10 +299,10 @@ pub trait TimeZone: Sized + Clone { note = "use `from_local_datetime()` with a `NaiveDateTime` instead" )] #[allow(deprecated)] - fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { + fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -335,10 +338,10 @@ pub trait TimeZone: Sized + Clone { note = "use `from_local_datetime()` with a `NaiveDateTime` instead" )] #[allow(deprecated)] - fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { + fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> MappedLocalTime> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -369,8 +372,8 @@ pub trait TimeZone: Sized + Clone { /// /// # Errors /// - /// Returns `LocalResult::None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise always returns `LocalResult::Single`. + /// Returns `MappedLocalTime::None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise always returns `MappedLocalTime::Single`. /// /// # Example /// @@ -379,10 +382,10 @@ pub trait TimeZone: Sized + Clone { /// /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); /// ``` - fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { + fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime> { match DateTime::from_timestamp(secs, nsecs) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -400,23 +403,23 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// - /// Returns `LocalResult::None` on out-of-range number of milliseconds + /// Returns `MappedLocalTime::None` on out-of-range number of milliseconds /// and/or invalid nanosecond, otherwise always returns - /// `LocalResult::Single`. + /// `MappedLocalTime::Single`. /// /// # Example /// /// ``` - /// use chrono::{LocalResult, TimeZone, Utc}; + /// use chrono::{MappedLocalTime, TimeZone, Utc}; /// match Utc.timestamp_millis_opt(1431648000) { - /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), + /// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), /// }; /// ``` - fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { + fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime> { match DateTime::from_timestamp_millis(millis) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -446,10 +449,10 @@ pub trait TimeZone: Sized + Clone { /// /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); /// ``` - fn timestamp_micros(&self, micros: i64) -> LocalResult> { + fn timestamp_micros(&self, micros: i64) -> MappedLocalTime> { match DateTime::from_timestamp_micros(micros) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - None => LocalResult::None, + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -482,16 +485,16 @@ pub trait TimeZone: Sized + Clone { fn from_offset(offset: &Self::Offset) -> Self; /// Creates the offset(s) for given local `NaiveDate` if possible. - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult; + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime; /// Creates the offset(s) for given local `NaiveDateTime` if possible. - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. #[allow(clippy::wrong_self_convention)] #[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")] #[allow(deprecated)] - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected Date::from_utc(*local, offset) @@ -500,22 +503,24 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - // Return `LocalResult::None` when the offset pushes a value out of range, instead of + fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime> { + // Return `MappedLocalTime::None` when the offset pushes a value out of range, instead of // panicking. match self.offset_from_local_datetime(local) { - LocalResult::None => LocalResult::None, - LocalResult::Single(offset) => match local.checked_sub_offset(offset.fix()) { - Some(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)), - None => LocalResult::None, + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(offset) => match local.checked_sub_offset(offset.fix()) { + Some(dt) => { + MappedLocalTime::Single(DateTime::from_naive_utc_and_offset(dt, offset)) + } + None => MappedLocalTime::None, }, - LocalResult::Ambiguous(o1, o2) => { + MappedLocalTime::Ambiguous(o1, o2) => { match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { - (Some(d1), Some(d2)) => LocalResult::Ambiguous( + (Some(d1), Some(d2)) => MappedLocalTime::Ambiguous( DateTime::from_naive_utc_and_offset(d1, o1), DateTime::from_naive_utc_and_offset(d2, o2), ), - _ => LocalResult::None, + _ => MappedLocalTime::None, } } } @@ -563,13 +568,13 @@ mod tests { if offset_hour >= 0 { assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); } else { - assert_eq!(local_max, LocalResult::None); + assert_eq!(local_max, MappedLocalTime::None); } let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); if offset_hour <= 0 { assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); } else { - assert_eq!(local_min, LocalResult::None); + assert_eq!(local_min, MappedLocalTime::None); } } } @@ -599,7 +604,7 @@ mod tests { (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis_opt(*millis) { - LocalResult::Single(dt) => { + MappedLocalTime::Single(dt) => { assert_eq!(dt.to_string(), *expected); } e => panic!("Got {:?} instead of an okay answer", e), diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 5bfbe69e9c..5ae26ed86d 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -17,7 +17,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; -use super::{FixedOffset, LocalResult, Offset, TimeZone}; +use super::{FixedOffset, MappedLocalTime, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; #[cfg(feature = "now")] #[allow(deprecated)] @@ -118,11 +118,11 @@ impl TimeZone for Utc { Utc } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc { diff --git a/tests/dateutils.rs b/tests/dateutils.rs index a57d439d30..5aa00da6bf 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -29,18 +29,18 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // } // This is used while a decision is made whether the `date` output needs to - // be exactly matched, or whether LocalResult::Ambiguous should be handled + // be exactly matched, or whether MappedLocalTime::Ambiguous should be handled // differently let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { - chrono::LocalResult::Ambiguous(a, b) => assert!( + chrono::MappedLocalTime::Ambiguous(a, b) => assert!( format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str ), - chrono::LocalResult::Single(a) => { + chrono::MappedLocalTime::Single(a) => { assert_eq!(format!("{}\n", a), date_command_str); } - chrono::LocalResult::None => { + chrono::MappedLocalTime::None => { assert_eq!("", date_command_str); } } From 4189fb076cc3dbe4f0d7345127fb3a140e0357d2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 9 Mar 2024 13:25:48 +0100 Subject: [PATCH 2/2] Extend documentation for `MappedLocalTime` --- src/offset/mod.rs | 67 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 87beaa7a21..c1d1cc5619 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -37,21 +37,47 @@ pub use self::local::Local; pub(crate) mod utc; pub use self::utc::Utc; -/// The conversion result from the local time to the timezone-aware datetime types. +/// The result of mapping a local time to a concrete instant in a given time zone. +/// +/// The calculation to go from a local time (wall clock time) to an instant in UTC can end up in +/// three cases: +/// * A single, simple result. +/// * An ambiguous result when the clock is turned backwards during a transition due to for example +/// DST. +/// * No result when the clock is turned forwards during a transition due to for example DST. +/// +/// When the clock is turned backwards it creates a _fold_ in local time, during which the local +/// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during +/// which the local time is _missing_, or does not exist. +/// +/// Chrono does not return a default choice or invalid data during time zone transitions, but has +/// the `MappedLocalTime` type to help deal with the result correctly. +/// +/// The type of `T` is usually a [`DateTime`] but may also be only an offset. #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] pub enum MappedLocalTime { - /// Given local time representation is invalid. - /// This can occur when, for example, the positive timezone transition. - None, - /// Given local time representation has a single unique result. + /// The local time maps to a single unique result. Single(T), - /// Given local time representation has multiple results and thus ambiguous. - /// This can occur when, for example, the negative timezone transition. - Ambiguous(T /* min */, T /* max */), + + /// The local time is _ambiguous_ because there is a _fold_ in the local time. + /// + /// This variant contains the two possible results, in the order `(earliest, latest)`. + Ambiguous(T, T), + + /// The local time does not exist because there is a _gap_ in the local time. + /// + /// This variant may also be returned if there was an error while resolving the local time, + /// caused by for example missing time zone data files, an error in an OS API, or overflow. + None, } impl MappedLocalTime { - /// Returns `Some` only when the conversion result is unique, or `None` otherwise. + /// Returns `Some` if the time zone mapping has a single result. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _fold_ or _gap_ in the local time, or if there was + /// an error. #[must_use] pub fn single(self) -> Option { match self { @@ -60,7 +86,11 @@ impl MappedLocalTime { } } - /// Returns `Some` for the earliest possible conversion result, or `None` if none. + /// Returns the earliest possible result of a the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. #[must_use] pub fn earliest(self) -> Option { match self { @@ -69,7 +99,11 @@ impl MappedLocalTime { } } - /// Returns `Some` for the latest possible conversion result, or `None` if none. + /// Returns the latest possible result of a the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. #[must_use] pub fn latest(self) -> Option { match self { @@ -192,7 +226,16 @@ impl MappedLocalTime> { } impl MappedLocalTime { - /// Returns the single unique conversion result, or panics accordingly. + /// Returns a single unique conversion result or panics. + /// + /// `unwrap()` is best combined with time zone types where the mapping can never fail like + /// [`Utc`] and [`FixedOffset`]. Note that for [`FixedOffset`] there is a rare case where a + /// resulting [`DateTime`] can be out of range. + /// + /// # Panics + /// + /// Panics if the local time falls within a _fold_ or a _gap_ in the local time, and on any + /// error that may have been returned by the type implementing [`TimeZone`]. #[must_use] #[track_caller] pub fn unwrap(self) -> T {