diff --git a/Cargo.toml b/Cargo.toml index cf28a63..4a42d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "time-tz" -version = "2.1.0-rc.1.0.0" +version = "3.0.0-rc.1.0.0" edition = "2021" authors = ["Yuri Edward "] description = "Implementation of tz database (IANA) for the time Rust crate." @@ -42,6 +42,7 @@ default = ["db"] system = ["windows-sys", "js-sys", "thiserror", "db"] posix-tz = ["nom", "thiserror", "db"] db = [] +db_impl = [] [package.metadata.docs.rs] all-features = true diff --git a/build.rs b/build.rs index 895d258..f586959 100644 --- a/build.rs +++ b/build.rs @@ -151,7 +151,7 @@ fn intermal_write_module_tree( ) -> std::io::Result<()> { writeln!(file, "pub mod {} {{", tree.name.to_lowercase())?; for zone in &tree.items { - writeln!(file, "pub const {}: &crate::Tz = &crate::timezone_impl::internal_tz_new(&crate::timezones::{});", zone.name + writeln!(file, "pub const {}: &crate::timezone_impl::Tz = &crate::timezone_impl::internal_tz_new(&crate::timezones::{});", zone.name .to_uppercase() .replace('-', "_") .replace('+', "_PLUS_"), zone.name_static)?; diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..a314e50 --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2023, Yuri6037 +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of time-tz nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use time::{OffsetDateTime, PrimitiveDateTime}; + +use crate::{TimeZone, zoned, OffsetResult, Offset, timezones, ToTimezone}; + +mod sealing { + pub trait OffsetDateTimeExt {} + pub trait PrimitiveDateTimeExt {} + + impl OffsetDateTimeExt for time::OffsetDateTime {} + impl PrimitiveDateTimeExt for time::PrimitiveDateTime {} +} + +// This trait is sealed and is only implemented in this library. +pub trait OffsetDateTimeExt: sealing::OffsetDateTimeExt { + /// Converts this [OffsetDateTime](time::OffsetDateTime) to UTC. + fn to_utc(&self) -> OffsetDateTime; + + /// Creates a new [ZonedDateTime](crate::ZonedDateTime) from this [OffsetDateTime](time::OffsetDateTime). + /// + /// # Arguments + /// + /// * `tz`: the target timezone. + fn with_timezone<'a, T: TimeZone>(&self, tz: &'a T) -> zoned::ZonedDateTime<'a, T>; +} + +/// This trait is sealed and is only implemented in this library. +pub trait PrimitiveDateTimeExt: sealing::PrimitiveDateTimeExt { + /// Creates a new [OffsetDateTime](time::OffsetDateTime) from a [PrimitiveDateTime](time::PrimitiveDateTime) by assigning the main offset of the + /// target timezone. + /// + /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is already in the target timezone.* + /// + /// # Arguments + /// + /// * `tz`: the target timezone. + /// + /// returns: `OffsetResult` + fn assume_timezone(&self, tz: &T) -> OffsetResult; + + /// Creates a new [OffsetDateTime](time::OffsetDateTime) with the proper offset in the given timezone. + /// + /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is in UTC offset.* + /// + /// # Arguments + /// + /// * `tz`: the target timezone. + /// + /// returns: OffsetDateTime + fn assume_timezone_utc(&self, tz: &T) -> OffsetDateTime; + + /// Creates a new [ZonedDateTime](crate::ZonedDateTime) from this [PrimitiveDateTime](time::PrimitiveDateTime). + /// + /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is already in the target timezone.* + /// + /// # Arguments + /// + /// * `tz`: the target timezone. + fn with_timezone<'a, T: TimeZone>(self, tz: &'a T) -> OffsetResult>; +} + +impl PrimitiveDateTimeExt for PrimitiveDateTime { + fn assume_timezone(&self, tz: &T) -> OffsetResult { + match tz.get_offset_local(&self.assume_utc()) { + OffsetResult::Some(a) => OffsetResult::Some(self.assume_offset(a.to_utc())), + OffsetResult::Ambiguous(a, b) => OffsetResult::Ambiguous( + self.assume_offset(a.to_utc()), + self.assume_offset(b.to_utc()), + ), + OffsetResult::None => OffsetResult::None, + } + } + + fn assume_timezone_utc(&self, tz: &T) -> OffsetDateTime { + let offset = tz.get_offset_utc(&self.assume_utc()); + self.assume_offset(offset.to_utc()) + } + + fn with_timezone<'a, T: TimeZone>(self, tz: &'a T) -> OffsetResult> { + zoned::ZonedDateTime::from_local(self, tz) + } +} + +impl OffsetDateTimeExt for OffsetDateTime { + fn to_utc(&self) -> OffsetDateTime { + if self.offset().is_utc() { + *self + } else { + self.to_timezone(timezones::db::UTC) + } + } + + fn with_timezone<'a, T: TimeZone>(&self, tz: &'a T) -> zoned::ZonedDateTime<'a, T> { + zoned::ZonedDateTime::from_utc(self.to_utc(), tz) + } +} + +impl ToTimezone<&T> for OffsetDateTime { + type Out = OffsetDateTime; + + fn to_timezone(&self, tz: &T) -> OffsetDateTime { + let offset = tz.get_offset_utc(self); + self.to_offset(offset.to_utc()) + } +} diff --git a/src/interface.rs b/src/interface.rs index 69d7308..c0f25c8 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -28,6 +28,15 @@ use time::{OffsetDateTime, UtcOffset}; +/// This trait allows conversions from one timezone to another. +pub trait ToTimezone { + /// The output type. + type Out; + + /// Converts self to a different timezone. + fn to_timezone(&self, tz: T) -> Self::Out; +} + /// This trait represents a particular timezone offset. pub trait Offset { /// Converts this timezone offset to a [UtcOffset](time::UtcOffset). diff --git a/src/lib.rs b/src/lib.rs index a78ca29..88ba32f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,111 +33,8 @@ // See https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html & https://github.com/rust-lang/rust/pull/89596 #![cfg_attr(docsrs, feature(doc_auto_cfg))] -use time::{OffsetDateTime, PrimitiveDateTime}; - -mod zoned; -pub use zoned::ZonedDateTime; -pub use zoned::ComponentDuration; - -mod sealing { - pub trait OffsetDateTimeExt {} - pub trait PrimitiveDateTimeExt {} - - impl OffsetDateTimeExt for time::OffsetDateTime {} - impl PrimitiveDateTimeExt for time::PrimitiveDateTime {} -} - -// This trait is sealed and is only implemented in this library. -pub trait OffsetDateTimeExt: sealing::OffsetDateTimeExt { - /// Converts this [OffsetDateTime](time::OffsetDateTime) to a different [TimeZone](crate::TimeZone). - fn to_timezone(&self, tz: &T) -> OffsetDateTime; - - /// Converts this [OffsetDateTime](time::OffsetDateTime) to UTC. - fn to_utc(&self) -> OffsetDateTime; - - /// Creates a new [ZonedDateTime](crate::ZonedDateTime) from this [OffsetDateTime](time::OffsetDateTime). - /// - /// # Arguments - /// - /// * `tz`: the target timezone. - fn with_timezone<'a, T: TimeZone>(&self, tz: &'a T) -> ZonedDateTime<'a, T>; -} - -/// This trait is sealed and is only implemented in this library. -pub trait PrimitiveDateTimeExt: sealing::PrimitiveDateTimeExt { - /// Creates a new [OffsetDateTime](time::OffsetDateTime) from a [PrimitiveDateTime](time::PrimitiveDateTime) by assigning the main offset of the - /// target timezone. - /// - /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is already in the target timezone.* - /// - /// # Arguments - /// - /// * `tz`: the target timezone. - /// - /// returns: `OffsetResult` - fn assume_timezone(&self, tz: &T) -> OffsetResult; - - /// Creates a new [OffsetDateTime](time::OffsetDateTime) with the proper offset in the given timezone. - /// - /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is in UTC offset.* - /// - /// # Arguments - /// - /// * `tz`: the target timezone. - /// - /// returns: OffsetDateTime - fn assume_timezone_utc(&self, tz: &T) -> OffsetDateTime; - - /// Creates a new [ZonedDateTime](crate::ZonedDateTime) from this [PrimitiveDateTime](time::PrimitiveDateTime). - /// - /// *This assumes the [PrimitiveDateTime](time::PrimitiveDateTime) is already in the target timezone.* - /// - /// # Arguments - /// - /// * `tz`: the target timezone. - fn with_timezone<'a, T: TimeZone>(self, tz: &'a T) -> OffsetResult>; -} - -impl PrimitiveDateTimeExt for PrimitiveDateTime { - fn assume_timezone(&self, tz: &T) -> OffsetResult { - match tz.get_offset_local(&self.assume_utc()) { - OffsetResult::Some(a) => OffsetResult::Some(self.assume_offset(a.to_utc())), - OffsetResult::Ambiguous(a, b) => OffsetResult::Ambiguous( - self.assume_offset(a.to_utc()), - self.assume_offset(b.to_utc()), - ), - OffsetResult::None => OffsetResult::None, - } - } - - fn assume_timezone_utc(&self, tz: &T) -> OffsetDateTime { - let offset = tz.get_offset_utc(&self.assume_utc()); - self.assume_offset(offset.to_utc()) - } - - fn with_timezone<'a, T: TimeZone>(self, tz: &'a T) -> OffsetResult> { - ZonedDateTime::from_local(self, tz) - } -} - -impl OffsetDateTimeExt for OffsetDateTime { - fn to_timezone(&self, tz: &T) -> OffsetDateTime { - let offset = tz.get_offset_utc(self); - self.to_offset(offset.to_utc()) - } - - fn to_utc(&self) -> OffsetDateTime { - if self.offset().is_utc() { - *self - } else { - self.to_timezone(timezones::db::UTC) - } - } - - fn with_timezone<'a, T: TimeZone>(&self, tz: &'a T) -> ZonedDateTime<'a, T> { - ZonedDateTime::from_utc(self.to_utc(), tz) - } -} +mod ext; +pub mod zoned; mod binary_search; mod interface; @@ -147,6 +44,7 @@ mod timezone_impl; pub mod timezones; pub use interface::*; +pub use ext::*; #[cfg(feature = "system")] pub mod system; @@ -154,17 +52,18 @@ pub mod system; #[cfg(feature = "posix-tz")] pub mod posix_tz; -#[cfg(feature = "db")] +#[cfg(feature = "db_impl")] pub use timezone_impl::Tz; #[cfg(test)] mod tests { + use crate::ToTimezone; use crate::timezones; use crate::Offset; use crate::OffsetDateTimeExt; use crate::PrimitiveDateTimeExt; use crate::TimeZone; - use crate::ComponentDuration; + use crate::zoned::Duration; use time::macros::{datetime, offset}; use time::OffsetDateTime; @@ -203,6 +102,15 @@ mod tests { assert_eq!(converted, expected); } + #[test] + fn london_to_berlin_name() { + let dt = datetime!(2016-10-8 17:0:0).assume_timezone_utc(timezones::db::europe::LONDON); + let converted = dt.to_timezone("Europe/Berlin").unwrap(); + let expected = + datetime!(2016-10-8 18:0:0).assume_timezone_utc(timezones::db::europe::BERLIN); + assert_eq!(converted, expected); + } + #[test] fn dst() { let london = timezones::db::europe::LONDON; @@ -286,28 +194,28 @@ mod tests { fn zoned_date_time_add_duration() { assert_eq!( (datetime!(2023-01-01 22:00) - .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + ComponentDuration::days(1)) + .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + Duration::days(1)) .offset_date_time(), datetime!(2023-01-02 22:00).with_timezone(timezones::db::europe::STOCKHOLM) .unwrap_first().offset_date_time() ); assert_eq!( (datetime!(2023-03-25 22:00) - .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + ComponentDuration::days(1)) + .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + Duration::days(1)) .offset_date_time(), datetime!(2023-03-26 22:00).with_timezone(timezones::db::europe::STOCKHOLM) .unwrap_first().offset_date_time() ); assert_eq!( (datetime!(2023-03-25 22:00) - .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + ComponentDuration::hours(24)) + .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + Duration::hours(24)) .offset_date_time(), datetime!(2023-03-26 23:00).with_timezone(timezones::db::europe::STOCKHOLM) .unwrap_first().offset_date_time() ); assert_eq!( (datetime!(2023-03-26 1:00) - .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + ComponentDuration::hours(1)) + .with_timezone(timezones::db::europe::STOCKHOLM).unwrap_first() + Duration::hours(1)) .offset_date_time(), datetime!(2023-03-26 3:00).with_timezone(timezones::db::europe::STOCKHOLM) .unwrap_first().offset_date_time() diff --git a/src/posix_tz/abstract.rs b/src/posix_tz/abstract.rs index fa5d848..77e24c0 100644 --- a/src/posix_tz/abstract.rs +++ b/src/posix_tz/abstract.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Yuri6037 +// Copyright (c) 2023, Yuri6037 // // All rights reserved. // @@ -29,8 +29,7 @@ use crate::posix_tz::intermediate::ParsedTz; use crate::posix_tz::parser::Date; use crate::posix_tz::{Error, ParseError}; -use crate::timezone_impl::TzOffset; -use crate::Tz; +use crate::timezone_impl::{Tz, TzOffset}; use time::{OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; pub enum TzOrExpandedOffset<'a> { diff --git a/src/posix_tz/intermediate.rs b/src/posix_tz/intermediate.rs index 7cfbad1..21640e4 100644 --- a/src/posix_tz/intermediate.rs +++ b/src/posix_tz/intermediate.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Yuri6037 +// Copyright (c) 2023, Yuri6037 // // All rights reserved. // @@ -149,7 +149,7 @@ impl<'a> Tz<'a> { } pub enum ParsedTz<'a> { - Existing(&'static crate::Tz), + Existing(&'static crate::timezone_impl::Tz), Expanded((Std<'a>, Option>)), } diff --git a/src/posix_tz/mod.rs b/src/posix_tz/mod.rs index 0ee535e..e1a3553 100644 --- a/src/posix_tz/mod.rs +++ b/src/posix_tz/mod.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Yuri6037 +// Copyright (c) 2023, Yuri6037 // // All rights reserved. // @@ -26,7 +26,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{Offset, TimeZone, Tz}; +use crate::{Offset, TimeZone, timezone_impl::Tz, ToTimezone}; use std::fmt::{Display, Formatter}; use thiserror::Error; use time::{OffsetDateTime, UtcOffset}; @@ -204,3 +204,11 @@ impl<'a> PosixTz<'a> { } } } + +impl<'a> ToTimezone<&PosixTz<'a>> for OffsetDateTime { + type Out = Result; + + fn to_timezone(&self, tz: &PosixTz) -> Self::Out { + tz.convert(self) + } +} diff --git a/src/system.rs b/src/system.rs index 4945c6b..040f335 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Yuri6037 +// Copyright (c) 2023, Yuri6037 // // All rights reserved. // @@ -31,7 +31,7 @@ //! Currently only supported for Windows, Unix, and WASM targets. use crate::timezones::get_by_name; -use crate::Tz; +use crate::timezone_impl::Tz; use thiserror::Error; #[cfg(target_family = "wasm")] diff --git a/src/timezones.rs b/src/timezones.rs index 09cc23b..da14927 100644 --- a/src/timezones.rs +++ b/src/timezones.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Yuri6037 +// Copyright (c) 2023, Yuri6037 // // All rights reserved. // @@ -26,12 +26,14 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::ToTimezone; use crate::timezone_impl::internal_tz_new; use crate::timezone_impl::FixedTimespan; use crate::timezone_impl::FixedTimespanSet; -use crate::Tz; +use crate::timezone_impl::Tz; use phf::Map; +use time::OffsetDateTime; include!(concat!(env!("OUT_DIR"), "/timezones.rs")); @@ -58,3 +60,12 @@ pub fn get_by_name(name: &str) -> Option<&'static Tz> { TIMEZONES.get(name).copied() } } + +impl ToTimezone<&str> for OffsetDateTime { + type Out = Option; + + fn to_timezone(&self, tz: &str) -> Self::Out { + let tz = get_by_name(tz)?; + Some(self.to_timezone(tz)) + } +} diff --git a/src/zoned.rs b/src/zoned.rs index c398baa..a1fa1a6 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -28,9 +28,9 @@ use std::ops::{Add, Sub}; -use time::{UtcOffset, PrimitiveDateTime, OffsetDateTime, Date, Time, Duration}; +use time::{UtcOffset, PrimitiveDateTime, OffsetDateTime, Date, Time}; -use crate::{TimeZone, OffsetResult, PrimitiveDateTimeExt, OffsetDateTimeExt}; +use crate::{TimeZone, OffsetResult, PrimitiveDateTimeExt, ToTimezone}; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd)] pub struct ZonedDateTime<'a, T: TimeZone> { @@ -99,71 +99,67 @@ impl<'a, T: TimeZone> ZonedDateTime<'a, T> { } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ComponentDuration { - Date(Duration), - Time(Duration) +pub enum Duration { + Date(time::Duration), + Time(time::Duration) } -impl From for ComponentDuration { - fn from(value: Duration) -> Self { - ComponentDuration::Time(value) +impl From for Duration { + fn from(value: time::Duration) -> Self { + Duration::Time(value) } } -impl ComponentDuration { - /// Create a new `ComponentDuration` with the given number of weeks. Equivalent to - /// `ComponentDuration::seconds(weeks * 604_800)`. +impl Duration { + /// Create a new `Duration` with the given number of weeks. pub const fn weeks(weeks: i64) -> Self { - Self::Date(Duration::weeks(weeks)) + Self::Date(time::Duration::weeks(weeks)) } - /// Create a new `ComponentDuration` with the given number of days. Equivalent to - /// `ComponentDuration::seconds(days * 86_400)`. + /// Create a new `Duration` with the given number of days. pub const fn days(days: i64) -> Self { - Self::Date(Duration::days(days)) + Self::Date(time::Duration::days(days)) } - /// Create a new `ComponentDuration` with the given number of hours. Equivalent to - /// `ComponentDuration::seconds(hours * 3_600)`. + /// Create a new `Duration` with the given number of hours. pub const fn hours(hours: i64) -> Self { - Self::Time(Duration::hours(hours)) + Self::Time(time::Duration::hours(hours)) } - /// Create a new `ComponentDuration` with the given number of minutes. Equivalent to - /// `ComponentDuration::seconds(minutes * 60)`. + /// Create a new `Duration` with the given number of minutes. pub const fn minutes(minutes: i64) -> Self { - Self::Time(Duration::minutes(minutes)) + Self::Time(time::Duration::minutes(minutes)) } - /// Create a new `ComponentDuration` with the given number of seconds. + /// Create a new `Duration` with the given number of seconds. pub const fn seconds(seconds: i64) -> Self { - ComponentDuration::Time(Duration::seconds(seconds)) + Self::Time(time::Duration::seconds(seconds)) } } -impl<'a, T: TimeZone> Add for ZonedDateTime<'a, T> { +impl<'a, T: TimeZone> Add for ZonedDateTime<'a, T> { type Output = ZonedDateTime<'a, T>; - fn add(self, rhs: ComponentDuration) -> Self::Output { + fn add(self, rhs: Duration) -> Self::Output { match rhs { - ComponentDuration::Date(v) => ZonedDateTime::from_local_offset(self.date_time + v, self.timezone).unwrap_first(), - ComponentDuration::Time(v) => { + Duration::Date(v) => ZonedDateTime::from_local_offset(self.date_time + v, self.timezone).unwrap_first(), + Duration::Time(v) => { let offset = self.offset(); - ZonedDateTime::from_local_offset(self.date_time + v + Duration::seconds(offset.whole_seconds() as _), self.timezone).unwrap_first() + ZonedDateTime::from_local_offset(self.date_time + v + time::Duration::seconds(offset.whole_seconds() as _), self.timezone).unwrap_first() } } } } -impl<'a, T: TimeZone> Sub for ZonedDateTime<'a, T> { +impl<'a, T: TimeZone> Sub for ZonedDateTime<'a, T> { type Output = ZonedDateTime<'a, T>; - fn sub(self, rhs: ComponentDuration) -> Self::Output { + fn sub(self, rhs: Duration) -> Self::Output { match rhs { - ComponentDuration::Date(v) => ZonedDateTime::from_local_offset(self.date_time - v, self.timezone).unwrap_first(), - ComponentDuration::Time(v) => { + Duration::Date(v) => ZonedDateTime::from_local_offset(self.date_time - v, self.timezone).unwrap_first(), + Duration::Time(v) => { let offset = self.offset(); - ZonedDateTime::from_local_offset(self.date_time - v - Duration::seconds(offset.whole_seconds() as _), self.timezone).unwrap_first() + ZonedDateTime::from_local_offset(self.date_time - v - time::Duration::seconds(offset.whole_seconds() as _), self.timezone).unwrap_first() } } }