Coverage Report

Created: 2025-10-21 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/time-0.3.44/src/parsing/iso8601.rs
Line
Count
Source
1
//! Parse parts of an ISO 8601-formatted value.
2
3
#[allow(unused_imports, reason = "MSRV of 1.87")]
4
use num_conv::prelude::*;
5
6
use crate::convert::*;
7
use crate::error;
8
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
9
use crate::format_description::well_known::iso8601::EncodedConfig;
10
use crate::format_description::well_known::Iso8601;
11
use crate::parsing::combinator::rfc::iso8601::{
12
    day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
13
};
14
use crate::parsing::combinator::{ascii_char, sign};
15
use crate::parsing::{Parsed, ParsedItem};
16
17
impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
18
    // Basic: [year][month][day]
19
    // Extended: [year]["-"][month]["-"][day]
20
    // Basic: [year][dayo]
21
    // Extended: [year]["-"][dayo]
22
    // Basic: [year]["W"][week][dayk]
23
    // Extended: [year]["-"]["W"][week]["-"][dayk]
24
    /// Parse a date in the basic or extended format. Reduced precision is permitted.
25
0
    pub(crate) fn parse_date<'a>(
26
0
        parsed: &'a mut Parsed,
27
0
        extended_kind: &'a mut ExtendedKind,
28
0
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
29
0
        move |input| {
30
            // Same for any acceptable format.
31
0
            let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?;
32
0
            *extended_kind = match ascii_char::<b'-'>(input) {
33
0
                Some(ParsedItem(new_input, ())) => {
34
0
                    input = new_input;
35
0
                    ExtendedKind::Extended
36
                }
37
0
                None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
38
            };
39
40
0
            let parsed_month_day = (|| {
41
0
                let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
42
0
                if extended_kind.is_extended() {
43
0
                    input = ascii_char::<b'-'>(input)
44
0
                        .ok_or(InvalidLiteral)?
45
0
                        .into_inner();
46
0
                }
47
0
                let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
48
0
                Ok(ParsedItem(input, (month, day)))
49
            })();
50
0
            let mut ret_error = match parsed_month_day {
51
0
                Ok(ParsedItem(input, (month, day))) => {
52
0
                    *parsed = parsed
53
0
                        .with_year(year)
54
0
                        .ok_or(InvalidComponent("year"))?
55
0
                        .with_month(month)
56
0
                        .ok_or(InvalidComponent("month"))?
57
0
                        .with_day(day)
58
0
                        .ok_or(InvalidComponent("day"))?;
59
0
                    return Ok(input);
60
                }
61
0
                Err(err) => err,
62
            };
63
64
            // Don't check for `None`, as the error from year-month-day will always take priority.
65
0
            if let Some(ParsedItem(input, ordinal)) = dayo(input) {
66
0
                *parsed = parsed
67
0
                    .with_year(year)
68
0
                    .ok_or(InvalidComponent("year"))?
69
0
                    .with_ordinal(ordinal)
70
0
                    .ok_or(InvalidComponent("ordinal"))?;
71
0
                return Ok(input);
72
0
            }
73
74
0
            let parsed_week_weekday = (|| {
75
0
                let input = ascii_char::<b'W'>(input)
76
0
                    .ok_or((false, InvalidLiteral))?
77
0
                    .into_inner();
78
0
                let ParsedItem(mut input, week) =
79
0
                    week(input).ok_or((true, InvalidComponent("week")))?;
80
0
                if extended_kind.is_extended() {
81
0
                    input = ascii_char::<b'-'>(input)
82
0
                        .ok_or((true, InvalidLiteral))?
83
0
                        .into_inner();
84
0
                }
85
0
                let ParsedItem(input, weekday) =
86
0
                    dayk(input).ok_or((true, InvalidComponent("weekday")))?;
87
0
                Ok(ParsedItem(input, (week, weekday)))
88
            })();
89
0
            match parsed_week_weekday {
90
0
                Ok(ParsedItem(input, (week, weekday))) => {
91
0
                    *parsed = parsed
92
0
                        .with_iso_year(year)
93
0
                        .ok_or(InvalidComponent("year"))?
94
0
                        .with_iso_week_number(week)
95
0
                        .ok_or(InvalidComponent("week"))?
96
0
                        .with_weekday(weekday)
97
0
                        .ok_or(InvalidComponent("weekday"))?;
98
0
                    return Ok(input);
99
                }
100
0
                Err((false, _err)) => {}
101
                // This error is more accurate than the one from year-month-day.
102
0
                Err((true, err)) => ret_error = err,
103
            }
104
105
0
            Err(ret_error.into())
106
0
        }
107
0
    }
108
109
    // Basic: ["T"][hour][min][sec]
110
    // Extended: ["T"][hour][":"][min][":"][sec]
111
    // Reduced precision: components after [hour] (including their preceding separator) can be
112
    // omitted. ["T"] can be omitted if there is no date present.
113
    /// Parse a time in the basic or extended format. Reduced precision is permitted.
114
0
    pub(crate) fn parse_time<'a>(
115
0
        parsed: &'a mut Parsed,
116
0
        extended_kind: &'a mut ExtendedKind,
117
0
        date_is_present: bool,
118
0
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
119
0
        move |mut input| {
120
0
            if date_is_present {
121
0
                input = ascii_char::<b'T'>(input)
122
0
                    .ok_or(InvalidLiteral)?
123
0
                    .into_inner();
124
0
            }
125
126
0
            let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
127
0
            match hour {
128
0
                (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
129
0
                (hour, Some(fractional_part)) => {
130
0
                    *parsed = parsed
131
0
                        .with_hour_24(hour)
132
0
                        .ok_or(InvalidComponent("hour"))?
133
0
                        .with_minute((fractional_part * Second::per_t::<f64>(Minute)) as u8)
134
0
                        .ok_or(InvalidComponent("minute"))?
135
0
                        .with_second(
136
0
                            (fractional_part * Second::per_t::<f64>(Hour)
137
0
                                % Minute::per_t::<f64>(Hour)) as u8,
138
                        )
139
0
                        .ok_or(InvalidComponent("second"))?
140
0
                        .with_subsecond(
141
0
                            (fractional_part * Nanosecond::per_t::<f64>(Hour)
142
0
                                % Nanosecond::per_t::<f64>(Second))
143
0
                                as u32,
144
                        )
145
0
                        .ok_or(InvalidComponent("subsecond"))?;
146
0
                    return Ok(input);
147
                }
148
            };
149
150
0
            if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
151
0
                extended_kind
152
0
                    .coerce_extended()
153
0
                    .ok_or(InvalidComponent("minute"))?;
154
0
                input = new_input;
155
0
            };
156
157
0
            let mut input = match float(input) {
158
0
                Some(ParsedItem(input, (minute, None))) => {
159
0
                    extended_kind.coerce_basic();
160
0
                    parsed
161
0
                        .set_minute(minute)
162
0
                        .ok_or(InvalidComponent("minute"))?;
163
0
                    input
164
                }
165
0
                Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
166
                    // `None` is valid behavior, so don't error if this fails.
167
0
                    extended_kind.coerce_basic();
168
0
                    *parsed = parsed
169
0
                        .with_minute(minute)
170
0
                        .ok_or(InvalidComponent("minute"))?
171
0
                        .with_second((fractional_part * Second::per_t::<f64>(Minute)) as u8)
172
0
                        .ok_or(InvalidComponent("second"))?
173
0
                        .with_subsecond(
174
0
                            (fractional_part * Nanosecond::per_t::<f64>(Minute)
175
0
                                % Nanosecond::per_t::<f64>(Second))
176
0
                                as u32,
177
                        )
178
0
                        .ok_or(InvalidComponent("subsecond"))?;
179
0
                    return Ok(input);
180
                }
181
                // colon was present, so minutes are required
182
0
                None if extended_kind.is_extended() => {
183
0
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
184
0
                        "minute",
185
0
                    )));
186
                }
187
                None => {
188
                    // Missing components are assumed to be zero.
189
0
                    *parsed = parsed
190
0
                        .with_minute(0)
191
0
                        .ok_or(InvalidComponent("minute"))?
192
0
                        .with_second(0)
193
0
                        .ok_or(InvalidComponent("second"))?
194
0
                        .with_subsecond(0)
195
0
                        .ok_or(InvalidComponent("subsecond"))?;
196
0
                    return Ok(input);
197
                }
198
            };
199
200
0
            if extended_kind.is_extended() {
201
0
                match ascii_char::<b':'>(input) {
202
0
                    Some(ParsedItem(new_input, ())) => input = new_input,
203
                    None => {
204
0
                        *parsed = parsed
205
0
                            .with_second(0)
206
0
                            .ok_or(InvalidComponent("second"))?
207
0
                            .with_subsecond(0)
208
0
                            .ok_or(InvalidComponent("subsecond"))?;
209
0
                        return Ok(input);
210
                    }
211
                }
212
0
            }
213
214
0
            let (input, second, subsecond) = match float(input) {
215
0
                Some(ParsedItem(input, (second, None))) => (input, second, 0),
216
0
                Some(ParsedItem(input, (second, Some(fractional_part)))) => (
217
0
                    input,
218
0
                    second,
219
0
                    round(fractional_part * Nanosecond::per_t::<f64>(Second)) as u32,
220
0
                ),
221
0
                None if extended_kind.is_extended() => {
222
0
                    return Err(error::Parse::ParseFromDescription(InvalidComponent(
223
0
                        "second",
224
0
                    )));
225
                }
226
                // Missing components are assumed to be zero.
227
0
                None => (input, 0, 0),
228
            };
229
0
            *parsed = parsed
230
0
                .with_second(second)
231
0
                .ok_or(InvalidComponent("second"))?
232
0
                .with_subsecond(subsecond)
233
0
                .ok_or(InvalidComponent("subsecond"))?;
234
235
0
            Ok(input)
236
0
        }
237
0
    }
238
239
    // Basic: [±][hour][min] or ["Z"]
240
    // Extended: [±][hour][":"][min] or ["Z"]
241
    // Reduced precision: [±][hour] or ["Z"]
242
    /// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
243
0
    pub(crate) fn parse_offset<'a>(
244
0
        parsed: &'a mut Parsed,
245
0
        extended_kind: &'a mut ExtendedKind,
246
0
    ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
247
0
        move |input| {
248
0
            if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
249
0
                *parsed = parsed
250
0
                    .with_offset_hour(0)
251
0
                    .ok_or(InvalidComponent("offset hour"))?
252
0
                    .with_offset_minute_signed(0)
253
0
                    .ok_or(InvalidComponent("offset minute"))?
254
0
                    .with_offset_second_signed(0)
255
0
                    .ok_or(InvalidComponent("offset second"))?;
256
0
                return Ok(input);
257
0
            }
258
259
0
            let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
260
0
            let mut input = hour(input)
261
0
                .and_then(|parsed_item| {
262
0
                    parsed_item.consume_value(|hour| {
263
0
                        parsed.set_offset_hour(if sign == b'-' {
264
0
                            -hour.cast_signed()
265
                        } else {
266
0
                            hour.cast_signed()
267
                        })
268
0
                    })
269
0
                })
270
0
                .ok_or(InvalidComponent("offset hour"))?;
271
272
0
            if extended_kind.maybe_extended() {
273
0
                if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
274
0
                    extended_kind
275
0
                        .coerce_extended()
276
0
                        .ok_or(InvalidComponent("offset minute"))?;
277
0
                    input = new_input;
278
0
                };
279
0
            }
280
281
0
            match min(input) {
282
0
                Some(ParsedItem(new_input, min)) => {
283
0
                    input = new_input;
284
0
                    parsed
285
0
                        .set_offset_minute_signed(if sign == b'-' {
286
0
                            -min.cast_signed()
287
                        } else {
288
0
                            min.cast_signed()
289
                        })
290
0
                        .ok_or(InvalidComponent("offset minute"))?;
291
                }
292
0
                None => {
293
0
                    // Omitted offset minute is assumed to be zero.
294
0
                    parsed.set_offset_minute_signed(0);
295
0
                }
296
            }
297
298
            // If `:` was present, the format has already been set to extended. As such, this call
299
            // will do nothing in that case. If there wasn't `:` but minutes were
300
            // present, we know it's the basic format. Do not use `?` on the call, as
301
            // returning `None` is valid behavior.
302
0
            extended_kind.coerce_basic();
303
304
0
            Ok(input)
305
0
        }
306
0
    }
307
}
308
309
/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
310
/// implementation for `no_std`
311
#[inline]
312
0
fn round(value: f64) -> f64 {
313
    #[cfg(feature = "std")]
314
    {
315
0
        value.round()
316
    }
317
    #[cfg(not(feature = "std"))]
318
    {
319
        debug_assert!(value.is_sign_positive() && !value.is_nan());
320
321
        let f = value % 1.;
322
        if f < 0.5 {
323
            value - f
324
        } else {
325
            value - f + 1.
326
        }
327
    }
328
0
}