Skip to main content

tzif_codec/
model.rs

1use crate::{validate::validate_file, TzifError};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
4pub enum Version {
5    V1,
6    V2,
7    V3,
8    V4,
9}
10
11impl Version {
12    pub(crate) const fn from_byte(byte: u8) -> Result<Self, TzifError> {
13        match byte {
14            0 => Ok(Self::V1),
15            b'2' => Ok(Self::V2),
16            b'3' => Ok(Self::V3),
17            b'4' => Ok(Self::V4),
18            _ => Err(TzifError::InvalidVersion(byte)),
19        }
20    }
21
22    pub(crate) const fn byte(self) -> u8 {
23        match self {
24            Self::V1 => 0,
25            Self::V2 => b'2',
26            Self::V3 => b'3',
27            Self::V4 => b'4',
28        }
29    }
30
31    pub(crate) const fn is_v2_plus(self) -> bool {
32        !matches!(self, Self::V1)
33    }
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct TzifFile {
38    pub version: Version,
39    pub v1: DataBlock,
40    pub v2_plus: Option<DataBlock>,
41    pub footer: Option<String>,
42}
43
44impl TzifFile {
45    #[must_use]
46    pub const fn v1(block: DataBlock) -> Self {
47        Self {
48            version: Version::V1,
49            v1: block,
50            v2_plus: None,
51            footer: None,
52        }
53    }
54
55    pub fn v2(v1: DataBlock, v2: DataBlock, footer: impl Into<String>) -> Self {
56        Self::v2_plus(Version::V2, v1, v2, footer)
57    }
58
59    pub fn v3(v1: DataBlock, v3: DataBlock, footer: impl Into<String>) -> Self {
60        Self::v2_plus(Version::V3, v1, v3, footer)
61    }
62
63    pub fn v4(v1: DataBlock, v4: DataBlock, footer: impl Into<String>) -> Self {
64        Self::v2_plus(Version::V4, v1, v4, footer)
65    }
66
67    /// Validates this file against the structural `TZif` rules implemented by this crate.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error when the file contains invalid counts, indexes, version-specific
72    /// data, footer content, or leap-second records.
73    pub fn validate(&self) -> Result<(), TzifError> {
74        validate_file(self)
75    }
76
77    fn v2_plus(
78        version: Version,
79        v1: DataBlock,
80        block: DataBlock,
81        footer: impl Into<String>,
82    ) -> Self {
83        Self {
84            version,
85            v1,
86            v2_plus: Some(block),
87            footer: Some(footer.into()),
88        }
89    }
90}
91
92#[derive(Clone, Debug, Default, PartialEq, Eq)]
93pub struct DataBlock {
94    pub transition_times: Vec<i64>,
95    pub transition_types: Vec<u8>,
96    pub local_time_types: Vec<LocalTimeType>,
97    pub designations: Vec<u8>,
98    pub leap_seconds: Vec<LeapSecond>,
99    pub standard_wall_indicators: Vec<bool>,
100    pub ut_local_indicators: Vec<bool>,
101}
102
103impl DataBlock {
104    pub fn new(local_time_types: Vec<LocalTimeType>, designations: impl Into<Vec<u8>>) -> Self {
105        Self {
106            local_time_types,
107            designations: designations.into(),
108            ..Self::default()
109        }
110    }
111
112    #[must_use]
113    pub fn placeholder() -> Self {
114        Self::new(
115            vec![LocalTimeType {
116                utc_offset: 0,
117                is_dst: false,
118                designation_index: 0,
119            }],
120            vec![0],
121        )
122    }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub struct LocalTimeType {
127    pub utc_offset: i32,
128    pub is_dst: bool,
129    pub designation_index: u8,
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub struct LeapSecond {
134    pub occurrence: i64,
135    pub correction: i32,
136}