time/parsing/
iso8601.rs

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