| //! # Chrono-TZ |
| //! |
| //! `Chrono-TZ` is a library that provides implementors of the |
| //! [`TimeZone`][timezone] trait for [`chrono`][chrono]. The |
| //! impls are generated by a build script using the [`IANA database`][iana] |
| //! and [`zoneinfo_parse`][zoneinfo_parse]. |
| //! |
| //! [chrono]: https://github.com/lifthrasiir/rust-chrono |
| //! [timezone]: https://lifthrasiir.github.io/rust-chrono/chrono/offset/trait.TimeZone.html |
| //! [iana]: http://www.iana.org/time-zones |
| //! [zoneinfo_parse]: https://github.com/rust-datetime/zoneinfo-parse |
| //! |
| //! ## Examples |
| //! |
| //! Create a time in one timezone and convert it to UTC |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::{TimeZone, Utc}; |
| //! use chrono_tz::US::Pacific; |
| //! |
| //! # fn main() { |
| //! let pacific_time = Pacific.ymd(1990, 5, 6).and_hms(12, 30, 45); |
| //! let utc_time = pacific_time.with_timezone(&Utc); |
| //! assert_eq!(utc_time, Utc.ymd(1990, 5, 6).and_hms(19, 30, 45)); |
| //! # } |
| //! ``` |
| //! |
| //! Create a naive datetime and convert it to a timezone-aware datetime |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::{TimeZone, NaiveDate}; |
| //! use chrono_tz::Africa::Johannesburg; |
| //! |
| //! # fn main() { |
| //! let naive_dt = NaiveDate::from_ymd(2038, 1, 19).and_hms(3, 14, 08); |
| //! let tz_aware = Johannesburg.from_local_datetime(&naive_dt).unwrap(); |
| //! assert_eq!(tz_aware.to_string(), "2038-01-19 03:14:08 SAST"); |
| //! # } |
| //! ``` |
| //! |
| //! London and New York change their clocks on different days in March |
| //! so only have a 4-hour difference on certain days. |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::TimeZone; |
| //! use chrono_tz::Europe::London; |
| //! use chrono_tz::America::New_York; |
| //! |
| //! # fn main() { |
| //! let london_time = London.ymd(2016, 3, 18).and_hms(3, 0, 0); |
| //! let ny_time = london_time.with_timezone(&New_York); |
| //! assert_eq!(ny_time, New_York.ymd(2016, 3, 17).and_hms(23, 0, 0)); |
| //! # } |
| //! ``` |
| //! |
| //! Adding 24 hours across a daylight savings change causes a change |
| //! in local time |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::{TimeZone, Duration}; |
| //! use chrono_tz::Europe::London; |
| //! |
| //! # fn main() { |
| //! let dt = London.ymd(2016, 10, 29).and_hms(12, 0, 0); |
| //! let later = dt + Duration::hours(24); |
| //! assert_eq!(later, London.ymd(2016, 10, 30).and_hms(11, 0, 0)); |
| //! # } |
| //! ``` |
| //! |
| //! And of course you can always convert a local time to a unix timestamp |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::TimeZone; |
| //! use chrono_tz::Asia::Kolkata; |
| //! |
| //! # fn main() { |
| //! let dt = Kolkata.ymd(2000, 1, 1).and_hms(0, 0, 0); |
| //! let timestamp = dt.timestamp(); |
| //! assert_eq!(timestamp, 946665000); |
| //! # } |
| //! ``` |
| //! |
| //! Pretty-printing a string will use the correct abbreviation for the timezone |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::TimeZone; |
| //! use chrono_tz::Europe::London; |
| //! |
| //! # fn main() { |
| //! let dt = London.ymd(2016, 5, 10).and_hms(12, 0, 0); |
| //! assert_eq!(dt.to_string(), "2016-05-10 12:00:00 BST"); |
| //! assert_eq!(dt.to_rfc3339(), "2016-05-10T12:00:00+01:00"); |
| //! # } |
| //! ``` |
| //! |
| //! You can convert a timezone string to a timezone using the `FromStr` trait |
| //! |
| //! ``` |
| //! # extern crate chrono; |
| //! # extern crate chrono_tz; |
| //! use chrono::TimeZone; |
| //! use chrono_tz::Tz; |
| //! use chrono_tz::UTC; |
| //! |
| //! # fn main() { |
| //! let tz: Tz = "Antarctica/South_Pole".parse().unwrap(); |
| //! let dt = tz.ymd(2016, 10, 22).and_hms(12, 0, 0); |
| //! let utc = dt.with_timezone(&UTC); |
| //! assert_eq!(utc.to_string(), "2016-10-21 23:00:00 UTC"); |
| //! # } |
| //! ``` |
| //! |
| //! If you need to iterate over all variants you can use the `TZ_VARIANTS` array |
| //! ``` |
| //! use chrono_tz::{TZ_VARIANTS, Tz}; |
| //! assert!(TZ_VARIANTS.iter().any(|v| *v == Tz::UTC)); |
| //! ``` |
| |
| #![cfg_attr(not(any(feature = "std", test)), no_std)] |
| #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] |
| |
| #[cfg(feature = "serde")] |
| mod serde; |
| |
| mod binary_search; |
| mod directory; |
| mod timezone_impl; |
| mod timezones; |
| |
| pub use crate::directory::*; |
| pub use crate::timezone_impl::{OffsetComponents, OffsetName, TzOffset}; |
| pub use crate::timezones::ParseError; |
| pub use crate::timezones::Tz; |
| pub use crate::timezones::TZ_VARIANTS; |
| pub use crate::IANA_TZDB_VERSION; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::Africa::Addis_Ababa; |
| use super::America::Danmarkshavn; |
| use super::America::Scoresbysund; |
| use super::Antarctica::Casey; |
| use super::Asia::Dhaka; |
| use super::Australia::Adelaide; |
| use super::Europe::Berlin; |
| use super::Europe::London; |
| use super::Europe::Moscow; |
| use super::Europe::Vilnius; |
| use super::Europe::Warsaw; |
| use super::Pacific::Apia; |
| use super::Pacific::Noumea; |
| use super::Pacific::Tahiti; |
| use super::Tz; |
| use super::IANA_TZDB_VERSION; |
| use super::US::Eastern; |
| use super::UTC; |
| use chrono::{Duration, NaiveDate, TimeZone}; |
| |
| #[test] |
| fn london_to_berlin() { |
| let dt = London.with_ymd_and_hms(2016, 10, 8, 17, 0, 0).unwrap(); |
| let converted = dt.with_timezone(&Berlin); |
| let expected = Berlin.with_ymd_and_hms(2016, 10, 8, 18, 0, 0).unwrap(); |
| assert_eq!(converted, expected); |
| } |
| |
| #[test] |
| fn us_eastern_dst_commutativity() { |
| let dt = UTC.with_ymd_and_hms(2002, 4, 7, 7, 0, 0).unwrap(); |
| for days in -420..720 { |
| let dt1 = (dt + Duration::days(days)).with_timezone(&Eastern); |
| let dt2 = dt.with_timezone(&Eastern) + Duration::days(days); |
| assert_eq!(dt1, dt2); |
| } |
| } |
| |
| #[test] |
| fn test_addition_across_dst_boundary() { |
| use chrono::TimeZone; |
| let two_hours = Duration::hours(2); |
| let edt = Eastern.with_ymd_and_hms(2019, 11, 3, 0, 0, 0).unwrap(); |
| let est = edt + two_hours; |
| |
| assert_eq!(edt.to_string(), "2019-11-03 00:00:00 EDT".to_string()); |
| assert_eq!(est.to_string(), "2019-11-03 01:00:00 EST".to_string()); |
| assert_eq!(est.timestamp(), edt.timestamp() + two_hours.num_seconds()); |
| } |
| |
| #[test] |
| fn warsaw_tz_name() { |
| let dt = UTC.with_ymd_and_hms(1915, 8, 4, 22, 35, 59).unwrap(); |
| assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "WMT"); |
| let dt = dt + Duration::seconds(1); |
| assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "CET"); |
| } |
| |
| #[test] |
| fn vilnius_utc_offset() { |
| let dt = UTC |
| .with_ymd_and_hms(1916, 12, 31, 22, 35, 59) |
| .unwrap() |
| .with_timezone(&Vilnius); |
| assert_eq!( |
| dt, |
| Vilnius.with_ymd_and_hms(1916, 12, 31, 23, 59, 59).unwrap() |
| ); |
| let dt = dt + Duration::seconds(1); |
| assert_eq!(dt, Vilnius.with_ymd_and_hms(1917, 1, 1, 0, 11, 36).unwrap()); |
| } |
| |
| #[test] |
| fn victorian_times() { |
| let dt = UTC |
| .with_ymd_and_hms(1847, 12, 1, 0, 1, 14) |
| .unwrap() |
| .with_timezone(&London); |
| assert_eq!( |
| dt, |
| London.with_ymd_and_hms(1847, 11, 30, 23, 59, 59).unwrap() |
| ); |
| let dt = dt + Duration::seconds(1); |
| assert_eq!(dt, London.with_ymd_and_hms(1847, 12, 1, 0, 1, 15).unwrap()); |
| } |
| |
| #[test] |
| fn london_dst() { |
| let dt = London.with_ymd_and_hms(2016, 3, 10, 5, 0, 0).unwrap(); |
| let later = dt + Duration::days(180); |
| let expected = London.with_ymd_and_hms(2016, 9, 6, 6, 0, 0).unwrap(); |
| assert_eq!(later, expected); |
| } |
| |
| #[test] |
| fn international_date_line_change() { |
| let dt = UTC |
| .with_ymd_and_hms(2011, 12, 30, 9, 59, 59) |
| .unwrap() |
| .with_timezone(&Apia); |
| assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 29, 23, 59, 59).unwrap()); |
| let dt = dt + Duration::seconds(1); |
| assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 31, 0, 0, 0).unwrap()); |
| } |
| |
| #[test] |
| fn negative_offset_with_minutes_and_seconds() { |
| let dt = UTC |
| .with_ymd_and_hms(1900, 1, 1, 12, 0, 0) |
| .unwrap() |
| .with_timezone(&Danmarkshavn); |
| assert_eq!( |
| dt, |
| Danmarkshavn |
| .with_ymd_and_hms(1900, 1, 1, 10, 45, 20) |
| .unwrap() |
| ); |
| } |
| |
| #[test] |
| fn monotonicity() { |
| let mut dt = Noumea.with_ymd_and_hms(1800, 1, 1, 12, 0, 0).unwrap(); |
| for _ in 0..24 * 356 * 400 { |
| let new = dt + Duration::hours(1); |
| assert!(new > dt); |
| assert!(new.with_timezone(&UTC) > dt.with_timezone(&UTC)); |
| dt = new; |
| } |
| } |
| |
| fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) { |
| for y in begin..end { |
| for d in 1..366 { |
| let date = NaiveDate::from_yo_opt(y, d).unwrap(); |
| for h in 0..24 { |
| for m in 0..60 { |
| let dt = date.and_hms_opt(h, m, 0).unwrap().and_utc(); |
| let with_tz = dt.with_timezone(&tz); |
| let utc = with_tz.with_timezone(&UTC); |
| assert_eq!(dt, utc); |
| } |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn inverse_london() { |
| test_inverse(London, 1989, 1994); |
| } |
| |
| #[test] |
| fn inverse_dhaka() { |
| test_inverse(Dhaka, 1995, 2000); |
| } |
| |
| #[test] |
| fn inverse_apia() { |
| test_inverse(Apia, 2011, 2012); |
| } |
| |
| #[test] |
| fn inverse_tahiti() { |
| test_inverse(Tahiti, 1911, 1914); |
| } |
| |
| #[test] |
| fn string_representation() { |
| let dt = UTC |
| .with_ymd_and_hms(2000, 9, 1, 12, 30, 15) |
| .unwrap() |
| .with_timezone(&Adelaide); |
| assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST"); |
| assert_eq!(format!("{:?}", dt), "2000-09-01T22:00:15ACST"); |
| assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30"); |
| assert_eq!(format!("{}", dt), "2000-09-01 22:00:15 ACST"); |
| } |
| |
| #[test] |
| fn tahiti() { |
| let dt = UTC |
| .with_ymd_and_hms(1912, 10, 1, 9, 58, 16) |
| .unwrap() |
| .with_timezone(&Tahiti); |
| let before = dt - Duration::hours(1); |
| assert_eq!( |
| before, |
| Tahiti.with_ymd_and_hms(1912, 9, 30, 23, 0, 0).unwrap() |
| ); |
| let after = dt + Duration::hours(1); |
| assert_eq!( |
| after, |
| Tahiti.with_ymd_and_hms(1912, 10, 1, 0, 58, 16).unwrap() |
| ); |
| } |
| |
| #[test] |
| fn nonexistent_time() { |
| assert!(London |
| .with_ymd_and_hms(2016, 3, 27, 1, 30, 0) |
| .single() |
| .is_none()); |
| } |
| |
| #[test] |
| fn nonexistent_time_2() { |
| assert!(London |
| .with_ymd_and_hms(2016, 3, 27, 1, 0, 0) |
| .single() |
| .is_none()); |
| } |
| |
| #[test] |
| fn time_exists() { |
| assert!(London |
| .with_ymd_and_hms(2016, 3, 27, 2, 0, 0) |
| .single() |
| .is_some()); |
| } |
| |
| #[test] |
| fn ambiguous_time() { |
| let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 0, 0); |
| let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
| .unwrap() |
| .and_hms_opt(0, 0, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.earliest().unwrap(), |
| London.from_utc_datetime(&earliest_utc) |
| ); |
| let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
| .unwrap() |
| .and_hms_opt(1, 0, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.latest().unwrap(), |
| London.from_utc_datetime(&latest_utc) |
| ); |
| } |
| |
| #[test] |
| fn ambiguous_time_2() { |
| let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 30, 0); |
| let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
| .unwrap() |
| .and_hms_opt(0, 30, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.earliest().unwrap(), |
| London.from_utc_datetime(&earliest_utc) |
| ); |
| let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
| .unwrap() |
| .and_hms_opt(1, 30, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.latest().unwrap(), |
| London.from_utc_datetime(&latest_utc) |
| ); |
| } |
| |
| #[test] |
| fn ambiguous_time_3() { |
| let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 30, 0); |
| let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
| .unwrap() |
| .and_hms_opt(21, 30, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.earliest().unwrap().fixed_offset(), |
| Moscow.from_utc_datetime(&earliest_utc).fixed_offset() |
| ); |
| let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
| .unwrap() |
| .and_hms_opt(22, 30, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.latest().unwrap(), |
| Moscow.from_utc_datetime(&latest_utc) |
| ); |
| } |
| |
| #[test] |
| fn ambiguous_time_4() { |
| let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 0, 0); |
| let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
| .unwrap() |
| .and_hms_opt(21, 0, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.earliest().unwrap().fixed_offset(), |
| Moscow.from_utc_datetime(&earliest_utc).fixed_offset() |
| ); |
| let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
| .unwrap() |
| .and_hms_opt(22, 0, 0) |
| .unwrap(); |
| assert_eq!( |
| ambiguous.latest().unwrap(), |
| Moscow.from_utc_datetime(&latest_utc) |
| ); |
| } |
| |
| #[test] |
| fn unambiguous_time() { |
| assert!(London |
| .with_ymd_and_hms(2016, 10, 30, 2, 0, 0) |
| .single() |
| .is_some()); |
| } |
| |
| #[test] |
| fn unambiguous_time_2() { |
| assert!(Moscow |
| .with_ymd_and_hms(2014, 10, 26, 2, 0, 0) |
| .single() |
| .is_some()); |
| } |
| |
| #[test] |
| fn test_get_name() { |
| assert_eq!(London.name(), "Europe/London"); |
| assert_eq!(Tz::Africa__Abidjan.name(), "Africa/Abidjan"); |
| assert_eq!(Tz::UTC.name(), "UTC"); |
| assert_eq!(Tz::Zulu.name(), "Zulu"); |
| } |
| |
| #[test] |
| fn test_display() { |
| assert_eq!(format!("{}", London), "Europe/London"); |
| assert_eq!(format!("{}", Tz::Africa__Abidjan), "Africa/Abidjan"); |
| assert_eq!(format!("{}", Tz::UTC), "UTC"); |
| assert_eq!(format!("{}", Tz::Zulu), "Zulu"); |
| } |
| |
| #[test] |
| fn test_impl_hash() { |
| #[allow(dead_code)] |
| #[derive(Hash)] |
| struct Foo(Tz); |
| } |
| |
| #[test] |
| fn test_iana_tzdb_version() { |
| // Format should be something like 2023c. |
| assert_eq!(5, IANA_TZDB_VERSION.len()); |
| let numbers: Vec<&str> = IANA_TZDB_VERSION.matches(char::is_numeric).collect(); |
| assert_eq!(4, numbers.len()); |
| assert!(IANA_TZDB_VERSION.ends_with(|c: char| c.is_ascii_lowercase())); |
| } |
| |
| #[test] |
| fn test_numeric_names() { |
| let dt = Scoresbysund |
| .with_ymd_and_hms(2024, 05, 01, 0, 0, 0) |
| .unwrap(); |
| assert_eq!(format!("{}", dt.offset()), "-01"); |
| assert_eq!(format!("{:?}", dt.offset()), "-01"); |
| let dt = Casey.with_ymd_and_hms(2022, 11, 01, 0, 0, 0).unwrap(); |
| assert_eq!(format!("{}", dt.offset()), "+11"); |
| assert_eq!(format!("{:?}", dt.offset()), "+11"); |
| let dt = Addis_Ababa.with_ymd_and_hms(1937, 02, 01, 0, 0, 0).unwrap(); |
| assert_eq!(format!("{}", dt.offset()), "+0245"); |
| assert_eq!(format!("{:?}", dt.offset()), "+0245"); |
| } |
| } |