1 //! Formatting and parsing for well-known formats (typically RFCs).
2 
3 use crate::{
4     format::{
5         date,
6         parse::{
7             try_consume_char, try_consume_char_case_insensitive, try_consume_exact_digits,
8             try_consume_first_match,
9         },
10         time, Padding, ParsedItems,
11     },
12     DeferredFormat, ParseResult,
13 };
14 #[cfg(not(feature = "std"))]
15 use alloc::string::String;
16 use core::fmt::Formatter;
17 #[allow(unused_imports)]
18 use standback::prelude::*;
19 
20 /// The format as specified by RFC3339.
21 pub(crate) mod rfc3339 {
22     use super::*;
23     use crate::{error, UtcOffset};
24 
25     /// Format `df` according to the RFC3339 specification.
fmt(df: &DeferredFormat, f: &mut Formatter<'_>) -> Result<(), error::Format>26     pub(crate) fn fmt(df: &DeferredFormat, f: &mut Formatter<'_>) -> Result<(), error::Format> {
27         let (date, time, offset) = match (df.date(), df.time(), df.offset()) {
28             (Some(date), Some(time), Some(offset)) => (date, time, offset),
29             _ => return Err(error::Format::InsufficientTypeInformation),
30         };
31 
32         date::fmt_Y(f, date, Padding::Zero)?;
33         f.write_str("-")?;
34         date::fmt_m(f, date, Padding::Zero)?;
35         f.write_str("-")?;
36         date::fmt_d(f, date, Padding::Zero)?;
37         f.write_str("T")?;
38         time::fmt_H(f, time, Padding::Zero)?;
39         f.write_str(":")?;
40         time::fmt_M(f, time, Padding::Zero)?;
41         f.write_str(":")?;
42         time::fmt_S(f, time, Padding::Zero)?;
43         write!(
44             f,
45             "{:+03}:{:02}",
46             offset.as_hours(),
47             offset.as_minutes().rem_euclid(60)
48         )?;
49 
50         Ok(())
51     }
52 
53     /// Parse `s` as specified by RFC3339.
parse(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>54     pub(crate) fn parse(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
55         items.year = try_consume_exact_digits::<i32>(s, 4, Padding::None)
56             .ok_or(error::Parse::InvalidYear)?
57             .into();
58         try_consume_char(s, '-')?;
59         date::parse_m(items, s, Padding::Zero)?;
60         try_consume_char(s, '-')?;
61         date::parse_d(items, s, Padding::Zero)?;
62         try_consume_char_case_insensitive(s, 'T')?;
63         time::parse_H(items, s, Padding::Zero)?;
64         try_consume_char(s, ':')?;
65         time::parse_M(items, s, Padding::Zero)?;
66         try_consume_char(s, ':')?;
67         time::parse_S(items, s, Padding::Zero)?;
68 
69         if try_consume_char(s, '.').is_ok() {
70             let num_digits = s.chars().take_while(char::is_ascii_digit).count();
71             if num_digits == 0 {
72                 return Err(error::Parse::InvalidNanosecond);
73             }
74             let num_digits_used = core::cmp::min(num_digits, 9);
75 
76             let nanos_raw: String = s.chars().take(num_digits_used).collect();
77             // At most 9 decimal digits will always fit in a u32.
78             // `num_digits_used` is at most 9, which can safely be cast.
79             #[allow(clippy::unwrap_used)]
80             let nanos = nanos_raw.parse::<u32>().unwrap() * 10_u32.pow(9 - num_digits_used as u32);
81             items.nanosecond = Some(nanos);
82             *s = &s[num_digits..];
83         }
84 
85         if try_consume_char_case_insensitive(s, 'Z').is_ok() {
86             items.offset = Some(UtcOffset::UTC);
87         } else {
88             let offset_sign =
89                 match try_consume_first_match(s, [("+", 1), ("-", -1)].iter().cloned()) {
90                     Some(sign) => sign,
91                     None => {
92                         return Err(match s.chars().next() {
93                             Some(actual) => error::Parse::UnexpectedCharacter {
94                                 actual,
95                                 expected: '+',
96                             },
97                             None => error::Parse::UnexpectedEndOfString,
98                         })
99                     }
100                 };
101             let offset_hour: i16 =
102                 try_consume_exact_digits(s, 2, Padding::Zero).ok_or(error::Parse::InvalidOffset)?;
103             try_consume_char(s, ':')?;
104             let offset_minute: i16 =
105                 try_consume_exact_digits(s, 2, Padding::Zero).ok_or(error::Parse::InvalidOffset)?;
106             items.offset = Some(UtcOffset::minutes(
107                 offset_sign * (offset_hour * 60 + offset_minute),
108             ));
109         }
110 
111         Ok(())
112     }
113 }
114