@@ -195,6 +195,7 @@ pub struct StrftimeItems<'a> {
195195 /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
196196 /// `queue` stores a slice of `Item`s that have to be returned one by one.
197197 queue : & ' static [ Item < ' static > ] ,
198+ lenient : bool ,
198199 #[ cfg( feature = "unstable-locales" ) ]
199200 locale_str : & ' a str ,
200201 #[ cfg( feature = "unstable-locales" ) ]
@@ -227,15 +228,47 @@ impl<'a> StrftimeItems<'a> {
227228 /// ```
228229 #[ must_use]
229230 pub const fn new ( s : & ' a str ) -> StrftimeItems < ' a > {
230- {
231- StrftimeItems {
232- remainder : s,
233- queue : & [ ] ,
234- #[ cfg( feature = "unstable-locales" ) ]
235- locale_str : "" ,
236- #[ cfg( feature = "unstable-locales" ) ]
237- locale : None ,
238- }
231+ StrftimeItems {
232+ remainder : s,
233+ queue : & [ ] ,
234+ lenient : false ,
235+ #[ cfg( feature = "unstable-locales" ) ]
236+ locale_str : "" ,
237+ #[ cfg( feature = "unstable-locales" ) ]
238+ locale : None ,
239+ }
240+ }
241+
242+ /// The same as [`StrftimeItems::new`], but returns [`Item::Literal`] instead of [`Item::Error`].
243+ ///
244+ /// Useful for formatting according to potentially invalid format strings.
245+ ///
246+ /// # Example
247+ ///
248+ /// ```
249+ /// use chrono::format::*;
250+ ///
251+ /// let strftime_parser = StrftimeItems::new_lenient("%Y-%Q"); // %Y: year, %Q: invalid
252+ ///
253+ /// const ITEMS: &[Item<'static>] = &[
254+ /// Item::Numeric(Numeric::Year, Pad::Zero),
255+ /// Item::Literal("-"),
256+ /// Item::Literal("%"),
257+ /// Item::Literal("Q"),
258+ /// ];
259+ /// println!("{:?}", strftime_parser.clone().collect::<Vec<_>>());
260+ /// assert!(strftime_parser.eq(ITEMS.iter().cloned()));
261+ /// ```
262+ #[ must_use]
263+ pub const fn new_lenient ( s : & ' a str ) -> StrftimeItems < ' a > {
264+ StrftimeItems {
265+ remainder : s,
266+ queue : & [ ] ,
267+ lenient : true ,
268+ #[ cfg( feature = "unstable-locales" ) ]
269+ locale_str : "" ,
270+ #[ cfg( feature = "unstable-locales" ) ]
271+ locale : None ,
239272 }
240273 }
241274
@@ -288,7 +321,13 @@ impl<'a> StrftimeItems<'a> {
288321 #[ cfg( feature = "unstable-locales" ) ]
289322 #[ must_use]
290323 pub const fn new_with_locale ( s : & ' a str , locale : Locale ) -> StrftimeItems < ' a > {
291- StrftimeItems { remainder : s, queue : & [ ] , locale_str : "" , locale : Some ( locale) }
324+ StrftimeItems {
325+ remainder : s,
326+ queue : & [ ] ,
327+ lenient : false ,
328+ locale_str : "" ,
329+ locale : Some ( locale) ,
330+ }
292331 }
293332
294333 /// Parse format string into a `Vec` of formatting [`Item`]'s.
@@ -310,7 +349,7 @@ impl<'a> StrftimeItems<'a> {
310349 /// # Errors
311350 ///
312351 /// Returns an error if the format string contains an invalid or unrecognized formatting
313- /// specifier.
352+ /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient] .
314353 ///
315354 /// # Example
316355 ///
@@ -354,7 +393,7 @@ impl<'a> StrftimeItems<'a> {
354393 /// # Errors
355394 ///
356395 /// Returns an error if the format string contains an invalid or unrecognized formatting
357- /// specifier.
396+ /// specifier and the [`StrftimeItems`] wasn't constructed with [`new_lenient`][Self::new_lenient] .
358397 ///
359398 /// # Example
360399 ///
@@ -416,6 +455,22 @@ impl<'a> Iterator for StrftimeItems<'a> {
416455}
417456
418457impl < ' a > StrftimeItems < ' a > {
458+ fn error < ' b > (
459+ & mut self ,
460+ original : & ' b str ,
461+ error_len : & mut usize ,
462+ ch : Option < char > ,
463+ ) -> ( & ' b str , Item < ' b > ) {
464+ if !self . lenient {
465+ return ( & original[ * error_len..] , Item :: Error ) ;
466+ }
467+
468+ if let Some ( c) = ch {
469+ * error_len -= c. len_utf8 ( ) ;
470+ }
471+ ( & original[ * error_len..] , Item :: Literal ( & original[ ..* error_len] ) )
472+ }
473+
419474 fn parse_next_item ( & mut self , mut remainder : & ' a str ) -> Option < ( & ' a str , Item < ' a > ) > {
420475 use InternalInternal :: * ;
421476 use Item :: { Literal , Space } ;
@@ -456,16 +511,24 @@ impl<'a> StrftimeItems<'a> {
456511
457512 // the next item is a specifier
458513 Some ( '%' ) => {
514+ let original = remainder;
459515 remainder = & remainder[ 1 ..] ;
516+ let mut error_len = 0 ;
517+ if self . lenient {
518+ error_len += 1 ;
519+ }
460520
461521 macro_rules! next {
462522 ( ) => {
463523 match remainder. chars( ) . next( ) {
464524 Some ( x) => {
465525 remainder = & remainder[ x. len_utf8( ) ..] ;
526+ if self . lenient {
527+ error_len += x. len_utf8( ) ;
528+ }
466529 x
467530 }
468- None => return Some ( ( remainder , Item :: Error ) ) , // premature end of string
531+ None => return Some ( self . error ( original , & mut error_len , None ) ) , // premature end of string
469532 }
470533 } ;
471534 }
@@ -480,7 +543,7 @@ impl<'a> StrftimeItems<'a> {
480543 let is_alternate = spec == '#' ;
481544 let spec = if pad_override. is_some ( ) || is_alternate { next ! ( ) } else { spec } ;
482545 if is_alternate && !HAVE_ALTERNATES . contains ( spec) {
483- return Some ( ( remainder , Item :: Error ) ) ;
546+ return Some ( self . error ( original , & mut error_len , Some ( spec ) ) ) ;
484547 }
485548
486549 macro_rules! queue {
@@ -592,39 +655,71 @@ impl<'a> StrftimeItems<'a> {
592655 remainder = & remainder[ 1 ..] ;
593656 fixed ( Fixed :: TimezoneOffsetColon )
594657 } else {
595- Item :: Error
658+ self . error ( original , & mut error_len , None ) . 1
596659 }
597660 }
598661 '.' => match next ! ( ) {
599662 '3' => match next ! ( ) {
600663 'f' => fixed ( Fixed :: Nanosecond3 ) ,
601- _ => Item :: Error ,
664+ c => {
665+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
666+ remainder = res. 0 ;
667+ res. 1
668+ }
602669 } ,
603670 '6' => match next ! ( ) {
604671 'f' => fixed ( Fixed :: Nanosecond6 ) ,
605- _ => Item :: Error ,
672+ c => {
673+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
674+ remainder = res. 0 ;
675+ res. 1
676+ }
606677 } ,
607678 '9' => match next ! ( ) {
608679 'f' => fixed ( Fixed :: Nanosecond9 ) ,
609- _ => Item :: Error ,
680+ c => {
681+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
682+ remainder = res. 0 ;
683+ res. 1
684+ }
610685 } ,
611686 'f' => fixed ( Fixed :: Nanosecond ) ,
612- _ => Item :: Error ,
687+ c => {
688+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
689+ remainder = res. 0 ;
690+ res. 1
691+ }
613692 } ,
614693 '3' => match next ! ( ) {
615694 'f' => internal_fixed ( Nanosecond3NoDot ) ,
616- _ => Item :: Error ,
695+ c => {
696+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
697+ remainder = res. 0 ;
698+ res. 1
699+ }
617700 } ,
618701 '6' => match next ! ( ) {
619702 'f' => internal_fixed ( Nanosecond6NoDot ) ,
620- _ => Item :: Error ,
703+ c => {
704+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
705+ remainder = res. 0 ;
706+ res. 1
707+ }
621708 } ,
622709 '9' => match next ! ( ) {
623710 'f' => internal_fixed ( Nanosecond9NoDot ) ,
624- _ => Item :: Error ,
711+ c => {
712+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
713+ remainder = res. 0 ;
714+ res. 1
715+ }
625716 } ,
626717 '%' => Literal ( "%" ) ,
627- _ => Item :: Error , // no such specifier
718+ c => {
719+ let res = self . error ( original, & mut error_len, Some ( c) ) ;
720+ remainder = res. 0 ;
721+ res. 1
722+ }
628723 } ;
629724
630725 // Adjust `item` if we have any padding modifier.
@@ -635,7 +730,7 @@ impl<'a> StrftimeItems<'a> {
635730 Item :: Numeric ( ref kind, _pad) if self . queue . is_empty ( ) => {
636731 Some ( ( remainder, Item :: Numeric ( kind. clone ( ) , new_pad) ) )
637732 }
638- _ => Some ( ( remainder , Item :: Error ) ) ,
733+ _ => Some ( self . error ( original , & mut error_len , None ) ) ,
639734 }
640735 } else {
641736 Some ( ( remainder, item) )
@@ -1139,4 +1234,16 @@ mod tests {
11391234 let dt = Utc . with_ymd_and_hms ( 2014 , 5 , 7 , 12 , 34 , 56 ) . unwrap ( ) ;
11401235 assert_eq ! ( & dt. format_with_items( fmt_items. iter( ) ) . to_string( ) , "2014-05-07T12:34:56+0000" ) ;
11411236 }
1237+
1238+ #[ test]
1239+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
1240+ fn test_strftime_parse_lenient ( ) {
1241+ let fmt_str = StrftimeItems :: new_lenient ( "%Y-%m-%dT%H:%M:%S%z%Q%.2f%%%" ) ;
1242+ let fmt_items = fmt_str. parse ( ) . unwrap ( ) ;
1243+ let dt = Utc . with_ymd_and_hms ( 2014 , 5 , 7 , 12 , 34 , 56 ) . unwrap ( ) ;
1244+ assert_eq ! (
1245+ & dt. format_with_items( fmt_items. iter( ) ) . to_string( ) ,
1246+ "2014-05-07T12:34:56+0000%Q%.2f%%"
1247+ ) ;
1248+ }
11421249}
0 commit comments