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::{GapInfo, OffsetComponents, OffsetName, TzOffset};
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::Africa::Addis_Ababa;
152    use super::America::Danmarkshavn;
153    use super::America::Scoresbysund;
154    use super::Antarctica::Casey;
155    use super::Asia::Dhaka;
156    use super::Australia::Adelaide;
157    use super::Europe::Berlin;
158    use super::Europe::London;
159    use super::Europe::Moscow;
160    use super::Europe::Vilnius;
161    use super::Europe::Warsaw;
162    use super::GapInfo;
163    use super::Pacific::Apia;
164    use super::Pacific::Noumea;
165    use super::Pacific::Tahiti;
166    use super::Tz;
167    use super::IANA_TZDB_VERSION;
168    use super::US::Eastern;
169    use super::UTC;
170    use chrono::NaiveDateTime;
171    use chrono::{Duration, NaiveDate, TimeZone};
172
173    #[test]
174    fn london_to_berlin() {
175        let dt = London.with_ymd_and_hms(2016, 10, 8, 17, 0, 0).unwrap();
176        let converted = dt.with_timezone(&Berlin);
177        let expected = Berlin.with_ymd_and_hms(2016, 10, 8, 18, 0, 0).unwrap();
178        assert_eq!(converted, expected);
179    }
180
181    #[test]
182    fn us_eastern_dst_commutativity() {
183        let dt = UTC.with_ymd_and_hms(2002, 4, 7, 7, 0, 0).unwrap();
184        for days in -420..720 {
185            let dt1 = (dt + Duration::days(days)).with_timezone(&Eastern);
186            let dt2 = dt.with_timezone(&Eastern) + Duration::days(days);
187            assert_eq!(dt1, dt2);
188        }
189    }
190
191    #[test]
192    fn test_addition_across_dst_boundary() {
193        use chrono::TimeZone;
194        let two_hours = Duration::hours(2);
195        let edt = Eastern.with_ymd_and_hms(2019, 11, 3, 0, 0, 0).unwrap();
196        let est = edt + two_hours;
197
198        assert_eq!(edt.to_string(), "2019-11-03 00:00:00 EDT".to_string());
199        assert_eq!(est.to_string(), "2019-11-03 01:00:00 EST".to_string());
200        assert_eq!(est.timestamp(), edt.timestamp() + two_hours.num_seconds());
201    }
202
203    #[test]
204    fn warsaw_tz_name() {
205        let dt = UTC.with_ymd_and_hms(1915, 8, 4, 22, 35, 59).unwrap();
206        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "WMT");
207        let dt = dt + Duration::seconds(1);
208        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "CET");
209    }
210
211    #[test]
212    fn vilnius_utc_offset() {
213        let dt = UTC
214            .with_ymd_and_hms(1916, 12, 31, 22, 35, 59)
215            .unwrap()
216            .with_timezone(&Vilnius);
217        assert_eq!(
218            dt,
219            Vilnius.with_ymd_and_hms(1916, 12, 31, 23, 59, 59).unwrap()
220        );
221        let dt = dt + Duration::seconds(1);
222        assert_eq!(dt, Vilnius.with_ymd_and_hms(1917, 1, 1, 0, 11, 36).unwrap());
223    }
224
225    #[test]
226    fn victorian_times() {
227        let dt = UTC
228            .with_ymd_and_hms(1847, 12, 1, 0, 1, 14)
229            .unwrap()
230            .with_timezone(&London);
231        assert_eq!(
232            dt,
233            London.with_ymd_and_hms(1847, 11, 30, 23, 59, 59).unwrap()
234        );
235        let dt = dt + Duration::seconds(1);
236        assert_eq!(dt, London.with_ymd_and_hms(1847, 12, 1, 0, 1, 15).unwrap());
237    }
238
239    #[test]
240    fn london_dst() {
241        let dt = London.with_ymd_and_hms(2016, 3, 10, 5, 0, 0).unwrap();
242        let later = dt + Duration::days(180);
243        let expected = London.with_ymd_and_hms(2016, 9, 6, 6, 0, 0).unwrap();
244        assert_eq!(later, expected);
245    }
246
247    #[test]
248    fn international_date_line_change() {
249        let dt = UTC
250            .with_ymd_and_hms(2011, 12, 30, 9, 59, 59)
251            .unwrap()
252            .with_timezone(&Apia);
253        assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 29, 23, 59, 59).unwrap());
254        let dt = dt + Duration::seconds(1);
255        assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 31, 0, 0, 0).unwrap());
256    }
257
258    #[test]
259    fn negative_offset_with_minutes_and_seconds() {
260        let dt = UTC
261            .with_ymd_and_hms(1900, 1, 1, 12, 0, 0)
262            .unwrap()
263            .with_timezone(&Danmarkshavn);
264        assert_eq!(
265            dt,
266            Danmarkshavn
267                .with_ymd_and_hms(1900, 1, 1, 10, 45, 20)
268                .unwrap()
269        );
270    }
271
272    #[test]
273    fn monotonicity() {
274        let mut dt = Noumea.with_ymd_and_hms(1800, 1, 1, 12, 0, 0).unwrap();
275        for _ in 0..24 * 356 * 400 {
276            let new = dt + Duration::hours(1);
277            assert!(new > dt);
278            assert!(new.with_timezone(&UTC) > dt.with_timezone(&UTC));
279            dt = new;
280        }
281    }
282
283    fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) {
284        for y in begin..end {
285            for d in 1..366 {
286                let date = NaiveDate::from_yo_opt(y, d).unwrap();
287                for h in 0..24 {
288                    for m in 0..60 {
289                        let dt = date.and_hms_opt(h, m, 0).unwrap().and_utc();
290                        let with_tz = dt.with_timezone(&tz);
291                        let utc = with_tz.with_timezone(&UTC);
292                        assert_eq!(dt, utc);
293                    }
294                }
295            }
296        }
297    }
298
299    #[test]
300    fn inverse_london() {
301        test_inverse(London, 1989, 1994);
302    }
303
304    #[test]
305    fn inverse_dhaka() {
306        test_inverse(Dhaka, 1995, 2000);
307    }
308
309    #[test]
310    fn inverse_apia() {
311        test_inverse(Apia, 2011, 2012);
312    }
313
314    #[test]
315    fn inverse_tahiti() {
316        test_inverse(Tahiti, 1911, 1914);
317    }
318
319    #[test]
320    fn string_representation() {
321        let dt = UTC
322            .with_ymd_and_hms(2000, 9, 1, 12, 30, 15)
323            .unwrap()
324            .with_timezone(&Adelaide);
325        assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST");
326        assert_eq!(format!("{:?}", dt), "2000-09-01T22:00:15ACST");
327        assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30");
328        assert_eq!(format!("{}", dt), "2000-09-01 22:00:15 ACST");
329    }
330
331    #[test]
332    fn tahiti() {
333        let dt = UTC
334            .with_ymd_and_hms(1912, 10, 1, 9, 58, 16)
335            .unwrap()
336            .with_timezone(&Tahiti);
337        let before = dt - Duration::hours(1);
338        assert_eq!(
339            before,
340            Tahiti.with_ymd_and_hms(1912, 9, 30, 23, 0, 0).unwrap()
341        );
342        let after = dt + Duration::hours(1);
343        assert_eq!(
344            after,
345            Tahiti.with_ymd_and_hms(1912, 10, 1, 0, 58, 16).unwrap()
346        );
347    }
348
349    #[test]
350    fn nonexistent_time() {
351        assert!(London
352            .with_ymd_and_hms(2016, 3, 27, 1, 30, 0)
353            .single()
354            .is_none());
355    }
356
357    #[test]
358    fn nonexistent_time_2() {
359        assert!(London
360            .with_ymd_and_hms(2016, 3, 27, 1, 0, 0)
361            .single()
362            .is_none());
363    }
364
365    #[test]
366    fn time_exists() {
367        assert!(London
368            .with_ymd_and_hms(2016, 3, 27, 2, 0, 0)
369            .single()
370            .is_some());
371    }
372
373    #[test]
374    fn ambiguous_time() {
375        let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 0, 0);
376        let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30)
377            .unwrap()
378            .and_hms_opt(0, 0, 0)
379            .unwrap();
380        assert_eq!(
381            ambiguous.earliest().unwrap(),
382            London.from_utc_datetime(&earliest_utc)
383        );
384        let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30)
385            .unwrap()
386            .and_hms_opt(1, 0, 0)
387            .unwrap();
388        assert_eq!(
389            ambiguous.latest().unwrap(),
390            London.from_utc_datetime(&latest_utc)
391        );
392    }
393
394    #[test]
395    fn ambiguous_time_2() {
396        let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 30, 0);
397        let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30)
398            .unwrap()
399            .and_hms_opt(0, 30, 0)
400            .unwrap();
401        assert_eq!(
402            ambiguous.earliest().unwrap(),
403            London.from_utc_datetime(&earliest_utc)
404        );
405        let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30)
406            .unwrap()
407            .and_hms_opt(1, 30, 0)
408            .unwrap();
409        assert_eq!(
410            ambiguous.latest().unwrap(),
411            London.from_utc_datetime(&latest_utc)
412        );
413    }
414
415    #[test]
416    fn ambiguous_time_3() {
417        let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 30, 0);
418        let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25)
419            .unwrap()
420            .and_hms_opt(21, 30, 0)
421            .unwrap();
422        assert_eq!(
423            ambiguous.earliest().unwrap().fixed_offset(),
424            Moscow.from_utc_datetime(&earliest_utc).fixed_offset()
425        );
426        let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25)
427            .unwrap()
428            .and_hms_opt(22, 30, 0)
429            .unwrap();
430        assert_eq!(
431            ambiguous.latest().unwrap(),
432            Moscow.from_utc_datetime(&latest_utc)
433        );
434    }
435
436    #[test]
437    fn ambiguous_time_4() {
438        let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 0, 0);
439        let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25)
440            .unwrap()
441            .and_hms_opt(21, 0, 0)
442            .unwrap();
443        assert_eq!(
444            ambiguous.earliest().unwrap().fixed_offset(),
445            Moscow.from_utc_datetime(&earliest_utc).fixed_offset()
446        );
447        let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25)
448            .unwrap()
449            .and_hms_opt(22, 0, 0)
450            .unwrap();
451        assert_eq!(
452            ambiguous.latest().unwrap(),
453            Moscow.from_utc_datetime(&latest_utc)
454        );
455    }
456
457    #[test]
458    fn unambiguous_time() {
459        assert!(London
460            .with_ymd_and_hms(2016, 10, 30, 2, 0, 0)
461            .single()
462            .is_some());
463    }
464
465    #[test]
466    fn unambiguous_time_2() {
467        assert!(Moscow
468            .with_ymd_and_hms(2014, 10, 26, 2, 0, 0)
469            .single()
470            .is_some());
471    }
472
473    #[test]
474    fn test_get_name() {
475        assert_eq!(London.name(), "Europe/London");
476        assert_eq!(Tz::Africa__Abidjan.name(), "Africa/Abidjan");
477        assert_eq!(Tz::UTC.name(), "UTC");
478        assert_eq!(Tz::Zulu.name(), "Zulu");
479    }
480
481    #[test]
482    fn test_display() {
483        assert_eq!(format!("{}", London), "Europe/London");
484        assert_eq!(format!("{}", Tz::Africa__Abidjan), "Africa/Abidjan");
485        assert_eq!(format!("{}", Tz::UTC), "UTC");
486        assert_eq!(format!("{}", Tz::Zulu), "Zulu");
487    }
488
489    #[test]
490    fn test_impl_hash() {
491        #[allow(dead_code)]
492        #[derive(Hash)]
493        struct Foo(Tz);
494    }
495
496    #[test]
497    fn test_iana_tzdb_version() {
498        // Format should be something like 2023c.
499        assert_eq!(5, IANA_TZDB_VERSION.len());
500        let numbers: Vec<&str> = IANA_TZDB_VERSION.matches(char::is_numeric).collect();
501        assert_eq!(4, numbers.len());
502        assert!(IANA_TZDB_VERSION.ends_with(|c: char| c.is_ascii_lowercase()));
503    }
504
505    #[test]
506    fn test_numeric_names() {
507        let dt = Scoresbysund
508            .with_ymd_and_hms(2024, 05, 01, 0, 0, 0)
509            .unwrap();
510        assert_eq!(format!("{}", dt.offset()), "-01");
511        assert_eq!(format!("{:?}", dt.offset()), "-01");
512        let dt = Casey.with_ymd_and_hms(2022, 11, 01, 0, 0, 0).unwrap();
513        assert_eq!(format!("{}", dt.offset()), "+11");
514        assert_eq!(format!("{:?}", dt.offset()), "+11");
515        let dt = Addis_Ababa.with_ymd_and_hms(1937, 02, 01, 0, 0, 0).unwrap();
516        assert_eq!(format!("{}", dt.offset()), "+0245");
517        assert_eq!(format!("{:?}", dt.offset()), "+0245");
518    }
519
520    fn gap_info_test(tz: Tz, gap_begin: NaiveDateTime, gap_end: NaiveDateTime) {
521        let before = gap_begin - Duration::seconds(1);
522        let before_offset = tz.offset_from_local_datetime(&before).single().unwrap();
523
524        let gap_end = tz.from_local_datetime(&gap_end).single().unwrap();
525
526        let in_gap = gap_begin + Duration::seconds(1);
527        let GapInfo { begin, end } = GapInfo::new(&in_gap, &tz).unwrap();
528        let (begin_time, begin_offset) = begin.unwrap();
529        let end = end.unwrap();
530
531        assert_eq!(gap_begin, begin_time);
532        assert_eq!(before_offset, begin_offset);
533        assert_eq!(gap_end, end);
534    }
535
536    #[test]
537    fn gap_info_europe_london() {
538        gap_info_test(
539            Tz::Europe__London,
540            NaiveDate::from_ymd_opt(2024, 3, 31)
541                .unwrap()
542                .and_hms_opt(1, 0, 0)
543                .unwrap(),
544            NaiveDate::from_ymd_opt(2024, 3, 31)
545                .unwrap()
546                .and_hms_opt(2, 0, 0)
547                .unwrap(),
548        );
549    }
550
551    #[test]
552    fn gap_info_europe_dublin() {
553        gap_info_test(
554            Tz::Europe__Dublin,
555            NaiveDate::from_ymd_opt(2024, 3, 31)
556                .unwrap()
557                .and_hms_opt(1, 0, 0)
558                .unwrap(),
559            NaiveDate::from_ymd_opt(2024, 3, 31)
560                .unwrap()
561                .and_hms_opt(2, 0, 0)
562                .unwrap(),
563        );
564    }
565
566    #[test]
567    fn gap_info_australia_adelaide() {
568        gap_info_test(
569            Tz::Australia__Adelaide,
570            NaiveDate::from_ymd_opt(2024, 10, 6)
571                .unwrap()
572                .and_hms_opt(2, 0, 0)
573                .unwrap(),
574            NaiveDate::from_ymd_opt(2024, 10, 6)
575                .unwrap()
576                .and_hms_opt(3, 0, 0)
577                .unwrap(),
578        );
579    }
580
581    #[test]
582    fn gap_info_samoa_skips_a_day() {
583        gap_info_test(
584            Tz::Pacific__Apia,
585            NaiveDate::from_ymd_opt(2011, 12, 30)
586                .unwrap()
587                .and_hms_opt(0, 0, 0)
588                .unwrap(),
589            NaiveDate::from_ymd_opt(2011, 12, 31)
590                .unwrap()
591                .and_hms_opt(0, 0, 0)
592                .unwrap(),
593        );
594    }
595
596    #[test]
597    fn gap_info_libya_2013() {
598        gap_info_test(
599            Tz::Libya,
600            NaiveDate::from_ymd_opt(2013, 3, 29)
601                .unwrap()
602                .and_hms_opt(1, 0, 0)
603                .unwrap(),
604            NaiveDate::from_ymd_opt(2013, 3, 29)
605                .unwrap()
606                .and_hms_opt(2, 0, 0)
607                .unwrap(),
608        );
609    }
610}