zoneinfo_compiled-0.5.1/.cargo_vcs_info.json0000644000000001120000000000000145330ustar { "git": { "sha1": "f430dcdd2bbab75484e9937078d99b927dadf31e" } } zoneinfo_compiled-0.5.1/.gitignore000064400000000000000000000000220000000000000152710ustar 00000000000000target Cargo.lock zoneinfo_compiled-0.5.1/Cargo.lock0000644000000032660000000000000125230ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "datetime" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ "libc", "redox_syscall", "winapi", ] [[package]] name = "libc" version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zoneinfo_compiled" version = "0.5.1" dependencies = [ "byteorder", "datetime", ] zoneinfo_compiled-0.5.1/Cargo.toml0000644000000020060000000000000125350ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "zoneinfo_compiled" version = "0.5.1" authors = ["Benjamin Sago "] exclude = ["/.rustfmt.toml", "/.travis.yml"] description = "Library for parsing compiled zoneinfo files" documentation = "https://docs.rs/zoneinfo_compiled/" readme = "README.md" license = "MIT" repository = "https://github.com/rust-datetime/zoneinfo-compiled/" [lib] name = "zoneinfo_compiled" [dependencies.byteorder] version = "1.0" [dependencies.datetime] version = "0.5.2" default_features = false zoneinfo_compiled-0.5.1/Cargo.toml.orig000064400000000000000000000007520000000000000162020ustar 00000000000000[package] name = "zoneinfo_compiled" description = "Library for parsing compiled zoneinfo files" authors = ["Benjamin Sago "] documentation = "https://docs.rs/zoneinfo_compiled/" exclude = ["/.rustfmt.toml", "/.travis.yml"] license = "MIT" repository = "https://github.com/rust-datetime/zoneinfo-compiled/" readme = "README.md" version = "0.5.1" [lib] name = "zoneinfo_compiled" [dependencies] byteorder = "1.0" datetime = { version = "0.5.2", default_features = false } zoneinfo_compiled-0.5.1/LICENCE000064400000000000000000000020560000000000000142770ustar 00000000000000MIT License Copyright (c) 2018 Benjamin Sago Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. zoneinfo_compiled-0.5.1/README.md000064400000000000000000000014350000000000000145710ustar 00000000000000# zoneinfo_compiled [![zoneinfo_compiled on crates.io](https://meritbadge.herokuapp.com/zoneinfo_compiled)](https://crates.io/rust-datetime/zoneinfo_compiled) [![Build Status](https://travis-ci.org/rust-datetime/zoneinfo-compiled.svg?branch=master)](https://travis-ci.org/rust-datetime/zoneinfo-compiled) This is a library for parsing compiled versions of the Olson zoneinfo database. ### [View the Rustdoc](https://docs.rs/zoneinfo_compiled/) # Installation This crate works with [Cargo](https://crates.io). Add the following to your `Cargo.toml` dependencies section: ```toml [dependencies] datetime = "0.5" zoneinfo_compiled = "0.5" ``` The earliest version of Rust that this crate is tested against is [Rust v1.31.0](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html). zoneinfo_compiled-0.5.1/examples/tzdump.rs000064400000000000000000000022140000000000000170150ustar 00000000000000extern crate zoneinfo_compiled; use std::env; use std::fs::File; use std::io::Read; use std::path::Path; // This example is broken until we have a way to get at the transitions in // time zone data directly. Right now it just does a Rust Debug dump of the // file... fn main() { for arg in env::args().skip(1) { match File::open(&Path::new(&arg)) { Ok(mut file) => { let mut contents = Vec::new(); file.read_to_end(&mut contents).unwrap(); match zoneinfo_compiled::parse(contents) { Ok(tzdata) => println!("{:#?}", tzdata), Err(e) => println!("Error: {}", e), } }, Err(e) => println!("Couldn't open file {}: {}", arg, e), } } } // fn tzdump(mut tz: zoneinfo_compiled::TZData) { // tz.transitions.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); // // for t in tz.transitions { // let l = &*t.local_time_type; // println!("{:11?}: name:{:5} offset:{:5} DST:{:5} type:{:?}", // t.timestamp, l.name, l.offset, l.is_dst, l.transition_type); // } // } zoneinfo_compiled-0.5.1/src/lib.rs000064400000000000000000000146430000000000000152220ustar 00000000000000#![warn(missing_copy_implementations)] //#![warn(missing_docs)] #![warn(nonstandard_style)] #![warn(trivial_numeric_casts)] #![warn(unreachable_pub)] #![warn(unused)] //! This is a library for parsing compiled zoneinfo files. use std::borrow::Cow; use std::convert::AsRef; use std::path::Path; use std::sync::Arc; extern crate byteorder; extern crate datetime; use datetime::zone::{TimeZone, TimeType, TimeZoneSource, FixedTimespan}; use datetime::zone::runtime::{OwnedTimeZone, OwnedFixedTimespanSet}; pub mod parser; pub use parser::Result; pub trait CompiledData { fn parse(input: Vec) -> Result; fn from_file>(path: P) -> Result { use std::io::{Read, BufReader}; use std::fs::File; let f = File::open(path)?; let mut r = BufReader::new(f); let mut contents: Vec = Vec::new(); r.read_to_end(&mut contents)?; let tz = Self::parse(contents)?; Ok(tz) } } impl CompiledData for TimeZone { fn parse(input: Vec) -> Result { let data = parse(input)?; let arc = Arc::new(data.time_zone); let tz = TimeZone(TimeZoneSource::Runtime(arc)); Ok(tz) } } /// Parsed, interpreted contents of a zoneinfo file. #[derive(PartialEq, Debug)] pub struct TZData { /// Vector of transitions that are described in this data. pub time_zone: OwnedTimeZone, /// Vector of leap seconds that are described in this data. pub leap_seconds: Vec, } /// A leap second specification. #[derive(Debug, PartialEq, Copy, Clone)] pub struct LeapSecond { /// Unix timestamp at which a leap second occurs. pub timestamp: i32, /// Number of leap seconds to be added. pub leap_second_count: i32, } /// A description of the local time in a particular timezone, during the /// period in which the clocks do not change. #[derive(Debug, PartialEq, Clone)] pub struct LocalTimeType { /// The time zone abbreviation - such as "GMT" or "UTC". pub name: String, /// Number of seconds to be added to Universal Time. pub offset: i64, /// Whether to set DST. pub is_dst: bool, /// The current 'type' of time. pub transition_type: TimeType, } impl LocalTimeType { /// Convert this set of fields into datetime’s `FixedTimespan` /// representation. /// /// It doesn’t actually contain any `'static` data, but if the lifetime is /// not specified, Rust ties its lifetime to `self`, when they’re actually /// completely unrelated. fn to_fixed_timespan(&self) -> FixedTimespan<'static> { FixedTimespan { offset: self.offset, is_dst: self.is_dst, name: Cow::Owned(self.name.clone()), } } } /// Parses a series of bytes into a timezone data structure. pub fn parse(input: Vec) -> Result { let tz = parser::parse(input, parser::Limits::sensible())?; cook(tz) } /// Interpret a set of internal time zone data. pub fn cook(tz: parser::TZData) -> Result { let mut transitions = Vec::with_capacity(tz.header.num_transitions as usize); let mut local_time_types = Vec::with_capacity(tz.header.num_local_time_types as usize); // First, build up a list of local time types... for i in 0 .. tz.header.num_local_time_types as usize { let ltt = &tz.time_info[i]; // Isolate the relevant bytes by the index of the start of the // string and the next available null char let name_bytes = tz.strings.iter() .cloned() .skip(ltt.name_offset as usize) .take_while(|&c| c != 0) .collect(); // (TODO: move to ‘copied’ when the library supports that Rust version) let std_flag = tz.standard_flags.get(i).cloned().unwrap_or_default() != 0; let gmt_flag = tz.gmt_flags.get(i).cloned().unwrap_or_default() != 0; let info = LocalTimeType { name: String::from_utf8(name_bytes)?, offset: ltt.offset as i64, is_dst: ltt.is_dst != 0, transition_type: flags_to_transition_type(std_flag, gmt_flag), }; local_time_types.push(info); } // ...then, link each transition with the time type it refers to. for i in 0 .. tz.header.num_transitions as usize { let t = &tz.transitions[i]; let ltt = local_time_types[t.local_time_type_index as usize].clone(); let timespan = ltt.to_fixed_timespan(); let transition = (t.timestamp as i64, timespan); transitions.push(transition); } let mut leap_seconds = Vec::new(); for ls in &tz.leap_seconds { let leap_second = LeapSecond { timestamp: ls.timestamp, leap_second_count: ls.leap_second_count, }; leap_seconds.push(leap_second); } // The `OwnedTimeZone` struct *requires* there to be at least one // transition. If there aren’t any in the file, we need to reach back into // the structure to get the *base* offset time, as it won’t be in the // transitions list. if transitions.is_empty() { let time_zone = OwnedTimeZone { name: None, fixed_timespans: OwnedFixedTimespanSet { first: local_time_types[0].to_fixed_timespan(), rest: Vec::new(), }, }; Ok(TZData { time_zone, leap_seconds }) } else { // We don’t care about the timestamp that the first transition happens // at: we assume it to have been in effect forever. let first = transitions.remove(0); let time_zone = OwnedTimeZone { name: None, fixed_timespans: OwnedFixedTimespanSet { first: first.1, rest: transitions, } }; Ok(TZData { time_zone, leap_seconds }) } } /// Combine the two flags to get the type of this transition. /// /// The transition type is stored as two separate flags in the data file. The /// first set comes completely before the second, so these can only be /// combined after the entire file has been read. fn flags_to_transition_type(standard: bool, gmt: bool) -> TimeType { match (standard, gmt) { (_, true) => TimeType::UTC, (true, _) => TimeType::Standard, (false, false) => TimeType::Wall, } } zoneinfo_compiled-0.5.1/src/parser.rs000064400000000000000000000367500000000000000157530ustar 00000000000000//! Parsing and structures of time zone files //! //! This module reads data from a buffer of bytes, and parses it into a //! `TZData` structure -- **doing a minimum of interpretation as to what these //! values mean!** The data read are all kept as primitive numeric types. For //! the code that turns these numbers into actual timezone data, see the root //! module. //! //! For more information on what these values mean, see //! [man 5 tzfile](ftp://ftp.iana.org/tz/code/tzfile.5.txt). use byteorder::{ReadBytesExt, BigEndian}; use std::error::Error as ErrorTrait; use std::fmt; use std::io::{Cursor, Read}; use std::result; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct Header { /// The version of this file's format - either '\0', or '2', or '3'. pub version: u8, /// The number of GMT flags in this file. /// (Equivalent to `tzh_ttisgmtcnt` in C) pub num_gmt_flags: u32, /// The number of Standard Time flags in this file. /// (Equivalent to `tzh_ttisstdcnt` in C) pub num_standard_flags: u32, /// The number of leap second entries in this file. /// (Equivalent to `tzh_leapcnt` in C) pub num_leap_seconds: u32, /// The number of transition entries in this file. /// (Equivalent to `tzh_timecnt` in C) pub num_transitions: u32, /// The number of local time types in this file. /// (Equivalent to `tzh_typecnt` in C) pub num_local_time_types: u32, /// The number of characters of time zone abbreviation strings in this file. /// (Equivalent to `tzh_charcnt` in C) pub num_abbr_chars: u32, } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct TransitionData { /// The time at which the rules for computing local time change. pub timestamp: i32, /// Index into the local time types array for this transition. pub local_time_type_index: u8, } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct LocalTimeTypeData { /// Number of seconds to be added to Universal Time. /// (Equivalent to `tt_gmtoff` in C) pub offset: i32, /// Whether to set DST. /// (Equivalent to `tt_isdst` in C) pub is_dst: u8, /// Position in the array of time zone abbreviation characters, elsewhere /// in the file. /// (Equivalent to `tt_abbrind` in C) pub name_offset: u8, } #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct LeapSecondData { /// The time, as a number of seconds, at which a leap second occurs. pub timestamp: i32, /// Number of leap seconds to be added. pub leap_second_count: i32, } /// Maximum numbers of structures that can be loaded from a time zone data /// file. If more than these would be loaded, an error will be returned /// instead. /// /// Why have limits? Well, the header portion of the file (see `Header`) /// specifies the numbers of structures that should be read as a `u32` /// four-byte integer. This means that an invalid (or maliciously-crafted!) /// file could try to read *gigabytes* of data while trying to read time zone /// information. To prevent this, reasonable defaults are set, although they /// can be turned off if necessary. #[derive(Debug, Copy, Clone)] pub struct Limits { /// Maximum number of transition structures pub max_transitions: Option, /// Maximum number of local time type structures pub max_local_time_types: Option, /// Maximum number of characters (bytes, technically) in timezone /// abbreviations pub max_abbreviation_chars: Option, /// Maximum number of leap second specifications pub max_leap_seconds: Option, } impl Limits { /// No size limits. This might use *lots* of memory when reading an /// invalid file, so be careful. pub fn none() -> Limits { Limits { max_transitions: None, max_local_time_types: None, max_abbreviation_chars: None, max_leap_seconds: None, } } /// A reasonable set of default values that pose no danger of using lots /// of memory. /// /// These values are taken from `tz_file.h`, at /// [ftp://ftp.iana.org/tz/code/tzfile.h]. pub fn sensible() -> Limits { Limits { max_transitions: Some(2000), max_local_time_types: Some(256), max_abbreviation_chars: Some(50), max_leap_seconds: Some(50), } } /// Makes sure the values we just read from the header are within this set /// of limits. Returns `Ok(())` if everything is within the limits, and a /// boxed `Error` if at least one count is over. pub fn verify(self, header: &Header) -> Result<()> { let check = |structures, intended_count, limit| { if let Some(max) = limit { if intended_count > max { return Err(Error::LimitReached { structures, intended_count, limit: max, }); } } Ok(()) }; check(Structures::Transitions, header.num_transitions, self.max_transitions)?; check(Structures::LocalTimeTypes, header.num_local_time_types, self.max_local_time_types)?; check(Structures::LeapSeconds, header.num_leap_seconds, self.max_leap_seconds)?; check(Structures::GMTFlags, header.num_gmt_flags, self.max_local_time_types)?; check(Structures::StandardFlags, header.num_standard_flags, self.max_local_time_types)?; check(Structures::TimezoneAbbrChars, header.num_abbr_chars, self.max_abbreviation_chars)?; Ok(()) } } struct Parser { cursor: Cursor>, } impl Parser { fn new(buf: Vec) -> Parser { Parser { cursor: Cursor::new(buf), } } fn read_magic_number(&mut self) -> Result<()> { let mut magic = [0u8; 4]; self.cursor.read(&mut magic)?; if magic == *b"TZif" { Ok(()) } else { Err(Box::new(Error::InvalidMagicNumber)) } } fn skip_initial_zeroes(&mut self) -> Result<()> { let mut magic = [0u8; 15]; self.cursor.read(&mut magic)?; Ok(()) } fn read_header(&mut self) -> Result
{ Ok(Header { version: self.cursor.read_u8()?, num_gmt_flags: self.cursor.read_u32::()?, num_standard_flags: self.cursor.read_u32::()?, num_leap_seconds: self.cursor.read_u32::()?, num_transitions: self.cursor.read_u32::()?, num_local_time_types: self.cursor.read_u32::()?, num_abbr_chars: self.cursor.read_u32::()?, }) } fn read_transition_data(&mut self, count: usize) -> Result> { let mut times = Vec::with_capacity(count); for _ in 0 .. count { times.push(self.cursor.read_i32::()?); } let mut types = Vec::with_capacity(count); for _ in 0 .. count { types.push(self.cursor.read_u8()?); } Ok(times.iter().zip(types.iter()).map(|(&ti, &ty)| { TransitionData { timestamp: ti, local_time_type_index: ty, } }).collect()) } fn read_octets(&mut self, count: usize) -> Result> { let mut buf = Vec::with_capacity(count); for _ in 0 .. count { buf.push(self.cursor.read_u8()?); } Ok(buf) } fn read_local_time_type_data(&mut self, count: usize) -> Result> { let mut buf = Vec::with_capacity(count); for _ in 0 .. count { buf.push(LocalTimeTypeData { offset: self.cursor.read_i32::()?, is_dst: self.cursor.read_u8()?, name_offset: self.cursor.read_u8()?, }); } Ok(buf) } fn read_leap_second_data(&mut self, count: usize) -> Result> { let mut buf = Vec::with_capacity(count); for _ in 0 .. count { buf.push(LeapSecondData { timestamp: self.cursor.read_i32::()?, leap_second_count: self.cursor.read_i32::()?, }); } Ok(buf) } } /// A `std::result::Result` with a `Box` as the error type. /// This is used to return a bunch of errors early, including a limit being /// reached, the buffer failed to be read from, or a string not being valid /// UTF-8. pub type Result = result::Result>; #[derive(Debug, Copy, Clone)] pub enum Error { /// The error when the first four bytes of the buffer weren't what they /// should be. InvalidMagicNumber, /// The error when too many structures would have been read from the /// buffer, in order to prevent this library from using too much memory. LimitReached { /// The type of structure that we attempted to read. structures: Structures, /// The number of these structures that we attempted to read. intended_count: u32, /// The maximum number of structures that we can get away with. limit: u32, }, /// The error when a file doesn’t actually contain any transitions. (It /// should always contain at least one, so we know what the *base* offset /// from UTC is.) NoTransitions, } impl ErrorTrait for Error { fn description(&self) -> &str { match *self { Error::InvalidMagicNumber => "invalid magic number", Error::LimitReached { .. } => "limit reached", Error::NoTransitions => "no transitions", } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { match *self { Error::InvalidMagicNumber => write!(f, "invalid magic number"), Error::LimitReached { ref structures, ref intended_count, ref limit } => { write!(f, "too many {} (tried to read {}, limit was {})", structures, intended_count, limit) }, Error::NoTransitions => { write!(f, "read 0 transitions") }, } } } /// A description of which value is being read. This gets used solely for /// error reporting. #[derive(Debug, Copy, Clone)] pub enum Structures { Transitions, LocalTimeTypes, LeapSeconds, GMTFlags, StandardFlags, TimezoneAbbrChars, } impl fmt::Display for Structures { fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { match *self { Structures::Transitions => "transitions".fmt(f), Structures::LocalTimeTypes => "local time types".fmt(f), Structures::LeapSeconds => "leap second".fmt(f), Structures::GMTFlags => "GMT flags".fmt(f), Structures::StandardFlags => "Standard Time flags".fmt(f), Structures::TimezoneAbbrChars => "timezone abbreviation chars".fmt(f), } } } /// The internal structure of a zoneinfo file. #[derive(Debug, PartialEq, Eq, Clone)] pub struct TZData { pub header: Header, pub transitions: Vec, pub time_info: Vec, pub leap_seconds: Vec, pub strings: Vec, pub standard_flags: Vec, pub gmt_flags: Vec, } /// Parse a series of bytes into a `TZData` structure, returning an error if /// the buffer fails to be read from, or a limit is reached. pub fn parse(buf: Vec, limits: Limits) -> Result { let mut parser = Parser::new(buf); parser.read_magic_number()?; parser.skip_initial_zeroes()?; let header = parser.read_header()?; limits.verify(&header)?; let transitions = parser.read_transition_data(header.num_transitions as usize)?; let time_info = parser.read_local_time_type_data(header.num_local_time_types as usize)?; let strings = parser.read_octets(header.num_abbr_chars as usize)?; let leap_seconds = parser.read_leap_second_data(header.num_leap_seconds as usize)?; let standard_flags = parser.read_octets(header.num_standard_flags as usize)?; let gmt_flags = parser.read_octets(header.num_gmt_flags as usize)?; Ok(TZData { header, transitions, time_info, leap_seconds, strings, standard_flags, gmt_flags, }) } #[cfg(test)] mod test { use super::*; #[test] fn est() { let bytes = vec![ 0x54, 0x5A, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xFF, 0xFF, 0xB9, 0xB0, 0x00, 0x00, 0x45, 0x53, 0x54, 0x00, 0x00, 0x00, ]; let data = parse(bytes, Limits::sensible()).unwrap(); assert_eq!(data.header.num_transitions, 0); assert_eq!(data.header.num_leap_seconds, 0); assert_eq!(data.header.num_local_time_types, 1); } #[test] fn japan() { let bytes = vec![ 0x54, 0x5A, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xC3, 0x55, 0x3B, 0x70, 0xD7, 0x3E, 0x1E, 0x90, 0xD7, 0xEC, 0x16, 0x80, 0xD8, 0xF9, 0x16, 0x90, 0xD9, 0xCB, 0xF8, 0x80, 0xDB, 0x07, 0x1D, 0x10, 0xDB, 0xAB, 0xDA, 0x80, 0xDC, 0xE6, 0xFF, 0x10, 0xDD, 0x8B, 0xBC, 0x80, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x7E, 0x90, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xA0, 0x01, 0x05, 0x00, 0x00, 0x7E, 0x90, 0x00, 0x09, 0x4A, 0x43, 0x53, 0x54, 0x00, 0x4A, 0x44, 0x54, 0x00, 0x4A, 0x53, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let data = parse(bytes, Limits::sensible()).unwrap(); assert_eq!(data.header.num_transitions, 9); assert_eq!(data.header.num_leap_seconds, 0); assert_eq!(data.header.num_local_time_types, 3); assert_eq!(data.transitions, vec![ TransitionData { timestamp: -1_017_824_400, local_time_type_index: 2 }, TransitionData { timestamp: -683_794_800, local_time_type_index: 1 }, TransitionData { timestamp: -672_393_600, local_time_type_index: 2 }, TransitionData { timestamp: -654_764_400, local_time_type_index: 1 }, TransitionData { timestamp: -640_944_000, local_time_type_index: 2 }, TransitionData { timestamp: -620_290_800, local_time_type_index: 1 }, TransitionData { timestamp: -609_494_400, local_time_type_index: 2 }, TransitionData { timestamp: -588_841_200, local_time_type_index: 1 }, TransitionData { timestamp: -578_044_800, local_time_type_index: 2 }, ]); assert_eq!(data.time_info, vec![ LocalTimeTypeData { offset: 32400, is_dst: 0, name_offset: 0 }, LocalTimeTypeData { offset: 36000, is_dst: 1, name_offset: 5 }, LocalTimeTypeData { offset: 32400, is_dst: 0, name_offset: 9 }, ]); } }