tzif/data/
tzif.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use super::time::Seconds;
6use crate::data::posix::PosixTzString;
7
8/// A `TZif` file header.
9/// See <https://datatracker.ietf.org/doc/html/rfc8536> for more information.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct TzifHeader {
12    /// An byte identifying the version of the file's format.
13    /// The value MUST be one of the following:
14    ///
15    /// NUL (0x00)  Version 1 - The file contains only the version 1
16    /// header and data block.  Version 1 files MUST NOT contain a
17    /// version-2+ header, data block, or footer.
18    ///
19    /// '2' (0x32)  Version 2 - The file MUST contain the version 1 header
20    /// and data block, a version-2+ header and data block, and a
21    /// footer.  The TZ string in the footer (Section 3.3), if
22    /// nonempty, MUST strictly adhere to the requirements for the TZ
23    /// environment variable as defined in Section 8.3 of the "Base
24    /// Definitions" volume of \[POSIX\] and MUST encode the POSIX
25    /// portable character set as ASCII.
26    ///
27    /// '3' (0x33)  Version 3 - The file MUST contain the version 1 header
28    /// and data block, a version-2+ header and data block, and a
29    /// footer.  The TZ string in the footer (Section 3.3), if
30    /// nonempty, MUST conform to POSIX requirements with ASCII
31    /// encoding, except that it MAY use the TZ string extensions
32    /// described below (Section 3.3.1).
33    pub version: usize,
34
35    /// A four-byte unsigned integer specifying the number of UT/
36    /// local indicators contained in the data block -- MUST either be
37    /// zero or equal to "typecnt".
38    pub isutcnt: usize,
39
40    /// A four-byte unsigned integer specifying the number of
41    /// standard/wall indicators contained in the data block -- MUST
42    /// either be zero or equal to "typecnt".
43    pub isstdcnt: usize,
44
45    /// A four-byte unsigned integer specifying the number of
46    /// leap-second records contained in the data block.
47    pub leapcnt: usize,
48
49    /// A four-byte unsigned integer specifying the number of
50    /// transition times contained in the data block.
51    pub timecnt: usize,
52
53    /// A four-byte unsigned integer specifying the number of
54    /// local time type records contained in the data block -- MUST NOT be
55    /// zero.  (Although local time type records convey no useful
56    /// information in files that have nonempty TZ strings but no
57    /// transitions, at least one such record is nevertheless required
58    /// because many `TZif` readers reject files that have zero time types.)
59    pub typecnt: usize,
60
61    /// A four-byte unsigned integer specifying the total number
62    /// of bytes used by the set of time zone designations contained in
63    /// the data block - MUST NOT be zero.  The count includes the
64    /// trailing NUL (0x00) byte at the end of the last time zone
65    /// designation.
66    pub charcnt: usize,
67}
68
69impl TzifHeader {
70    /// Returns the version number of the `TZif` header.
71    pub fn version(&self) -> usize {
72        self.version
73    }
74
75    /// Returns the number of bytes per time object based on the version number.
76    pub fn time_size<const V: usize>() -> usize {
77        match V {
78            1 => 4,
79            _ => 8,
80        }
81    }
82
83    /// Returns the exact size of the data block in bytes based on the header.
84    pub fn block_size<const V: usize>(&self) -> usize {
85        let time_size = Self::time_size::<V>();
86        self.timecnt * time_size
87            + self.timecnt
88            + self.typecnt * 6
89            + self.charcnt
90            + self.leapcnt * (time_size + 4)
91            + self.isstdcnt
92            + self.isutcnt
93    }
94}
95
96/// A struct containing the data of a `TZif` file.
97/// > A `TZif` file is structured as follows:
98/// > ```text
99/// >      Version 1       Versions 2 & 3
100/// >   +-------------+   +-------------+
101/// >   |  Version 1  |   |  Version 1  |
102/// >   |   Header    |   |   Header    |
103/// >   +-------------+   +-------------+
104/// >   |  Version 1  |   |  Version 1  |
105/// >   |  Data Block |   |  Data Block |
106/// >   +-------------+   +-------------+
107/// >                     |  Version 2+ |
108/// >                     |   Header    |
109/// >                     +-------------+
110/// >                     |  Version 2+ |
111/// >                     |  Data Block |
112/// >                     +-------------+
113/// >                     |   Footer    |
114/// >                     +-------------+
115/// > ```
116#[derive(Debug)]
117pub struct TzifData {
118    /// The version-1 header, which is always present.
119    pub header1: TzifHeader,
120    /// The version-1 data block, which is always present.
121    pub data_block1: DataBlock,
122    /// The version-2+ header, which is present only in version 2 and 3 `TZif` files.
123    pub header2: Option<TzifHeader>,
124    /// The vesrion-2+ data block, which is present only in version 2 and 3 `TZif` files.
125    pub data_block2: Option<DataBlock>,
126    /// The version-2+ footer, which is present only in version 2 and 3 `TZif` files.
127    pub footer: Option<PosixTzString>,
128}
129
130impl TzifData {
131    /// Returns the version number of this `TZif` data.
132    pub fn version_number(&self) -> usize {
133        self.header2
134            .as_ref()
135            .map_or(self.header1.version(), TzifHeader::version)
136    }
137
138    /// Returns the number of bytes per time object based on the version number.
139    pub fn time_size(&self) -> usize {
140        match self.version_number() {
141            1 => 4,
142            _ => 8,
143        }
144    }
145
146    /// Returns the exact size of the data block in bytes based on the header.
147    pub fn block_size<const V: usize>(&self) -> Option<usize> {
148        match V {
149            1 => Some(self.header1.block_size::<V>()),
150            _ => self.header2.as_ref().map(TzifHeader::block_size::<V>),
151        }
152    }
153}
154
155/// A record specifying a local time type.
156#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
157pub struct LocalTimeTypeRecord {
158    /// A signed integer specifying the number of
159    /// seconds to be added to UT in order to determine local time.
160    /// The value MUST NOT be -2**31 and SHOULD be in the range
161    /// [-89999, 93599] (i.e., its value SHOULD be more than -25 hours
162    /// and less than 26 hours).  Avoiding -2**31 allows 32-bit clients
163    /// to negate the value without overflow.  Restricting it to
164    /// [-89999, 93599] allows easy support by implementations that
165    /// already support the POSIX-required range [-24:59:59, 25:59:59].
166    pub utoff: Seconds,
167
168    /// A value indicating whether local time should
169    /// be considered Daylight Saving Time (DST).  The value MUST be 0
170    /// A value of [`true`] indicates that this type of time is DST.
171    /// A value of [`false`] indicates that this time type is standard time.
172    pub is_dst: bool,
173
174    /// An unsigned integer specifying a zero-based
175    /// index into the series of time zone designation bytes, thereby
176    /// selecting a particular designation string.  Each index MUST be
177    /// in the range [0, "charcnt" - 1]; it designates the
178    /// NUL-terminated string of bytes starting at position "idx" in
179    /// the time zone designations.  (This string MAY be empty.)  A NUL
180    /// byte MUST exist in the time zone designations at or after
181    /// position "idx".
182    pub idx: usize,
183}
184
185/// A record specifying the corrections that need to be applied to the UTC in
186/// in order to determine TAI.
187#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
188pub struct LeapSecondRecord {
189    /// A UNIX leap time value
190    /// specifying the time at which a leap-second correction occurs.
191    /// The first value, if present, MUST be nonnegative, and each
192    /// later value MUST be at least 2419199 greater than the previous
193    /// value.  (This is 28 days' worth of seconds, minus a potential
194    /// negative leap second.)
195    pub occurrence: Seconds,
196
197    /// A signed integer specifying the value of
198    /// LEAPCORR on or after the occurrence.  The correction value in
199    /// the first leap-second record, if present, MUST be either one
200    /// (1) or minus one (-1).  The correction values in adjacent leap-
201    /// second records MUST differ by exactly one (1).  The value of
202    /// LEAPCORR is zero for timestamps that occur before the
203    /// occurrence time in the first leap-second record (or for all
204    /// timestamps if there are no leap-second records).
205    pub correction: i32,
206}
207
208/// Indicates whether the transition times associated with local time types were
209/// specified as standard time or wall-clock time.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum StandardWallIndicator {
212    /// Standard time
213    Standard,
214    /// Wall-clock time
215    Wall,
216}
217
218/// Indicates whether the transition times associated with local time types were
219/// specified as UT or local time.
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub enum UtLocalIndicator {
222    /// UT time
223    Ut,
224    /// Local time
225    Local,
226}
227
228/// A `TZif` data block.
229///
230/// A `TZif` data block consists of seven variable-length elements, each of
231/// which is a series of items.  The number of items in each series is
232/// determined by the corresponding count field in the header.  The total
233/// length of each element is calculated by multiplying the number of
234/// items by the size of each item.  Therefore, implementations that do
235/// not wish to parse or use the version 1 data block can calculate its
236/// total length and skip directly to the header of the version-2+ data
237/// block.
238///
239/// In the version 1 data block, time values are 32 bits (`TIME_SIZE` = 4
240/// bytes).  In the version-2+ data block, present only in version 2 and
241/// 3 files, time values are 64 bits (`TIME_SIZE` = 8 bytes).
242///
243/// For consistency, this struct stores all time values in 64-bit integers
244/// even for version 1 data blocks.
245///
246/// The data block is structured as follows (the lengths of multi-byte
247/// fields are shown in parentheses):
248///
249/// > ```text
250/// >      +---------------------------------------------------------+
251/// >      |  transition times          (timecnt x TIME_SIZE)        |
252/// >      +---------------------------------------------------------+
253/// >      |  transition types          (timecnt)                    |
254/// >      +---------------------------------------------------------+
255/// >      |  local time type records   (typecnt x 6)                |
256/// >      +---------------------------------------------------------+
257/// >      |  time zone designations    (charcnt)                    |
258/// >      +---------------------------------------------------------+
259/// >      |  leap-second records       (leapcnt x (TIME_SIZE + 4))  |
260/// >      +---------------------------------------------------------+
261/// >      |  standard/wall indicators  (isstdcnt)                   |
262/// >      +---------------------------------------------------------+
263/// >      |  UT/local indicators       (isutcnt)                    |
264/// >      +---------------------------------------------------------+
265/// > ```
266#[derive(Debug, Clone, Default)]
267pub struct DataBlock {
268    /// A series of four- or eight-byte UNIX leap-time
269    /// values sorted in strictly ascending order.  Each value is used as
270    /// a transition time at which the rules for computing local time may
271    /// change.  The number of time values is specified by the "timecnt"
272    /// field in the header.  Each time value SHOULD be at least -2**59.
273    ///
274    /// (-2**59 is the greatest negated power of 2 that predates the Big
275    /// Bang, and avoiding earlier timestamps works around known TZif
276    /// reader bugs relating to outlandishly negative timestamps.)
277    pub transition_times: Vec<Seconds>,
278
279    /// A series of one-byte unsigned integers specifying
280    /// the type of local time of the corresponding transition time.
281    /// These values serve as zero-based indices into the array of local
282    /// time type records.  The number of type indices is specified by the
283    /// "timecnt" field in the header.  Each type index MUST be in the
284    /// range [0, "typecnt" - 1].
285    pub transition_types: Vec<usize>,
286
287    /// A series of [`LocalTimeTypeRecord`] objects.
288    pub local_time_type_records: Vec<LocalTimeTypeRecord>,
289
290    /// The string representations for a time-zone desigation, such as "PST" or "PDT".
291    pub time_zone_designations: Vec<String>,
292
293    /// A series of [`LeapSecondRecord`] objects.
294    pub leap_second_records: Vec<LeapSecondRecord>,
295
296    /// A series of [`StandardWallIndicator`] objects.
297    pub standard_wall_indicators: Vec<StandardWallIndicator>,
298
299    /// A series of [`UtLocalIndicator`] objects.
300    pub ut_local_indicators: Vec<UtLocalIndicator>,
301}
302
303impl DataBlock {
304    /// Retrieves the timezone designation at index `idx`.
305    pub fn time_zone_designation(&self, mut idx: usize) -> Option<&str> {
306        self.time_zone_designations.iter().find_map(|d| {
307            if idx <= d.len() {
308                Some(&d[idx..])
309            } else {
310                idx -= d.len() + 1;
311                None
312            }
313        })
314    }
315}