chrono_tz/
lib.rs

1//! # Chrono-TZ 0.4.1
2//!
3//! `Chrono-TZ` is a library that provides implementors of the
4//! [`TimeZone`][timezone] trait for [`rust-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//! ## Usage
14//!
15//! Put this in your `Cargo.toml`:
16//!
17//! ```toml
18//! [dependencies]
19//! chrono = "0.4"
20//! chrono-tz = "0.4"
21//! ```
22//!
23//! If you want Serde support, specify it like this:
24//!
25//! ```toml
26//! chrono-tz = { version = "0.4", features = ["serde"] }
27//! ```
28//!
29//! Then you will need to write (in your crate root):
30//!
31//! ```
32//! extern crate chrono;
33//! extern crate chrono_tz;
34//! ```
35//!
36//! ## Examples
37//!
38//! Create a time in one timezone and convert it to UTC
39//!
40//! ```
41//! # extern crate chrono;
42//! # extern crate chrono_tz;
43//! use chrono::{TimeZone, Utc};
44//! use chrono_tz::US::Pacific;
45//!
46//! # fn main() {
47//! let pacific_time = Pacific.ymd(1990, 5, 6).and_hms(12, 30, 45);
48//! let utc_time = pacific_time.with_timezone(&Utc);
49//! assert_eq!(utc_time, Utc.ymd(1990, 5, 6).and_hms(19, 30, 45));
50//! # }
51//! ```
52//!
53//! Create a naive datetime and convert it to a timezone-aware datetime
54//!
55//! ```
56//! # extern crate chrono;
57//! # extern crate chrono_tz;
58//! use chrono::{TimeZone, NaiveDate};
59//! use chrono_tz::Africa::Johannesburg;
60//!
61//! # fn main() {
62//! let naive_dt = NaiveDate::from_ymd(2038, 1, 19).and_hms(3, 14, 08);
63//! let tz_aware = Johannesburg.from_local_datetime(&naive_dt).unwrap();
64//! assert_eq!(tz_aware.to_string(), "2038-01-19 03:14:08 SAST");
65//! # }
66//! ```
67//!
68//! London and New York change their clocks on different days in March
69//! so only have a 4-hour difference on certain days.
70//!
71//! ```
72//! # extern crate chrono;
73//! # extern crate chrono_tz;
74//! use chrono::TimeZone;
75//! use chrono_tz::Europe::London;
76//! use chrono_tz::America::New_York;
77//!
78//! # fn main() {
79//! let london_time = London.ymd(2016, 3, 18).and_hms(3, 0, 0);
80//! let ny_time = london_time.with_timezone(&New_York);
81//! assert_eq!(ny_time, New_York.ymd(2016, 3, 17).and_hms(23, 0, 0));
82//! # }
83//! ```
84//!
85//! Adding 24 hours across a daylight savings change causes a change
86//! in local time
87//!
88//! ```
89//! # extern crate chrono;
90//! # extern crate chrono_tz;
91//! use chrono::{TimeZone, Duration};
92//! use chrono_tz::Europe::London;
93//!
94//! # fn main() {
95//! let dt = London.ymd(2016, 10, 29).and_hms(12, 0, 0);
96//! let later = dt + Duration::hours(24);
97//! assert_eq!(later, London.ymd(2016, 10, 30).and_hms(11, 0, 0));
98//! # }
99//! ```
100//!
101//! And of course you can always convert a local time to a unix timestamp
102//!
103//! ```
104//! # extern crate chrono;
105//! # extern crate chrono_tz;
106//! use chrono::TimeZone;
107//! use chrono_tz::Asia::Kolkata;
108//!
109//! # fn main() {
110//! let dt = Kolkata.ymd(2000, 1, 1).and_hms(0, 0, 0);
111//! let timestamp = dt.timestamp();
112//! assert_eq!(timestamp, 946665000);
113//! # }
114//! ```
115//!
116//! Pretty-printing a string will use the correct abbreviation for the timezone
117//!
118//! ```
119//! # extern crate chrono;
120//! # extern crate chrono_tz;
121//! use chrono::TimeZone;
122//! use chrono_tz::Europe::London;
123//!
124//! # fn main() {
125//! let dt = London.ymd(2016, 5, 10).and_hms(12, 0, 0);
126//! assert_eq!(dt.to_string(), "2016-05-10 12:00:00 BST");
127//! assert_eq!(dt.to_rfc3339(), "2016-05-10T12:00:00+01:00");
128//! # }
129//! ```
130//!
131//! You can convert a timezone string to a timezone using the FromStr trait
132//!
133//! ```
134//! # extern crate chrono;
135//! # extern crate chrono_tz;
136//! use chrono::TimeZone;
137//! use chrono_tz::Tz;
138//! use chrono_tz::UTC;
139//!
140//! # fn main() {
141//! let tz: Tz = "Antarctica/South_Pole".parse().unwrap();
142//! let dt = tz.ymd(2016, 10, 22).and_hms(12, 0, 0);
143//! let utc = dt.with_timezone(&UTC);
144//! assert_eq!(utc.to_string(), "2016-10-21 23:00:00 UTC");
145//! # }
146//! ```
147//!
148//! If you need to iterate over all variants you can use the TZ_VARIANTS array
149//! ```
150//! use chrono_tz::{TZ_VARIANTS, Tz};
151//! assert!(TZ_VARIANTS.iter().any(|v| *v == Tz::UTC));
152//! ```
153
154#![cfg_attr(not(feature = "std"), no_std)]
155
156#[cfg(feature = "std")]
157extern crate std as core;
158
159extern crate chrono;
160extern crate phf;
161#[cfg(feature = "case-insensitive")]
162extern crate uncased;
163
164#[cfg(feature = "serde")]
165mod serde;
166
167mod binary_search;
168mod directory;
169mod timezone_impl;
170mod timezones;
171
172pub use directory::*;
173pub use timezone_impl::{OffsetComponents, OffsetName};
174pub use timezones::ParseError;
175pub use timezones::Tz;
176pub use timezones::TZ_VARIANTS;
177
178#[cfg(test)]
179mod tests {
180    use super::America::Danmarkshavn;
181    use super::Asia::Dhaka;
182    use super::Australia::Adelaide;
183    use super::Europe::Amsterdam;
184    use super::Europe::Berlin;
185    use super::Europe::London;
186    use super::Europe::Moscow;
187    use super::Europe::Vilnius;
188    use super::Europe::Warsaw;
189    use super::Pacific::Apia;
190    use super::Pacific::Noumea;
191    use super::Pacific::Tahiti;
192    use super::Tz;
193    use super::US::Eastern;
194    use super::UTC;
195    use chrono::{Duration, TimeZone};
196
197    #[test]
198    fn london_to_berlin() {
199        let dt = London.ymd(2016, 10, 8).and_hms(17, 0, 0);
200        let converted = dt.with_timezone(&Berlin);
201        let expected = Berlin.ymd(2016, 10, 8).and_hms(18, 0, 0);
202        assert_eq!(converted, expected);
203    }
204
205    #[test]
206    fn us_eastern_dst_commutativity() {
207        let dt = UTC.ymd(2002, 4, 7).and_hms(7, 0, 0);
208        for days in -420..720 {
209            let dt1 = (dt + Duration::days(days)).with_timezone(&Eastern);
210            let dt2 = dt.with_timezone(&Eastern) + Duration::days(days);
211            assert_eq!(dt1, dt2);
212        }
213    }
214
215    #[test]
216    fn test_addition_across_dst_boundary() {
217        use chrono::TimeZone;
218        let two_hours = Duration::hours(2);
219        let edt = Eastern.ymd(2019, 11, 3).and_hms(0, 0, 0);
220        let est = edt + two_hours;
221
222        assert_eq!(edt.to_string(), "2019-11-03 00:00:00 EDT".to_string());
223        assert_eq!(est.to_string(), "2019-11-03 01:00:00 EST".to_string());
224        assert_eq!(est.timestamp(), edt.timestamp() + two_hours.num_seconds());
225    }
226
227    #[test]
228    fn warsaw_tz_name() {
229        let dt = UTC.ymd(1915, 8, 4).and_hms(22, 35, 59);
230        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "WMT");
231        let dt = dt + Duration::seconds(1);
232        assert_eq!(dt.with_timezone(&Warsaw).format("%Z").to_string(), "CET");
233    }
234
235    #[test]
236    fn vilnius_utc_offset() {
237        let dt = UTC.ymd(1916, 12, 31).and_hms(22, 35, 59).with_timezone(&Vilnius);
238        assert_eq!(dt, Vilnius.ymd(1916, 12, 31).and_hms(23, 59, 59));
239        let dt = dt + Duration::seconds(1);
240        assert_eq!(dt, Vilnius.ymd(1917, 1, 1).and_hms(0, 11, 36));
241    }
242
243    #[test]
244    fn victorian_times() {
245        let dt = UTC.ymd(1847, 12, 1).and_hms(0, 1, 14).with_timezone(&London);
246        assert_eq!(dt, London.ymd(1847, 11, 30).and_hms(23, 59, 59));
247        let dt = dt + Duration::seconds(1);
248        assert_eq!(dt, London.ymd(1847, 12, 1).and_hms(0, 1, 15));
249    }
250
251    #[test]
252    fn london_dst() {
253        let dt = London.ymd(2016, 3, 10).and_hms(5, 0, 0);
254        let later = dt + Duration::days(180);
255        let expected = London.ymd(2016, 9, 6).and_hms(6, 0, 0);
256        assert_eq!(later, expected);
257    }
258
259    #[test]
260    fn international_date_line_change() {
261        let dt = UTC.ymd(2011, 12, 30).and_hms(9, 59, 59).with_timezone(&Apia);
262        assert_eq!(dt, Apia.ymd(2011, 12, 29).and_hms(23, 59, 59));
263        let dt = dt + Duration::seconds(1);
264        assert_eq!(dt, Apia.ymd(2011, 12, 31).and_hms(0, 0, 0));
265    }
266
267    #[test]
268    fn negative_offset_with_minutes_and_seconds() {
269        let dt = UTC.ymd(1900, 1, 1).and_hms(12, 0, 0).with_timezone(&Danmarkshavn);
270        assert_eq!(dt, Danmarkshavn.ymd(1900, 1, 1).and_hms(10, 45, 20));
271    }
272
273    #[test]
274    fn monotonicity() {
275        let mut dt = Noumea.ymd(1800, 1, 1).and_hms(12, 0, 0);
276        for _ in 0..24 * 356 * 400 {
277            let new = dt + Duration::hours(1);
278            assert!(new > dt);
279            assert!(new.with_timezone(&UTC) > dt.with_timezone(&UTC));
280            dt = new;
281        }
282    }
283
284    fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) {
285        for y in begin..end {
286            for d in 1..366 {
287                for h in 0..24 {
288                    for m in 0..60 {
289                        let dt = UTC.yo(y, d).and_hms(h, m, 0);
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.ymd(2000, 9, 1).and_hms(12, 30, 15).with_timezone(&Adelaide);
322        assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST");
323        assert_eq!(format!("{:?}", dt), "2000-09-01T22:00:15ACST");
324        assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30");
325        assert_eq!(format!("{}", dt), "2000-09-01 22:00:15 ACST");
326    }
327
328    #[test]
329    fn tahiti() {
330        let dt = UTC.ymd(1912, 10, 1).and_hms(9, 58, 16).with_timezone(&Tahiti);
331        let before = dt - Duration::hours(1);
332        assert_eq!(before, Tahiti.ymd(1912, 9, 30).and_hms(23, 0, 0));
333        let after = dt + Duration::hours(1);
334        assert_eq!(after, Tahiti.ymd(1912, 10, 1).and_hms(0, 58, 16));
335    }
336
337    #[test]
338    fn second_offsets() {
339        let dt = UTC.ymd(1914, 1, 1).and_hms(13, 40, 28).with_timezone(&Amsterdam);
340        assert_eq!(dt.to_string(), "1914-01-01 14:00:00 AMT");
341
342        // NOTE: pytz will give a different result here. The actual offset is +00:19:32.
343        //       The implementation of RFC3339 formatting in chrono rounds down the
344        //       number of minutes, whereas pytz rounds to nearest in cases such as this.
345        //       RFC3339 specifies that precision is not required in this case, and that
346        //       to retain precision, the time should be converted to a representable
347        //       format.
348        //       In any case, the actual datetime objects themselves always retain full
349        //       precision in this implementation (unlike pytz). It is only (some) string
350        //       representations that lack precision.
351        assert_eq!(dt.to_rfc3339(), "1914-01-01T14:00:00+00:19");
352    }
353
354    #[test]
355    #[should_panic]
356    fn nonexistent_time() {
357        let _ = London.ymd(2016, 3, 27).and_hms(1, 30, 0);
358    }
359
360    #[test]
361    #[should_panic]
362    fn nonexistent_time_2() {
363        let _ = London.ymd(2016, 3, 27).and_hms(1, 0, 0);
364    }
365
366    #[test]
367    fn time_exists() {
368        let _ = London.ymd(2016, 3, 27).and_hms(2, 0, 0);
369    }
370
371    #[test]
372    #[should_panic]
373    fn ambiguous_time() {
374        let _ = London.ymd(2016, 10, 30).and_hms(1, 0, 0);
375    }
376
377    #[test]
378    #[should_panic]
379    fn ambiguous_time_2() {
380        let _ = London.ymd(2016, 10, 30).and_hms(1, 30, 0);
381    }
382
383    #[test]
384    #[should_panic]
385    fn ambiguous_time_3() {
386        let _ = Moscow.ymd(2014, 10, 26).and_hms(1, 30, 0);
387    }
388
389    #[test]
390    #[should_panic]
391    fn ambiguous_time_4() {
392        let _ = Moscow.ymd(2014, 10, 26).and_hms(1, 0, 0);
393    }
394
395    #[test]
396    fn unambiguous_time() {
397        let _ = London.ymd(2016, 10, 30).and_hms(2, 0, 0);
398    }
399
400    #[test]
401    fn unambiguous_time_2() {
402        let _ = Moscow.ymd(2014, 10, 26).and_hms(2, 0, 0);
403    }
404
405    #[test]
406    fn test_get_name() {
407        assert_eq!(London.name(), "Europe/London");
408        assert_eq!(Tz::Africa__Abidjan.name(), "Africa/Abidjan");
409        assert_eq!(Tz::UTC.name(), "UTC");
410        assert_eq!(Tz::Zulu.name(), "Zulu");
411    }
412
413    #[test]
414    fn test_display() {
415        assert_eq!(format!("{}", London), "Europe/London");
416        assert_eq!(format!("{}", Tz::Africa__Abidjan), "Africa/Abidjan");
417        assert_eq!(format!("{}", Tz::UTC), "UTC");
418        assert_eq!(format!("{}", Tz::Zulu), "Zulu");
419    }
420
421    #[test]
422    fn test_impl_hash() {
423        #[allow(dead_code)]
424        #[derive(Hash)]
425        struct Foo(Tz);
426    }
427}