Skip to main content

tzif_codec/
error.rs

1use crate::Version;
2use thiserror::Error;
3
4#[derive(Debug, Error, PartialEq, Eq)]
5pub enum TzifBuildError {
6    #[error("time zone designation must not be empty")]
7    EmptyDesignation,
8    #[error("time zone designation {designation:?} must be ASCII")]
9    NonAsciiDesignation { designation: String },
10    #[error("time zone designation {designation:?} contains unsupported character {character:?}")]
11    UnsupportedDesignationCharacter {
12        designation: String,
13        character: char,
14    },
15    #[error("time zone designation {designation:?} must be at least 3 characters")]
16    DesignationTooShort { designation: String },
17    #[error("time zone designation {designation:?} must be at most 6 characters")]
18    DesignationTooLong { designation: String },
19    #[error("duplicate time zone designation {0:?}")]
20    DuplicateDesignation(String),
21    #[error("unknown time zone designation {0:?}")]
22    UnknownDesignation(String),
23    #[error("explicit transition times must be sorted in ascending order")]
24    UnsortedTransitions,
25    #[error("UTC offset -2^31 is not valid in TZif local time types")]
26    InvalidUtcOffset,
27    #[error("UTC offset {seconds} cannot be represented in a POSIX TZ footer")]
28    PosixOffsetOutOfRange { seconds: i32 },
29    #[error("TZif version {version:?} cannot represent transition timestamp {timestamp}")]
30    TransitionOutOfRangeForVersion { version: Version, timestamp: i64 },
31    #[error("TZif version {version:?} cannot include a footer")]
32    VersionCannotIncludeFooter { version: Version },
33    #[error("TZif version {version:?} cannot represent the POSIX TZ string extension")]
34    VersionCannotRepresentFooterExtension { version: Version },
35    #[error("POSIX TZ month {month} is outside the range 1..=12")]
36    InvalidPosixMonth { month: u8 },
37    #[error("POSIX TZ week {week} is outside the range 1..=5")]
38    InvalidPosixWeek { week: u8 },
39    #[error("POSIX TZ weekday {weekday} is outside the range 0..=6")]
40    InvalidPosixWeekday { weekday: u8 },
41    #[error("POSIX TZ Julian day {day} is outside the range 1..=365")]
42    InvalidPosixJulianDay { day: u16 },
43    #[error("POSIX TZ zero-based day {day} is outside the range 0..=365")]
44    InvalidPosixZeroBasedDay { day: u16 },
45    #[error("POSIX TZ transition time {seconds} seconds is outside the supported range")]
46    InvalidPosixTransitionTime { seconds: i32 },
47    #[error(transparent)]
48    InvalidTzif(#[from] TzifError),
49}
50
51#[derive(Debug, Error, PartialEq, Eq)]
52pub enum TzdistError {
53    #[error(transparent)]
54    InvalidTzif(#[from] TzifError),
55    #[error("unsupported TZif media type {0}")]
56    UnsupportedMediaType(String),
57    #[error("TZDIST capabilities must advertise application/tzif when advertising application/tzif-leap")]
58    TzifLeapCapabilityRequiresTzif,
59    #[error("application/tzif MUST NOT contain leap-second records; use application/tzif-leap")]
60    LeapSecondsNotAllowedForApplicationTzif,
61    #[error("TZDIST truncation requires a version 2 or later TZif file")]
62    TruncationRequiresVersion2Plus,
63    #[error("TZDIST truncation requires at least one version 2+ transition")]
64    TruncationRequiresVersion2PlusTransitions,
65    #[error("start truncation transition mismatch: expected {expected}, got {actual}")]
66    StartTruncationTransitionMismatch { expected: i64, actual: i64 },
67    #[error("start truncation requires time type 0 to be a -00 placeholder")]
68    StartTruncationTypeZeroMustBePlaceholder,
69    #[error("end truncation transition mismatch: expected {expected}, got {actual}")]
70    EndTruncationTransitionMismatch { expected: i64, actual: i64 },
71    #[error("end truncation requires an empty TZ string footer")]
72    EndTruncationRequiresEmptyFooter,
73    #[error("end truncation requires the last transition type to be a -00 placeholder")]
74    EndTruncationLastTypeMustBePlaceholder,
75    #[error("invalid TZDIST truncation range: start {start} must be before end {end}")]
76    InvalidTruncationRange { start: i64, end: i64 },
77}
78
79#[derive(Debug, Error, PartialEq, Eq)]
80pub enum TzifError {
81    #[error("input ended unexpectedly at byte {offset} while reading {context}")]
82    UnexpectedEof {
83        offset: usize,
84        context: &'static str,
85    },
86    #[error("expected TZif magic at byte {offset}")]
87    InvalidMagic { offset: usize },
88    #[error("invalid TZif version byte 0x{0:02x}")]
89    InvalidVersion(u8),
90    #[error("version mismatch between first header {first:?} and second header {second:?}")]
91    VersionMismatch { first: Version, second: Version },
92    #[error("expected newline at byte {offset} before TZif footer")]
93    MissingFooterStart { offset: usize },
94    #[error("missing newline terminator for TZif footer starting at byte {offset}")]
95    MissingFooterEnd { offset: usize },
96    #[error("TZif footer is not valid UTF-8")]
97    InvalidFooterUtf8,
98    #[error("trailing data starts at byte {offset}")]
99    TrailingData { offset: usize },
100    #[error("{field} count {count} cannot fit in memory on this platform")]
101    CountTooLarge { field: &'static str, count: u32 },
102    #[error("data block byte length overflow while calculating {field}")]
103    DataBlockLengthOverflow { field: &'static str },
104    #[error("local time type {index} has invalid isdst value {value}")]
105    InvalidDstIndicator { index: usize, value: u8 },
106    #[error("{field} indicator {index} has invalid value {value}")]
107    InvalidBooleanIndicator {
108        field: &'static str,
109        index: usize,
110        value: u8,
111    },
112    #[error("version 1 files must not include a v2+ data block or footer")]
113    UnexpectedV2PlusData,
114    #[error("version {0:?} files must include a v2+ data block and footer")]
115    MissingV2PlusData(Version),
116    #[error("{field} has {actual} entries, but expected {expected}")]
117    CountMismatch {
118        field: &'static str,
119        expected: usize,
120        actual: usize,
121    },
122    #[error("{field} count {count} exceeds TZif u32 count range")]
123    CountOverflow { field: &'static str, count: usize },
124    #[error("local time type count must not be zero")]
125    EmptyLocalTimeTypes,
126    #[error("designation table must not be empty")]
127    EmptyDesignations,
128    #[error("local time type count {0} exceeds 256")]
129    TooManyLocalTimeTypes(usize),
130    #[error(
131        "transition type {transition_type} at index {index} does not reference a local time type"
132    )]
133    InvalidTransitionType { index: usize, transition_type: u8 },
134    #[error("designation index {designation_index} at local time type {index} is out of range")]
135    InvalidDesignationIndex { index: usize, designation_index: u8 },
136    #[error("transition time {value} at index {index} is outside the version 1 i32 range")]
137    Version1TransitionOutOfRange { index: usize, value: i64 },
138    #[error("leap-second occurrence {value} at index {index} is outside the version 1 i32 range")]
139    Version1LeapSecondOutOfRange { index: usize, value: i64 },
140    #[error("transition time at index {index} is not strictly ascending")]
141    TransitionTimesNotAscending { index: usize },
142    #[error(
143        "designation index {designation_index} at local time type {index} has no NUL terminator"
144    )]
145    UnterminatedDesignation { index: usize, designation_index: u8 },
146    #[error("local time type {index} has invalid UTC offset -2^31")]
147    InvalidUtcOffset { index: usize },
148    #[error("time zone designation at local time type {index} violates RFC 9636 designation requirements")]
149    InvalidDesignation { index: usize, designation: Vec<u8> },
150    #[error("UT/local indicator {index} is set without the corresponding standard/wall indicator")]
151    InvalidUtLocalIndicatorCombination { index: usize },
152    #[error("first leap-second occurrence {value} at index 0 must be non-negative")]
153    FirstLeapSecondOccurrenceNegative { value: i64 },
154    #[error("leap-second occurrence at index {index} is not strictly ascending")]
155    LeapSecondOccurrencesNotAscending { index: usize },
156    #[error("first leap-second correction {correction} must be +1 or -1 unless using version 4 truncation")]
157    InvalidFirstLeapSecondCorrection { correction: i32 },
158    #[error("leap-second correction at index {index} must differ from the previous correction by +1 or -1")]
159    InvalidLeapSecondCorrection { index: usize },
160    #[error("version {version:?} cannot contain a leap-second table truncated at the start")]
161    LeapSecondTruncationRequiresVersion4 { version: Version },
162    #[error("version {version:?} cannot contain a leap-second table expiration time")]
163    LeapSecondExpirationRequiresVersion4 { version: Version },
164    #[error("leap-second occurrence at index {index} does not occur at a UTC month boundary")]
165    LeapSecondOccurrenceNotAtMonthEnd { index: usize },
166    #[error("TZif footer must be ASCII")]
167    InvalidFooterAscii,
168    #[error("TZif footer must not contain NUL or newline bytes")]
169    InvalidFooterControlByte,
170    #[error("TZif version {version:?} cannot use the POSIX TZ string extension")]
171    FooterExtensionRequiresVersion3 { version: Version },
172    #[error("TZif footer is not a valid POSIX TZ string")]
173    InvalidFooterSyntax,
174    #[error("TZif footer is inconsistent with the last transition type")]
175    FooterInconsistentWithLastTransition,
176}