@@ -11,7 +11,7 @@ use super::scan;
1111use super :: { BAD_FORMAT , INVALID , OUT_OF_RANGE , TOO_LONG , TOO_SHORT } ;
1212use super :: { Fixed , InternalFixed , InternalInternal , Item , Numeric , Pad , Parsed } ;
1313use super :: { ParseError , ParseResult } ;
14- use crate :: { DateTime , FixedOffset , Weekday } ;
14+ use crate :: { DateTime , FixedOffset , MappedLocalTime , NaiveDate , NaiveTime , Weekday } ;
1515
1616fn set_weekday_with_num_days_from_sunday ( p : & mut Parsed , v : i64 ) -> ParseResult < ( ) > {
1717 p. set_weekday ( match v {
@@ -151,7 +151,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
151151 Ok ( ( s, ( ) ) )
152152}
153153
154- pub ( crate ) fn parse_rfc3339 < ' a > ( parsed : & mut Parsed , mut s : & ' a str ) -> ParseResult < ( & ' a str , ( ) ) > {
154+ pub ( crate ) fn parse_rfc3339 ( mut s : & str ) -> ParseResult < DateTime < FixedOffset > > {
155155 macro_rules! try_consume {
156156 ( $e: expr) => { {
157157 let ( s_, v) = $e?;
@@ -189,40 +189,81 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes
189189 //
190190 // - For readability a full-date and a full-time may be separated by a space character.
191191
192- parsed. set_year ( try_consume ! ( scan:: number( s, 4 , 4 ) ) ) ?;
193- s = scan:: char ( s, b'-' ) ?;
194- parsed. set_month ( try_consume ! ( scan:: number( s, 2 , 2 ) ) ) ?;
195- s = scan:: char ( s, b'-' ) ?;
196- parsed. set_day ( try_consume ! ( scan:: number( s, 2 , 2 ) ) ) ?;
192+ let bytes = s. as_bytes ( ) ;
193+ if bytes. len ( ) < 19 {
194+ return Err ( TOO_SHORT ) ;
195+ }
197196
198- s = match s. as_bytes ( ) . first ( ) {
199- Some ( & b't' | & b'T' | & b' ' ) => & s[ 1 ..] ,
200- Some ( _) => return Err ( INVALID ) ,
201- None => return Err ( TOO_SHORT ) ,
202- } ;
197+ let fixed = <& [ u8 ; 19 ] >:: try_from ( & bytes[ ..19 ] ) . unwrap ( ) ; // we just checked the length
198+ let year = digit ( fixed, 0 ) ? as u16 * 1000
199+ + digit ( fixed, 1 ) ? as u16 * 100
200+ + digit ( fixed, 2 ) ? as u16 * 10
201+ + digit ( fixed, 3 ) ? as u16 ;
202+ if bytes. get ( 4 ) != Some ( & b'-' ) {
203+ return Err ( INVALID ) ;
204+ }
203205
204- parsed. set_hour ( try_consume ! ( scan:: number( s, 2 , 2 ) ) ) ?;
205- s = scan:: char ( s, b':' ) ?;
206- parsed. set_minute ( try_consume ! ( scan:: number( s, 2 , 2 ) ) ) ?;
207- s = scan:: char ( s, b':' ) ?;
208- parsed. set_second ( try_consume ! ( scan:: number( s, 2 , 2 ) ) ) ?;
209- if s. starts_with ( '.' ) {
210- let nanosecond = try_consume ! ( scan:: nanosecond( & s[ 1 ..] ) ) ;
211- parsed. set_nanosecond ( nanosecond) ?;
206+ let month = digit ( fixed, 5 ) ? * 10 + digit ( fixed, 6 ) ?;
207+ if bytes. get ( 7 ) != Some ( & b'-' ) {
208+ return Err ( INVALID ) ;
212209 }
213210
214- let offset = try_consume ! ( scan:: timezone_offset( s, |s| scan:: char ( s, b':' ) , true , false , true ) ) ;
215- // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant.
216- // But it is possible to read the offset directly from `Parsed`. We want to only successfully
217- // populate `Parsed` if the input is fully valid RFC 3339.
211+ let day = digit ( fixed, 8 ) ? * 10 + digit ( fixed, 9 ) ?;
212+ let date =
213+ NaiveDate :: from_ymd_opt ( year as i32 , month as u32 , day as u32 ) . ok_or ( OUT_OF_RANGE ) ?;
214+
215+ if !matches ! ( bytes. get( 10 ) , Some ( & b't' | & b'T' | & b' ' ) ) {
216+ return Err ( INVALID ) ;
217+ }
218+
219+ let hour = digit ( fixed, 11 ) ? * 10 + digit ( fixed, 12 ) ?;
220+ if bytes. get ( 13 ) != Some ( & b':' ) {
221+ return Err ( INVALID ) ;
222+ }
223+
224+ let min = digit ( fixed, 14 ) ? * 10 + digit ( fixed, 15 ) ?;
225+ if bytes. get ( 16 ) != Some ( & b':' ) {
226+ return Err ( INVALID ) ;
227+ }
228+
229+ let sec = digit ( fixed, 17 ) ? * 10 + digit ( fixed, 18 ) ?;
230+ let ( sec, extra_nanos) = match sec {
231+ 60 => ( 59 , 1_000_000_000 ) , // rfc3339 allows leap seconds
232+ _ => ( sec, 0 ) ,
233+ } ;
234+
235+ let nano = if bytes. get ( 19 ) == Some ( & b'.' ) {
236+ let nanosecond = try_consume ! ( scan:: nanosecond( & s[ 20 ..] ) ) ;
237+ extra_nanos + nanosecond
238+ } else {
239+ s = & s[ 19 ..] ;
240+ extra_nanos
241+ } ;
242+
243+ let time = NaiveTime :: from_hms_nano_opt ( hour as u32 , min as u32 , sec as u32 , nano)
244+ . ok_or ( OUT_OF_RANGE ) ?;
245+
218246 // Max for the hours field is `23`, and for the minutes field `59`.
219- const MAX_RFC3339_OFFSET : i32 = ( 23 * 60 + 59 ) * 60 ;
220- if !( - MAX_RFC3339_OFFSET ..= MAX_RFC3339_OFFSET ) . contains ( & offset ) {
221- return Err ( OUT_OF_RANGE ) ;
247+ let offset = try_consume ! ( scan :: timezone_offset ( s , |s| scan :: char ( s , b':' ) , true , false , true ) ) ;
248+ if !s . is_empty ( ) {
249+ return Err ( TOO_LONG ) ;
222250 }
223- parsed. set_offset ( i64:: from ( offset) ) ?;
224251
225- Ok ( ( s, ( ) ) )
252+ let tz = FixedOffset :: east_opt ( offset) . ok_or ( OUT_OF_RANGE ) ?;
253+ Ok ( match date. and_time ( time) . and_local_timezone ( tz) {
254+ MappedLocalTime :: Single ( dt) => dt,
255+ // `FixedOffset::with_ymd_and_hms` doesn't return `MappedLocalTime::Ambiguous`
256+ // and returns `MappedLocalTime::None` on invalid data
257+ MappedLocalTime :: Ambiguous ( _, _) | MappedLocalTime :: None => unreachable ! ( ) ,
258+ } )
259+ }
260+
261+ #[ inline]
262+ fn digit ( bytes : & [ u8 ; 19 ] , index : usize ) -> ParseResult < u8 > {
263+ match bytes[ index] . is_ascii_digit ( ) {
264+ true => Ok ( bytes[ index] - b'0' ) ,
265+ false => Err ( INVALID ) ,
266+ }
226267}
227268
228269/// Tries to parse given string into `parsed` with given formatting items.
@@ -420,7 +461,7 @@ where
420461 & Nanosecond => {
421462 if s. starts_with ( '.' ) {
422463 let nano = try_consume ! ( scan:: nanosecond( & s[ 1 ..] ) ) ;
423- parsed. set_nanosecond ( nano) ?;
464+ parsed. set_nanosecond ( nano as i64 ) ?;
424465 }
425466 }
426467
@@ -1830,29 +1871,34 @@ mod tests {
18301871 "2015-01-20T17:35:20.000000000452−08:00" ,
18311872 Ok ( ymd_hmsn ( 2015 , 1 , 20 , 17 , 35 , 20 , 0 , -8 ) ) ,
18321873 ) , // too small with MINUS SIGN (U+2212)
1874+ ( "2023-11-05T01:30:00-04:00" , Ok ( ymd_hmsn ( 2023 , 11 , 5 , 1 , 30 , 0 , 0 , -4 ) ) ) , // ambiguous timestamp
18331875 ( "2015-01-20 17:35:20-08:00" , Ok ( ymd_hmsn ( 2015 , 1 , 20 , 17 , 35 , 20 , 0 , -8 ) ) ) , // without 'T'
1834- ( "2015/01/20T17:35:20.001-08:00" , Err ( INVALID ) ) , // wrong separator char YMD
1835- ( "2015-01-20T17-35-20.001-08:00" , Err ( INVALID ) ) , // wrong separator char HMS
1836- ( "-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // missing year
1837- ( "99-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year format
1838- ( "99999-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year value
1839- ( "-2000-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year value
1876+ ( "2015-01-20_17:35:20-08:00" , Err ( INVALID ) ) , // wrong date time separator
1877+ ( "2015/01/20T17:35:20.001-08:00" , Err ( INVALID ) ) , // wrong separator char YM
1878+ ( "2015-01/20T17:35:20.001-08:00" , Err ( INVALID ) ) , // wrong separator char MD
1879+ ( "2015-01-20T17-35-20.001-08:00" , Err ( INVALID ) ) , // wrong separator char HM
1880+ ( "2015-01-20T17-35:20.001-08:00" , Err ( INVALID ) ) , // wrong separator char MS
1881+ ( "-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // missing year
1882+ ( "99-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year format
1883+ ( "99999-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year value
1884+ ( "-2000-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year value
1885+ ( "2015-00-30T17:35:20-08:00" , Err ( OUT_OF_RANGE ) ) , // bad month value
18401886 ( "2015-02-30T17:35:20-08:00" , Err ( OUT_OF_RANGE ) ) , // bad day of month value
18411887 ( "2015-01-20T25:35:20-08:00" , Err ( OUT_OF_RANGE ) ) , // bad hour value
18421888 ( "2015-01-20T17:65:20-08:00" , Err ( OUT_OF_RANGE ) ) , // bad minute value
18431889 ( "2015-01-20T17:35:90-08:00" , Err ( OUT_OF_RANGE ) ) , // bad second value
18441890 ( "2015-01-20T17:35:20-24:00" , Err ( OUT_OF_RANGE ) ) , // bad offset value
1845- ( "15-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year format
1846- ( "15-01-20T17:35:20-08:00:00" , Err ( INVALID ) ) , // bad year format, bad offset format
1847- ( "2015-01-20T17:35:2008:00" , Err ( INVALID ) ) , // missing offset sign
1848- ( "2015-01-20T17:35:20 08:00" , Err ( INVALID ) ) , // missing offset sign
1849- ( "2015-01-20T17:35:20Zulu" , Err ( TOO_LONG ) ) , // bad offset format
1850- ( "2015-01-20T17:35:20 Zulu" , Err ( INVALID ) ) , // bad offset format
1851- ( "2015-01-20T17:35:20GMT" , Err ( INVALID ) ) , // bad offset format
1852- ( "2015-01-20T17:35:20 GMT" , Err ( INVALID ) ) , // bad offset format
1853- ( "2015-01-20T17:35:20+GMT" , Err ( INVALID ) ) , // bad offset format
1854- ( "2015-01-20T17:35:20++08:00" , Err ( INVALID ) ) , // bad offset format
1855- ( "2015-01-20T17:35:20--08:00" , Err ( INVALID ) ) , // bad offset format
1891+ ( "15-01-20T17:35:20-08:00" , Err ( INVALID ) ) , // bad year format
1892+ ( "15-01-20T17:35:20-08:00:00" , Err ( INVALID ) ) , // bad year format, bad offset format
1893+ ( "2015-01-20T17:35:2008:00" , Err ( INVALID ) ) , // missing offset sign
1894+ ( "2015-01-20T17:35:20 08:00" , Err ( INVALID ) ) , // missing offset sign
1895+ ( "2015-01-20T17:35:20Zulu" , Err ( TOO_LONG ) ) , // bad offset format
1896+ ( "2015-01-20T17:35:20 Zulu" , Err ( INVALID ) ) , // bad offset format
1897+ ( "2015-01-20T17:35:20GMT" , Err ( INVALID ) ) , // bad offset format
1898+ ( "2015-01-20T17:35:20 GMT" , Err ( INVALID ) ) , // bad offset format
1899+ ( "2015-01-20T17:35:20+GMT" , Err ( INVALID ) ) , // bad offset format
1900+ ( "2015-01-20T17:35:20++08:00" , Err ( INVALID ) ) , // bad offset format
1901+ ( "2015-01-20T17:35:20--08:00" , Err ( INVALID ) ) , // bad offset format
18561902 ( "2015-01-20T17:35:20−−08:00" , Err ( INVALID ) ) , // bad offset format with MINUS SIGN (U+2212)
18571903 ( "2015-01-20T17:35:20±08:00" , Err ( INVALID ) ) , // bad offset sign
18581904 ( "2015-01-20T17:35:20-08-00" , Err ( INVALID ) ) , // bad offset separator
0 commit comments