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" )] |
135 | mod serde; |
136 | |
137 | mod binary_search; |
138 | mod directory; |
139 | mod timezone_impl; |
140 | mod timezones; |
141 | |
142 | pub use crate::directory::*; |
143 | pub use crate::timezone_impl::{OffsetComponents, OffsetName, TzOffset}; |
144 | pub use crate::timezones::ParseError; |
145 | pub use crate::timezones::Tz; |
146 | pub use crate::timezones::TZ_VARIANTS; |
147 | pub use crate::IANA_TZDB_VERSION; |
148 | |
149 | #[cfg (test)] |
150 | mod 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::Pacific::Apia; |
163 | use super::Pacific::Noumea; |
164 | use super::Pacific::Tahiti; |
165 | use super::Tz; |
166 | use super::IANA_TZDB_VERSION; |
167 | use super::US::Eastern; |
168 | use super::UTC; |
169 | use chrono::{Duration, NaiveDate, TimeZone}; |
170 | |
171 | #[test ] |
172 | fn london_to_berlin() { |
173 | let dt = London.with_ymd_and_hms(2016, 10, 8, 17, 0, 0).unwrap(); |
174 | let converted = dt.with_timezone(&Berlin); |
175 | let expected = Berlin.with_ymd_and_hms(2016, 10, 8, 18, 0, 0).unwrap(); |
176 | assert_eq!(converted, expected); |
177 | } |
178 | |
179 | #[test ] |
180 | fn us_eastern_dst_commutativity() { |
181 | let dt = UTC.with_ymd_and_hms(2002, 4, 7, 7, 0, 0).unwrap(); |
182 | for days in -420..720 { |
183 | let dt1 = (dt + Duration::days(days)).with_timezone(&Eastern); |
184 | let dt2 = dt.with_timezone(&Eastern) + Duration::days(days); |
185 | assert_eq!(dt1, dt2); |
186 | } |
187 | } |
188 | |
189 | #[test ] |
190 | fn test_addition_across_dst_boundary() { |
191 | use chrono::TimeZone; |
192 | let two_hours = Duration::hours(2); |
193 | let edt = Eastern.with_ymd_and_hms(2019, 11, 3, 0, 0, 0).unwrap(); |
194 | let est = edt + two_hours; |
195 | |
196 | assert_eq!(edt.to_string(), "2019-11-03 00:00:00 EDT" .to_string()); |
197 | assert_eq!(est.to_string(), "2019-11-03 01:00:00 EST" .to_string()); |
198 | assert_eq!(est.timestamp(), edt.timestamp() + two_hours.num_seconds()); |
199 | } |
200 | |
201 | #[test ] |
202 | fn warsaw_tz_name() { |
203 | let dt = UTC.with_ymd_and_hms(1915, 8, 4, 22, 35, 59).unwrap(); |
204 | assert_eq!(dt.with_timezone(&Warsaw).format("%Z" ).to_string(), "WMT" ); |
205 | let dt = dt + Duration::seconds(1); |
206 | assert_eq!(dt.with_timezone(&Warsaw).format("%Z" ).to_string(), "CET" ); |
207 | } |
208 | |
209 | #[test ] |
210 | fn vilnius_utc_offset() { |
211 | let dt = UTC |
212 | .with_ymd_and_hms(1916, 12, 31, 22, 35, 59) |
213 | .unwrap() |
214 | .with_timezone(&Vilnius); |
215 | assert_eq!( |
216 | dt, |
217 | Vilnius.with_ymd_and_hms(1916, 12, 31, 23, 59, 59).unwrap() |
218 | ); |
219 | let dt = dt + Duration::seconds(1); |
220 | assert_eq!(dt, Vilnius.with_ymd_and_hms(1917, 1, 1, 0, 11, 36).unwrap()); |
221 | } |
222 | |
223 | #[test ] |
224 | fn victorian_times() { |
225 | let dt = UTC |
226 | .with_ymd_and_hms(1847, 12, 1, 0, 1, 14) |
227 | .unwrap() |
228 | .with_timezone(&London); |
229 | assert_eq!( |
230 | dt, |
231 | London.with_ymd_and_hms(1847, 11, 30, 23, 59, 59).unwrap() |
232 | ); |
233 | let dt = dt + Duration::seconds(1); |
234 | assert_eq!(dt, London.with_ymd_and_hms(1847, 12, 1, 0, 1, 15).unwrap()); |
235 | } |
236 | |
237 | #[test ] |
238 | fn london_dst() { |
239 | let dt = London.with_ymd_and_hms(2016, 3, 10, 5, 0, 0).unwrap(); |
240 | let later = dt + Duration::days(180); |
241 | let expected = London.with_ymd_and_hms(2016, 9, 6, 6, 0, 0).unwrap(); |
242 | assert_eq!(later, expected); |
243 | } |
244 | |
245 | #[test ] |
246 | fn international_date_line_change() { |
247 | let dt = UTC |
248 | .with_ymd_and_hms(2011, 12, 30, 9, 59, 59) |
249 | .unwrap() |
250 | .with_timezone(&Apia); |
251 | assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 29, 23, 59, 59).unwrap()); |
252 | let dt = dt + Duration::seconds(1); |
253 | assert_eq!(dt, Apia.with_ymd_and_hms(2011, 12, 31, 0, 0, 0).unwrap()); |
254 | } |
255 | |
256 | #[test ] |
257 | fn negative_offset_with_minutes_and_seconds() { |
258 | let dt = UTC |
259 | .with_ymd_and_hms(1900, 1, 1, 12, 0, 0) |
260 | .unwrap() |
261 | .with_timezone(&Danmarkshavn); |
262 | assert_eq!( |
263 | dt, |
264 | Danmarkshavn |
265 | .with_ymd_and_hms(1900, 1, 1, 10, 45, 20) |
266 | .unwrap() |
267 | ); |
268 | } |
269 | |
270 | #[test ] |
271 | fn monotonicity() { |
272 | let mut dt = Noumea.with_ymd_and_hms(1800, 1, 1, 12, 0, 0).unwrap(); |
273 | for _ in 0..24 * 356 * 400 { |
274 | let new = dt + Duration::hours(1); |
275 | assert!(new > dt); |
276 | assert!(new.with_timezone(&UTC) > dt.with_timezone(&UTC)); |
277 | dt = new; |
278 | } |
279 | } |
280 | |
281 | fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) { |
282 | for y in begin..end { |
283 | for d in 1..366 { |
284 | let date = NaiveDate::from_yo_opt(y, d).unwrap(); |
285 | for h in 0..24 { |
286 | for m in 0..60 { |
287 | let dt = date.and_hms_opt(h, m, 0).unwrap().and_utc(); |
288 | let with_tz = dt.with_timezone(&tz); |
289 | let utc = with_tz.with_timezone(&UTC); |
290 | assert_eq!(dt, utc); |
291 | } |
292 | } |
293 | } |
294 | } |
295 | } |
296 | |
297 | #[test ] |
298 | fn inverse_london() { |
299 | test_inverse(London, 1989, 1994); |
300 | } |
301 | |
302 | #[test ] |
303 | fn inverse_dhaka() { |
304 | test_inverse(Dhaka, 1995, 2000); |
305 | } |
306 | |
307 | #[test ] |
308 | fn inverse_apia() { |
309 | test_inverse(Apia, 2011, 2012); |
310 | } |
311 | |
312 | #[test ] |
313 | fn inverse_tahiti() { |
314 | test_inverse(Tahiti, 1911, 1914); |
315 | } |
316 | |
317 | #[test ] |
318 | fn string_representation() { |
319 | let dt = UTC |
320 | .with_ymd_and_hms(2000, 9, 1, 12, 30, 15) |
321 | .unwrap() |
322 | .with_timezone(&Adelaide); |
323 | assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST" ); |
324 | assert_eq!(format!("{:?}" , dt), "2000-09-01T22:00:15ACST" ); |
325 | assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30" ); |
326 | assert_eq!(format!("{}" , dt), "2000-09-01 22:00:15 ACST" ); |
327 | } |
328 | |
329 | #[test ] |
330 | fn tahiti() { |
331 | let dt = UTC |
332 | .with_ymd_and_hms(1912, 10, 1, 9, 58, 16) |
333 | .unwrap() |
334 | .with_timezone(&Tahiti); |
335 | let before = dt - Duration::hours(1); |
336 | assert_eq!( |
337 | before, |
338 | Tahiti.with_ymd_and_hms(1912, 9, 30, 23, 0, 0).unwrap() |
339 | ); |
340 | let after = dt + Duration::hours(1); |
341 | assert_eq!( |
342 | after, |
343 | Tahiti.with_ymd_and_hms(1912, 10, 1, 0, 58, 16).unwrap() |
344 | ); |
345 | } |
346 | |
347 | #[test ] |
348 | fn nonexistent_time() { |
349 | assert!(London |
350 | .with_ymd_and_hms(2016, 3, 27, 1, 30, 0) |
351 | .single() |
352 | .is_none()); |
353 | } |
354 | |
355 | #[test ] |
356 | fn nonexistent_time_2() { |
357 | assert!(London |
358 | .with_ymd_and_hms(2016, 3, 27, 1, 0, 0) |
359 | .single() |
360 | .is_none()); |
361 | } |
362 | |
363 | #[test ] |
364 | fn time_exists() { |
365 | assert!(London |
366 | .with_ymd_and_hms(2016, 3, 27, 2, 0, 0) |
367 | .single() |
368 | .is_some()); |
369 | } |
370 | |
371 | #[test ] |
372 | fn ambiguous_time() { |
373 | let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 0, 0); |
374 | let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
375 | .unwrap() |
376 | .and_hms_opt(0, 0, 0) |
377 | .unwrap(); |
378 | assert_eq!( |
379 | ambiguous.earliest().unwrap(), |
380 | London.from_utc_datetime(&earliest_utc) |
381 | ); |
382 | let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
383 | .unwrap() |
384 | .and_hms_opt(1, 0, 0) |
385 | .unwrap(); |
386 | assert_eq!( |
387 | ambiguous.latest().unwrap(), |
388 | London.from_utc_datetime(&latest_utc) |
389 | ); |
390 | } |
391 | |
392 | #[test ] |
393 | fn ambiguous_time_2() { |
394 | let ambiguous = London.with_ymd_and_hms(2016, 10, 30, 1, 30, 0); |
395 | let earliest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
396 | .unwrap() |
397 | .and_hms_opt(0, 30, 0) |
398 | .unwrap(); |
399 | assert_eq!( |
400 | ambiguous.earliest().unwrap(), |
401 | London.from_utc_datetime(&earliest_utc) |
402 | ); |
403 | let latest_utc = NaiveDate::from_ymd_opt(2016, 10, 30) |
404 | .unwrap() |
405 | .and_hms_opt(1, 30, 0) |
406 | .unwrap(); |
407 | assert_eq!( |
408 | ambiguous.latest().unwrap(), |
409 | London.from_utc_datetime(&latest_utc) |
410 | ); |
411 | } |
412 | |
413 | #[test ] |
414 | fn ambiguous_time_3() { |
415 | let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 30, 0); |
416 | let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
417 | .unwrap() |
418 | .and_hms_opt(21, 30, 0) |
419 | .unwrap(); |
420 | assert_eq!( |
421 | ambiguous.earliest().unwrap().fixed_offset(), |
422 | Moscow.from_utc_datetime(&earliest_utc).fixed_offset() |
423 | ); |
424 | let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
425 | .unwrap() |
426 | .and_hms_opt(22, 30, 0) |
427 | .unwrap(); |
428 | assert_eq!( |
429 | ambiguous.latest().unwrap(), |
430 | Moscow.from_utc_datetime(&latest_utc) |
431 | ); |
432 | } |
433 | |
434 | #[test ] |
435 | fn ambiguous_time_4() { |
436 | let ambiguous = Moscow.with_ymd_and_hms(2014, 10, 26, 1, 0, 0); |
437 | let earliest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
438 | .unwrap() |
439 | .and_hms_opt(21, 0, 0) |
440 | .unwrap(); |
441 | assert_eq!( |
442 | ambiguous.earliest().unwrap().fixed_offset(), |
443 | Moscow.from_utc_datetime(&earliest_utc).fixed_offset() |
444 | ); |
445 | let latest_utc = NaiveDate::from_ymd_opt(2014, 10, 25) |
446 | .unwrap() |
447 | .and_hms_opt(22, 0, 0) |
448 | .unwrap(); |
449 | assert_eq!( |
450 | ambiguous.latest().unwrap(), |
451 | Moscow.from_utc_datetime(&latest_utc) |
452 | ); |
453 | } |
454 | |
455 | #[test ] |
456 | fn unambiguous_time() { |
457 | assert!(London |
458 | .with_ymd_and_hms(2016, 10, 30, 2, 0, 0) |
459 | .single() |
460 | .is_some()); |
461 | } |
462 | |
463 | #[test ] |
464 | fn unambiguous_time_2() { |
465 | assert!(Moscow |
466 | .with_ymd_and_hms(2014, 10, 26, 2, 0, 0) |
467 | .single() |
468 | .is_some()); |
469 | } |
470 | |
471 | #[test ] |
472 | fn test_get_name() { |
473 | assert_eq!(London.name(), "Europe/London" ); |
474 | assert_eq!(Tz::Africa__Abidjan.name(), "Africa/Abidjan" ); |
475 | assert_eq!(Tz::UTC.name(), "UTC" ); |
476 | assert_eq!(Tz::Zulu.name(), "Zulu" ); |
477 | } |
478 | |
479 | #[test ] |
480 | fn test_display() { |
481 | assert_eq!(format!("{}" , London), "Europe/London" ); |
482 | assert_eq!(format!("{}" , Tz::Africa__Abidjan), "Africa/Abidjan" ); |
483 | assert_eq!(format!("{}" , Tz::UTC), "UTC" ); |
484 | assert_eq!(format!("{}" , Tz::Zulu), "Zulu" ); |
485 | } |
486 | |
487 | #[test ] |
488 | fn test_impl_hash() { |
489 | #[allow (dead_code)] |
490 | #[derive (Hash)] |
491 | struct Foo(Tz); |
492 | } |
493 | |
494 | #[test ] |
495 | fn test_iana_tzdb_version() { |
496 | // Format should be something like 2023c. |
497 | assert_eq!(5, IANA_TZDB_VERSION.len()); |
498 | let numbers: Vec<&str> = IANA_TZDB_VERSION.matches(char::is_numeric).collect(); |
499 | assert_eq!(4, numbers.len()); |
500 | assert!(IANA_TZDB_VERSION.ends_with(|c: char| c.is_ascii_lowercase())); |
501 | } |
502 | |
503 | #[test ] |
504 | fn test_numeric_names() { |
505 | let dt = Scoresbysund |
506 | .with_ymd_and_hms(2024, 05, 01, 0, 0, 0) |
507 | .unwrap(); |
508 | assert_eq!(format!("{}" , dt.offset()), "-01" ); |
509 | assert_eq!(format!("{:?}" , dt.offset()), "-01" ); |
510 | let dt = Casey.with_ymd_and_hms(2022, 11, 01, 0, 0, 0).unwrap(); |
511 | assert_eq!(format!("{}" , dt.offset()), "+11" ); |
512 | assert_eq!(format!("{:?}" , dt.offset()), "+11" ); |
513 | let dt = Addis_Ababa.with_ymd_and_hms(1937, 02, 01, 0, 0, 0).unwrap(); |
514 | assert_eq!(format!("{}" , dt.offset()), "+0245" ); |
515 | assert_eq!(format!("{:?}" , dt.offset()), "+0245" ); |
516 | } |
517 | } |
518 | |