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