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}