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