@@ -234,8 +234,34 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
234
234
// TODO: Replace with a proper customiseable parsing solution using `nom`, `grmtools`, or
235
235
// similar
236
236
237
- // Formats with offsets don't require NaiveDateTime workaround
238
- //
237
+ // Try to parse a reference date first. Try parsing from longest
238
+ // pattern to shortest pattern. If a reference date can be parsed,
239
+ // then try to parse a time delta from the remaining slice. If no
240
+ // reference date could be parsed, then try to parse the entire
241
+ // string as a time delta. If no time delta could be parsed,
242
+ // return an error.
243
+ let ( ref_date, n) = match parse_reference_date ( date, s. as_ref ( ) ) {
244
+ Some ( ( ref_date, n) ) => ( ref_date, n) ,
245
+ None => {
246
+ let tz = TimeZone :: from_offset ( date. offset ( ) ) ;
247
+ match date. naive_local ( ) . and_local_timezone ( tz) {
248
+ MappedLocalTime :: Single ( ref_date) => ( ref_date, 0 ) ,
249
+ _ => return Err ( ParseDateTimeError :: InvalidInput ) ,
250
+ }
251
+ }
252
+ } ;
253
+ parse_relative_time_at_date ( ref_date, & s. as_ref ( ) [ n..] )
254
+ }
255
+
256
+ /// Parse an absolute datetime from a prefix of s, if possible.
257
+ ///
258
+ /// Try to parse the longest possible absolute datetime at the beginning
259
+ /// of string `s`. Return the parsed datetime and the index in `s` at
260
+ /// which the datetime ended.
261
+ fn parse_reference_date < S > ( date : DateTime < Local > , s : S ) -> Option < ( DateTime < FixedOffset > , usize ) >
262
+ where
263
+ S : AsRef < str > ,
264
+ {
239
265
// HACK: if the string ends with a single digit preceded by a + or -
240
266
// sign, then insert a 0 between the sign and the digit to make it
241
267
// possible for `chrono` to parse it.
@@ -244,7 +270,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
244
270
for ( fmt, n) in format:: PATTERNS_TZ {
245
271
if tmp_s. len ( ) >= n {
246
272
if let Ok ( parsed) = DateTime :: parse_from_str ( & tmp_s[ 0 ..n] , fmt) {
247
- return Ok ( parsed) ;
273
+ if tmp_s == s. as_ref ( ) {
274
+ return Some ( ( parsed, n) ) ;
275
+ } else {
276
+ return Some ( ( parsed, n - 1 ) ) ;
277
+ }
248
278
}
249
279
}
250
280
}
@@ -261,11 +291,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
261
291
. unwrap ( )
262
292
. from_local_datetime ( & parsed)
263
293
{
264
- MappedLocalTime :: Single ( datetime) => return Ok ( datetime) ,
265
- _ => return Err ( ParseDateTimeError :: InvalidInput ) ,
294
+ MappedLocalTime :: Single ( datetime) => return Some ( ( datetime, n ) ) ,
295
+ _ => return None ,
266
296
}
267
297
} else if let Ok ( dt) = naive_dt_to_fixed_offset ( date, parsed) {
268
- return Ok ( dt ) ;
298
+ return Some ( ( dt , n ) ) ;
269
299
}
270
300
}
271
301
}
@@ -289,13 +319,13 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
289
319
290
320
let dt = DateTime :: < FixedOffset > :: from ( beginning_of_day) ;
291
321
292
- return Ok ( dt ) ;
322
+ return Some ( ( dt , s . as_ref ( ) . len ( ) ) ) ;
293
323
}
294
324
295
325
// Parse epoch seconds
296
326
if let Ok ( timestamp) = parse_timestamp ( s. as_ref ( ) ) {
297
327
if let Some ( timestamp_date) = DateTime :: from_timestamp ( timestamp, 0 ) {
298
- return Ok ( timestamp_date. into ( ) ) ;
328
+ return Some ( ( timestamp_date. into ( ) , s . as_ref ( ) . len ( ) ) ) ;
299
329
}
300
330
}
301
331
@@ -305,7 +335,7 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
305
335
if let Ok ( parsed) = NaiveDate :: parse_from_str ( & s. as_ref ( ) [ 0 ..n] , fmt) {
306
336
let datetime = parsed. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
307
337
if let Ok ( dt) = naive_dt_to_fixed_offset ( date, datetime) {
308
- return Ok ( dt ) ;
338
+ return Some ( ( dt , n ) ) ;
309
339
}
310
340
}
311
341
}
@@ -320,25 +350,21 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
320
350
if ts. len ( ) == n + 12 {
321
351
let f = format:: YYYYMMDDHHMM . to_owned ( ) + fmt;
322
352
if let Ok ( parsed) = DateTime :: parse_from_str ( & ts, & f) {
323
- return Ok ( parsed) ;
353
+ if tmp_s == s. as_ref ( ) {
354
+ return Some ( ( parsed, n) ) ;
355
+ } else {
356
+ return Some ( ( parsed, n - 1 ) ) ;
357
+ }
324
358
}
325
359
}
326
360
}
327
361
328
- // Parse relative time.
329
- if let Ok ( datetime) = parse_relative_time_at_date ( date, s. as_ref ( ) ) {
330
- return Ok ( DateTime :: < FixedOffset > :: from ( datetime) ) ;
331
- }
332
-
333
362
// parse time only dates
334
363
if let Some ( date_time) = parse_time_only_str:: parse_time_only ( date, s. as_ref ( ) ) {
335
- return Ok ( date_time) ;
364
+ return Some ( ( date_time, s . as_ref ( ) . len ( ) ) ) ;
336
365
}
337
366
338
- // Default parse and failure
339
- s. as_ref ( )
340
- . parse ( )
341
- . map_err ( |_| ( ParseDateTimeError :: InvalidInput ) )
367
+ None
342
368
}
343
369
344
370
// Convert NaiveDateTime to DateTime<FixedOffset> by assuming the offset
@@ -664,14 +690,10 @@ mod tests {
664
690
assert ! ( crate :: parse_datetime( "bogus +1 day" ) . is_err( ) ) ;
665
691
}
666
692
667
- // TODO Re-enable this when we parse the absolute datetime and the
668
- // time delta separately, see
669
- // <https://github.com/uutils/parse_datetime/issues/104>.
670
- //
671
- // #[test]
672
- // fn test_parse_invalid_delta() {
673
- // assert!(crate::parse_datetime("1997-01-01 bogus").is_err());
674
- // }
693
+ #[ test]
694
+ fn test_parse_invalid_delta ( ) {
695
+ assert ! ( crate :: parse_datetime( "1997-01-01 bogus" ) . is_err( ) ) ;
696
+ }
675
697
676
698
#[ test]
677
699
fn test_parse_datetime_tz_nodelta ( ) {
@@ -743,6 +765,80 @@ mod tests {
743
765
}
744
766
}
745
767
768
+ #[ test]
769
+ fn test_parse_datetime_tz_delta ( ) {
770
+ std:: env:: set_var ( "TZ" , "UTC0" ) ;
771
+
772
+ // 1998-01-01
773
+ let expected = chrono:: NaiveDate :: from_ymd_opt ( 1998 , 1 , 1 )
774
+ . unwrap ( )
775
+ . and_hms_opt ( 0 , 0 , 0 )
776
+ . unwrap ( )
777
+ . and_utc ( )
778
+ . fixed_offset ( ) ;
779
+
780
+ for s in [
781
+ "1997-01-01 00:00:00 +0000 +1 year" ,
782
+ "1997-01-01 00:00:00 +00 +1 year" ,
783
+ "199701010000 +0000 +1 year" ,
784
+ "199701010000UTC+0000 +1 year" ,
785
+ "199701010000Z+0000 +1 year" ,
786
+ "1997-01-01T00:00:00Z +1 year" ,
787
+ "1997-01-01 00:00 +0000 +1 year" ,
788
+ "1997-01-01 00:00:00 +0000 +1 year" ,
789
+ "1997-01-01T00:00:00+0000 +1 year" ,
790
+ "1997-01-01T00:00:00+00 +1 year" ,
791
+ ] {
792
+ let actual = crate :: parse_datetime ( s) . unwrap ( ) ;
793
+ assert_eq ! ( actual, expected) ;
794
+ }
795
+ }
796
+
797
+ #[ test]
798
+ fn test_parse_datetime_notz_delta ( ) {
799
+ std:: env:: set_var ( "TZ" , "UTC0" ) ;
800
+ let expected = chrono:: NaiveDate :: from_ymd_opt ( 1998 , 1 , 1 )
801
+ . unwrap ( )
802
+ . and_hms_opt ( 0 , 0 , 0 )
803
+ . unwrap ( )
804
+ . and_utc ( )
805
+ . fixed_offset ( ) ;
806
+
807
+ for s in [
808
+ "1997-01-01 00:00:00.000000000 +1 year" ,
809
+ "Wed Jan 1 00:00:00 1997 +1 year" ,
810
+ "1997-01-01T00:00:00 +1 year" ,
811
+ "1997-01-01 00:00:00 +1 year" ,
812
+ "1997-01-01 00:00 +1 year" ,
813
+ "199701010000.00 +1 year" ,
814
+ "199701010000 +1 year" ,
815
+ ] {
816
+ let actual = crate :: parse_datetime ( s) . unwrap ( ) ;
817
+ assert_eq ! ( actual, expected) ;
818
+ }
819
+ }
820
+
821
+ #[ test]
822
+ fn test_parse_date_notz_delta ( ) {
823
+ std:: env:: set_var ( "TZ" , "UTC0" ) ;
824
+ let expected = chrono:: NaiveDate :: from_ymd_opt ( 1998 , 1 , 1 )
825
+ . unwrap ( )
826
+ . and_hms_opt ( 0 , 0 , 0 )
827
+ . unwrap ( )
828
+ . and_utc ( )
829
+ . fixed_offset ( ) ;
830
+
831
+ for s in [
832
+ "1997-01-01 +1 year" ,
833
+ "19970101 +1 year" ,
834
+ "01/01/1997 +1 year" ,
835
+ "01/01/97 +1 year" ,
836
+ ] {
837
+ let actual = crate :: parse_datetime ( s) . unwrap ( ) ;
838
+ assert_eq ! ( actual, expected) ;
839
+ }
840
+ }
841
+
746
842
#[ test]
747
843
fn test_time_only ( ) {
748
844
use chrono:: { FixedOffset , Local } ;
0 commit comments