bp3d_util/
tzif.rs

1// Copyright (c) 2024, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! A simple TZIF reader module used to get UTC to local offset.
30//!
31//! **See [RFC](https://www.rfc-editor.org/rfc/rfc8536.html)**
32
33use bytesutil::ReadBytes;
34use std::{fmt::Display, io::Read};
35
36/// A series of six-octet records specifying a local time type.
37/// The number of records is specified by the "typecnt" field in the header.
38/// Each record has the following format (the lengths of multi-octet fields are shown in parentheses).
39pub struct LocalTimeTypeRecord {
40    /// A four-octet signed integer specifying the number of seconds to be added to UT in order to determine local time.
41    pub utoff: i32,
42
43    /// A one-octet value indicating whether local time should be considered Daylight Saving Time (DST).
44    pub dst: bool,
45
46    /// A one-octet unsigned integer specifying a zero-based index into the series of time zone designation octets,
47    /// thereby selecting a particular designation string.
48    pub idx: u8,
49}
50
51/// A series of eight- or twelve-octet records specifying the corrections that need to be applied to UTC in order to determine TAI.
52pub struct LeapSecondRecord {
53    /// A four or eight-octet UNIX leap time value specifying the time at which a leap-second correction occurs.
54    pub occurrence: i64,
55
56    /// A four-octet signed integer specifying the value of LEAPCORR on or after the occurrence.
57    pub correction: i32,
58}
59
60/// Possible errors when parsing TZIF.
61#[derive(Debug)]
62pub enum Error {
63    /// An Io error.
64    Io(std::io::Error),
65
66    /// The signature of the file cannot be recognized.
67    InvalidSignature,
68}
69
70impl Display for Error {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            Error::Io(e) => write!(f, "io error: {}", e),
74            Error::InvalidSignature => f.write_str("invalid TZIF signature"),
75        }
76    }
77}
78
79impl std::error::Error for Error {}
80
81/// A data block.
82pub struct Data {
83    /// A series of four- or eight-octet UNIX leap-time values sorted in strictly ascending order.
84    pub transition_times: Vec<i64>,
85
86    /// A series of one-octet unsigned integers specifying the type of local time of the corresponding transition time.
87    pub transition_types: Vec<u8>,
88
89    /// A series of six-octet records specifying a local time type.
90    pub local_time_type_records: Vec<LocalTimeTypeRecord>,
91
92    /// A series of eight- or twelve-octet records specifying the corrections that need to be applied to UTC in order to determine TAI.
93    pub leap_second_records: Vec<LeapSecondRecord>,
94}
95
96/// TZIF header.
97pub struct Header {
98    /// Block version.
99    pub version: u8, //4
100
101    /// A four-octet unsigned integer specifying the number of UT/local indicators contained in the data block -- MUST either be zero or equal to "typecnt".
102    pub isutcnt: u32, //20..24
103
104    /// A four-octet unsigned integer specifying the number of standard/wall indicators contained in the data block -- MUST either be zero or equal to "typecnt".
105    pub isstdcnt: u32, //24..28
106
107    /// A four-octet unsigned integer specifying the number of leap-second records contained in the data block.
108    pub leapcnt: u32, //28..32
109
110    /// A four-octet unsigned integer specifying the number of transition times contained in the data block.
111    pub timecnt: u32, //32..36
112
113    /// A four-octet unsigned integer specifying the number of local time type records contained in the data block -- MUST NOT be zero.
114    pub typecnt: u32, //36..40
115
116    /// A four-octet unsigned integer specifying the total number of octets used by the set of time zone designations contained in the data block - MUST NOT be zero.
117    pub charcnt: u32, //40..44
118}
119
120/// Combined header and data block.
121///
122/// According to RFC, there can be currently 2 blocks: 1 for V1 header/data and 1 for V2+ header/data.
123pub struct Block {
124    /// The header block.
125    pub header: Header,
126
127    /// The associated data block.
128    pub data: Data,
129}
130
131/// Simplified TZIF decoded structure.
132pub struct TZIF {
133    /// The V1 combined header/data block.
134    pub block_v1: Block,
135
136    /// The V2+ combined header/data block.
137    pub block_v2p: Option<Block>,
138}
139
140impl Header {
141    fn time_size(&self) -> usize {
142        match self.version {
143            0x00 => 4,
144            _ => 8,
145        }
146    }
147
148    fn read<R: Read>(mut reader: R) -> Result<Header, Error> {
149        let mut header: [u8; 44] = [0; 44];
150        reader.read_exact(&mut header).map_err(Error::Io)?;
151        if &header[0..4] != b"TZif" {
152            return Err(Error::InvalidSignature);
153        }
154        Ok(Header {
155            version: header[4],
156            isutcnt: u32::read_bytes_be(&header[20..24]),
157            isstdcnt: u32::read_bytes_be(&header[24..28]),
158            leapcnt: u32::read_bytes_be(&header[28..32]),
159            timecnt: u32::read_bytes_be(&header[32..36]),
160            typecnt: u32::read_bytes_be(&header[36..40]),
161            charcnt: u32::read_bytes_be(&header[40..44]),
162        })
163    }
164}
165
166impl Data {
167    fn read<R: Read>(mut reader: R, header: &Header) -> Result<Data, Error> {
168        let size = header.time_size();
169        let mut transition_times = vec![0; size * header.timecnt as usize];
170        reader
171            .read_exact(&mut transition_times)
172            .map_err(Error::Io)?;
173        let mut transition_types = vec![0; header.timecnt as usize];
174        reader
175            .read_exact(&mut transition_types)
176            .map_err(Error::Io)?;
177        let mut local_time_type_records = vec![0; 6 * header.typecnt as usize];
178        reader
179            .read_exact(&mut local_time_type_records)
180            .map_err(Error::Io)?;
181        let mut time_zone_designations = vec![0; header.charcnt as usize];
182        reader
183            .read_exact(&mut time_zone_designations)
184            .map_err(Error::Io)?;
185        let mut leap_second_records = vec![0; (size + 4) * header.leapcnt as usize];
186        reader
187            .read_exact(&mut leap_second_records)
188            .map_err(Error::Io)?;
189        let mut std_wall_indicators = vec![0; header.isstdcnt as usize];
190        reader
191            .read_exact(&mut std_wall_indicators)
192            .map_err(Error::Io)?;
193        let mut ut_indicators = vec![0; header.isutcnt as usize];
194        reader.read_exact(&mut ut_indicators).map_err(Error::Io)?;
195        let local_time_type_records = local_time_type_records
196            .as_slice()
197            .chunks(6)
198            .map(|v| LocalTimeTypeRecord {
199                utoff: i32::read_bytes_be(&v[0..4]),
200                dst: v[4] == 1,
201                idx: v[5],
202            })
203            .collect();
204        if header.version == 0x00 {
205            Ok(Data {
206                transition_types,
207                transition_times: transition_times
208                    .as_slice()
209                    .chunks(4)
210                    .map(|v| i32::read_bytes_be(v) as i64)
211                    .collect(),
212                leap_second_records: leap_second_records
213                    .as_slice()
214                    .chunks(8)
215                    .map(|v| LeapSecondRecord {
216                        occurrence: i32::read_bytes_be(&v[0..4]) as i64,
217                        correction: i32::read_bytes_be(&v[4..8]),
218                    })
219                    .collect(),
220                local_time_type_records,
221            })
222        } else {
223            Ok(Data {
224                transition_types,
225                transition_times: transition_times
226                    .as_slice()
227                    .chunks(8)
228                    .map(i64::read_bytes_be)
229                    .collect(),
230                leap_second_records: leap_second_records
231                    .as_slice()
232                    .chunks(12)
233                    .map(|v| LeapSecondRecord {
234                        occurrence: i64::read_bytes_be(&v[0..8]),
235                        correction: i32::read_bytes_be(&v[8..12]),
236                    })
237                    .collect(),
238                local_time_type_records,
239            })
240        }
241    }
242}
243
244impl TZIF {
245    /// Reads and decodes a TZIF stream.
246    ///
247    /// # Arguments
248    ///
249    /// * `reader`: the [Read](Read) to read and decode from.
250    ///
251    /// # Errors
252    ///
253    /// This function returns an [Error](Error) if the simplified TZIF structure could not be decoded.
254    pub fn read<R: Read>(mut reader: R) -> Result<TZIF, Error> {
255        let mut header_v1 = Header::read(&mut reader)?;
256        header_v1.version = 0x00; //RFC is badly broken it says bullshit.
257        let block_v1 = Block {
258            data: Data::read(&mut reader, &header_v1)?,
259            header: header_v1,
260        };
261        let block_v2p = match Header::read(&mut reader) {
262            Ok(header_v2) => Some(Block {
263                data: Data::read(&mut reader, &header_v2)?,
264                header: header_v2,
265            }),
266            _ => None,
267        };
268        Ok(TZIF {
269            block_v1,
270            block_v2p,
271        })
272    }
273}