time/parsing/
iso8601.rs

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