diff --git a/README.md b/README.md index 64f7cf1591..943a95eb17 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/mod.rs b/src/datetime/mod.rs index 7f1331120a..67a22617db 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -25,7 +25,7 @@ use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone, Utc}; #[cfg(any(feature = "clock", feature = "std"))] use crate::OutOfRange; use crate::{try_err, try_ok_or, Datelike, Error, Months, TimeDelta, Timelike, Weekday}; @@ -673,6 +673,32 @@ impl DateTime { map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) } + /// Set the time to a new fixed time on the existing date. + /// + /// # Errors + /// + /// Returns `MappedLocalTime::None` if the datetime is at the edge of the representable range + /// for a `DateTime`, and `with_time` would push the value in UTC out of range. + /// + /// # Example + /// + #[cfg_attr(not(feature = "clock"), doc = "```ignore")] + #[cfg_attr(feature = "clock", doc = "```rust")] + /// use chrono::{Local, NaiveTime}; + /// + /// let noon = NaiveTime::from_hms(12, 0, 0)?; + /// let today_noon = Local::now().with_time(noon); + /// let today_midnight = Local::now().with_time(NaiveTime::MIN); + /// + /// assert_eq!(today_noon.single().unwrap().time(), noon); + /// assert_eq!(today_midnight.single().unwrap().time(), NaiveTime::MIN); + /// # Ok::<(), chrono::Error>(()) + /// ``` + #[must_use] + pub fn with_time(&self, time: NaiveTime) -> MappedLocalTime { + self.timezone().from_local_datetime(&self.overflowing_naive_local().date().and_time(time)) + } + /// Makes a new `DateTime` with the hour number changed. /// /// See also the [`NaiveTime::with_hour`] method. diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 1459bd2c93..93635d754d 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1235,7 +1235,7 @@ mod tests { #[test] fn test_serde_no_offset_debug() { - use crate::{LocalResult, NaiveDateTime, Offset}; + use crate::{MappedLocalTime, NaiveDateTime, Offset}; use core::fmt::Debug; #[derive(Clone)] @@ -1253,8 +1253,8 @@ mod tests { fn offset_from_local_datetime( &self, _local: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(TestTimeZone) + ) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) } fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> TestTimeZone { TestTimeZone diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 64afafe130..1ef61a491c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,7 +4,7 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; use crate::{ - Datelike, Days, Error, LocalResult, Months, NaiveDateTime, TimeDelta, Timelike, Weekday, + Datelike, Days, Error, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday, }; #[derive(Clone)] @@ -36,7 +36,7 @@ impl TimeZone for DstTester { fn offset_from_local_datetime( &self, local: &NaiveDateTime, - ) -> crate::LocalResult { + ) -> crate::MappedLocalTime { let local_to_winter_transition_start = NaiveDate::from_ymd( local.year(), DstTester::TO_WINTER_MONTH_DAY.0, @@ -70,19 +70,19 @@ impl TimeZone for DstTester { .and_time(DstTester::transition_start_local() + TimeDelta::hours(1)); 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) } @@ -592,7 +592,7 @@ fn signed_duration_since_autoref() { let diff2 = dt2.signed_duration_since(&dt1); // Take by reference assert_eq!(diff1, -diff2); - let diff1 = dt1 - &dt2; // We can choose to substract rhs by reference + let diff1 = dt1 - &dt2; // We can choose to subtract rhs by reference let diff2 = dt2 - dt1; // Or consume rhs assert_eq!(diff1, -diff2); } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 199a504f71..7af32f24d1 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. @@ -859,9 +859,9 @@ impl Parsed { let offset = FixedOffset::east(offset).map_err(|_| 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), } } @@ -915,15 +915,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 f4a93d6741..ddc02da8b1 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. @@ -129,7 +129,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<()> { @@ -147,10 +147,14 @@ //! assert_eq!(dt, NaiveDate::from_ymd(2014, 7, 8).unwrap().and_hms_nano(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap()); //! //! // dynamic verification -//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), -//! LocalResult::Single(NaiveDate::from_ymd(2014, 7, 8).unwrap().and_hms(21, 15, 33).unwrap().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, 21, 15, 33), +//! MappedLocalTime::Single( +//! NaiveDate::from_ymd(2014, 7, 8).unwrap().and_hms(21, 15, 33).unwrap().and_utc() +//! ) +//! ); +//! 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. @@ -512,7 +516,7 @@ pub mod offset; #[cfg(feature = "clock")] #[doc(inline)] pub use offset::Local; -pub use offset::LocalResult; +pub use offset::MappedLocalTime; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 54bd946802..9a12e1b226 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -20,8 +20,8 @@ use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; use crate::{ - expect, ok, try_err, try_ok_or, try_opt, DateTime, Datelike, Error, FixedOffset, LocalResult, - Months, TimeDelta, TimeZone, Timelike, Weekday, + expect, ok, try_err, try_ok_or, try_opt, DateTime, Datelike, Error, FixedOffset, + MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, Weekday, }; /// Tools to help serializing/deserializing `NaiveDateTime`s @@ -686,7 +686,7 @@ impl NaiveDateTime { /// This can fail in cases where the local time represented by the `NaiveDateTime` /// is not a valid local timestamp in the target timezone due to an offset transition /// for example if the target timezone had a change from +00:00 to +01:00 - /// occuring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 + /// occurring at 2015-09-05 22:59:59, then a local time of 2015-09-05 23:56:04 /// could never occur. Similarly, if the offset transitioned in the opposite direction /// then there would be two local times of 2015-09-05 23:56:04, one at +00:00 and one /// at +01:00. @@ -706,7 +706,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 8fbbf5fe60..39b24ce025 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,5 +1,5 @@ use super::NaiveDateTime; -use crate::{Datelike, Error, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc}; +use crate::{Datelike, Error, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; #[test] fn test_datetime_add() -> Result<(), Error> { @@ -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 2f16e1ff2c..ada184c7a9 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, OUT_OF_RANGE}; use crate::{Error, NaiveDateTime, ParseError}; @@ -113,8 +113,8 @@ impl TimeZone for FixedOffset { *offset } - 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_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 92eaea0bbb..5c160d00c4 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::{DateTime, NaiveDateTime, Utc}; #[cfg(unix)] @@ -35,16 +35,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(0).unwrap()) + pub(super) fn offset_from_utc_datetime( + _utc_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east(0).unwrap()) } pub(super) fn offset_from_local_datetime( _local_time: &NaiveDateTime, - ) -> LocalResult { - LocalResult::Single(FixedOffset::east(0).unwrap()) + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east(0).unwrap()) } } @@ -54,14 +56,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((offset as i32) * 60).unwrap()) + MappedLocalTime::Single(FixedOffset::west((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. @@ -81,7 +85,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((offset as i32) * 60).unwrap()) + MappedLocalTime::Single(FixedOffset::west((offset as i32) * 60).unwrap()) } } @@ -154,7 +158,7 @@ impl TimeZone for Local { Local } - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime { inner::offset_from_local_datetime(local) } @@ -206,7 +210,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 @@ -224,24 +228,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)] @@ -252,7 +256,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() { @@ -344,7 +348,7 @@ mod tests { h: u32, n: u32, s: u32, - result: LocalResult, + result: MappedLocalTime, ) { let dt = NaiveDate::from_ymd(y, m, d).unwrap().and_hms(h, n, s).unwrap(); assert_eq!(lookup_with_dst_transitions(transitions, dt), result); @@ -358,17 +362,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 @@ -378,17 +382,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 @@ -398,17 +402,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 @@ -418,17 +422,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(3 * 60 * 60).unwrap(); @@ -436,18 +440,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(3 * 60 * 60).unwrap(); let dst = FixedOffset::east(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] @@ -462,17 +466,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` @@ -484,17 +488,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 033c8e79fc..8055bcb4ef 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -12,24 +12,24 @@ 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) }) } // we have to store the `Cache` in an option as it can't -// be initalized in a static context. +// be initialized in a static context. thread_local! { static TZ_INFO: RefCell> = Default::default(); } @@ -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(offset) { - Ok(offset) => LocalResult::Single(offset), - Err(_) => LocalResult::None, + Ok(offset) => MappedLocalTime::Single(offset), + Err(_) => 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 9efb1d7392..73d6992e99 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -16,7 +16,7 @@ use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_I use crate::offset::local::{lookup_with_dst_transitions, Transition}; use crate::{ - Datelike, Error, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Weekday, + Datelike, Error, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday, }; // We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates @@ -26,13 +26,13 @@ use crate::{ // 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)) => { @@ -66,16 +66,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) { @@ -89,7 +89,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) @@ -104,7 +104,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), } } @@ -279,7 +279,7 @@ mod tests { SystemTimeToFileTime(st, init.as_mut_ptr()); } // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is - // initalized. + // initialized. let filetime = unsafe { init.assume_init() }; let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 7708b21f24..146078e68b 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -34,67 +34,101 @@ 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 LocalResult { - /// 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. +pub enum MappedLocalTime { + /// 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 LocalResult { - /// Returns `Some` only when the conversion result is unique, or `None` otherwise. +impl MappedLocalTime { + /// 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 { - LocalResult::Single(t) => Some(t), + MappedLocalTime::Single(t) => Some(t), _ => None, } } - /// 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 { - LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t), _ => None, } } - /// 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 { - 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)), } } } -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) } } @@ -120,7 +154,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, @@ -129,10 +163,10 @@ pub trait TimeZone: Sized + Clone { hour: u32, min: u32, sec: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match NaiveDate::from_ymd(year, month, day).and_then(|d| d.and_hms(hour, min, sec)) { Ok(dt) => self.from_local_datetime(&dt), - Err(_) => LocalResult::None, + Err(_) => MappedLocalTime::None, } } @@ -146,8 +180,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 /// @@ -156,10 +190,10 @@ pub trait TimeZone: Sized + Clone { /// /// assert_eq!(Utc.timestamp(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); /// ``` - fn timestamp(&self, secs: i64, nsecs: u32) -> LocalResult> { + fn timestamp(&self, secs: i64, nsecs: u32) -> MappedLocalTime> { match DateTime::from_timestamp(secs, nsecs) { - Ok(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - Err(_) => LocalResult::None, + Ok(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + Err(_) => MappedLocalTime::None, } } @@ -167,23 +201,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(1431648000) { - /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), + /// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), /// }; /// ``` - fn timestamp_millis(&self, millis: i64) -> LocalResult> { + fn timestamp_millis(&self, millis: i64) -> MappedLocalTime> { match DateTime::from_timestamp_millis(millis) { - Ok(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - Err(_) => LocalResult::None, + Ok(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + Err(_) => MappedLocalTime::None, } } @@ -213,10 +247,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) { - Ok(dt) => LocalResult::Single(self.from_utc_datetime(&dt.naive_utc())), - Err(_) => LocalResult::None, + Ok(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + Err(_) => MappedLocalTime::None, } } @@ -224,26 +258,26 @@ pub trait TimeZone: Sized + Clone { fn from_offset(offset: &Self::Offset) -> Self; /// 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 `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()) { - Ok(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)), - Err(_) => LocalResult::None, + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(offset) => match local.checked_sub_offset(offset.fix()) { + Ok(dt) => MappedLocalTime::Single(DateTime::from_naive_utc_and_offset(dt, offset)), + Err(_) => MappedLocalTime::None, }, - LocalResult::Ambiguous(o1, o2) => { + MappedLocalTime::Ambiguous(o1, o2) => { match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { - (Ok(d1), Ok(d2)) => LocalResult::Ambiguous( + (Ok(d1), Ok(d2)) => MappedLocalTime::Ambiguous( DateTime::from_naive_utc_and_offset(d1, o1), DateTime::from_naive_utc_and_offset(d2, o2), ), - _ => LocalResult::None, + _ => MappedLocalTime::None, } } } @@ -279,13 +313,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); } } } @@ -315,7 +349,7 @@ mod tests { (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis(*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 ce3e259767..6e3909ece7 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -17,7 +17,7 @@ use std::time::SystemTime; #[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::NaiveDateTime; #[cfg(feature = "now")] use crate::DateTime; @@ -114,8 +114,8 @@ impl TimeZone for Utc { 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_datetime(&self, _utc: &NaiveDateTime) -> Utc { diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 07c2594ba3..a6b2a78e1f 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -28,19 +28,19 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { // assert_eq!("", date_command_str); // } - // This is used while a decision is made wheter the `date` output needs to - // be exactly matched, or whether LocalResult::Ambigious should be handled + // This is used while a decision is made whether the `date` output needs to + // be exactly matched, or whether MappedLocalTime::Ambiguous should be handled // differently let date = NaiveDate::from_ymd(dt.year(), dt.month(), dt.day()).unwrap(); match Local.from_local_datetime(&date.and_hms(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); } }