/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.17/src/shared/tzif.rs
Line | Count | Source |
1 | | use alloc::{string::String, vec}; |
2 | | |
3 | | use super::{ |
4 | | util::{ |
5 | | array_str::Abbreviation, |
6 | | itime::{IOffset, ITimestamp}, |
7 | | }, |
8 | | PosixTimeZone, TzifDateTime, TzifFixed, TzifIndicator, TzifLocalTimeType, |
9 | | TzifOwned, TzifTransitionInfo, TzifTransitionKind, TzifTransitions, |
10 | | TzifTransitionsOwned, |
11 | | }; |
12 | | |
13 | | // These are Jiff min and max timestamp (in seconds) values. |
14 | | // |
15 | | // The TZif parser will clamp timestamps to this range. It's |
16 | | // not ideal, but Jiff can't handle values outside of this range |
17 | | // and completely refusing to use TZif data with pathological |
18 | | // timestamps in typically irrelevant transitions is bad juju. |
19 | | // |
20 | | // Ref: https://github.com/BurntSushi/jiff/issues/163 |
21 | | // Ref: https://github.com/BurntSushi/jiff/pull/164 |
22 | | const TIMESTAMP_MIN: i64 = -377705023201; |
23 | | const TIMESTAMP_MAX: i64 = 253402207200; |
24 | | |
25 | | // Similarly for offsets, although in this case, if we find |
26 | | // an offset outside of this range, we do actually error. This |
27 | | // is because it could result in true incorrect datetimes for |
28 | | // actual transitions. |
29 | | // |
30 | | // But our supported offset range is `-25:59:59..=+25:59:59`. |
31 | | // There's no real time zone with offsets even close to those |
32 | | // boundaries. |
33 | | // |
34 | | // If there is pathological data that we should ignore, then |
35 | | // we should wait for a real bug report in order to determine |
36 | | // the right way to ignore/clamp it. |
37 | | const OFFSET_MIN: i32 = -93599; |
38 | | const OFFSET_MAX: i32 = 93599; |
39 | | |
40 | | // When fattening TZif data, this is the year to go up to. |
41 | | // |
42 | | // This year was chosen because it's what the "fat" TZif data generated |
43 | | // by `zic` uses. |
44 | | const FATTEN_UP_TO_YEAR: i16 = 2038; |
45 | | |
46 | | // This is a "sanity" limit on the maximum number of transitions we'll |
47 | | // add to TZif data when fattening them up. |
48 | | // |
49 | | // This is mostly just a defense-in-depth limit to avoid weird cases |
50 | | // where a pathological POSIX time zone could be defined to create |
51 | | // many transitions. It's not clear that this is actually possible, |
52 | | // but I felt a little uneasy doing unbounded work that isn't linearly |
53 | | // proportional to the input data. So, this limit is put into place for |
54 | | // reasons of "good sense." |
55 | | // |
56 | | // For "normal" cases, there should be at most two transitions per |
57 | | // year. So this limit permits 300/2=150 years of transition data. |
58 | | // (Although we won't go above `FATTEN_UP_TO_YEAR`. See above.) |
59 | | const FATTEN_MAX_TRANSITIONS: usize = 300; |
60 | | |
61 | | impl TzifOwned { |
62 | | /// Parses the given data as a TZif formatted file. |
63 | | /// |
64 | | /// The name given is attached to the `Tzif` value returned, but is |
65 | | /// otherwise not significant. |
66 | | /// |
67 | | /// If the given data is not recognized to be valid TZif, then an error is |
68 | | /// returned. |
69 | | /// |
70 | | /// In general, callers may assume that it is safe to pass arbitrary or |
71 | | /// even untrusted data to this function and count on it not panicking |
72 | | /// or using resources that aren't limited to a small constant factor of |
73 | | /// the size of the data itself. That is, callers can reliably limit the |
74 | | /// resources used by limiting the size of the data given to this parse |
75 | | /// function. |
76 | 0 | pub(crate) fn parse( |
77 | 0 | name: Option<String>, |
78 | 0 | bytes: &[u8], |
79 | 0 | ) -> Result<TzifOwned, TzifError> { |
80 | 0 | let original = bytes; |
81 | 0 | let name = name.into(); |
82 | 0 | let (header32, rest) = |
83 | 0 | Header::parse(4, bytes).map_err(TzifErrorKind::Header32)?; |
84 | 0 | let (mut tzif, rest) = if header32.version == 0 { |
85 | 0 | TzifOwned::parse32(name, header32, rest)? |
86 | | } else { |
87 | 0 | TzifOwned::parse64(name, header32, rest)? |
88 | | }; |
89 | 0 | tzif.fatten(); |
90 | | // This should come after fattening, because fattening may add new |
91 | | // transitions and we want to add civil datetimes to those. |
92 | 0 | tzif.add_civil_datetimes_to_transitions(); |
93 | 0 | tzif.verify_posix_time_zone_consistency()?; |
94 | | // Compute the checksum using the entire contents of the TZif data. |
95 | 0 | let tzif_raw_len = (rest.as_ptr() as usize) |
96 | 0 | .checked_sub(original.as_ptr() as usize) |
97 | 0 | .unwrap(); |
98 | 0 | let tzif_raw_bytes = &original[..tzif_raw_len]; |
99 | 0 | tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes); |
100 | | |
101 | | // Shrink all of our allocs so we don't keep excess capacity around. |
102 | 0 | tzif.fixed.designations.shrink_to_fit(); |
103 | 0 | tzif.types.shrink_to_fit(); |
104 | 0 | tzif.transitions.timestamps.shrink_to_fit(); |
105 | 0 | tzif.transitions.civil_starts.shrink_to_fit(); |
106 | 0 | tzif.transitions.civil_ends.shrink_to_fit(); |
107 | 0 | tzif.transitions.infos.shrink_to_fit(); |
108 | | |
109 | 0 | Ok(tzif) |
110 | 0 | } |
111 | | |
112 | 0 | fn parse32<'b>( |
113 | 0 | name: Option<String>, |
114 | 0 | header32: Header, |
115 | 0 | bytes: &'b [u8], |
116 | 0 | ) -> Result<(TzifOwned, &'b [u8]), TzifError> { |
117 | 0 | let mut tzif = TzifOwned { |
118 | 0 | fixed: TzifFixed { |
119 | 0 | name, |
120 | 0 | version: header32.version, |
121 | 0 | // filled in later |
122 | 0 | checksum: 0, |
123 | 0 | designations: String::new(), |
124 | 0 | posix_tz: None, |
125 | 0 | }, |
126 | 0 | types: vec![], |
127 | 0 | transitions: TzifTransitions { |
128 | 0 | timestamps: vec![], |
129 | 0 | civil_starts: vec![], |
130 | 0 | civil_ends: vec![], |
131 | 0 | infos: vec![], |
132 | 0 | }, |
133 | 0 | }; |
134 | 0 | let rest = tzif.parse_transitions(&header32, bytes)?; |
135 | 0 | let rest = tzif.parse_transition_types(&header32, rest)?; |
136 | 0 | let rest = tzif.parse_local_time_types(&header32, rest)?; |
137 | 0 | let rest = tzif.parse_time_zone_designations(&header32, rest)?; |
138 | 0 | let rest = tzif.parse_leap_seconds(&header32, rest)?; |
139 | 0 | let rest = tzif.parse_indicators(&header32, rest)?; |
140 | 0 | Ok((tzif, rest)) |
141 | 0 | } |
142 | | |
143 | 0 | fn parse64<'b>( |
144 | 0 | name: Option<String>, |
145 | 0 | header32: Header, |
146 | 0 | bytes: &'b [u8], |
147 | 0 | ) -> Result<(TzifOwned, &'b [u8]), TzifError> { |
148 | 0 | let (_, rest) = |
149 | 0 | try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?; |
150 | 0 | let (header64, rest) = |
151 | 0 | Header::parse(8, rest).map_err(TzifErrorKind::Header64)?; |
152 | 0 | let mut tzif = TzifOwned { |
153 | 0 | fixed: TzifFixed { |
154 | 0 | name, |
155 | 0 | version: header64.version, |
156 | 0 | // filled in later |
157 | 0 | checksum: 0, |
158 | 0 | designations: String::new(), |
159 | 0 | posix_tz: None, |
160 | 0 | }, |
161 | 0 | types: vec![], |
162 | 0 | transitions: TzifTransitions { |
163 | 0 | timestamps: vec![], |
164 | 0 | civil_starts: vec![], |
165 | 0 | civil_ends: vec![], |
166 | 0 | infos: vec![], |
167 | 0 | }, |
168 | 0 | }; |
169 | 0 | let rest = tzif.parse_transitions(&header64, rest)?; |
170 | 0 | let rest = tzif.parse_transition_types(&header64, rest)?; |
171 | 0 | let rest = tzif.parse_local_time_types(&header64, rest)?; |
172 | 0 | let rest = tzif.parse_time_zone_designations(&header64, rest)?; |
173 | 0 | let rest = tzif.parse_leap_seconds(&header64, rest)?; |
174 | 0 | let rest = tzif.parse_indicators(&header64, rest)?; |
175 | 0 | let rest = tzif.parse_footer(&header64, rest)?; |
176 | | // Note that we don't check that the TZif data is fully valid. It is |
177 | | // possible for it to contain superfluous information. For example, a |
178 | | // non-zero local time type that is never referenced by a transition. |
179 | 0 | Ok((tzif, rest)) |
180 | 0 | } |
181 | | |
182 | 0 | fn parse_transitions<'b>( |
183 | 0 | &mut self, |
184 | 0 | header: &Header, |
185 | 0 | bytes: &'b [u8], |
186 | 0 | ) -> Result<&'b [u8], TzifError> { |
187 | 0 | let (bytes, rest) = try_split_at( |
188 | 0 | SplitAtError::TransitionTimes, |
189 | 0 | bytes, |
190 | 0 | header.transition_times_len()?, |
191 | 0 | )?; |
192 | 0 | let mut it = bytes.chunks_exact(header.time_size); |
193 | | // RFC 8536 says: "If there are no transitions, local time for all |
194 | | // timestamps is specified by the TZ string in the footer if present |
195 | | // and nonempty; otherwise, it is specified by time type 0." |
196 | | // |
197 | | // RFC 8536 also says: "Local time for timestamps before the first |
198 | | // transition is specified by the first time type (time type |
199 | | // 0)." |
200 | | // |
201 | | // So if there are no transitions, pushing this dummy one will result |
202 | | // in the desired behavior even when it's the only transition. |
203 | | // Similarly, since this is the minimum timestamp value, it will |
204 | | // trigger for any times before the first transition found in the TZif |
205 | | // data. |
206 | 0 | self.transitions.add_with_type_index(TIMESTAMP_MIN, 0); |
207 | 0 | while let Some(chunk) = it.next() { |
208 | 0 | let mut timestamp = if header.is_32bit() { |
209 | 0 | i64::from(from_be_bytes_i32(chunk)) |
210 | | } else { |
211 | 0 | from_be_bytes_i64(chunk) |
212 | | }; |
213 | 0 | if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) { |
214 | 0 | // We really shouldn't error here just because the Unix |
215 | 0 | // timestamp is outside what Jiff supports. Since what Jiff |
216 | 0 | // supports is _somewhat_ arbitrary. But Jiff's supported |
217 | 0 | // range is good enough for all realistic purposes, so we |
218 | 0 | // just clamp an out-of-range Unix timestamp to the Jiff |
219 | 0 | // min or max value. |
220 | 0 | // |
221 | 0 | // This can't result in the sorting order being wrong, but |
222 | 0 | // it can result in a transition that is duplicative with |
223 | 0 | // the dummy transition we inserted above. This should be |
224 | 0 | // fine. |
225 | 0 | let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX); |
226 | 0 | // only-jiff-start |
227 | 0 | warn!( |
228 | 0 | "found Unix timestamp `{timestamp}` that is outside \ |
229 | 0 | Jiff's supported range, clamping to `{clamped}`", |
230 | 0 | ); |
231 | 0 | // only-jiff-end |
232 | 0 | timestamp = clamped; |
233 | 0 | } |
234 | 0 | self.transitions.add(timestamp); |
235 | | } |
236 | 0 | assert!(it.remainder().is_empty()); |
237 | 0 | Ok(rest) |
238 | 0 | } |
239 | | |
240 | 0 | fn parse_transition_types<'b>( |
241 | 0 | &mut self, |
242 | 0 | header: &Header, |
243 | 0 | bytes: &'b [u8], |
244 | 0 | ) -> Result<&'b [u8], TransitionTypeError> { |
245 | 0 | let (bytes, rest) = try_split_at( |
246 | 0 | SplitAtError::TransitionTypes, |
247 | 0 | bytes, |
248 | 0 | header.transition_types_len(), |
249 | 0 | )?; |
250 | | // We skip the first transition because it is our minimum dummy |
251 | | // transition. |
252 | 0 | for (transition_index, &type_index) in (1..).zip(bytes) { |
253 | 0 | if usize::from(type_index) >= header.tzh_typecnt { |
254 | 0 | return Err(TransitionTypeError::ExceedsLocalTimeTypes); |
255 | 0 | } |
256 | 0 | self.transitions.infos[transition_index].type_index = type_index; |
257 | | } |
258 | 0 | Ok(rest) |
259 | 0 | } |
260 | | |
261 | 0 | fn parse_local_time_types<'b>( |
262 | 0 | &mut self, |
263 | 0 | header: &Header, |
264 | 0 | bytes: &'b [u8], |
265 | 0 | ) -> Result<&'b [u8], TzifError> { |
266 | 0 | let (bytes, rest) = try_split_at( |
267 | 0 | SplitAtError::LocalTimeTypes, |
268 | 0 | bytes, |
269 | 0 | header.local_time_types_len()?, |
270 | 0 | )?; |
271 | 0 | let mut it = bytes.chunks_exact(6); |
272 | 0 | while let Some(chunk) = it.next() { |
273 | 0 | let offset = from_be_bytes_i32(&chunk[..4]); |
274 | 0 | if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) { |
275 | 0 | return Err(TzifError::from( |
276 | 0 | LocalTimeTypeError::InvalidOffset { offset }, |
277 | 0 | )); |
278 | 0 | } |
279 | 0 | let is_dst = chunk[4] == 1; |
280 | 0 | let designation = (chunk[5], chunk[5]); |
281 | 0 | self.types.push(TzifLocalTimeType { |
282 | 0 | offset, |
283 | 0 | is_dst, |
284 | 0 | designation, |
285 | 0 | indicator: TzifIndicator::LocalWall, |
286 | 0 | }); |
287 | | } |
288 | 0 | assert!(it.remainder().is_empty()); |
289 | 0 | Ok(rest) |
290 | 0 | } |
291 | | |
292 | 0 | fn parse_time_zone_designations<'b>( |
293 | 0 | &mut self, |
294 | 0 | header: &Header, |
295 | 0 | bytes: &'b [u8], |
296 | 0 | ) -> Result<&'b [u8], TimeZoneDesignatorError> { |
297 | 0 | let (bytes, rest) = try_split_at( |
298 | 0 | SplitAtError::TimeZoneDesignations, |
299 | 0 | bytes, |
300 | 0 | header.time_zone_designations_len(), |
301 | 0 | )?; |
302 | 0 | self.fixed.designations = String::from_utf8(bytes.to_vec()) |
303 | 0 | .map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?; |
304 | | // Holy hell, this is brutal. The boundary conditions are crazy. |
305 | 0 | for typ in self.types.iter_mut() { |
306 | 0 | let start = usize::from(typ.designation.0); |
307 | 0 | let suffix = self |
308 | 0 | .fixed |
309 | 0 | .designations |
310 | 0 | .get(start..) |
311 | 0 | .ok_or(TimeZoneDesignatorError::InvalidStart)?; |
312 | 0 | let len = suffix |
313 | 0 | .find('\x00') |
314 | 0 | .ok_or(TimeZoneDesignatorError::MissingNul)?; |
315 | 0 | let end = start |
316 | 0 | .checked_add(len) |
317 | 0 | .ok_or(TimeZoneDesignatorError::InvalidLength)?; |
318 | 0 | typ.designation.1 = u8::try_from(end) |
319 | 0 | .map_err(|_| TimeZoneDesignatorError::InvalidEnd)?; |
320 | | } |
321 | 0 | Ok(rest) |
322 | 0 | } |
323 | | |
324 | | /// This parses the leap second corrections in the TZif data. |
325 | | /// |
326 | | /// Note that we only parse and verify them. We don't actually use them. |
327 | | /// Jiff effectively ignores leap seconds. |
328 | 0 | fn parse_leap_seconds<'b>( |
329 | 0 | &mut self, |
330 | 0 | header: &Header, |
331 | 0 | bytes: &'b [u8], |
332 | 0 | ) -> Result<&'b [u8], TzifError> { |
333 | 0 | let (bytes, rest) = try_split_at( |
334 | 0 | SplitAtError::LeapSeconds, |
335 | 0 | bytes, |
336 | 0 | header.leap_second_len()?, |
337 | 0 | )?; |
338 | 0 | let chunk_len = header |
339 | 0 | .time_size |
340 | 0 | .checked_add(4) |
341 | 0 | .expect("time_size plus 4 fits in usize"); |
342 | 0 | let mut it = bytes.chunks_exact(chunk_len); |
343 | 0 | while let Some(chunk) = it.next() { |
344 | 0 | let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size); |
345 | 0 | let occur = if header.is_32bit() { |
346 | 0 | i64::from(from_be_bytes_i32(occur_bytes)) |
347 | | } else { |
348 | 0 | from_be_bytes_i64(occur_bytes) |
349 | | }; |
350 | 0 | if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) { |
351 | 0 | // only-jiff-start |
352 | 0 | warn!( |
353 | 0 | "leap second occurrence `{occur}` is \ |
354 | 0 | not in Jiff's supported range" |
355 | 0 | ) |
356 | 0 | // only-jiff-end |
357 | 0 | } |
358 | | } |
359 | 0 | assert!(it.remainder().is_empty()); |
360 | 0 | Ok(rest) |
361 | 0 | } |
362 | | |
363 | 0 | fn parse_indicators<'b>( |
364 | 0 | &mut self, |
365 | 0 | header: &Header, |
366 | 0 | bytes: &'b [u8], |
367 | 0 | ) -> Result<&'b [u8], IndicatorError> { |
368 | 0 | let (std_wall_bytes, rest) = try_split_at( |
369 | 0 | SplitAtError::StandardWallIndicators, |
370 | 0 | bytes, |
371 | 0 | header.standard_wall_len(), |
372 | 0 | )?; |
373 | 0 | let (ut_local_bytes, rest) = try_split_at( |
374 | 0 | SplitAtError::UTLocalIndicators, |
375 | 0 | rest, |
376 | 0 | header.ut_local_len(), |
377 | 0 | )?; |
378 | 0 | if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { |
379 | | // This is a weird case, but technically possible only if all |
380 | | // UT/local indicators are 0. If any are 1, then it's an error, |
381 | | // because it would require the corresponding std/wall indicator |
382 | | // to be 1 too. Which it can't be, because there aren't any. So |
383 | | // we just check that they're all zeros. |
384 | 0 | if ut_local_bytes.iter().any(|&byte| byte != 0) { |
385 | 0 | return Err(IndicatorError::UtLocalNonZero); |
386 | 0 | } |
387 | 0 | } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() { |
388 | 0 | for (i, &byte) in std_wall_bytes.iter().enumerate() { |
389 | | // Indexing is OK because Header guarantees that the number of |
390 | | // indicators is 0 or equal to the number of types. |
391 | 0 | self.types[i].indicator = if byte == 0 { |
392 | 0 | TzifIndicator::LocalWall |
393 | 0 | } else if byte == 1 { |
394 | 0 | TzifIndicator::LocalStandard |
395 | | } else { |
396 | 0 | return Err(IndicatorError::InvalidStdWallIndicator); |
397 | | }; |
398 | | } |
399 | 0 | } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { |
400 | 0 | assert_eq!(std_wall_bytes.len(), ut_local_bytes.len()); |
401 | 0 | let it = std_wall_bytes.iter().zip(ut_local_bytes); |
402 | 0 | for (i, (&stdwall, &utlocal)) in it.enumerate() { |
403 | | // Indexing is OK because Header guarantees that the number of |
404 | | // indicators is 0 or equal to the number of types. |
405 | 0 | self.types[i].indicator = match (stdwall, utlocal) { |
406 | 0 | (0, 0) => TzifIndicator::LocalWall, |
407 | 0 | (1, 0) => TzifIndicator::LocalStandard, |
408 | 0 | (1, 1) => TzifIndicator::UTStandard, |
409 | | (0, 1) => { |
410 | 0 | return Err(IndicatorError::InvalidUtWallCombination); |
411 | | } |
412 | 0 | _ => return Err(IndicatorError::InvalidCombination), |
413 | | }; |
414 | | } |
415 | | } else { |
416 | | // If they're both empty then we don't need to do anything. Every |
417 | | // local time type record already has the correct default for this |
418 | | // case set. |
419 | 0 | debug_assert!(std_wall_bytes.is_empty()); |
420 | 0 | debug_assert!(ut_local_bytes.is_empty()); |
421 | | } |
422 | 0 | Ok(rest) |
423 | 0 | } |
424 | | |
425 | 0 | fn parse_footer<'b>( |
426 | 0 | &mut self, |
427 | 0 | _header: &Header, |
428 | 0 | bytes: &'b [u8], |
429 | 0 | ) -> Result<&'b [u8], FooterError> { |
430 | 0 | if bytes.is_empty() { |
431 | 0 | return Err(FooterError::UnexpectedEnd); |
432 | 0 | } |
433 | 0 | if bytes[0] != b'\n' { |
434 | 0 | return Err(FooterError::MismatchEnd); |
435 | 0 | } |
436 | 0 | let bytes = &bytes[1..]; |
437 | | // Only scan up to 1KB for a NUL terminator in case we somehow got |
438 | | // passed a huge block of bytes. |
439 | 0 | let toscan = &bytes[..bytes.len().min(1024)]; |
440 | 0 | let nlat = toscan |
441 | 0 | .iter() |
442 | 0 | .position(|&b| b == b'\n') |
443 | 0 | .ok_or(FooterError::TerminatorNotFound)?; |
444 | 0 | let (bytes, rest) = bytes.split_at(nlat); |
445 | 0 | if !bytes.is_empty() { |
446 | | // We could in theory limit TZ strings to their strict POSIX |
447 | | // definition here for TZif V2, but I don't think there is any |
448 | | // harm in allowing the extensions in V2 formatted TZif data. Note |
449 | | // that GNU tooling allows it via the `TZ` environment variable |
450 | | // even though POSIX doesn't specify it. This all seems okay to me |
451 | | // because the V3+ extension is a strict superset of functionality. |
452 | 0 | let posix_tz = PosixTimeZone::parse(bytes) |
453 | 0 | .map_err(FooterError::InvalidPosixTz)?; |
454 | 0 | self.fixed.posix_tz = Some(posix_tz); |
455 | 0 | } |
456 | 0 | Ok(&rest[1..]) |
457 | 0 | } |
458 | | |
459 | | /// Validates that the POSIX TZ string we parsed (if one exists) is |
460 | | /// consistent with the last transition in this time zone. This is |
461 | | /// required by RFC 8536. |
462 | | /// |
463 | | /// RFC 8536 says, "If the string is nonempty and one or more |
464 | | /// transitions appear in the version 2+ data, the string MUST be |
465 | | /// consistent with the last version 2+ transition." |
466 | 0 | fn verify_posix_time_zone_consistency( |
467 | 0 | &self, |
468 | 0 | ) -> Result<(), InconsistentPosixTimeZoneError> { |
469 | | // We need to be a little careful, since we always have at least one |
470 | | // transition (accounting for the dummy `Timestamp::MIN` transition). |
471 | | // So if we only have 1 transition and a POSIX TZ string, then we |
472 | | // should not validate it since it's equivalent to the case of 0 |
473 | | // transitions and a POSIX TZ string. |
474 | 0 | if self.transitions.timestamps.len() <= 1 { |
475 | 0 | return Ok(()); |
476 | 0 | } |
477 | 0 | let Some(ref tz) = self.fixed.posix_tz else { |
478 | 0 | return Ok(()); |
479 | | }; |
480 | 0 | let last = self |
481 | 0 | .transitions |
482 | 0 | .timestamps |
483 | 0 | .last() |
484 | 0 | .expect("last transition timestamp"); |
485 | 0 | let type_index = self |
486 | 0 | .transitions |
487 | 0 | .infos |
488 | 0 | .last() |
489 | 0 | .expect("last transition info") |
490 | 0 | .type_index; |
491 | 0 | let typ = &self.types[usize::from(type_index)]; |
492 | 0 | let (ioff, abbrev, is_dst) = |
493 | 0 | tz.to_offset_info(ITimestamp::from_second(*last)); |
494 | 0 | if ioff.second != typ.offset { |
495 | 0 | return Err(InconsistentPosixTimeZoneError::Offset); |
496 | 0 | } |
497 | 0 | if is_dst != typ.is_dst { |
498 | 0 | return Err(InconsistentPosixTimeZoneError::Dst); |
499 | 0 | } |
500 | 0 | if abbrev != self.designation(&typ) { |
501 | 0 | return Err(InconsistentPosixTimeZoneError::Designation); |
502 | 0 | } |
503 | 0 | Ok(()) |
504 | 0 | } |
505 | | |
506 | | /// Add civil datetimes to our transitions. |
507 | | /// |
508 | | /// This isn't strictly necessary, but it speeds up time zone lookups when |
509 | | /// the input is a civil datetime. It lets us do comparisons directly on |
510 | | /// the civil datetime as given, instead of needing to convert the civil |
511 | | /// datetime given to a timestamp first. (Even if we didn't do this, I |
512 | | /// believe we'd still need at least one additional timestamp that is |
513 | | /// offset, because TZ lookups for a civil datetime are done in local time, |
514 | | /// and the timestamps in TZif data are, of course, all in UTC.) |
515 | 0 | fn add_civil_datetimes_to_transitions(&mut self) { |
516 | 0 | fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime { |
517 | 0 | let its = ITimestamp { second: timestamp, nanosecond: 0 }; |
518 | 0 | let ioff = IOffset { second: offset }; |
519 | 0 | let dt = its.to_datetime(ioff); |
520 | 0 | TzifDateTime::new( |
521 | 0 | dt.date.year, |
522 | 0 | dt.date.month, |
523 | 0 | dt.date.day, |
524 | 0 | dt.time.hour, |
525 | 0 | dt.time.minute, |
526 | 0 | dt.time.second, |
527 | | ) |
528 | 0 | } |
529 | | |
530 | 0 | let trans = &mut self.transitions; |
531 | 0 | for i in 0..trans.timestamps.len() { |
532 | 0 | let timestamp = trans.timestamps[i]; |
533 | 0 | let offset = { |
534 | 0 | let type_index = trans.infos[i].type_index; |
535 | 0 | self.types[usize::from(type_index)].offset |
536 | | }; |
537 | 0 | let prev_offset = { |
538 | 0 | let type_index = trans.infos[i.saturating_sub(1)].type_index; |
539 | 0 | self.types[usize::from(type_index)].offset |
540 | | }; |
541 | | |
542 | 0 | if prev_offset == offset { |
543 | 0 | // Equivalent offsets means there can never be any ambiguity. |
544 | 0 | let start = to_datetime(timestamp, prev_offset); |
545 | 0 | trans.infos[i].kind = TzifTransitionKind::Unambiguous; |
546 | 0 | trans.civil_starts[i] = start; |
547 | 0 | } else if prev_offset < offset { |
548 | 0 | // When the offset of the previous transition is less, that |
549 | 0 | // means there is some non-zero amount of time that is |
550 | 0 | // "skipped" when moving to the next transition. Thus, we have |
551 | 0 | // a gap. The start of the gap is the offset which gets us the |
552 | 0 | // earliest time, i.e., the smaller of the two offsets. |
553 | 0 | trans.infos[i].kind = TzifTransitionKind::Gap; |
554 | 0 | trans.civil_starts[i] = to_datetime(timestamp, prev_offset); |
555 | 0 | trans.civil_ends[i] = to_datetime(timestamp, offset); |
556 | 0 | } else { |
557 | | // When the offset of the previous transition is greater, that |
558 | | // means there is some non-zero amount of time that will be |
559 | | // replayed on a wall clock in this time zone. Thus, we have |
560 | | // a fold. The start of the gold is the offset which gets us |
561 | | // the earliest time, i.e., the smaller of the two offsets. |
562 | 0 | assert!(prev_offset > offset); |
563 | 0 | trans.infos[i].kind = TzifTransitionKind::Fold; |
564 | 0 | trans.civil_starts[i] = to_datetime(timestamp, offset); |
565 | 0 | trans.civil_ends[i] = to_datetime(timestamp, prev_offset); |
566 | | } |
567 | | } |
568 | 0 | } |
569 | | |
570 | | /// Fatten up this TZif data with additional transitions. |
571 | | /// |
572 | | /// These additional transitions often make time zone lookups faster, and |
573 | | /// they smooth out the performance difference between using "slim" and |
574 | | /// "fat" tzdbs. |
575 | 0 | fn fatten(&mut self) { |
576 | | // Note that this is a crate feature for *both* `jiff` and |
577 | | // `jiff-static`. |
578 | 0 | if !cfg!(feature = "tz-fat") { |
579 | 0 | return; |
580 | 0 | } |
581 | 0 | let Some(posix_tz) = self.fixed.posix_tz.clone() else { return }; |
582 | 0 | let last = |
583 | 0 | self.transitions.timestamps.last().expect("last transition"); |
584 | 0 | let mut i = 0; |
585 | 0 | let mut prev = ITimestamp::from_second(*last); |
586 | | loop { |
587 | 0 | if i > FATTEN_MAX_TRANSITIONS { |
588 | | // only-jiff-start |
589 | | warn!( |
590 | | "fattening TZif data for `{name:?}` somehow generated \ |
591 | | more than {max} transitions, so giving up to avoid \ |
592 | | doing too much work", |
593 | | name = self.fixed.name, |
594 | | max = FATTEN_MAX_TRANSITIONS, |
595 | | ); |
596 | | // only-jiff-end |
597 | 0 | return; |
598 | 0 | } |
599 | 0 | i += 1; |
600 | 0 | prev = match self.add_transition(&posix_tz, prev) { |
601 | 0 | None => break, |
602 | 0 | Some(next) => next, |
603 | | }; |
604 | | } |
605 | 0 | } |
606 | | |
607 | | /// If there's a transition strictly after the given timestamp for the |
608 | | /// given POSIX time zone, then add it to this TZif data. |
609 | 0 | fn add_transition( |
610 | 0 | &mut self, |
611 | 0 | posix_tz: &PosixTimeZone<Abbreviation>, |
612 | 0 | prev: ITimestamp, |
613 | 0 | ) -> Option<ITimestamp> { |
614 | 0 | let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?; |
615 | 0 | if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR { |
616 | 0 | return None; |
617 | 0 | } |
618 | 0 | let type_index = |
619 | 0 | self.find_or_create_local_time_type(ioff, abbrev, is_dst)?; |
620 | 0 | self.transitions.add_with_type_index(its.second, type_index); |
621 | 0 | Some(its) |
622 | 0 | } |
623 | | |
624 | | /// Look for a local time type matching the data given. |
625 | | /// |
626 | | /// If one could not be found, then one is created and its index is |
627 | | /// returned. |
628 | | /// |
629 | | /// If one could not be found and one could not be created (e.g., the index |
630 | | /// would overflow `u8`), then `None` is returned. |
631 | 0 | fn find_or_create_local_time_type( |
632 | 0 | &mut self, |
633 | 0 | offset: IOffset, |
634 | 0 | abbrev: &str, |
635 | 0 | is_dst: bool, |
636 | 0 | ) -> Option<u8> { |
637 | 0 | for (i, typ) in self.types.iter().enumerate() { |
638 | 0 | if offset.second == typ.offset |
639 | 0 | && abbrev == self.designation(typ) |
640 | 0 | && is_dst == typ.is_dst |
641 | | { |
642 | 0 | return u8::try_from(i).ok(); |
643 | 0 | } |
644 | | } |
645 | 0 | let i = u8::try_from(self.types.len()).ok()?; |
646 | 0 | let designation = self.find_or_create_designation(abbrev)?; |
647 | 0 | self.types.push(TzifLocalTimeType { |
648 | 0 | offset: offset.second, |
649 | 0 | is_dst, |
650 | 0 | designation, |
651 | 0 | // Not really clear if this is correct, but Jiff |
652 | 0 | // ignores this anyway, so ¯\_(ツ)_/¯. |
653 | 0 | indicator: TzifIndicator::LocalWall, |
654 | 0 | }); |
655 | 0 | Some(i) |
656 | 0 | } |
657 | | |
658 | | /// Look for a designation (i.e., time zone abbreviation) matching the data |
659 | | /// given, and return its range into `self.fixed.designations`. |
660 | | /// |
661 | | /// If one could not be found, then one is created and its range is |
662 | | /// returned. |
663 | | /// |
664 | | /// If one could not be found and one could not be created (e.g., the range |
665 | | /// would overflow `u8`), then `None` is returned. |
666 | 0 | fn find_or_create_designation( |
667 | 0 | &mut self, |
668 | 0 | needle: &str, |
669 | 0 | ) -> Option<(u8, u8)> { |
670 | 0 | let mut start = 0; |
671 | 0 | while let Some(offset) = self.fixed.designations[start..].find('\0') { |
672 | 0 | let end = start + offset; |
673 | 0 | let abbrev = &self.fixed.designations[start..end]; |
674 | 0 | if needle == abbrev { |
675 | 0 | return Some((start.try_into().ok()?, end.try_into().ok()?)); |
676 | 0 | } |
677 | 0 | start = end + 1; |
678 | | } |
679 | | |
680 | | // Now we need to add a new abbreviation. This |
681 | | // should generally only happen for malformed TZif |
682 | | // data. i.e., TZif data with a POSIX time zone that |
683 | | // contains an TZ abbreviation that isn't found in |
684 | | // the TZif's designation list. |
685 | | // |
686 | | // And since we're guarding against malformed data, |
687 | | // the designation list might not end with NUL. If |
688 | | // not, add one. |
689 | 0 | if !self.fixed.designations.ends_with('\0') { |
690 | 0 | self.fixed.designations.push('\0'); |
691 | 0 | } |
692 | 0 | let start = self.fixed.designations.len(); |
693 | 0 | self.fixed.designations.push_str(needle); |
694 | 0 | self.fixed.designations.push('\0'); |
695 | 0 | let end = self.fixed.designations.len(); |
696 | 0 | Some((start.try_into().ok()?, end.try_into().ok()?)) |
697 | 0 | } |
698 | | |
699 | 0 | fn designation(&self, typ: &TzifLocalTimeType) -> &str { |
700 | 0 | let range = |
701 | 0 | usize::from(typ.designation.0)..usize::from(typ.designation.1); |
702 | | // OK because we verify that the designation range on every local |
703 | | // time type is a valid range into `self.designations`. |
704 | 0 | &self.fixed.designations[range] |
705 | 0 | } |
706 | | } |
707 | | |
708 | | impl TzifTransitionsOwned { |
709 | | /// Add a single transition with the given timestamp. |
710 | | /// |
711 | | /// This also fills in the other columns (civil starts, civil ends and |
712 | | /// infos) with sensible default values. It is expected that callers will |
713 | | /// later fill them in. |
714 | 0 | fn add(&mut self, timestamp: i64) { |
715 | 0 | self.add_with_type_index(timestamp, 0); |
716 | 0 | } |
717 | | |
718 | | /// Like `TzifTransitionsOwned::add`, but let's the caller provide a type |
719 | | /// index if it is known. |
720 | 0 | fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) { |
721 | 0 | self.timestamps.push(timestamp); |
722 | 0 | self.civil_starts.push(TzifDateTime::ZERO); |
723 | 0 | self.civil_ends.push(TzifDateTime::ZERO); |
724 | 0 | self.infos.push(TzifTransitionInfo { |
725 | 0 | type_index, |
726 | 0 | kind: TzifTransitionKind::Unambiguous, |
727 | 0 | }); |
728 | 0 | } |
729 | | } |
730 | | |
731 | | /// The header for a TZif formatted file. |
732 | | /// |
733 | | /// V2+ TZif format have two headers: one for V1 data, and then a second |
734 | | /// following the V1 data block that describes another data block which uses |
735 | | /// 64-bit timestamps. The two headers both have the same format and both |
736 | | /// use 32-bit big-endian encoded integers. |
737 | | #[derive(Debug)] |
738 | | struct Header { |
739 | | /// The size of the timestamps encoded in the data block. |
740 | | /// |
741 | | /// This is guaranteed to be either 4 (for V1) or 8 (for the 64-bit header |
742 | | /// block in V2+). |
743 | | time_size: usize, |
744 | | /// The file format version. |
745 | | /// |
746 | | /// Note that this is either a NUL byte (for version 1), or an ASCII byte |
747 | | /// corresponding to the version number. That is, `0x32` for `2`, `0x33` |
748 | | /// for `3` or `0x34` for `4`. Note also that just because zoneinfo might |
749 | | /// have been recently generated does not mean it uses the latest format |
750 | | /// version. It seems like newer versions are only compiled by `zic` when |
751 | | /// they are needed. For example, `America/New_York` on my system (as of |
752 | | /// `2024-03-25`) has version `0x32`, but `Asia/Jerusalem` has version |
753 | | /// `0x33`. |
754 | | version: u8, |
755 | | /// Number of UT/local indicators stored in the file. |
756 | | /// |
757 | | /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. |
758 | | tzh_ttisutcnt: usize, |
759 | | /// The number of standard/wall indicators stored in the file. |
760 | | /// |
761 | | /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. |
762 | | tzh_ttisstdcnt: usize, |
763 | | /// The number of leap seconds for which data entries are stored in the |
764 | | /// file. |
765 | | tzh_leapcnt: usize, |
766 | | /// The number of transition times for which data entries are stored in |
767 | | /// the file. |
768 | | tzh_timecnt: usize, |
769 | | /// The number of local time types for which data entries are stored in the |
770 | | /// file. |
771 | | /// |
772 | | /// This is checked to be at least `1`. |
773 | | tzh_typecnt: usize, |
774 | | /// The number of bytes of time zone abbreviation strings stored in the |
775 | | /// file. |
776 | | /// |
777 | | /// This is checked to be at least `1`. |
778 | | tzh_charcnt: usize, |
779 | | } |
780 | | |
781 | | impl Header { |
782 | | /// Parse the header record from the given bytes. |
783 | | /// |
784 | | /// Upon success, return the header and all bytes after the header. |
785 | | /// |
786 | | /// The given `time_size` must be 4 or 8, corresponding to either the |
787 | | /// V1 header block or the V2+ header block, respectively. |
788 | 0 | fn parse( |
789 | 0 | time_size: usize, |
790 | 0 | bytes: &[u8], |
791 | 0 | ) -> Result<(Header, &[u8]), HeaderError> { |
792 | 0 | assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8"); |
793 | 0 | if bytes.len() < 44 { |
794 | 0 | return Err(HeaderError::TooShort); |
795 | 0 | } |
796 | 0 | let (magic, rest) = bytes.split_at(4); |
797 | 0 | if magic != b"TZif" { |
798 | 0 | return Err(HeaderError::MismatchMagic); |
799 | 0 | } |
800 | 0 | let (version, rest) = rest.split_at(1); |
801 | 0 | let (_reserved, rest) = rest.split_at(15); |
802 | | |
803 | 0 | let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4); |
804 | 0 | let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4); |
805 | 0 | let (tzh_leapcnt_bytes, rest) = rest.split_at(4); |
806 | 0 | let (tzh_timecnt_bytes, rest) = rest.split_at(4); |
807 | 0 | let (tzh_typecnt_bytes, rest) = rest.split_at(4); |
808 | 0 | let (tzh_charcnt_bytes, rest) = rest.split_at(4); |
809 | | |
810 | 0 | let tzh_ttisutcnt = |
811 | 0 | from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes).map_err(|e| { |
812 | 0 | HeaderError::ParseCount { kind: CountKind::Ut, convert: e } |
813 | 0 | })?; |
814 | 0 | let tzh_ttisstdcnt = |
815 | 0 | from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| { |
816 | 0 | HeaderError::ParseCount { kind: CountKind::Std, convert: e } |
817 | 0 | })?; |
818 | 0 | let tzh_leapcnt = |
819 | 0 | from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| { |
820 | 0 | HeaderError::ParseCount { kind: CountKind::Leap, convert: e } |
821 | 0 | })?; |
822 | 0 | let tzh_timecnt = |
823 | 0 | from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| { |
824 | 0 | HeaderError::ParseCount { kind: CountKind::Time, convert: e } |
825 | 0 | })?; |
826 | 0 | let tzh_typecnt = |
827 | 0 | from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| { |
828 | 0 | HeaderError::ParseCount { kind: CountKind::Type, convert: e } |
829 | 0 | })?; |
830 | 0 | let tzh_charcnt = |
831 | 0 | from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| { |
832 | 0 | HeaderError::ParseCount { kind: CountKind::Char, convert: e } |
833 | 0 | })?; |
834 | | |
835 | 0 | if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt { |
836 | 0 | return Err(HeaderError::MismatchUtType); |
837 | 0 | } |
838 | 0 | if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt { |
839 | 0 | return Err(HeaderError::MismatchStdType); |
840 | 0 | } |
841 | 0 | if tzh_typecnt < 1 { |
842 | 0 | return Err(HeaderError::ZeroType); |
843 | 0 | } |
844 | 0 | if tzh_charcnt < 1 { |
845 | 0 | return Err(HeaderError::ZeroChar); |
846 | 0 | } |
847 | | |
848 | 0 | let header = Header { |
849 | 0 | time_size, |
850 | 0 | version: version[0], |
851 | 0 | tzh_ttisutcnt, |
852 | 0 | tzh_ttisstdcnt, |
853 | 0 | tzh_leapcnt, |
854 | 0 | tzh_timecnt, |
855 | 0 | tzh_typecnt, |
856 | 0 | tzh_charcnt, |
857 | 0 | }; |
858 | 0 | Ok((header, rest)) |
859 | 0 | } |
860 | | |
861 | | /// Returns true if this header is for a 32-bit data block. |
862 | | /// |
863 | | /// When false, it is guaranteed that this header is for a 64-bit data |
864 | | /// block. |
865 | 0 | fn is_32bit(&self) -> bool { |
866 | 0 | self.time_size == 4 |
867 | 0 | } |
868 | | |
869 | | /// Returns the size of the data block, in bytes, for this header. |
870 | | /// |
871 | | /// This returns an error if the arithmetic required to compute the |
872 | | /// length would overflow. |
873 | | /// |
874 | | /// This is useful for, e.g., skipping over the 32-bit V1 data block in |
875 | | /// V2+ TZif formatted files. |
876 | 0 | fn data_block_len(&self) -> Result<usize, HeaderError> { |
877 | 0 | let a = self.transition_times_len()?; |
878 | 0 | let b = self.transition_types_len(); |
879 | 0 | let c = self.local_time_types_len()?; |
880 | 0 | let d = self.time_zone_designations_len(); |
881 | 0 | let e = self.leap_second_len()?; |
882 | 0 | let f = self.standard_wall_len(); |
883 | 0 | let g = self.ut_local_len(); |
884 | 0 | a.checked_add(b) |
885 | 0 | .and_then(|z| z.checked_add(c)) |
886 | 0 | .and_then(|z| z.checked_add(d)) |
887 | 0 | .and_then(|z| z.checked_add(e)) |
888 | 0 | .and_then(|z| z.checked_add(f)) |
889 | 0 | .and_then(|z| z.checked_add(g)) |
890 | 0 | .ok_or(HeaderError::InvalidDataBlock { version: self.version }) |
891 | 0 | } |
892 | | |
893 | 0 | fn transition_times_len(&self) -> Result<usize, HeaderError> { |
894 | 0 | self.tzh_timecnt |
895 | 0 | .checked_mul(self.time_size) |
896 | 0 | .ok_or(HeaderError::InvalidTimeCount) |
897 | 0 | } |
898 | | |
899 | 0 | fn transition_types_len(&self) -> usize { |
900 | 0 | self.tzh_timecnt |
901 | 0 | } |
902 | | |
903 | 0 | fn local_time_types_len(&self) -> Result<usize, HeaderError> { |
904 | 0 | self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount) |
905 | 0 | } |
906 | | |
907 | 0 | fn time_zone_designations_len(&self) -> usize { |
908 | 0 | self.tzh_charcnt |
909 | 0 | } |
910 | | |
911 | 0 | fn leap_second_len(&self) -> Result<usize, HeaderError> { |
912 | 0 | let record_len = self |
913 | 0 | .time_size |
914 | 0 | .checked_add(4) |
915 | 0 | .expect("4-or-8 plus 4 always fits in usize"); |
916 | 0 | self.tzh_leapcnt |
917 | 0 | .checked_mul(record_len) |
918 | 0 | .ok_or(HeaderError::InvalidLeapSecondCount) |
919 | 0 | } |
920 | | |
921 | 0 | fn standard_wall_len(&self) -> usize { |
922 | 0 | self.tzh_ttisstdcnt |
923 | 0 | } |
924 | | |
925 | 0 | fn ut_local_len(&self) -> usize { |
926 | 0 | self.tzh_ttisutcnt |
927 | 0 | } |
928 | | } |
929 | | |
930 | | #[derive(Clone, Debug, Eq, PartialEq)] |
931 | | pub(crate) struct TzifError { |
932 | | kind: TzifErrorKind, |
933 | | } |
934 | | |
935 | | #[derive(Clone, Debug, Eq, PartialEq)] |
936 | | enum TzifErrorKind { |
937 | | Footer(FooterError), |
938 | | Header(HeaderError), |
939 | | Header32(HeaderError), |
940 | | Header64(HeaderError), |
941 | | InconsistentPosixTimeZone(InconsistentPosixTimeZoneError), |
942 | | Indicator(IndicatorError), |
943 | | LocalTimeType(LocalTimeTypeError), |
944 | | SplitAt(SplitAtError), |
945 | | TimeZoneDesignator(TimeZoneDesignatorError), |
946 | | TransitionType(TransitionTypeError), |
947 | | } |
948 | | |
949 | | #[cfg(feature = "std")] |
950 | | impl std::error::Error for TzifError {} |
951 | | |
952 | | impl core::fmt::Display for TzifError { |
953 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
954 | | use self::TzifErrorKind::*; |
955 | 0 | match self.kind { |
956 | 0 | Footer(ref err) => { |
957 | 0 | f.write_str("invalid TZif footer: ")?; |
958 | 0 | err.fmt(f) |
959 | | } |
960 | 0 | Header(ref err) => { |
961 | 0 | f.write_str("invalid TZif header: ")?; |
962 | 0 | err.fmt(f) |
963 | | } |
964 | 0 | Header32(ref err) => { |
965 | 0 | f.write_str("invalid 32-bit TZif header: ")?; |
966 | 0 | err.fmt(f) |
967 | | } |
968 | 0 | Header64(ref err) => { |
969 | 0 | f.write_str("invalid 64-bit TZif header: ")?; |
970 | 0 | err.fmt(f) |
971 | | } |
972 | 0 | InconsistentPosixTimeZone(ref err) => { |
973 | 0 | f.write_str( |
974 | 0 | "found inconsistency with \ |
975 | 0 | POSIX time zone transition rule \ |
976 | 0 | in TZif file footer: ", |
977 | 0 | )?; |
978 | 0 | err.fmt(f) |
979 | | } |
980 | 0 | Indicator(ref err) => { |
981 | 0 | f.write_str("failed to parse indicators: ")?; |
982 | 0 | err.fmt(f) |
983 | | } |
984 | 0 | LocalTimeType(ref err) => { |
985 | 0 | f.write_str("failed to parse local time types: ")?; |
986 | 0 | err.fmt(f) |
987 | | } |
988 | 0 | SplitAt(ref err) => err.fmt(f), |
989 | 0 | TimeZoneDesignator(ref err) => { |
990 | 0 | f.write_str("failed to parse time zone designators: ")?; |
991 | 0 | err.fmt(f) |
992 | | } |
993 | 0 | TransitionType(ref err) => { |
994 | 0 | f.write_str("failed to parse time zone transition types: ")?; |
995 | 0 | err.fmt(f) |
996 | | } |
997 | | } |
998 | 0 | } |
999 | | } |
1000 | | |
1001 | | impl From<TzifErrorKind> for TzifError { |
1002 | 0 | fn from(kind: TzifErrorKind) -> TzifError { |
1003 | 0 | TzifError { kind } |
1004 | 0 | } |
1005 | | } |
1006 | | |
1007 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1008 | | enum TransitionTypeError { |
1009 | | ExceedsLocalTimeTypes, |
1010 | | Split(SplitAtError), |
1011 | | } |
1012 | | |
1013 | | impl From<TransitionTypeError> for TzifError { |
1014 | 0 | fn from(err: TransitionTypeError) -> TzifError { |
1015 | 0 | TzifErrorKind::TransitionType(err).into() |
1016 | 0 | } |
1017 | | } |
1018 | | |
1019 | | impl From<SplitAtError> for TransitionTypeError { |
1020 | 0 | fn from(err: SplitAtError) -> TransitionTypeError { |
1021 | 0 | TransitionTypeError::Split(err) |
1022 | 0 | } |
1023 | | } |
1024 | | |
1025 | | impl core::fmt::Display for TransitionTypeError { |
1026 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1027 | | use self::TransitionTypeError::*; |
1028 | | |
1029 | 0 | match *self { |
1030 | 0 | ExceedsLocalTimeTypes => f.write_str( |
1031 | 0 | "found time zone transition type index \ |
1032 | 0 | that exceeds the number of local time types", |
1033 | | ), |
1034 | 0 | Split(ref err) => err.fmt(f), |
1035 | | } |
1036 | 0 | } |
1037 | | } |
1038 | | |
1039 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1040 | | enum LocalTimeTypeError { |
1041 | | InvalidOffset { offset: i32 }, |
1042 | | Split(SplitAtError), |
1043 | | } |
1044 | | |
1045 | | impl From<LocalTimeTypeError> for TzifError { |
1046 | 0 | fn from(err: LocalTimeTypeError) -> TzifError { |
1047 | 0 | TzifErrorKind::LocalTimeType(err).into() |
1048 | 0 | } |
1049 | | } |
1050 | | |
1051 | | impl From<SplitAtError> for LocalTimeTypeError { |
1052 | 0 | fn from(err: SplitAtError) -> LocalTimeTypeError { |
1053 | 0 | LocalTimeTypeError::Split(err) |
1054 | 0 | } |
1055 | | } |
1056 | | |
1057 | | impl core::fmt::Display for LocalTimeTypeError { |
1058 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1059 | | use self::LocalTimeTypeError::*; |
1060 | | |
1061 | 0 | match *self { |
1062 | 0 | InvalidOffset { offset } => write!( |
1063 | 0 | f, |
1064 | 0 | "found local time type with \ |
1065 | 0 | out-of-bounds time zone offset: {offset}, \ |
1066 | 0 | Jiff's allowed range is `{OFFSET_MIN}..={OFFSET_MAX}`" |
1067 | | ), |
1068 | 0 | Split(ref err) => err.fmt(f), |
1069 | | } |
1070 | 0 | } |
1071 | | } |
1072 | | |
1073 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1074 | | enum TimeZoneDesignatorError { |
1075 | | InvalidEnd, |
1076 | | InvalidLength, |
1077 | | InvalidStart, |
1078 | | InvalidUtf8, |
1079 | | MissingNul, |
1080 | | Split(SplitAtError), |
1081 | | } |
1082 | | |
1083 | | impl From<TimeZoneDesignatorError> for TzifError { |
1084 | 0 | fn from(err: TimeZoneDesignatorError) -> TzifError { |
1085 | 0 | TzifErrorKind::TimeZoneDesignator(err).into() |
1086 | 0 | } |
1087 | | } |
1088 | | |
1089 | | impl From<SplitAtError> for TimeZoneDesignatorError { |
1090 | 0 | fn from(err: SplitAtError) -> TimeZoneDesignatorError { |
1091 | 0 | TimeZoneDesignatorError::Split(err) |
1092 | 0 | } |
1093 | | } |
1094 | | |
1095 | | impl core::fmt::Display for TimeZoneDesignatorError { |
1096 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1097 | | use self::TimeZoneDesignatorError::*; |
1098 | | |
1099 | 0 | match *self { |
1100 | 0 | InvalidEnd => f.write_str( |
1101 | 0 | "found invalid end of time zone designator \ |
1102 | 0 | for local time type", |
1103 | | ), |
1104 | 0 | InvalidLength => f.write_str( |
1105 | 0 | "found invalid length of time zone designator \ |
1106 | 0 | for local time type", |
1107 | | ), |
1108 | 0 | InvalidStart => f.write_str( |
1109 | 0 | "found invalid start of time zone designator \ |
1110 | 0 | for local time type", |
1111 | | ), |
1112 | | InvalidUtf8 => { |
1113 | 0 | f.write_str("found invalid UTF-8 in time zone designators") |
1114 | | } |
1115 | 0 | MissingNul => f.write_str( |
1116 | 0 | "could not find NUL terminator for time zone designator", |
1117 | | ), |
1118 | 0 | Split(ref err) => err.fmt(f), |
1119 | | } |
1120 | 0 | } |
1121 | | } |
1122 | | |
1123 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1124 | | enum IndicatorError { |
1125 | | InvalidCombination, |
1126 | | InvalidStdWallIndicator, |
1127 | | InvalidUtWallCombination, |
1128 | | Split(SplitAtError), |
1129 | | UtLocalNonZero, |
1130 | | } |
1131 | | |
1132 | | impl From<IndicatorError> for TzifError { |
1133 | 0 | fn from(err: IndicatorError) -> TzifError { |
1134 | 0 | TzifErrorKind::Indicator(err).into() |
1135 | 0 | } |
1136 | | } |
1137 | | |
1138 | | impl From<SplitAtError> for IndicatorError { |
1139 | 0 | fn from(err: SplitAtError) -> IndicatorError { |
1140 | 0 | IndicatorError::Split(err) |
1141 | 0 | } |
1142 | | } |
1143 | | |
1144 | | impl core::fmt::Display for IndicatorError { |
1145 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1146 | | use self::IndicatorError::*; |
1147 | | |
1148 | 0 | match *self { |
1149 | 0 | InvalidCombination => f.write_str( |
1150 | 0 | "found invalid std/wall or UT/local value for \ |
1151 | 0 | local time type, each must be 0 or 1", |
1152 | | ), |
1153 | 0 | InvalidStdWallIndicator => f.write_str( |
1154 | 0 | "found invalid std/wall indicator, \ |
1155 | 0 | expected it to be 0 or 1", |
1156 | | ), |
1157 | 0 | InvalidUtWallCombination => f.write_str( |
1158 | 0 | "found invalid UT-wall combination for \ |
1159 | 0 | local time type, only local-wall, \ |
1160 | 0 | local-standard and UT-standard are allowed", |
1161 | | ), |
1162 | 0 | Split(ref err) => err.fmt(f), |
1163 | 0 | UtLocalNonZero => f.write_str( |
1164 | 0 | "found non-zero UT/local indicator, \ |
1165 | 0 | but all such indicators should be zero", |
1166 | | ), |
1167 | | } |
1168 | 0 | } |
1169 | | } |
1170 | | |
1171 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1172 | | enum InconsistentPosixTimeZoneError { |
1173 | | Designation, |
1174 | | Dst, |
1175 | | Offset, |
1176 | | } |
1177 | | |
1178 | | impl From<InconsistentPosixTimeZoneError> for TzifError { |
1179 | 0 | fn from(err: InconsistentPosixTimeZoneError) -> TzifError { |
1180 | 0 | TzifErrorKind::InconsistentPosixTimeZone(err).into() |
1181 | 0 | } |
1182 | | } |
1183 | | |
1184 | | impl core::fmt::Display for InconsistentPosixTimeZoneError { |
1185 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1186 | | use self::InconsistentPosixTimeZoneError::*; |
1187 | | |
1188 | 0 | match *self { |
1189 | 0 | Designation => f.write_str( |
1190 | 0 | "expected last transition in TZif file to have \ |
1191 | 0 | a time zone abbreviation matching the abbreviation \ |
1192 | 0 | derived from the POSIX time zone transition rule", |
1193 | | ), |
1194 | 0 | Dst => f.write_str( |
1195 | 0 | "expected last transition in TZif file to have \ |
1196 | 0 | a DST status matching the status derived from the \ |
1197 | 0 | POSIX time zone transition rule", |
1198 | | ), |
1199 | 0 | Offset => f.write_str( |
1200 | 0 | "expected last transition in TZif file to have \ |
1201 | 0 | DST offset matching the offset derived from the \ |
1202 | 0 | POSIX time zone transition rule", |
1203 | | ), |
1204 | | } |
1205 | 0 | } |
1206 | | } |
1207 | | |
1208 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1209 | | enum FooterError { |
1210 | | InvalidPosixTz(crate::shared::posix::PosixTimeZoneError), |
1211 | | MismatchEnd, |
1212 | | TerminatorNotFound, |
1213 | | UnexpectedEnd, |
1214 | | } |
1215 | | |
1216 | | impl From<FooterError> for TzifError { |
1217 | 0 | fn from(err: FooterError) -> TzifError { |
1218 | 0 | TzifErrorKind::Footer(err).into() |
1219 | 0 | } |
1220 | | } |
1221 | | |
1222 | | impl core::fmt::Display for FooterError { |
1223 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1224 | | use self::FooterError::*; |
1225 | | |
1226 | 0 | match *self { |
1227 | 0 | InvalidPosixTz(ref err) => { |
1228 | 0 | f.write_str("invalid POSIX time zone transition rule")?; |
1229 | 0 | core::fmt::Display::fmt(err, f) |
1230 | | } |
1231 | 0 | MismatchEnd => f.write_str( |
1232 | 0 | "expected to find `\\n` at the beginning of \ |
1233 | 0 | the TZif file footer, \ |
1234 | 0 | but found something else instead", |
1235 | | ), |
1236 | 0 | TerminatorNotFound => f.write_str( |
1237 | 0 | "expected to find `\\n` terminating \ |
1238 | 0 | the TZif file footer, \ |
1239 | 0 | but no line terminator could be found", |
1240 | | ), |
1241 | 0 | UnexpectedEnd => f.write_str( |
1242 | 0 | "expected to find `\\n` at the beginning of \ |
1243 | 0 | the TZif file footer, \ |
1244 | 0 | but found unexpected end of data", |
1245 | | ), |
1246 | | } |
1247 | 0 | } |
1248 | | } |
1249 | | |
1250 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1251 | | enum HeaderError { |
1252 | | InvalidDataBlock { version: u8 }, |
1253 | | InvalidLeapSecondCount, |
1254 | | InvalidTimeCount, |
1255 | | InvalidTypeCount, |
1256 | | MismatchMagic, |
1257 | | MismatchStdType, |
1258 | | MismatchUtType, |
1259 | | ParseCount { kind: CountKind, convert: U32UsizeError }, |
1260 | | TooShort, |
1261 | | ZeroChar, |
1262 | | ZeroType, |
1263 | | } |
1264 | | |
1265 | | impl From<HeaderError> for TzifError { |
1266 | 0 | fn from(err: HeaderError) -> TzifError { |
1267 | 0 | TzifErrorKind::Header(err).into() |
1268 | 0 | } |
1269 | | } |
1270 | | |
1271 | | impl core::fmt::Display for HeaderError { |
1272 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1273 | | use self::HeaderError::*; |
1274 | | |
1275 | 0 | match *self { |
1276 | 0 | InvalidDataBlock { version } => write!( |
1277 | 0 | f, |
1278 | 0 | "length of data block in V{version} TZif file is too big", |
1279 | | ), |
1280 | | InvalidLeapSecondCount => { |
1281 | 0 | f.write_str("number of leap seconds is too big") |
1282 | | } |
1283 | | InvalidTimeCount => { |
1284 | 0 | f.write_str("number of transition times is too big") |
1285 | | } |
1286 | | InvalidTypeCount => { |
1287 | 0 | f.write_str("number of local time types is too big") |
1288 | | } |
1289 | 0 | MismatchMagic => f.write_str("magic bytes mismatch"), |
1290 | 0 | MismatchStdType => f.write_str( |
1291 | 0 | "expected number of standard/wall indicators to be zero \ |
1292 | 0 | or equal to the number of local time types", |
1293 | | ), |
1294 | 0 | MismatchUtType => f.write_str( |
1295 | 0 | "expected number of UT/local indicators to be zero \ |
1296 | 0 | or equal to the number of local time types", |
1297 | | ), |
1298 | 0 | ParseCount { ref kind, ref convert } => { |
1299 | 0 | write!(f, "failed to parse `{kind}`: {convert}") |
1300 | | } |
1301 | 0 | TooShort => f.write_str("too short"), |
1302 | 0 | ZeroChar => f.write_str( |
1303 | 0 | "expected number of time zone abbreviations fo be at least 1", |
1304 | | ), |
1305 | 0 | ZeroType => f.write_str( |
1306 | 0 | "expected number of local time types fo be at least 1", |
1307 | | ), |
1308 | | } |
1309 | 0 | } |
1310 | | } |
1311 | | |
1312 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1313 | | enum CountKind { |
1314 | | Ut, |
1315 | | Std, |
1316 | | Leap, |
1317 | | Time, |
1318 | | Type, |
1319 | | Char, |
1320 | | } |
1321 | | |
1322 | | impl core::fmt::Display for CountKind { |
1323 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1324 | | use self::CountKind::*; |
1325 | 0 | match *self { |
1326 | 0 | Ut => f.write_str("tzh_ttisutcnt"), |
1327 | 0 | Std => f.write_str("tzh_ttisstdcnt"), |
1328 | 0 | Leap => f.write_str("tzh_leapcnt"), |
1329 | 0 | Time => f.write_str("tzh_timecnt"), |
1330 | 0 | Type => f.write_str("tzh_typecnt"), |
1331 | 0 | Char => f.write_str("tzh_charcnt"), |
1332 | | } |
1333 | 0 | } |
1334 | | } |
1335 | | |
1336 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1337 | | pub(crate) enum SplitAtError { |
1338 | | V1, |
1339 | | LeapSeconds, |
1340 | | LocalTimeTypes, |
1341 | | StandardWallIndicators, |
1342 | | TimeZoneDesignations, |
1343 | | TransitionTimes, |
1344 | | TransitionTypes, |
1345 | | UTLocalIndicators, |
1346 | | } |
1347 | | |
1348 | | impl From<SplitAtError> for TzifError { |
1349 | 0 | fn from(err: SplitAtError) -> TzifError { |
1350 | 0 | TzifErrorKind::SplitAt(err).into() |
1351 | 0 | } |
1352 | | } |
1353 | | |
1354 | | impl core::fmt::Display for SplitAtError { |
1355 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1356 | | use self::SplitAtError::*; |
1357 | | |
1358 | 0 | f.write_str("expected bytes for '")?; |
1359 | 0 | f.write_str(match *self { |
1360 | 0 | V1 => "v1 TZif", |
1361 | 0 | LeapSeconds => "leap seconds", |
1362 | 0 | LocalTimeTypes => "local time types", |
1363 | 0 | StandardWallIndicators => "standard/wall indicators", |
1364 | 0 | TimeZoneDesignations => "time zone designations", |
1365 | 0 | TransitionTimes => "transition times", |
1366 | 0 | TransitionTypes => "transition types", |
1367 | 0 | UTLocalIndicators => "UT/local indicators", |
1368 | 0 | })?; |
1369 | 0 | f.write_str("data block', but did not find enough bytes")?; |
1370 | 0 | Ok(()) |
1371 | 0 | } |
1372 | | } |
1373 | | |
1374 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1375 | | struct U32UsizeError; |
1376 | | |
1377 | | impl core::fmt::Display for U32UsizeError { |
1378 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
1379 | 0 | write!( |
1380 | 0 | f, |
1381 | 0 | "failed to parse integer because it is bigger than `{max}`", |
1382 | | max = usize::MAX, |
1383 | | ) |
1384 | 0 | } |
1385 | | } |
1386 | | |
1387 | | /// Splits the given slice of bytes at the index given. |
1388 | | /// |
1389 | | /// If the index is out of range (greater than `bytes.len()`) then an error is |
1390 | | /// returned. The error message will include the `what` string given, which is |
1391 | | /// meant to describe the thing being split. |
1392 | 0 | fn try_split_at<'b>( |
1393 | 0 | what: SplitAtError, |
1394 | 0 | bytes: &'b [u8], |
1395 | 0 | at: usize, |
1396 | 0 | ) -> Result<(&'b [u8], &'b [u8]), SplitAtError> { |
1397 | 0 | if at > bytes.len() { |
1398 | 0 | Err(what) |
1399 | | } else { |
1400 | 0 | Ok(bytes.split_at(at)) |
1401 | | } |
1402 | 0 | } |
1403 | | |
1404 | | /// Interprets the given slice as an unsigned 32-bit big endian integer, |
1405 | | /// attempts to convert it to a `usize` and returns it. |
1406 | | /// |
1407 | | /// # Panics |
1408 | | /// |
1409 | | /// When `bytes.len() != 4`. |
1410 | | /// |
1411 | | /// # Errors |
1412 | | /// |
1413 | | /// This errors if the `u32` parsed from the given bytes cannot fit in a |
1414 | | /// `usize`. |
1415 | 0 | fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result<usize, U32UsizeError> { |
1416 | 0 | let n = from_be_bytes_u32(bytes); |
1417 | 0 | usize::try_from(n).map_err(|_| U32UsizeError) |
1418 | 0 | } |
1419 | | |
1420 | | /// Interprets the given slice as an unsigned 32-bit big endian integer and |
1421 | | /// returns it. |
1422 | | /// |
1423 | | /// # Panics |
1424 | | /// |
1425 | | /// When `bytes.len() != 4`. |
1426 | 0 | fn from_be_bytes_u32(bytes: &[u8]) -> u32 { |
1427 | 0 | u32::from_be_bytes(bytes.try_into().unwrap()) |
1428 | 0 | } |
1429 | | |
1430 | | /// Interprets the given slice as a signed 32-bit big endian integer and |
1431 | | /// returns it. |
1432 | | /// |
1433 | | /// # Panics |
1434 | | /// |
1435 | | /// When `bytes.len() != 4`. |
1436 | 0 | fn from_be_bytes_i32(bytes: &[u8]) -> i32 { |
1437 | 0 | i32::from_be_bytes(bytes.try_into().unwrap()) |
1438 | 0 | } |
1439 | | |
1440 | | /// Interprets the given slice as a signed 64-bit big endian integer and |
1441 | | /// returns it. |
1442 | | /// |
1443 | | /// # Panics |
1444 | | /// |
1445 | | /// When `bytes.len() != 8`. |
1446 | 0 | fn from_be_bytes_i64(bytes: &[u8]) -> i64 { |
1447 | 0 | i64::from_be_bytes(bytes.try_into().unwrap()) |
1448 | 0 | } |