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;
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 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}