1use byteorder::{ByteOrder, BE};
6use chrono::{FixedOffset, LocalResult, NaiveDate, NaiveDateTime, TimeZone};
7use std::cmp::Ordering;
8#[cfg(unix)]
9use std::env;
10use std::error;
11use std::fmt;
12#[cfg(unix)]
13use std::fs::read;
14use std::hash::Hash;
15use std::io;
16use std::mem::size_of;
17use std::ops::Deref;
18use std::rc::Rc;
19use std::str::from_utf8;
20use std::sync::Arc;
21
22#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
25struct Oz {
26 offset: FixedOffset,
28 name: u8,
30}
31
32impl Oz {
33 fn to_local(self, utc_ts: i64) -> i64 {
35 utc_ts + i64::from(self.offset.local_minus_utc())
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct Tz {
72 names: Box<str>,
74 utc_to_local: Box<[(i64, Oz)]>,
78 local_to_utc: Box<[(i64, LocalResult<Oz>)]>,
82}
83
84fn to_lower_bound(bsr: Result<usize, usize>) -> usize {
86 bsr.unwrap_or_else(|i| i - 1)
87}
88
89impl Tz {
90 fn oz_from_local_date(&self, local_date: NaiveDate) -> Option<Oz> {
94 let min_ts = local_date.and_hms(0, 0, 0).timestamp();
95 self.oz_from_local_timestamp(min_ts)
96 .earliest()
97 .or_else(|| self.oz_from_local_timestamp(min_ts + 86399).earliest())
98 }
99
100 fn oz_from_local_timestamp(&self, local_ts: i64) -> LocalResult<Oz> {
102 let index = to_lower_bound(
103 self.local_to_utc
104 .binary_search_by(|&(local, _)| local.cmp(&local_ts)),
105 );
106 self.local_to_utc[index].1
107 }
108
109 fn oz_from_utc_timestamp(&self, timestamp: i64) -> Oz {
111 let index = to_lower_bound(
112 self.utc_to_local
113 .binary_search_by(|&(utc, _)| utc.cmp(×tamp)),
114 );
115 self.utc_to_local[index].1
116 }
117}
118
119#[derive(Clone, Hash, PartialEq, Eq)]
121pub struct Offset<T> {
122 oz: Oz,
123 tz: T,
124}
125
126impl<T: Clone + Deref<Target = Tz>> chrono::Offset for Offset<T> {
127 fn fix(&self) -> FixedOffset {
128 self.oz.offset
129 }
130}
131
132impl<T: Deref<Target = Tz>> fmt::Display for Offset<T> {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 let suffix = &self.tz.names[usize::from(self.oz.name)..];
135 let len = suffix.find('\0').unwrap_or_else(|| suffix.len());
136 f.write_str(&suffix[..len])
137 }
138}
139
140impl<T: Deref<Target = Tz>> fmt::Debug for Offset<T> {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 fmt::Display::fmt(self, f)
143 }
144}
145
146#[derive(Clone, Debug, Hash, Eq, PartialEq)]
151pub struct RcTz(pub Rc<Tz>);
152
153impl RcTz {
154 pub fn new(tz: Tz) -> Self {
156 Self(Rc::new(tz))
157 }
158
159 #[cfg(unix)]
166 pub fn named(name: &str) -> std::io::Result<Self> {
167 Tz::named(name).map(Self::new)
168 }
169}
170
171impl Deref for RcTz {
172 type Target = Tz;
173 fn deref(&self) -> &Tz {
174 &self.0
175 }
176}
177
178impl From<Tz> for RcTz {
179 fn from(tz: Tz) -> Self {
180 Self::new(tz)
181 }
182}
183
184#[derive(Clone, Debug, Hash, Eq, PartialEq)]
189pub struct ArcTz(pub Arc<Tz>);
190
191impl ArcTz {
192 pub fn new(tz: Tz) -> Self {
195 Self(Arc::new(tz))
196 }
197
198 #[cfg(unix)]
205 pub fn named(name: &str) -> std::io::Result<Self> {
206 Tz::named(name).map(Self::new)
207 }
208}
209
210impl Deref for ArcTz {
211 type Target = Tz;
212 fn deref(&self) -> &Tz {
213 &self.0
214 }
215}
216
217impl From<Tz> for ArcTz {
218 fn from(tz: Tz) -> Self {
219 Self::new(tz)
220 }
221}
222
223macro_rules! implements_time_zone {
224 () => {
225 type Offset = Offset<Self>;
226
227 fn from_offset(offset: &Self::Offset) -> Self {
228 Self::clone(&offset.tz)
229 }
230
231 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
232 Offset {
233 oz: self.oz_from_utc_timestamp(utc.timestamp()),
234 tz: self.clone(),
235 }
236 }
237
238 fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
239 self.offset_from_utc_datetime(&utc.and_hms(12, 0, 0))
240 }
241
242 fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset> {
243 if let Some(oz) = self.oz_from_local_date(*local) {
244 LocalResult::Single(Offset {
245 oz,
246 tz: self.clone(),
247 })
248 } else {
249 LocalResult::None
250 }
251 }
252
253 fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset> {
254 let timestamp = local.timestamp();
255 self.oz_from_local_timestamp(timestamp).map(|oz| Offset {
256 oz,
257 tz: self.clone(),
258 })
259 }
260 };
261}
262
263impl<'a> TimeZone for &'a Tz {
264 implements_time_zone!();
265}
266
267impl TimeZone for RcTz {
268 implements_time_zone!();
269}
270
271impl TimeZone for ArcTz {
272 implements_time_zone!();
273}
274
275#[derive(Debug, PartialEq, Eq, Clone)]
277pub enum Error {
278 HeaderTooShort,
280 InvalidMagic,
282 UnsupportedVersion,
284 InconsistentTypeCount,
287 NoTypes,
289 OffsetOverflow,
291 NonUtf8Abbr,
293 DataTooShort,
295 InvalidTimeZoneFileName,
297 InvalidType,
299 NameOffsetOutOfBounds,
301}
302
303impl fmt::Display for Error {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 f.write_str("tzfile error: ")?;
306 f.write_str(match self {
307 Error::HeaderTooShort => "header too short",
308 Error::InvalidMagic => "invalid magic",
309 Error::UnsupportedVersion => "unsupported version",
310 Error::InconsistentTypeCount => "inconsistent type count",
311 Error::NoTypes => "no types",
312 Error::OffsetOverflow => "time zone offset overflow",
313 Error::NonUtf8Abbr => "non-UTF-8 time zone abbreviations",
314 Error::DataTooShort => "data too short",
315 Error::InvalidTimeZoneFileName => "invalid time zone file name",
316 Error::InvalidType => "invalid time zone transition type",
317 Error::NameOffsetOutOfBounds => "name offset out of bounds",
318 })
319 }
320}
321
322impl error::Error for Error {}
323
324impl From<Error> for io::Error {
325 fn from(e: Error) -> io::Error {
326 io::Error::new(io::ErrorKind::Other, e)
327 }
328}
329
330static MAGIC: [u8; 4] = *b"TZif";
331
332struct Header {
333 tzh_ttisgmtcnt: usize,
334 tzh_ttisstdcnt: usize,
335 tzh_leapcnt: usize,
336 tzh_timecnt: usize,
337 tzh_typecnt: usize,
338 tzh_charcnt: usize,
339}
340
341impl Header {
342 fn parse(source: &[u8]) -> Result<Self, Error> {
344 if source.len() < Self::HEADER_LEN {
345 return Err(Error::HeaderTooShort);
346 }
347 if source[..4] != MAGIC {
348 return Err(Error::InvalidMagic);
349 }
350 match source[4] {
351 b'2' | b'3' => {}
352 _ => return Err(Error::UnsupportedVersion),
353 }
354 let tzh_ttisgmtcnt = BE::read_u32(&source[20..24]) as usize;
355 let tzh_ttisstdcnt = BE::read_u32(&source[24..28]) as usize;
356 let tzh_leapcnt = BE::read_u32(&source[28..32]) as usize;
357 let tzh_timecnt = BE::read_u32(&source[32..36]) as usize;
358 let tzh_typecnt = BE::read_u32(&source[36..40]) as usize;
359 let tzh_charcnt = BE::read_u32(&source[40..44]) as usize;
360
361 if (tzh_ttisgmtcnt != 0 && tzh_ttisgmtcnt != tzh_typecnt)
362 || (tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt)
363 {
364 return Err(Error::InconsistentTypeCount);
365 }
366 if tzh_typecnt == 0 {
367 return Err(Error::NoTypes);
368 }
369
370 Ok(Header {
371 tzh_ttisgmtcnt,
372 tzh_ttisstdcnt,
373 tzh_leapcnt,
374 tzh_timecnt,
375 tzh_typecnt,
376 tzh_charcnt,
377 })
378 }
379
380 const HEADER_LEN: usize = 44;
382
383 fn data_len<L>(&self) -> usize {
385 self.tzh_timecnt * (size_of::<L>() + 1)
386 + self.tzh_typecnt * 6
387 + self.tzh_charcnt
388 + self.tzh_leapcnt * (size_of::<L>() + 4)
389 + self.tzh_ttisstdcnt
390 + self.tzh_ttisgmtcnt
391 }
392
393 fn parse_content(&self, content: &[u8]) -> Result<Tz, Error> {
395 let trans_encoded_end = self.tzh_timecnt * 8;
397 let local_time_types_end = trans_encoded_end + self.tzh_timecnt;
398 let infos_end = local_time_types_end + self.tzh_typecnt * 6;
399 let abbr_end = infos_end + self.tzh_charcnt;
400
401 let names = from_utf8(&content[infos_end..abbr_end]).map_err(|_| Error::NonUtf8Abbr)?;
403
404 let ozs = content[local_time_types_end..infos_end]
406 .chunks_exact(6)
407 .map(|encoded| {
408 let seconds = BE::read_i32(&encoded[..4]);
409 let offset = FixedOffset::east_opt(seconds).ok_or(Error::OffsetOverflow)?;
410 let name = encoded[5];
411 if usize::from(name) >= names.len() {
412 return Err(Error::NameOffsetOutOfBounds);
413 }
414 Ok(Oz { offset, name })
415 })
416 .collect::<Result<Vec<_>, Error>>()?;
417
418 let trans_encoded = &content[..trans_encoded_end];
420 let local_time_types = &content[trans_encoded_end..local_time_types_end];
421
422 let mut prev_oz = ozs[0];
423
424 let mut utc_to_local = Vec::with_capacity(self.tzh_timecnt + 1);
425 utc_to_local.push((i64::min_value(), prev_oz));
426 for (te, <t) in trans_encoded.chunks_exact(8).zip(local_time_types) {
427 let oz = *ozs.get(usize::from(ltt)).ok_or(Error::InvalidType)?;
428 let timestamp = BE::read_i64(te);
429 utc_to_local.push((timestamp, oz));
430 }
431
432 let mut local_to_utc = Vec::with_capacity(self.tzh_timecnt * 2 + 1);
433 local_to_utc.push((i64::min_value(), LocalResult::Single(prev_oz)));
434 for &(utc_ts, cur_oz) in &utc_to_local[1..] {
435 let prev_local_ts = prev_oz.to_local(utc_ts);
436 let cur_local_ts = cur_oz.to_local(utc_ts);
437 match prev_local_ts.cmp(&cur_local_ts) {
438 Ordering::Less => {
439 local_to_utc.push((prev_local_ts, LocalResult::None));
440 local_to_utc.push((cur_local_ts, LocalResult::Single(cur_oz)));
441 }
442 Ordering::Equal => {
443 local_to_utc.push((cur_local_ts, LocalResult::Single(cur_oz)));
444 }
445 Ordering::Greater => {
446 local_to_utc.push((cur_local_ts, LocalResult::Ambiguous(prev_oz, cur_oz)));
447 local_to_utc.push((prev_local_ts, LocalResult::Single(cur_oz)));
448 }
449 };
450 prev_oz = cur_oz;
451 }
452
453 Ok(Tz {
454 names: names.into(),
455 utc_to_local: utc_to_local.into_boxed_slice(),
456 local_to_utc: local_to_utc.into_boxed_slice(),
457 })
458 }
459}
460
461impl Tz {
462 pub fn parse(_name: &str, source: &[u8]) -> Result<Self, Error> {
483 let header = Header::parse(source)?;
484 let first_ver_len = Header::HEADER_LEN + header.data_len::<i32>();
485 let source = source.get(first_ver_len..).ok_or(Error::DataTooShort)?;
486 let header = Header::parse(source)?;
487 let second_ver_len = Header::HEADER_LEN + header.data_len::<i64>();
488 if source.len() < second_ver_len {
489 return Err(Error::DataTooShort);
490 }
491 header.parse_content(&source[Header::HEADER_LEN..])
492 }
493
494 #[cfg(unix)]
501 pub fn named(name: &str) -> std::io::Result<Self> {
502 if name.contains('.') {
503 return Err(Error::InvalidTimeZoneFileName.into());
504 }
505 let content = read(format!("/usr/share/zoneinfo/{}", name))?;
506 Ok(Self::parse(name, &content)?)
507 }
508
509 #[cfg(unix)]
516 pub fn local() -> std::io::Result<Self> {
517 if let Ok(tz) = env::var("TZ") {
518 if !tz.is_empty() {
519 return Self::named(&tz);
520 }
521 }
522 let content = read("/etc/localtime")?;
523 Ok(Self::parse("Local", &content)?)
524 }
525}
526
527impl From<chrono::Utc> for Tz {
528 fn from(_: chrono::Utc) -> Self {
529 let oz = Oz {
530 offset: FixedOffset::east(0),
531 name: 0,
532 };
533 Self {
534 names: "UTC\0".into(),
535 utc_to_local: vec![(i64::min_value(), oz)].into_boxed_slice(),
536 local_to_utc: vec![(i64::min_value(), LocalResult::Single(oz))].into_boxed_slice(),
537 }
538 }
539}
540
541impl From<FixedOffset> for Tz {
542 fn from(offset: FixedOffset) -> Self {
543 let mut name = offset.to_string();
544 name.push('\0');
545 let oz = Oz { offset, name: 0 };
546 Self {
547 names: name.into_boxed_str(),
548 utc_to_local: vec![(i64::min_value(), oz)].into_boxed_slice(),
549 local_to_utc: vec![(i64::min_value(), LocalResult::Single(oz))].into_boxed_slice(),
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use chrono::TimeZone;
558
559 #[test]
560 fn tz_from_fixed_offset() {
561 let utc_tz = Tz::from(chrono::Utc);
562 let fixed_1_tz = Tz::from(chrono::FixedOffset::east(3600));
563
564 let dt_0 = (&utc_tz).ymd(1000, 1, 1).and_hms(15, 0, 0);
565 let dt_1_converted = dt_0.with_timezone(&&fixed_1_tz);
566 let dt_1_expected = (&fixed_1_tz).ymd(1000, 1, 1).and_hms(16, 0, 0);
567 assert_eq!(dt_1_converted, dt_1_expected);
568 }
569
570 #[test]
571 fn parse_valid_contents() {
572 let contents: Vec<&[u8]> = vec![
573 b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\0\0\
575 TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x04\xF8\0\0\0\0\0\0\0\0\0\0\0\0\0\0UTC\0\0\0\nUTC0\n",
576
577 b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\
579 TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\nUTC0\n",
580 ];
581
582 for (i, content) in contents.into_iter().enumerate() {
583 assert!(
584 Tz::parse("__valid__", content).is_ok(),
585 "test #{}: should be able to parse {:x?}",
586 i,
587 content
588 );
589 }
590 }
591
592 #[test]
593 fn parse_invalid_contents() {
594 let contents: Vec<(&[u8], Error)> = vec![
595 (
596 b"",
598 Error::HeaderTooShort,
599 ),
600 (
601 b"not valid",
603 Error::HeaderTooShort,
604 ),
605 (
606 b"TZif",
608 Error::HeaderTooShort,
609 ),
610 (
611 b"TZif\0",
613 Error::HeaderTooShort,
614 ),
615 (
616 b"file with invalid magic should produce error",
618 Error::InvalidMagic,
619 ),
620 (
621 b"TZifxxxxxxxxxxxxxxxx\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
623 Error::UnsupportedVersion,
624 ),
625 (
626 b"TZif1xxxxxxxxxxxxxxx\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
628 Error::UnsupportedVersion,
629 ),
630 (
631 b"TZif3xxxxxxxxxxxxxxx\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
633 Error::NoTypes,
634 ),
635 (
636 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0",
638 Error::InconsistentTypeCount,
639 ),
640 (
641 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\0",
643 Error::InconsistentTypeCount,
644 ),
645 (
646 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\0",
648 Error::InconsistentTypeCount,
649 ),
650 (
651 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0",
653 Error::DataTooShort,
654 ),
655 (
656 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxx",
658 Error::HeaderTooShort,
659 ),
660 (
661 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxxfile with invalid magic should produce error",
663 Error::InvalidMagic,
664 ),
665 (
666 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxxTZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0",
668 Error::DataTooShort,
669 ),
670 (
671 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxxTZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0aaaaaaaa",
673 Error::OffsetOverflow,
674 ),
675 (
676 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxxTZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0aaaaaa",
678 Error::NameOffsetOutOfBounds,
679 ),
680 (
681 b"TZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\0xxxxxxxxTZif2xxxxxxxxxxxxxxx\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x02tttttttti\0\0aaa\0A\0aa",
683 Error::InvalidType,
684 ),
685 ];
686
687 for (i, (content, error)) in contents.into_iter().enumerate() {
688 assert_eq!(
689 Tz::parse("__invalid__", content),
690 Err(error),
691 "test #{}: should not be able to parse {:x?}",
692 i,
693 content
694 );
695 }
696 }
697
698 #[test]
699 #[cfg(unix)]
700 fn invalid_timezone_filename() {
701 let err = Tz::named("../../../../../../../../etc/passwd").unwrap_err();
702 assert_eq!(err.kind(), std::io::ErrorKind::Other);
703 assert_eq!(
704 err.into_inner().unwrap().downcast::<Error>().unwrap(),
705 Box::new(Error::InvalidTimeZoneFileName)
706 );
707 }
708
709 #[test]
710 #[cfg(unix)]
711 fn rctz_arctz() {
712 let tz = Tz::named("Europe/London").unwrap();
713 let rctz = RcTz::named("Europe/London").unwrap();
714 let arctz = ArcTz::named("Europe/London").unwrap();
715 let rctz2 = RcTz::new(tz.clone());
716 let arctz2 = ArcTz::new(tz.clone());
717 assert_eq!(tz, *rctz);
718 assert_eq!(tz, *arctz);
719 assert_eq!(rctz, rctz2);
720 assert_eq!(arctz, arctz2);
721 }
722
723 #[test]
724 #[cfg(unix)]
725 fn local() {
726 let tz = Tz::local().unwrap();
727 let dt = (&tz).ymd(2019, 2, 13).and_hms(14, 5, 18);
728 let cdt = chrono::Local.ymd(2019, 2, 13).and_hms(14, 5, 18);
729 assert_eq!(dt.naive_utc(), cdt.naive_utc());
730 }
731}
732
733#[cfg(all(test, unix))]
734mod chrono_tz_tests {
735 use super::Tz;
736 use chrono::{Duration, TimeZone};
737 use lazy_static::lazy_static;
738
739 lazy_static! {
740 static ref ADELAIDE: Tz = Tz::named("Australia/Adelaide").unwrap();
741 static ref APIA: Tz = Tz::named("Pacific/Apia").unwrap();
742 static ref AMSTERDAM: Tz = Tz::named("Europe/Amsterdam").unwrap();
743 static ref BERLIN: Tz = Tz::named("Europe/Berlin").unwrap();
744 static ref DANMARKSHAVN: Tz = Tz::named("America/Danmarkshavn").unwrap();
745 static ref DHAKA: Tz = Tz::named("Asia/Dhaka").unwrap();
746 static ref EASTERN: Tz = Tz::named("US/Eastern").unwrap();
747 static ref GAZA: Tz = Tz::named("Asia/Gaza").unwrap();
748 static ref JERUSALEM: Tz = Tz::named("Asia/Jerusalem").unwrap();
749 static ref KATHMANDU: Tz = Tz::named("Asia/Kathmandu").unwrap();
750 static ref LONDON: Tz = Tz::named("Europe/London").unwrap();
751 static ref MOSCOW: Tz = Tz::named("Europe/Moscow").unwrap();
752 static ref NEW_YORK: Tz = Tz::named("America/New_York").unwrap();
753 static ref TAHITI: Tz = Tz::named("Pacific/Tahiti").unwrap();
754 static ref NOUMEA: Tz = Tz::named("Pacific/Noumea").unwrap();
755 static ref TRIPOLI: Tz = Tz::named("Africa/Tripoli").unwrap();
756 static ref UTC: Tz = Tz::named("Etc/UTC").unwrap();
757 static ref VILNIUS: Tz = Tz::named("Europe/Vilnius").unwrap();
758 static ref WARSAW: Tz = Tz::named("Europe/Warsaw").unwrap();
759 }
760
761 #[test]
762 fn london_to_berlin() {
763 let dt = (&*LONDON).ymd(2016, 10, 8).and_hms(17, 0, 0);
764 let converted = dt.with_timezone(&&*BERLIN);
765 let expected = (&*BERLIN).ymd(2016, 10, 8).and_hms(18, 0, 0);
766 assert_eq!(converted, expected);
767 }
768
769 #[test]
770 fn us_eastern_dst_commutativity() {
771 let dt = (&*UTC).ymd(2002, 4, 7).and_hms(7, 0, 0);
772 for days in -420..720 {
773 let dt1 = (dt.clone() + Duration::days(days)).with_timezone(&&*EASTERN);
774 let dt2 = dt.with_timezone(&&*EASTERN) + Duration::days(days);
775 assert_eq!(dt1, dt2);
776 }
777 }
778
779 #[test]
780 fn warsaw_tz_name() {
781 let dt = (&*UTC).ymd(1915, 8, 4).and_hms(22, 35, 59);
782 assert_eq!(dt.with_timezone(&&*WARSAW).format("%Z").to_string(), "WMT");
783 let dt = dt + Duration::seconds(1);
784 assert_eq!(dt.with_timezone(&&*WARSAW).format("%Z").to_string(), "CET");
785 }
786
787 #[test]
788 fn vilnius_utc_offset() {
789 let dt = (&*UTC)
790 .ymd(1916, 12, 31)
791 .and_hms(22, 35, 59)
792 .with_timezone(&&*VILNIUS);
793 assert_eq!(dt, (&*VILNIUS).ymd(1916, 12, 31).and_hms(23, 59, 59));
794 let dt = dt + Duration::seconds(1);
795 assert_eq!(dt, (&*VILNIUS).ymd(1917, 1, 1).and_hms(0, 11, 36));
796 }
797
798 #[test]
799 fn victorian_times() {
800 let dt = (&*UTC)
801 .ymd(1847, 12, 1)
802 .and_hms(0, 1, 14)
803 .with_timezone(&&*LONDON);
804 assert_eq!(dt, (&&*LONDON).ymd(1847, 11, 30).and_hms(23, 59, 59));
805 let dt = dt + Duration::seconds(1);
806 assert_eq!(dt, (&&*LONDON).ymd(1847, 12, 1).and_hms(0, 1, 15));
807 }
808
809 #[test]
810 fn london_dst() {
811 let dt = (&*LONDON).ymd(2016, 3, 10).and_hms(5, 0, 0);
812 let later = dt + Duration::days(180);
813 let expected = (&*LONDON).ymd(2016, 9, 6).and_hms(6, 0, 0);
814 assert_eq!(later, expected);
815 }
816
817 #[test]
818 fn international_date_line_change() {
819 let dt = (&*UTC)
820 .ymd(2011, 12, 30)
821 .and_hms(9, 59, 59)
822 .with_timezone(&&*APIA);
823 assert_eq!(dt, (&*APIA).ymd(2011, 12, 29).and_hms(23, 59, 59));
824 let dt = dt + Duration::seconds(1);
825 assert_eq!(dt, (&*APIA).ymd(2011, 12, 31).and_hms(0, 0, 0));
826 }
827
828 #[test]
829 fn negative_offset_with_minutes_and_seconds() {
830 let dt = (&*UTC)
831 .ymd(1900, 1, 1)
832 .and_hms(12, 0, 0)
833 .with_timezone(&&*DANMARKSHAVN);
834 assert_eq!(dt, (&*DANMARKSHAVN).ymd(1900, 1, 1).and_hms(10, 45, 20));
835 }
836
837 #[test]
838 fn monotonicity() {
839 let mut dt = (&*NOUMEA).ymd(1800, 1, 1).and_hms(12, 0, 0);
840 for _ in 0..24 * 356 * 400 {
841 let new = dt.clone() + Duration::hours(1);
842 assert!(new > dt);
843 assert!(new.with_timezone(&&*UTC) > dt.with_timezone(&&*UTC));
844 dt = new;
845 }
846 }
847
848 fn test_inverse<T: TimeZone>(tz: T, begin: i32, end: i32) {
849 for y in begin..end {
850 for d in 1..366 {
851 for h in 0..24 {
852 for m in 0..60 {
853 let dt = (&*UTC).yo(y, d).and_hms(h, m, 0);
854 let with_tz = dt.with_timezone(&tz);
855 let utc = with_tz.with_timezone(&&*UTC);
856 assert_eq!(dt, utc);
857 }
858 }
859 }
860 }
861 }
862
863 #[test]
864 fn inverse_london() {
865 test_inverse(&*LONDON, 1989, 1994);
866 }
867
868 #[test]
869 fn inverse_dhaka() {
870 test_inverse(&*DHAKA, 1995, 2000);
871 }
872
873 #[test]
874 fn inverse_apia() {
875 test_inverse(&*APIA, 2011, 2012);
876 }
877
878 #[test]
879 fn inverse_tahiti() {
880 test_inverse(&*TAHITI, 1911, 1914);
881 }
882
883 #[test]
884 fn string_representation() {
885 let dt = (&*UTC)
886 .ymd(2000, 9, 1)
887 .and_hms(12, 30, 15)
888 .with_timezone(&&*ADELAIDE);
889 assert_eq!(dt.to_string(), "2000-09-01 22:00:15 ACST");
890 assert_eq!(format!("{:?}", dt), "2000-09-01T22:00:15ACST");
891 assert_eq!(dt.to_rfc3339(), "2000-09-01T22:00:15+09:30");
892 assert_eq!(format!("{}", dt), "2000-09-01 22:00:15 ACST");
893 }
894
895 #[test]
896 fn tahiti() {
897 let dt = (&*UTC)
898 .ymd(1912, 10, 1)
899 .and_hms(9, 58, 16)
900 .with_timezone(&&*TAHITI);
901 let before = dt.clone() - Duration::hours(1);
902 assert_eq!(before, (&*TAHITI).ymd(1912, 9, 30).and_hms(23, 0, 0));
903 let after = dt + Duration::hours(1);
904 assert_eq!(after, (&*TAHITI).ymd(1912, 10, 1).and_hms(0, 58, 16));
905 }
906
907 #[test]
908 fn second_offsets() {
909 let dt = (&*UTC)
910 .ymd(1914, 1, 1)
911 .and_hms(13, 40, 28)
912 .with_timezone(&&*AMSTERDAM);
913 assert_eq!(dt.to_string(), "1914-01-01 14:00:00 AMT");
914 assert_eq!(dt.to_rfc3339(), "1914-01-01T14:00:00+00:19");
915 }
916
917 #[test]
918 #[should_panic]
919 fn nonexistent_time() {
920 let _ = (&*LONDON).ymd(2016, 3, 27).and_hms(1, 30, 0);
921 }
922
923 #[test]
924 #[should_panic]
925 fn nonexistent_time_2() {
926 let _ = (&*LONDON).ymd(2016, 3, 27).and_hms(1, 0, 0);
927 }
928
929 #[test]
930 fn time_exists() {
931 let _ = (&*LONDON).ymd(2016, 3, 27).and_hms(2, 0, 0);
932 }
933
934 #[test]
935 #[should_panic]
936 fn ambiguous_time() {
937 let _ = (&*LONDON).ymd(2016, 10, 30).and_hms(1, 0, 0);
938 }
939
940 #[test]
941 #[should_panic]
942 fn ambiguous_time_2() {
943 let _ = (&*LONDON).ymd(2016, 10, 30).and_hms(1, 30, 0);
944 }
945
946 #[test]
947 #[should_panic]
948 fn ambiguous_time_3() {
949 let _ = (&*MOSCOW).ymd(2014, 10, 26).and_hms(1, 30, 0);
950 }
951
952 #[test]
953 #[should_panic]
954 fn ambiguous_time_4() {
955 let _ = (&*MOSCOW).ymd(2014, 10, 26).and_hms(1, 0, 0);
956 }
957
958 #[test]
959 fn unambiguous_time() {
960 let _ = (&*LONDON).ymd(2016, 10, 30).and_hms(2, 0, 0);
961 }
962
963 #[test]
964 fn unambiguous_time_2() {
965 let _ = (&*MOSCOW).ymd(2014, 10, 26).and_hms(2, 0, 0);
966 }
967
968 #[test]
971 fn test_london_5_days_ago_to_new_york() {
972 let from = (&*LONDON).ymd(2013, 12, 25).and_hms(14, 0, 0);
973 let to = (&*NEW_YORK).ymd(2013, 12, 30).and_hms(14, 0, 0);
974 assert_eq!(
975 to.signed_duration_since(from),
976 Duration::days(5) + Duration::hours(5)
977 );
978 }
979
980 #[test]
981 fn london_to_australia() {
982 let from = (&*LONDON).ymd(2013, 12, 25).and_hms(14, 0, 0);
985 let to = (&*ADELAIDE).ymd(2013, 12, 30).and_hms(14, 0, 0);
986 assert_eq!(
987 to.signed_duration_since(from),
988 Duration::days(5) - Duration::minutes(630)
989 );
990 }
991
992 #[test]
993 fn london_to_nepal() {
994 let from = (&*LONDON).ymd(2013, 12, 25).and_hms(14, 0, 0);
996 let to = (&*KATHMANDU).ymd(2013, 12, 30).and_hms(14, 0, 0);
997 assert_eq!(
998 to.signed_duration_since(from),
999 Duration::days(5) - Duration::minutes(345)
1000 );
1001 }
1002
1003 #[test]
1004 fn autumn() {
1005 let from = (&*LONDON).ymd(2013, 10, 25).and_hms(12, 0, 0);
1006 let to = (&*LONDON).ymd(2013, 11, 1).and_hms(12, 0, 0);
1007 assert_eq!(
1008 to.signed_duration_since(from),
1009 Duration::days(7) + Duration::hours(1)
1010 );
1011 }
1012
1013 #[test]
1014 fn earlier_daylight_savings_in_new_york() {
1015 let from = (&*NEW_YORK).ymd(2013, 10, 25).and_hms(12, 0, 0);
1016 let to = (&*NEW_YORK).ymd(2013, 11, 1).and_hms(12, 0, 0);
1017 assert_eq!(to.signed_duration_since(from), Duration::days(7));
1018 }
1019
1020 #[test]
1021 fn southern_hemisphere_clocks_forward() {
1022 let from = (&*ADELAIDE).ymd(2013, 10, 1).and_hms(12, 0, 0);
1023 let to = (&*ADELAIDE).ymd(2013, 11, 1).and_hms(12, 0, 0);
1024 assert_eq!(
1025 to.signed_duration_since(from),
1026 Duration::days(31) - Duration::hours(1)
1027 );
1028 }
1029
1030 #[test]
1031 fn samoa_skips_a_day() {
1032 let from = (&*APIA).ymd(2011, 12, 29).and_hms(12, 0, 0);
1033 let to = (&*APIA).ymd(2011, 12, 31).and_hms(12, 0, 0);
1034 assert_eq!(to.signed_duration_since(from), Duration::days(1));
1035 }
1036
1037 #[test]
1038 fn double_bst() {
1039 let from = (&*LONDON).ymd(1942, 6, 1).and_hms(12, 0, 0);
1040 let to = (&*UTC).ymd(1942, 6, 1).and_hms(12, 0, 0);
1041 assert_eq!(to.signed_duration_since(from), Duration::hours(2));
1042 }
1043
1044 #[test]
1045 fn libya_2013() {
1046 let from = (&*TRIPOLI).ymd(2012, 3, 1).and_hms(12, 0, 0);
1048 let to = (&*TRIPOLI).ymd(2012, 4, 1).and_hms(12, 0, 0);
1049 assert_eq!(to.signed_duration_since(from), Duration::days(31));
1050
1051 let from = (&*TRIPOLI).ymd(2013, 3, 1).and_hms(12, 0, 0);
1052 let to = (&*TRIPOLI).ymd(2013, 4, 1).and_hms(12, 0, 0);
1053 assert_eq!(
1054 to.signed_duration_since(from),
1055 Duration::days(31) - Duration::hours(1)
1056 );
1057
1058 let from = (&*TRIPOLI).ymd(2014, 3, 1).and_hms(12, 0, 0);
1059 let to = (&*TRIPOLI).ymd(2014, 4, 1).and_hms(12, 0, 0);
1060 assert_eq!(to.signed_duration_since(from), Duration::days(31));
1061 }
1062
1063 #[test]
1064 fn israel_palestine() {
1065 let from = (&*JERUSALEM).ymd(2016, 10, 29).and_hms(12, 0, 0);
1066 let to = (&*GAZA).ymd(2016, 10, 29).and_hms(12, 0, 0);
1067 assert_eq!(to.signed_duration_since(from), Duration::hours(1));
1068 }
1069
1070 #[test]
1071 fn leapsecond() {
1072 let from = (&*UTC).ymd(2016, 6, 30).and_hms(23, 59, 59);
1073 let to = (&*UTC).ymd(2016, 6, 30).and_hms_milli(23, 59, 59, 1000);
1074 assert_eq!(to.signed_duration_since(from), Duration::seconds(1));
1075 }
1076}