/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 | } |