chrono_tz/
lib.rs

1//! # Chrono-TZ
2//!
3//! `Chrono-TZ` is a library that provides implementors of the
4//! [`TimeZone`][timezone] trait for [`chrono`][chrono]. The
5//! impls are generated by a build script using the [`IANA database`][iana]
6//! and [`zoneinfo_parse`][zoneinfo_parse].
7//!
8//! [chrono]: https://github.com/lifthrasiir/rust-chrono
9//! [timezone]: https://lifthrasiir.github.io/rust-chrono/chrono/offset/trait.TimeZone.html
10//! [iana]: http://www.iana.org/time-zones
11//! [zoneinfo_parse]: https://github.com/rust-datetime/zoneinfo-parse
12//!
13//! ## Examples
14//!
15//! Create a time in one timezone and convert it to UTC
16//!
17//! ```
18//! # extern crate chrono;
19//! # extern crate chrono_tz;
20//! use chrono::{TimeZone, Utc};
21//! use chrono_tz::US::Pacific;
22//!
23//! # fn main() {
24//! let pacific_time = Pacific.ymd(1990, 5, 6).and_hms(12, 30, 45);
25//! let utc_time = pacific_time.with_timezone(&Utc);
26//! assert_eq!(utc_time, Utc.ymd(1990, 5, 6).and_hms(19, 30, 45));
27//! # }
28//! ```
29//!
30//! Create a naive datetime and convert it to a timezone-aware datetime
31//!
32//! ```
33//! # extern crate chrono;
34//! # extern crate chrono_tz;
35//! use chrono::{TimeZone, NaiveDate};
36//! use chrono_tz::Africa::Johannesburg;
37//!
38//! # fn main() {
39//! let naive_dt = NaiveDate::from_ymd(2038, 1, 19).and_hms(3, 14, 08);
40//! let tz_aware = Johannesburg.from_local_datetime(&naive_dt).unwrap();
41//! assert_eq!(tz_aware.to_string(), "2038-01-19 03:14:08 SAST");
42//! # }
43//! ```
44//!
45//! London and New York change their clocks on different days in March
46//! so only have a 4-hour difference on certain days.
47//!
48//! ```
49//! # extern crate chrono;
50//! # extern crate chrono_tz;
51//! use chrono::TimeZone;
52//! use chrono_tz::Europe::London;
53//! use chrono_tz::America::New_York;
54//!
55//! # fn main() {
56//! let london_time = London.ymd(2016, 3, 18).and_hms(3, 0, 0);
57//! let ny_time = london_time.with_timezone(&New_York);
58//! assert_eq!(ny_time, New_York.ymd(2016, 3, 17).and_hms(23, 0, 0));
59//! # }
60//! ```
61//!
62//! Adding 24 hours across a daylight savings change causes a change
63//! in local time
64//!
65//! ```
66//! # extern crate chrono;
67//! # extern crate chrono_tz;
68//! use chrono::{TimeZone, Duration};
69//! use chrono_tz::Europe::London;
70//!
71//! # fn main() {
72//! let dt = London.ymd(2016, 10, 29).and_hms(12, 0, 0);
73//! let later = dt + Duration::hours(24);
74//! assert_eq!(later, London.ymd(2016, 10, 30).and_hms(11, 0, 0));
75//! # }
76//! ```
77//!
78//! And of course you can always convert a local time to a unix timestamp
79//!
80//! ```
81//! # extern crate chrono;
82//! # extern crate chrono_tz;
83//! use chrono::TimeZone;
84//! use chrono_tz::Asia::Kolkata;
85//!
86//! # fn main() {
87//! let dt = Kolkata.ymd(2000, 1, 1).and_hms(0, 0, 0);
88//! let timestamp = dt.timestamp();
89//! assert_eq!(timestamp, 946665000);
90//! # }
91//! ```
92//!
93//! Pretty-printing a string will use the correct abbreviation for the timezone
94//!
95//! ```
96//! # extern crate chrono;
97//! # extern crate chrono_tz;
98//! use chrono::TimeZone;
99//! use chrono_tz::Europe::London;
100//!
101//! # fn main() {
102//! let dt = London.ymd(2016, 5, 10).and_hms(12, 0, 0);
103//! assert_eq!(dt.to_string(), "2016-05-10 12:00:00 BST");
104//! assert_eq!(dt.to_rfc3339(), "2016-05-10T12:00:00+01:00");
105//! # }
106//! ```
107//!
108//! You can convert a timezone string to a timezone using the `FromStr` trait
109//!
110//! ```
111//! # extern crate chrono;
112//! # extern crate chrono_tz;
113//! use chrono::TimeZone;
114//! use chrono_tz::Tz;
115//! use chrono_tz::UTC;
116//!
117//! # fn main() {
118//! let tz: Tz = "Antarctica/South_Pole".parse().unwrap();
119//! let dt = tz.ymd(2016, 10, 22).and_hms(12, 0, 0);
120//! let utc = dt.with_timezone(&UTC);
121//! assert_eq!(utc.to_string(), "2016-10-21 23:00:00 UTC");
122//! # }
123//! ```
124//!
125//! If you need to iterate over all variants you can use the `TZ_VARIANTS` array
126//! ```
127//! use chrono_tz::{TZ_VARIANTS, Tz};
128//! assert!(TZ_VARIANTS.iter().any(|v| *v == Tz::UTC));
129//! ```
130
131#![cfg_attr(not(any(feature = "std", test)), no_std)]
132#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
133
134#[cfg(feature = "serde")]
135mod serde;
136
137mod binary_search;
138mod directory;
139mod timezone_impl;
140mod timezones;
141
142pub use crate::directory::*;
143pub use crate::timezone_impl::{OffsetComponents, OffsetName};
144pub use crate::timezones::ParseError;
145pub use crate::timezones::Tz;
146pub use crate::timezones::TZ_VARIANTS;
147pub use crate::IANA_TZDB_VERSION;
148
149#[cfg(test)]
150mod tests {
151    use super::America::Danmarkshavn;
152    use super::Asia::Dhaka;
153    use super::Australia::Adelaide;
154    use super::Europe::Berlin;
155    use super::Europe::London;
156    use super::Europe::Moscow;
157    use super::Europe::Vilnius;
158    use super::Europe::Warsaw;
159    use super::Pacific::Apia;
160    use super::Pacific::Noumea;
161    use super::Pacific::Tahiti;
162    use super::Tz;
163    use super::IANA_TZDB_VERSION;
164    use super::US::Eastern;
165    use super::UTC;
166    use chrono::{Duration, NaiveDate, TimeZone};
167
168    #[test]
169    fn london_to_berlin() {
170        let dt = London.with_ymd_and_hms(2016, 10, 8, 17, 0, 0).unwrap();
171        let converted = dt.with_timezone(&Berlin);
172        let expected = Berlin.with_ymd_and_hms(2016, 10, 8, 18, 0, 0).unwrap();
173        assert_eq!(converted, expected);
174    }
175
176    #[test]
177    fn us_eastern_dst_commutativity() {
178        let dt = UTC.with_ymd_and_hms(2002, 4, 7, 7, 0, 0).unwrap();
179        for days in -420..720 {
180            let dt1 = (dt + Duration::days(days)).with_timezone(&Eastern);
181            let dt2 = dt.with_timezone(&Eastern) + Duration::days(days);
182            assert_eq!(dt1, dt2);
183        }
184    }
185
186    #[test]
187    fn test_addition_across_dst_boundary() {
188        use chrono::TimeZone;
189        let two_hours = Duration::hours(2);
190        let edt = Eastern.with_ymd_and_hms(2019, 11, 3, 0, 0, 0).unwrap();
191        let est = edt + two_hours;
192
193        assert_eq!(edt.to_string(), "2019-11-03 00:00:00 EDT".to_string());
194        assert_eq!(est.to_string(), "2019-11-03 01:00:00 EST".to_string());
195        assert_eq!(est.timestamp(), edt.timestamp() + two_hours.num_seconds());
196    }
197
198    #[test]
199    fn warsaw_tz_name() {
200        let dt = UTC.with_ymd_and_hms(1915, 8, 4, 22, 35, 59).unwrap();
201        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "WMT");
202        let dt = dt + Duration::seconds(1);
203        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "CET");
204    }
205
206    #[test]
207    fn vilnius_utc_offset() {
208        let dt = UTC.with_ymd_and_hms(1916, 12, 31, 22, 35, 59).unwrap().with_timezone(&Vilnius);
209        assert_eq!(dt, Vilnius.with_ymd_and_hms(1916, 12, 31, 23, 59, 59).unwrap());
210        let dt = dt + Duration::seconds(1);
211        assert_eq!(dt, Vilnius.with_ymd_and_hms(1917, 1, 1, 0, 11, 36).unwrap());
212    }
213
214    #[test]
215    fn victorian_times() {
216        let dt = UTC.with_ymd_and_hms(1847, 12, 1, 0, 1, 14).unwrap().with_timezone(&London);
217        assert_eq!(dt, London.with_ymd_and_hms(1847, 11, 30, 23, 59, 59).unwrap());
218        let dt = dt + Duration::seconds(1);
219        assert_eq!(dt, London.with_ymd_and_hms(1847, 12, 1, 0, 1, 15).unwrap());
220    }
221
222    #[test]
223    fn london_dst() {
224        let dt = London.with_ymd_and_hms(2016, 3, 10, 5, 0, 0).unwrap();
225        let later = dt + Duration::days(180);
226        let expected = London.with_ymd_and_hms(2016, 9, 6, 6, 0, 0).unwrap();
227        assert_eq!(later, expected);
228    }
229
230    #[test]
231    fn international_date_line_change() {
232        let dt = UTC.with_ymd_and_hms(2011, 12, 30, 9, 59, 59).unwrap().with_timezone(&Apia);
233        assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 29, 23, 59, 59).unwrap());
234        let dt = dt + Duration::seconds(1);
235        assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 31, 0, 0, 0).unwrap());
236    }
237
238    #[test]
239    fn negative_offset_with_minutes_and_seconds() {
240        let dt = UTC.with_ymd_and_hms(1900, 1, 1, 12, 0, 0).unwrap().with_timezone(&Danmarkshavn);
241        assert_eq!(dt, Danmarkshavn.with_ymd_and_hms(1900, 1, 1, 10, 45, 20).unwrap());
242    }
243
244    #[test]
245    fn monotonicity() {
246        let mut dt = Noumea.with_ymd_and_hms(1800, 1, 1, 12, 0, 0).unwrap();
247        for _ in 0..24 * 356 * 400 {
248            let new = dt + Duration::hours(1);
249            assert!(new > dt);
250            assert!(new.with_timezone(&UTC) > dt.with_timezone(&UTC));
251            dt = new;
252        }
253    }
254
255    fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) {
256        for y in begin..end {
257            for d in 1..366 {
258                let date = NaiveDate::from_yo_opt(y, d).unwrap();
259                for h in 0..24 {
260                    for m in 0..60 {
261                        let dt = date.and_hms_opt(h, m, 0).unwrap().and_utc();
262                        let with_tz = dt.with_timezone(&tz);
263                        let utc = with_tz.with_timezone(&UTC);
264                        assert_eq!(dt, utc);
265                    }
266                }
267            }
268        }
269    }
270
271    #[test]
272    fn inverse_london() {
273        test_inverse(London, 1989, 1994);
274    }
275
276    #[test]
277    fn inverse_dhaka() {
278        test_inverse(Dhaka, 1995, 2000);
279    }
280
281    #[test]
282    fn inverse_apia() {
283        test_inverse(Apia, 2011, 2012);
284    }
285
286    #[test]
287    fn inverse_tahiti() {
288        test_inverse(Tahiti, 1911, 1914);
289    }
290
291    #[test]
292    fn string_representation() {
293        let dt = UTC.with_ymd_and_hms(2000, 9, 1, 12, 30, 15).unwrap().with_timezone(&Adelaide);
294        assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST");
295        assert_eq!(format!("{:?}", dt), "2000-09-01T22:00:15ACST");
296        assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30");
297        assert_eq!(format!("{}", dt), "2000-09-01 22:00:15 ACST");
298    }
299
300    #[test]
301    fn tahiti() {
302        let dt = UTC.with_ymd_and_hms(1912, 10, 1, 9, 58, 16).unwrap().with_timezone(&Tahiti);
303        let before = dt - Duration::hours(1);
304        assert_eq!(before, Tahiti.with_ymd_and_hms(1912, 9, 30, 23, 0, 0).unwrap());
305        let after = dt + Duration::hours(1);
306        assert_eq!(after, Tahiti.with_ymd_and_hms(1912, 10, 1, 0, 58, 16).unwrap());
307    }
308
309    #[test]
310    fn nonexistent_time() {
311        assert!(London.with_ymd_and_hms(2016, 3, 27, 1, 30, 0).single().is_none());
312    }
313
314    #[test]
315    fn nonexistent_time_2() {
316        assert!(London.with_ymd_and_hms(2016, 3, 27, 1, 0, 0).single().is_none());
317    }
318
319    #[test]
320    fn time_exists() {
321        assert!(London.with_ymd_and_hms(2016, 3, 27, 2, 0, 0).single().is_some());
322    }
323
324    #[test]
325    fn ambiguous_time() {
326        let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 0, 0);
327        let earliest_utc =
328            NaiveDate::from_ymd_opt(2016, 10, 30).unwrap().and_hms_opt(0, 0, 0).unwrap();
329        assert_eq!(ambiguous.earliest().unwrap(), London.from_utc_datetime(&earliest_utc));
330        let latest_utc =
331            NaiveDate::from_ymd_opt(2016, 10, 30).unwrap().and_hms_opt(1, 0, 0).unwrap();
332        assert_eq!(ambiguous.latest().unwrap(), London.from_utc_datetime(&latest_utc));
333    }
334
335    #[test]
336    fn ambiguous_time_2() {
337        let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 30, 0);
338        let earliest_utc =
339            NaiveDate::from_ymd_opt(2016, 10, 30).unwrap().and_hms_opt(0, 30, 0).unwrap();
340        assert_eq!(ambiguous.earliest().unwrap(), London.from_utc_datetime(&earliest_utc));
341        let latest_utc =
342            NaiveDate::from_ymd_opt(2016, 10, 30).unwrap().and_hms_opt(1, 30, 0).unwrap();
343        assert_eq!(ambiguous.latest().unwrap(), London.from_utc_datetime(&latest_utc));
344    }
345
346    #[test]
347    fn ambiguous_time_3() {
348        let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 30, 0);
349        let earliest_utc =
350            NaiveDate::from_ymd_opt(2014, 10, 25).unwrap().and_hms_opt(21, 30, 0).unwrap();
351        assert_eq!(
352            ambiguous.earliest().unwrap().fixed_offset(),
353            Moscow.from_utc_datetime(&earliest_utc).fixed_offset()
354        );
355        let latest_utc =
356            NaiveDate::from_ymd_opt(2014, 10, 25).unwrap().and_hms_opt(22, 30, 0).unwrap();
357        assert_eq!(ambiguous.latest().unwrap(), Moscow.from_utc_datetime(&latest_utc));
358    }
359
360    #[test]
361    fn ambiguous_time_4() {
362        let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 0, 0);
363        let earliest_utc =
364            NaiveDate::from_ymd_opt(2014, 10, 25).unwrap().and_hms_opt(21, 0, 0).unwrap();
365        assert_eq!(
366            ambiguous.earliest().unwrap().fixed_offset(),
367            Moscow.from_utc_datetime(&earliest_utc).fixed_offset()
368        );
369        let latest_utc =
370            NaiveDate::from_ymd_opt(2014, 10, 25).unwrap().and_hms_opt(22, 0, 0).unwrap();
371        assert_eq!(ambiguous.latest().unwrap(), Moscow.from_utc_datetime(&latest_utc));
372    }
373
374    #[test]
375    fn unambiguous_time() {
376        assert!(London.with_ymd_and_hms(2016, 10, 30, 2, 0, 0).single().is_some());
377    }
378
379    #[test]
380    fn unambiguous_time_2() {
381        assert!(Moscow.with_ymd_and_hms(2014, 10, 26, 2, 0, 0).single().is_some());
382    }
383
384    #[test]
385    fn test_get_name() {
386        assert_eq!(London.name(), "Europe/London");
387        assert_eq!(Tz::Africa__Abidjan.name(), "Africa/Abidjan");
388        assert_eq!(Tz::UTC.name(), "UTC");
389        assert_eq!(Tz::Zulu.name(), "Zulu");
390    }
391
392    #[test]
393    fn test_display() {
394        assert_eq!(format!("{}", London), "Europe/London");
395        assert_eq!(format!("{}", Tz::Africa__Abidjan), "Africa/Abidjan");
396        assert_eq!(format!("{}", Tz::UTC), "UTC");
397        assert_eq!(format!("{}", Tz::Zulu), "Zulu");
398    }
399
400    #[test]
401    fn test_impl_hash() {
402        #[allow(dead_code)]
403        #[derive(Hash)]
404        struct Foo(Tz);
405    }
406
407    #[test]
408    fn test_iana_tzdb_version() {
409        // Format should be something like 2023c.
410        assert_eq!(5, IANA_TZDB_VERSION.len());
411        let numbers: Vec<&str> = IANA_TZDB_VERSION.matches(char::is_numeric).collect();
412        assert_eq!(4, numbers.len());
413        assert!(IANA_TZDB_VERSION.ends_with(|c: char| c.is_ascii_lowercase()));
414    }
415}