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