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, 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::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