1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#![crate_name = "tz"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]

//! This is a library for parsing zoneinfo files.
//!
//! ## Example
//!
//! ```no_run
//! use std::fs::File;
//! use std::io::Read;
//! use std::path::Path;
//! use tz::parse;
//!
//! let path = Path::new("/etc/localtime");
//! let mut contents = Vec::new();
//! File::open(path).unwrap().read_to_end(&mut contents).unwrap();
//! let tz = parse(contents).unwrap();
//!
//! for t in tz.transitions {
//!     println!("{:?}", t);
//! }
//! ```

extern crate byteorder;
use std::sync::Arc;

pub mod internals;
pub use internals::Result;


/// Parsed, interpreted contents of a zoneinfo file.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct TZData {

    /// Vector of transitions that are described in this data.
    pub transitions: Vec<Transition>,

    /// Vector of leap seconds that are described in this data.
    pub leap_seconds: Vec<LeapSecond>,
}

/// The 'type' of time that the change was announced in.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TransitionType {

    /// Standard Time ("non-summer" time)
    Standard,

    /// Wall clock time
    Wall,

    /// Co-ordinated Universal Time
    UTC,
}

/// A time change specification.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Transition {

    /// Unix timestamp when the clocks change.
    pub timestamp: i32,

    /// The new description of the local time.
    pub local_time_type: Arc<LocalTimeType>,
}

/// A leap second specification.
#[derive(Debug, PartialEq, Eq, 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: u32,
}

/// A description of the local time in a particular timezone, during the
/// period in which the clocks do not change.
#[derive(Debug, PartialEq, Eq, 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: i32,

    /// Whether to set DST.
    pub is_dst: bool,

    /// The current 'type' of time.
    pub transition_type: TransitionType,
}

/// Parses a series of bytes into a timezone data structure.
pub fn parse(input: Vec<u8>) -> Result<TZData> {
    let tz = try!(internals::parse(input, internals::Limits::sensible()));
    cook(tz)
}

/// Interpret a set of internal time zone data.
pub fn cook(tz: internals::TZData) -> Result<TZData> {
    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 ref data = 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(data.name_offset as usize)
                                   .take_while(|&c| c != 0)
                                   .collect();

        let info = LocalTimeType {
            name:             try!(String::from_utf8(name_bytes)),
            offset:           data.offset,
            is_dst:           data.is_dst != 0,
            transition_type:  flags_to_transition_type(tz.standard_flags[i] != 0,
                                                       tz.gmt_flags[i]      != 0),
        };

        local_time_types.push(Arc::new(info));
    }

    // ...then, link each transition with the time type it refers to.
    for i in 0 .. tz.header.num_transitions as usize {
        let ref t = tz.transitions[i];

        let transition = Transition {
            timestamp:        t.timestamp,
            local_time_type:  local_time_types[t.local_time_type_index as usize].clone(),
        };

        transitions.push(transition);
    }

    let mut leap_seconds = Vec::new();
    for ls in tz.leap_seconds.iter() {
        let leap_second = LeapSecond {
            timestamp: ls.timestamp,
            leap_second_count: ls.leap_second_count,
        };

        leap_seconds.push(leap_second);
    }

    Ok(TZData {
        transitions: transitions,
        leap_seconds: 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) -> TransitionType {
    match (standard, gmt) {
        (_,     true)   => TransitionType::UTC,
        (true,  _)      => TransitionType::Standard,
        (false, false)  => TransitionType::Wall,
    }
}