Coverage Report

Created: 2026-07-13 08:11

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