@@ -80,6 +80,7 @@ mod format {
80
80
pub const YYYYMMDDHHMMSS_HYPHENATED_OFFSET : & str = "%Y-%m-%d %H:%M:%S %#z" ;
81
81
pub const YYYYMMDDHHMMSS_HYPHENATED_ZULU : & str = "%Y-%m-%d %H:%M:%SZ" ;
82
82
pub const YYYYMMDDHHMMSS_T_SEP_HYPHENATED_OFFSET : & str = "%Y-%m-%dT%H:%M:%S%#z" ;
83
+ pub const YYYYMMDDHHMMSS_T_SEP_HYPHENATED_ZULU : & str = "%Y-%m-%dT%H:%M:%SZ" ;
83
84
pub const YYYYMMDDHHMMSS_T_SEP_HYPHENATED_SPACE_OFFSET : & str = "%Y-%m-%dT%H:%M:%S %#z" ;
84
85
pub const YYYYMMDDHHMMS_T_SEP : & str = "%Y-%m-%dT%H:%M:%S" ;
85
86
pub const UTC_OFFSET : & str = "UTC%#z" ;
@@ -88,7 +89,7 @@ mod format {
88
89
89
90
/// Whether the pattern ends in the character `Z`.
90
91
pub ( crate ) fn is_zulu ( pattern : & str ) -> bool {
91
- pattern == YYYYMMDDHHMMSS_HYPHENATED_ZULU
92
+ pattern. ends_with ( 'Z' )
92
93
}
93
94
94
95
/// Patterns for datetimes with timezones.
@@ -113,10 +114,11 @@ mod format {
113
114
/// Patterns for datetimes without timezones.
114
115
///
115
116
/// These are in decreasing order of length.
116
- pub ( crate ) const PATTERNS_NO_TZ : [ ( & str , usize ) ; 8 ] = [
117
+ pub ( crate ) const PATTERNS_NO_TZ : [ ( & str , usize ) ; 9 ] = [
117
118
( YYYYMMDDHHMMSS , 29 ) ,
118
119
( POSIX_LOCALE , 24 ) ,
119
120
( YYYYMMDDHHMMSS_HYPHENATED_ZULU , 20 ) ,
121
+ ( YYYYMMDDHHMMSS_T_SEP_HYPHENATED_ZULU , 20 ) ,
120
122
( YYYYMMDDHHMMS_T_SEP , 19 ) ,
121
123
( YYYYMMDDHHMMS , 19 ) ,
122
124
( YYYY_MM_DD_HH_MM , 16 ) ,
@@ -232,8 +234,34 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
232
234
// TODO: Replace with a proper customiseable parsing solution using `nom`, `grmtools`, or
233
235
// similar
234
236
235
- // Formats with offsets don't require NaiveDateTime workaround
236
- //
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
+ {
237
265
// HACK: if the string ends with a single digit preceded by a + or -
238
266
// sign, then insert a 0 between the sign and the digit to make it
239
267
// possible for `chrono` to parse it.
@@ -242,7 +270,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
242
270
for ( fmt, n) in format:: PATTERNS_TZ {
243
271
if tmp_s. len ( ) >= n {
244
272
if let Ok ( parsed) = DateTime :: parse_from_str ( & tmp_s[ 0 ..n] , fmt) {
245
- return Ok ( parsed) ;
273
+ if tmp_s == s. as_ref ( ) {
274
+ return Some ( ( parsed, n) ) ;
275
+ } else {
276
+ return Some ( ( parsed, n - 1 ) ) ;
277
+ }
246
278
}
247
279
}
248
280
}
@@ -259,11 +291,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
259
291
. unwrap ( )
260
292
. from_local_datetime ( & parsed)
261
293
{
262
- MappedLocalTime :: Single ( datetime) => return Ok ( datetime) ,
263
- _ => return Err ( ParseDateTimeError :: InvalidInput ) ,
294
+ MappedLocalTime :: Single ( datetime) => return Some ( ( datetime, n ) ) ,
295
+ _ => return None ,
264
296
}
265
297
} else if let Ok ( dt) = naive_dt_to_fixed_offset ( date, parsed) {
266
- return Ok ( dt ) ;
298
+ return Some ( ( dt , n ) ) ;
267
299
}
268
300
}
269
301
}
@@ -287,13 +319,13 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
287
319
288
320
let dt = DateTime :: < FixedOffset > :: from ( beginning_of_day) ;
289
321
290
- return Ok ( dt ) ;
322
+ return Some ( ( dt , s . as_ref ( ) . len ( ) ) ) ;
291
323
}
292
324
293
325
// Parse epoch seconds
294
326
if let Ok ( timestamp) = parse_timestamp ( s. as_ref ( ) ) {
295
327
if let Some ( timestamp_date) = DateTime :: from_timestamp ( timestamp, 0 ) {
296
- return Ok ( timestamp_date. into ( ) ) ;
328
+ return Some ( ( timestamp_date. into ( ) , s . as_ref ( ) . len ( ) ) ) ;
297
329
}
298
330
}
299
331
@@ -303,7 +335,7 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
303
335
if let Ok ( parsed) = NaiveDate :: parse_from_str ( & s. as_ref ( ) [ 0 ..n] , fmt) {
304
336
let datetime = parsed. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
305
337
if let Ok ( dt) = naive_dt_to_fixed_offset ( date, datetime) {
306
- return Ok ( dt ) ;
338
+ return Some ( ( dt , n ) ) ;
307
339
}
308
340
}
309
341
}
@@ -318,25 +350,21 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
318
350
if ts. len ( ) == n + 12 {
319
351
let f = format:: YYYYMMDDHHMM . to_owned ( ) + fmt;
320
352
if let Ok ( parsed) = DateTime :: parse_from_str ( & ts, & f) {
321
- return Ok ( parsed) ;
353
+ if tmp_s == s. as_ref ( ) {
354
+ return Some ( ( parsed, n) ) ;
355
+ } else {
356
+ return Some ( ( parsed, n - 1 ) ) ;
357
+ }
322
358
}
323
359
}
324
360
}
325
361
326
- // Parse relative time.
327
- if let Ok ( datetime) = parse_relative_time_at_date ( date, s. as_ref ( ) ) {
328
- return Ok ( DateTime :: < FixedOffset > :: from ( datetime) ) ;
329
- }
330
-
331
362
// parse time only dates
332
363
if let Some ( date_time) = parse_time_only_str:: parse_time_only ( date, s. as_ref ( ) ) {
333
- return Ok ( date_time) ;
364
+ return Some ( ( date_time, s . as_ref ( ) . len ( ) ) ) ;
334
365
}
335
366
336
- // Default parse and failure
337
- s. as_ref ( )
338
- . parse ( )
339
- . map_err ( |_| ( ParseDateTimeError :: InvalidInput ) )
367
+ None
340
368
}
341
369
342
370
// Convert NaiveDateTime to DateTime<FixedOffset> by assuming the offset
@@ -662,14 +690,10 @@ mod tests {
662
690
assert ! ( crate :: parse_datetime( "bogus +1 day" ) . is_err( ) ) ;
663
691
}
664
692
665
- // TODO Re-enable this when we parse the absolute datetime and the
666
- // time delta separately, see
667
- // <https://github.com/uutils/parse_datetime/issues/104>.
668
- //
669
- // #[test]
670
- // fn test_parse_invalid_delta() {
671
- // assert!(crate::parse_datetime("1997-01-01 bogus").is_err());
672
- // }
693
+ #[ test]
694
+ fn test_parse_invalid_delta ( ) {
695
+ assert ! ( crate :: parse_datetime( "1997-01-01 bogus" ) . is_err( ) ) ;
696
+ }
673
697
674
698
#[ test]
675
699
fn test_parse_datetime_tz_nodelta ( ) {
@@ -741,6 +765,80 @@ mod tests {
741
765
}
742
766
}
743
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
+
744
842
#[ test]
745
843
fn test_time_only ( ) {
746
844
use chrono:: { FixedOffset , Local } ;
0 commit comments