@@ -95,7 +95,7 @@ impl Timestamp {
9595 /// The maximum length of a timestamp in bytes
9696 pub const MAX_LENGTH : usize = 19 ;
9797
98- const SEPARATORS : [ u8 ; 3 ] = [ b'-' , b'T' , b':' ] ;
98+ const SEPARATORS : [ u8 ; 4 ] = [ b'-' , b'. ', b'T' , b':' ] ;
9999
100100 /// Read a [`Timestamp`]
101101 ///
@@ -178,23 +178,29 @@ impl Timestamp {
178178 loop {
179179 timestamp. month = read_segment ! ( Self :: segment:: <2 >(
180180 reader,
181- timestamp_contains_separators. then_some( b'-' ) ,
181+ timestamp_contains_separators. then_some( & Self :: SEPARATORS [ .. 2 ] ) ,
182182 parse_mode
183183 ) ) ;
184184 timestamp. day = read_segment ! ( Self :: segment:: <2 >(
185185 reader,
186- timestamp_contains_separators. then_some( b'-' ) ,
186+ timestamp_contains_separators. then_some( & Self :: SEPARATORS [ ..2 ] ) ,
187+ parse_mode
188+ ) ) ;
189+ timestamp. hour = read_segment ! ( Self :: segment:: <2 >(
190+ reader,
191+ Some ( core:: slice:: from_ref( & Self :: SEPARATORS [ 2 ] ) ) ,
187192 parse_mode
188193 ) ) ;
189- timestamp. hour = read_segment ! ( Self :: segment:: <2 >( reader, Some ( b'T' ) , parse_mode) ) ;
190194 timestamp. minute = read_segment ! ( Self :: segment:: <2 >(
191195 reader,
192- timestamp_contains_separators. then_some( b':' ) ,
196+ timestamp_contains_separators
197+ . then_some( core:: slice:: from_ref( & Self :: SEPARATORS [ 3 ] ) ) ,
193198 parse_mode
194199 ) ) ;
195200 timestamp. second = read_segment ! ( Self :: segment:: <2 >(
196201 reader,
197- timestamp_contains_separators. then_some( b':' ) ,
202+ timestamp_contains_separators
203+ . then_some( core:: slice:: from_ref( & Self :: SEPARATORS [ 3 ] ) ) ,
198204 parse_mode
199205 ) ) ;
200206 break ;
@@ -205,7 +211,7 @@ impl Timestamp {
205211
206212 fn segment < const SIZE : usize > (
207213 content : & mut & [ u8 ] ,
208- sep : Option < u8 > ,
214+ sep : Option < & [ u8 ] > ,
209215 parse_mode : ParsingMode ,
210216 ) -> Result < ( u16 , usize ) > {
211217 const STOP_PARSING : ( u16 , usize ) = ( 0 , 0 ) ;
@@ -216,11 +222,20 @@ impl Timestamp {
216222
217223 if let Some ( sep) = sep {
218224 let byte = content. read_u8 ( ) ?;
219- if byte != sep {
220- if parse_mode == ParsingMode :: Strict {
221- err ! ( BadTimestamp ( "Expected a separator" ) )
222- }
223- return Ok ( STOP_PARSING ) ;
225+ match sep. iter ( ) . position ( |s| * s == byte) {
226+ // The first separator in the list is the only *strictly* valid one (by ISO 8601 standards).
227+ // Some encoders may prefer other separators, which are harmless to pass through in
228+ // other modes.
229+ Some ( pos) if pos > 0 && parse_mode == ParsingMode :: Strict => {
230+ err ! ( BadTimestamp ( "Unexpected separator" ) )
231+ } ,
232+ Some ( _) => { } ,
233+ None => {
234+ if parse_mode == ParsingMode :: Strict {
235+ err ! ( BadTimestamp ( "Expected a separator" ) )
236+ }
237+ return Ok ( STOP_PARSING ) ;
238+ } ,
224239 }
225240 }
226241
@@ -626,4 +641,25 @@ mod tests {
626641 } )
627642 ) ;
628643 }
644+
645+ #[ test_log:: test]
646+ fn timestamp_dot_separators ( ) {
647+ let timestamp = "2024.06.03" ;
648+
649+ let parsed_timestamp_strict =
650+ Timestamp :: parse ( & mut timestamp. as_bytes ( ) , ParsingMode :: Strict ) ;
651+ assert ! ( parsed_timestamp_strict. is_err( ) ) ;
652+
653+ let parsed_timestamp_best_attempt =
654+ Timestamp :: parse ( & mut timestamp. as_bytes ( ) , ParsingMode :: BestAttempt ) . unwrap ( ) ;
655+ assert_eq ! (
656+ parsed_timestamp_best_attempt,
657+ Some ( Timestamp {
658+ year: 2024 ,
659+ month: Some ( 6 ) ,
660+ day: Some ( 3 ) ,
661+ ..Timestamp :: default ( )
662+ } )
663+ ) ;
664+ }
629665}
0 commit comments