tzfile/
lib.rs

1//! `tzfile` is a [`chrono::TimeZone`] implementation using the system tz
2//! database. It can parse compiled (binary) time zone files inside
3//! `/usr/share/zoneinfo` into time zone objects for `chrono`.
4
5use 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/// `Oz` ("offset zone") represents a continuous period of time where the offset
23/// from UTC is constant and has the same abbreviation.
24#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
25struct Oz {
26    /// The time zone offset from UTC.
27    offset: FixedOffset,
28    /// The byte index to the time zone abbreviation.
29    name: u8,
30}
31
32impl Oz {
33    /// Converts a UTC timestamp to a local timestamp.
34    fn to_local(self, utc_ts: i64) -> i64 {
35        utc_ts + i64::from(self.offset.local_minus_utc())
36    }
37}
38
39/// Time zone parsed from a tz database file.
40///
41/// When a time zone has complex transition rules, a `Tz` object can be very
42/// large and expensive to clone. As every [`DateTime`](chrono::DateTime)
43/// instant would store a copy of the time zone object, it would be very slow to
44/// support `DateTime<Tz>` directly. Therefore, `Tz` itself does not implement
45/// [`TimeZone`]. Rather, you may use one of the following instead:
46///
47/// * `&'a Tz` — zero cost to clone, but only valid within the lifetime `'a`.
48/// * [`RcTz`] — uses reference counting ([`Rc`]) to support shallow cloning,
49///     but is not thread-safe.
50/// * [`ArcTz`] — uses atomic reference counting ([`Arc`]) to support shallow
51///     cloning, slightly more expensive than [`RcTz`] but is thread-safe.
52///
53/// # Examples
54///
55/// Read the time zone information from the system, and use `&Tz` as `TimeZone`.
56///
57/// ```
58/// # #[cfg(unix)] {
59/// use chrono::{Utc, TimeZone};
60/// use tzfile::Tz;
61///
62/// let tz = Tz::named("America/New_York")?;
63/// let dt1 = Utc.ymd(2019, 3, 10).and_hms(6, 45, 0);
64/// assert_eq!(dt1.with_timezone(&&tz).to_string(), "2019-03-10 01:45:00 EST");
65/// let dt2 = Utc.ymd(2019, 3, 10).and_hms(7, 15, 0);
66/// assert_eq!(dt2.with_timezone(&&tz).to_string(), "2019-03-10 03:15:00 EDT");
67///
68/// # } Ok::<_, std::io::Error>(())
69/// ```
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct Tz {
72    /// Null-terminated time zone abbreviations concatenated as a single string.
73    names: Box<str>,
74    /// Sorted array of UTC-to-local conversion results. The first item of the
75    /// tuple is the starting UTC timestamp, and second item is the
76    /// corresponding local offset.
77    utc_to_local: Box<[(i64, Oz)]>,
78    /// Sorted array of local-to-UTC conversion results. The first item of the
79    /// tuple is the starting local timestamp, and second item is the
80    /// corresponding local offset (with ambiguity/invalid time handling).
81    local_to_utc: Box<[(i64, LocalResult<Oz>)]>,
82}
83
84/// Extracts the lower bound index from the result of standard `binary_search`.
85fn to_lower_bound(bsr: Result<usize, usize>) -> usize {
86    bsr.unwrap_or_else(|i| i - 1)
87}
88
89impl Tz {
90    /// Obtains an [`Oz`] at the local date.
91    ///
92    /// Returns `None` if the date is invalid.
93    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    /// Obtains the [`Oz`] at the local timestamp.
101    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    /// Obtains the [`Oz`] at the UTC timestamp.
110    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(&timestamp)),
114        );
115        self.utc_to_local[index].1
116    }
117}
118
119/// Offset type associated with [`Tz`].
120#[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/// Reference-counted time zone.
147///
148/// This type is equivalent to [`Rc`]`<`[`Tz`]`>`, but needed to workaround
149/// Rust's coherence rule to implement [`TimeZone`].
150#[derive(Clone, Debug, Hash, Eq, PartialEq)]
151pub struct RcTz(pub Rc<Tz>);
152
153impl RcTz {
154    /// Wraps an existing [`Tz`] object in this reference-counted container.
155    pub fn new(tz: Tz) -> Self {
156        Self(Rc::new(tz))
157    }
158
159    /// Reads and parses a system time zone.
160    ///
161    /// Equivalent to calling [`Tz::named()`] and wraps the result in this
162    /// reference-counted container.
163    ///
164    /// This function is currently only supported on Unix.
165    #[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/// Atomic reference-counted time zone.
185///
186/// This type is equivalent to [`Arc`]`<`[`Tz`]`>`, but needed to workaround
187/// Rust's coherence rule to implement [`TimeZone`].
188#[derive(Clone, Debug, Hash, Eq, PartialEq)]
189pub struct ArcTz(pub Arc<Tz>);
190
191impl ArcTz {
192    /// Wraps an existing [`Tz`] object in this atomic reference-counted
193    /// container.
194    pub fn new(tz: Tz) -> Self {
195        Self(Arc::new(tz))
196    }
197
198    /// Reads and parses a system time zone.
199    ///
200    /// Equivalent to calling [`Tz::named()`] and wraps the result in this
201    /// atomic reference-counted container.
202    ///
203    /// This function is currently only supported on Unix.
204    #[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/// Parse errors from [`Tz::parse()`].
276#[derive(Debug, PartialEq, Eq, Clone)]
277pub enum Error {
278    /// The source bytes is too short to parse the header.
279    HeaderTooShort,
280    /// The source does not start with the correct magic string (`"TZif"`).
281    InvalidMagic,
282    /// Unsupported tzfile version. Currently we only support versions 2 and 3.
283    UnsupportedVersion,
284    /// The lengths of several related arrays in the file are not the same,
285    /// making the file invalid.
286    InconsistentTypeCount,
287    /// The tzfile contains no time zone information.
288    NoTypes,
289    /// The time zone offset exceeds ±86400s (1 day).
290    OffsetOverflow,
291    /// Some time zone abbreviations are not valid UTF-8.
292    NonUtf8Abbr,
293    /// The source bytes is too short to parse the content.
294    DataTooShort,
295    /// Invalid time zone file name.
296    InvalidTimeZoneFileName,
297    /// The time zone transition type is invalid.
298    InvalidType,
299    /// Name offset is out of bounds.
300    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    /// Parses the header from the prefix of the `source`.
343    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    /// The length of the header.
381    const HEADER_LEN: usize = 44;
382
383    /// The length of the content, when `time_t` is represented by type `L`.
384    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    /// Parses the time zone information from the prefix of `content`.
394    fn parse_content(&self, content: &[u8]) -> Result<Tz, Error> {
395        // Obtain the byte indices where each array ends.
396        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        // Collect the timezone abbreviations.
402        let names = from_utf8(&content[infos_end..abbr_end]).map_err(|_| Error::NonUtf8Abbr)?;
403
404        // Collect the timezone infos.
405        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        // Collect the transition times.
419        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, &ltt) 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    /// Parses the content of the tz database file.
463    ///
464    /// This crate can only recognize version 2 and 3 of the tz database. Like
465    /// `chrono`, leap second information is ignored. The embedded POSIX TZ
466    /// string, which describes non-hard-coded transition rules in the far
467    /// future, is also not handled.
468    ///
469    /// # Examples
470    ///
471    /// Read a file into bytes and then parse it.
472    ///
473    /// ```rust
474    /// # #[cfg(unix)] {
475    /// use tzfile::Tz;
476    ///
477    /// let content = std::fs::read("/usr/share/zoneinfo/Etc/UTC")?;
478    /// let tz = Tz::parse("Etc/UTC", &content)?;
479    ///
480    /// # } Ok::<_, Box<dyn std::error::Error>>(())
481    /// ```
482    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    /// Reads and parses a system time zone.
495    ///
496    /// This function is equivalent to reading `/usr/share/zoneinfo/{name}` and
497    /// the constructs a time zone via [`parse()`](Tz::parse).
498    ///
499    /// This function is currently only supported on Unix.
500    #[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    /// Reads the parses the current local time zone.
510    ///
511    /// This function calls [`Tz::named`]`($TZ)` if the environment variable
512    /// `$TZ` is set, otherwise reads and parses `/etc/localtime`.
513    ///
514    /// This function is currently only supported on Unix.
515    #[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            // #0
574            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            // #1
578            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                // #0
597                b"",
598                Error::HeaderTooShort,
599            ),
600            (
601                // #1
602                b"not valid",
603                Error::HeaderTooShort,
604            ),
605            (
606                // #2
607                b"TZif",
608                Error::HeaderTooShort,
609            ),
610            (
611                // #3
612                b"TZif\0",
613                Error::HeaderTooShort,
614            ),
615            (
616                // #4
617                b"file with invalid magic should produce error",
618                Error::InvalidMagic,
619            ),
620            (
621                // #5
622                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                // #6
627                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                // #7
632                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                // #8
637                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                // #9
642                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                // #10
647                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                // #11
652                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                // #12
657                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                // #13
662                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                // #14
667                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                // #15
672                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                // #16
677                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                // #17
682                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    // the numberphile tests
969
970    #[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        // at the time Tom was speaking, Adelaide was 10 1/2 hours ahead
983        // many other parts of Australia use different time zones
984        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        // note Tom gets this wrong, it's 5 3/4 hours as he is speaking
995        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        // Libya actually put their clocks *forward* in 2013, but not in any other year
1047        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}