Skip to content

Commit

Permalink
Large refactor and replacement of to_timezone by a new ToTimezone tra…
Browse files Browse the repository at this point in the history
…it to support multiple possible inputs
  • Loading branch information
Yuri6037 committed Oct 29, 2023
1 parent 99a0043 commit 088f34d
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 157 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
description = "Implementation of tz database (IANA) for the time Rust crate."
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
132 changes: 132 additions & 0 deletions src/ext.rs
Original file line number Diff line number Diff line change
@@ -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<OffsetDateTime>`
fn assume_timezone<T: TimeZone>(&self, tz: &T) -> OffsetResult<OffsetDateTime>;

/// 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<T: TimeZone>(&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<zoned::ZonedDateTime<'a, T>>;
}

impl PrimitiveDateTimeExt for PrimitiveDateTime {
fn assume_timezone<T: TimeZone>(&self, tz: &T) -> OffsetResult<OffsetDateTime> {
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<T: TimeZone>(&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<'a, T>> {
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<T: TimeZone> 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())
}
}
9 changes: 9 additions & 0 deletions src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@

use time::{OffsetDateTime, UtcOffset};

/// This trait allows conversions from one timezone to another.
pub trait ToTimezone<T> {
/// 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).
Expand Down
130 changes: 19 additions & 111 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: 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<OffsetDateTime>`
fn assume_timezone<T: TimeZone>(&self, tz: &T) -> OffsetResult<OffsetDateTime>;

/// 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<T: TimeZone>(&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<ZonedDateTime<'a, T>>;
}

impl PrimitiveDateTimeExt for PrimitiveDateTime {
fn assume_timezone<T: TimeZone>(&self, tz: &T) -> OffsetResult<OffsetDateTime> {
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<T: TimeZone>(&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<'a, T>> {
ZonedDateTime::from_local(self, tz)
}
}

impl OffsetDateTimeExt for OffsetDateTime {
fn to_timezone<T: 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;
Expand All @@ -147,24 +44,26 @@ mod timezone_impl;
pub mod timezones;

pub use interface::*;
pub use ext::*;

#[cfg(feature = "system")]
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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 2 additions & 3 deletions src/posix_tz/abstract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2022, Yuri6037
// Copyright (c) 2023, Yuri6037
//
// All rights reserved.
//
Expand Down Expand Up @@ -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> {
Expand Down
4 changes: 2 additions & 2 deletions src/posix_tz/intermediate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2022, Yuri6037
// Copyright (c) 2023, Yuri6037
//
// All rights reserved.
//
Expand Down Expand Up @@ -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<Dst<'a>>)),
}

Expand Down
Loading

0 comments on commit 088f34d

Please sign in to comment.