Coverage Report

Created: 2026-01-13 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.2.3/src/parser.rs
Line
Count
Source
1
/*
2
* Hifitime
3
* Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors)
4
* This Source Code Form is subject to the terms of the Mozilla Public
5
* License, v. 2.0. If a copy of the MPL was not distributed with this
6
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
*
8
* Documentation: https://nyxspace.com/
9
*/
10
11
use crate::{HifitimeError, ParsingError};
12
13
#[cfg_attr(kani, derive(kani::Arbitrary))]
14
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
15
pub(crate) enum Token {
16
    #[default]
17
    Year,
18
    YearShort,
19
    Month,
20
    Day,
21
    Hour,
22
    Minute,
23
    Second,
24
    Subsecond,
25
    OffsetHours,
26
    OffsetMinutes,
27
    Timescale,
28
    DayOfYearInteger,
29
    DayOfYear,
30
    Weekday,
31
    WeekdayShort,
32
    WeekdayDecimal,
33
    MonthName,
34
    MonthNameShort,
35
}
36
37
impl Token {
38
    // Check that the _integer_ value is valid at first sight.
39
4.15k
    pub fn value_ok(&self, val: i32) -> Result<(), HifitimeError> {
40
4.15k
        match &self {
41
1.59k
            Self::Year => Ok(()),      // No validation
42
0
            Self::YearShort => Ok(()), // No validation
43
            Self::Month => {
44
1.34k
                if !(0..=13).contains(&val) {
45
196
                    Err(HifitimeError::Parse {
46
196
                        source: ParsingError::ValueError,
47
196
                        details: "invalid month",
48
196
                    })
49
                } else {
50
1.14k
                    Ok(())
51
                }
52
            }
53
            Self::Day => {
54
1.05k
                if !(0..=31).contains(&val) {
55
101
                    Err(HifitimeError::Parse {
56
101
                        source: ParsingError::ValueError,
57
101
                        details: "invalid day",
58
101
                    })
59
                } else {
60
958
                    Ok(())
61
                }
62
            }
63
            Self::Hour | Self::OffsetHours => {
64
126
                if !(0..=23).contains(&val) {
65
37
                    Err(HifitimeError::Parse {
66
37
                        source: ParsingError::ValueError,
67
37
                        details: "invalid hour",
68
37
                    })
69
                } else {
70
89
                    Ok(())
71
                }
72
            }
73
            Self::Minute | Self::OffsetMinutes => {
74
20
                if !(0..=59).contains(&val) {
75
7
                    Err(HifitimeError::Parse {
76
7
                        source: ParsingError::ValueError,
77
7
                        details: "invalid minutes",
78
7
                    })
79
                } else {
80
13
                    Ok(())
81
                }
82
            }
83
            Self::Second => {
84
8
                if !(0..=60).contains(&val) {
85
1
                    Err(HifitimeError::Parse {
86
1
                        source: ParsingError::ValueError,
87
1
                        details: "invalid seconds",
88
1
                    })
89
                } else {
90
7
                    Ok(())
91
                }
92
            }
93
            Self::Subsecond => {
94
0
                if val < 0 {
95
0
                    Err(HifitimeError::Parse {
96
0
                        source: ParsingError::ValueError,
97
0
                        details: "invalid subseconds",
98
0
                    })
99
                } else {
100
0
                    Ok(())
101
                }
102
            }
103
0
            Self::Timescale => Ok(()),
104
            Self::DayOfYearInteger => {
105
0
                if !(0..=366).contains(&val) {
106
0
                    Err(HifitimeError::Parse {
107
0
                        source: ParsingError::ValueError,
108
0
                        details: "invalid day of year",
109
0
                    })
110
                } else {
111
0
                    Ok(())
112
                }
113
            }
114
            Self::WeekdayDecimal => {
115
0
                if !(0..=6).contains(&val) {
116
0
                    Err(HifitimeError::Parse {
117
0
                        source: ParsingError::ValueError,
118
0
                        details: "invalid weekday decimal (must be 0-6)",
119
0
                    })
120
                } else {
121
0
                    Ok(())
122
                }
123
            }
124
            Self::Weekday
125
            | Self::WeekdayShort
126
            | Self::MonthName
127
            | Self::MonthNameShort
128
            | Self::DayOfYear => {
129
                // These cannot be parsed as integers
130
0
                Err(HifitimeError::Parse {
131
0
                    source: ParsingError::ValueError,
132
0
                    details: "invalid name or day of year",
133
0
                })
134
            }
135
        }
136
4.15k
    }
137
138
    /// Returns the position in the array for a Gregorian date for this token
139
5.55k
    pub(crate) fn gregorian_position(&self) -> Option<usize> {
140
5.55k
        match &self {
141
2.72k
            Token::Year | Token::YearShort => Some(0),
142
1.49k
            Token::Month => Some(1),
143
1.12k
            Token::Day => Some(2),
144
172
            Token::Hour => Some(3),
145
30
            Token::Minute => Some(4),
146
9
            Token::Second => Some(5),
147
1
            Token::Subsecond => Some(6),
148
2
            Token::OffsetHours => Some(7),
149
0
            Token::OffsetMinutes => Some(8),
150
0
            _ => None,
151
        }
152
5.55k
    }
153
154
    /// Updates the token to what it should be seeking next given the delimiting character
155
    /// and returns the position in the array where the parsed integer should live
156
4.34k
    pub fn advance_with(&mut self, ending_char: char) -> Result<(), HifitimeError> {
157
4.34k
        match &self {
158
            Token::Year | Token::YearShort => {
159
2.58k
                if ending_char == '-' {
160
1.54k
                    *self = Token::Month;
161
1.54k
                    Ok(())
162
                } else {
163
1.03k
                    Err(HifitimeError::Parse {
164
1.03k
                        source: ParsingError::UnknownFormat,
165
1.03k
                        details: "invalid year",
166
1.03k
                    })
167
                }
168
            }
169
            Token::Month => {
170
1.33k
                if ending_char == '-' {
171
1.19k
                    *self = Token::Day;
172
1.19k
                    Ok(())
173
                } else {
174
139
                    Err(HifitimeError::Parse {
175
139
                        source: ParsingError::UnknownFormat,
176
139
                        details: "invalid month",
177
139
                    })
178
                }
179
            }
180
            Token::Day => {
181
311
                if ending_char == 'T' || ending_char == ' ' {
182
255
                    *self = Token::Hour;
183
255
                    Ok(())
184
                } else {
185
56
                    Err(HifitimeError::Parse {
186
56
                        source: ParsingError::UnknownFormat,
187
56
                        details: "invalid day",
188
56
                    })
189
                }
190
            }
191
            Token::Hour => {
192
86
                if ending_char == ':' {
193
40
                    *self = Token::Minute;
194
40
                    Ok(())
195
                } else {
196
46
                    Err(HifitimeError::Parse {
197
46
                        source: ParsingError::UnknownFormat,
198
46
                        details: "invalid hour",
199
46
                    })
200
                }
201
            }
202
            Token::Minute => {
203
22
                if ending_char == ':' {
204
12
                    *self = Token::Second;
205
12
                    Ok(())
206
                } else {
207
10
                    Err(HifitimeError::Parse {
208
10
                        source: ParsingError::UnknownFormat,
209
10
                        details: "invalid minutes",
210
10
                    })
211
                }
212
            }
213
            Token::Second => {
214
8
                if ending_char == '.' {
215
1
                    *self = Token::Subsecond;
216
7
                } else if ending_char == ' ' || ending_char == 'Z' {
217
4
                    // There are no subseconds here, only room for a time scale
218
4
                    *self = Token::Timescale;
219
4
                } else if ending_char == '-' || ending_char == '+' {
220
2
                    // There are no subseconds here, but we're seeing the start of an offset
221
2
                    *self = Token::OffsetHours;
222
2
                } else {
223
1
                    return Err(HifitimeError::Parse {
224
1
                        source: ParsingError::UnknownFormat,
225
1
                        details: "invalid seconds",
226
1
                    });
227
                }
228
7
                Ok(())
229
            }
230
            Token::Subsecond => {
231
1
                if ending_char == ' ' || ending_char == 'Z' {
232
0
                    // There are no subseconds here, only room for a time scale
233
0
                    *self = Token::Timescale;
234
1
                } else if ending_char == '-' || ending_char == '+' {
235
0
                    // There are no subseconds here, but we're seeing the start of an offset
236
0
                    *self = Token::OffsetHours;
237
0
                } else {
238
1
                    return Err(HifitimeError::Parse {
239
1
                        source: ParsingError::UnknownFormat,
240
1
                        details: "invalid subseconds",
241
1
                    });
242
                }
243
0
                Ok(())
244
            }
245
            Token::OffsetHours => {
246
1
                if ending_char == ':' {
247
0
                    *self = Token::OffsetMinutes;
248
0
                    Ok(())
249
                } else {
250
1
                    Err(HifitimeError::Parse {
251
1
                        source: ParsingError::UnknownFormat,
252
1
                        details: "invalid hours offset",
253
1
                    })
254
                }
255
            }
256
            Token::OffsetMinutes => {
257
0
                if ending_char == ' ' || ending_char == 'Z' {
258
                    // Only room for a time scale
259
0
                    *self = Token::Timescale;
260
0
                    Ok(())
261
                } else {
262
0
                    Err(HifitimeError::Parse {
263
0
                        source: ParsingError::UnknownFormat,
264
0
                        details: "invalid minutes offset",
265
0
                    })
266
                }
267
            }
268
0
            _ => Ok(()),
269
        }
270
4.34k
    }
271
272
0
    pub(crate) const fn is_numeric(self) -> bool {
273
0
        !matches!(
274
0
            self,
275
            Token::Timescale
276
                | Token::Weekday
277
                | Token::WeekdayShort
278
                | Token::MonthName
279
                | Token::MonthNameShort
280
        )
281
0
    }
282
}