@@ -195,6 +195,7 @@ pub struct StrftimeItems<'a> {
195
195
/// If the current specifier is composed of multiple formatting items (e.g. `%+`),
196
196
/// `queue` stores a slice of `Item`s that have to be returned one by one.
197
197
queue : & ' static [ Item < ' static > ] ,
198
+ lenient : bool ,
198
199
#[ cfg( feature = "unstable-locales" ) ]
199
200
locale_str : & ' a str ,
200
201
#[ cfg( feature = "unstable-locales" ) ]
@@ -227,15 +228,47 @@ impl<'a> StrftimeItems<'a> {
227
228
/// ```
228
229
#[ must_use]
229
230
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 ,
239
272
}
240
273
}
241
274
@@ -288,7 +321,13 @@ impl<'a> StrftimeItems<'a> {
288
321
#[ cfg( feature = "unstable-locales" ) ]
289
322
#[ must_use]
290
323
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
+ }
292
331
}
293
332
294
333
/// Parse format string into a `Vec` of formatting [`Item`]'s.
@@ -310,7 +349,7 @@ impl<'a> StrftimeItems<'a> {
310
349
/// # Errors
311
350
///
312
351
/// 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] .
314
353
///
315
354
/// # Example
316
355
///
@@ -354,7 +393,7 @@ impl<'a> StrftimeItems<'a> {
354
393
/// # Errors
355
394
///
356
395
/// 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] .
358
397
///
359
398
/// # Example
360
399
///
@@ -416,6 +455,22 @@ impl<'a> Iterator for StrftimeItems<'a> {
416
455
}
417
456
418
457
impl < ' 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
+
419
474
fn parse_next_item ( & mut self , mut remainder : & ' a str ) -> Option < ( & ' a str , Item < ' a > ) > {
420
475
use InternalInternal :: * ;
421
476
use Item :: { Literal , Space } ;
@@ -456,16 +511,24 @@ impl<'a> StrftimeItems<'a> {
456
511
457
512
// the next item is a specifier
458
513
Some ( '%' ) => {
514
+ let original = remainder;
459
515
remainder = & remainder[ 1 ..] ;
516
+ let mut error_len = 0 ;
517
+ if self . lenient {
518
+ error_len += 1 ;
519
+ }
460
520
461
521
macro_rules! next {
462
522
( ) => {
463
523
match remainder. chars( ) . next( ) {
464
524
Some ( x) => {
465
525
remainder = & remainder[ x. len_utf8( ) ..] ;
526
+ if self . lenient {
527
+ error_len += x. len_utf8( ) ;
528
+ }
466
529
x
467
530
}
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
469
532
}
470
533
} ;
471
534
}
@@ -480,7 +543,7 @@ impl<'a> StrftimeItems<'a> {
480
543
let is_alternate = spec == '#' ;
481
544
let spec = if pad_override. is_some ( ) || is_alternate { next ! ( ) } else { spec } ;
482
545
if is_alternate && !HAVE_ALTERNATES . contains ( spec) {
483
- return Some ( ( remainder , Item :: Error ) ) ;
546
+ return Some ( self . error ( original , & mut error_len , Some ( spec ) ) ) ;
484
547
}
485
548
486
549
macro_rules! queue {
@@ -592,39 +655,71 @@ impl<'a> StrftimeItems<'a> {
592
655
remainder = & remainder[ 1 ..] ;
593
656
fixed ( Fixed :: TimezoneOffsetColon )
594
657
} else {
595
- Item :: Error
658
+ self . error ( original , & mut error_len , None ) . 1
596
659
}
597
660
}
598
661
'.' => match next ! ( ) {
599
662
'3' => match next ! ( ) {
600
663
'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
+ }
602
669
} ,
603
670
'6' => match next ! ( ) {
604
671
'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
+ }
606
677
} ,
607
678
'9' => match next ! ( ) {
608
679
'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
+ }
610
685
} ,
611
686
'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
+ }
613
692
} ,
614
693
'3' => match next ! ( ) {
615
694
'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
+ }
617
700
} ,
618
701
'6' => match next ! ( ) {
619
702
'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
+ }
621
708
} ,
622
709
'9' => match next ! ( ) {
623
710
'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
+ }
625
716
} ,
626
717
'%' => 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
+ }
628
723
} ;
629
724
630
725
// Adjust `item` if we have any padding modifier.
@@ -635,7 +730,7 @@ impl<'a> StrftimeItems<'a> {
635
730
Item :: Numeric ( ref kind, _pad) if self . queue . is_empty ( ) => {
636
731
Some ( ( remainder, Item :: Numeric ( kind. clone ( ) , new_pad) ) )
637
732
}
638
- _ => Some ( ( remainder , Item :: Error ) ) ,
733
+ _ => Some ( self . error ( original , & mut error_len , None ) ) ,
639
734
}
640
735
} else {
641
736
Some ( ( remainder, item) )
@@ -1139,4 +1234,16 @@ mod tests {
1139
1234
let dt = Utc . with_ymd_and_hms ( 2014 , 5 , 7 , 12 , 34 , 56 ) . unwrap ( ) ;
1140
1235
assert_eq ! ( & dt. format_with_items( fmt_items. iter( ) ) . to_string( ) , "2014-05-07T12:34:56+0000" ) ;
1141
1236
}
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
+ }
1142
1249
}
0 commit comments