@@ -234,8 +234,34 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
234234 // TODO: Replace with a proper customiseable parsing solution using `nom`, `grmtools`, or
235235 // similar
236236
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+ {
239265 // HACK: if the string ends with a single digit preceded by a + or -
240266 // sign, then insert a 0 between the sign and the digit to make it
241267 // possible for `chrono` to parse it.
@@ -244,7 +270,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
244270 for ( fmt, n) in format:: PATTERNS_TZ {
245271 if tmp_s. len ( ) >= n {
246272 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+ }
248278 }
249279 }
250280 }
@@ -261,11 +291,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
261291 . unwrap ( )
262292 . from_local_datetime ( & parsed)
263293 {
264- MappedLocalTime :: Single ( datetime) => return Ok ( datetime) ,
265- _ => return Err ( ParseDateTimeError :: InvalidInput ) ,
294+ MappedLocalTime :: Single ( datetime) => return Some ( ( datetime, n ) ) ,
295+ _ => return None ,
266296 }
267297 } else if let Ok ( dt) = naive_dt_to_fixed_offset ( date, parsed) {
268- return Ok ( dt ) ;
298+ return Some ( ( dt , n ) ) ;
269299 }
270300 }
271301 }
@@ -289,13 +319,13 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
289319
290320 let dt = DateTime :: < FixedOffset > :: from ( beginning_of_day) ;
291321
292- return Ok ( dt ) ;
322+ return Some ( ( dt , s . as_ref ( ) . len ( ) ) ) ;
293323 }
294324
295325 // Parse epoch seconds
296326 if let Ok ( timestamp) = parse_timestamp ( s. as_ref ( ) ) {
297327 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 ( ) ) ) ;
299329 }
300330 }
301331
@@ -305,7 +335,7 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
305335 if let Ok ( parsed) = NaiveDate :: parse_from_str ( & s. as_ref ( ) [ 0 ..n] , fmt) {
306336 let datetime = parsed. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
307337 if let Ok ( dt) = naive_dt_to_fixed_offset ( date, datetime) {
308- return Ok ( dt ) ;
338+ return Some ( ( dt , n ) ) ;
309339 }
310340 }
311341 }
@@ -320,25 +350,21 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
320350 if ts. len ( ) == n + 12 {
321351 let f = format:: YYYYMMDDHHMM . to_owned ( ) + fmt;
322352 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+ }
324358 }
325359 }
326360 }
327361
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-
333362 // parse time only dates
334363 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 ( ) ) ) ;
336365 }
337366
338- // Default parse and failure
339- s. as_ref ( )
340- . parse ( )
341- . map_err ( |_| ( ParseDateTimeError :: InvalidInput ) )
367+ None
342368}
343369
344370// Convert NaiveDateTime to DateTime<FixedOffset> by assuming the offset
@@ -664,14 +690,10 @@ mod tests {
664690 assert ! ( crate :: parse_datetime( "bogus +1 day" ) . is_err( ) ) ;
665691 }
666692
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+ }
675697
676698 #[ test]
677699 fn test_parse_datetime_tz_nodelta ( ) {
@@ -743,6 +765,80 @@ mod tests {
743765 }
744766 }
745767
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+
746842 #[ test]
747843 fn test_time_only ( ) {
748844 use chrono:: { FixedOffset , Local } ;
0 commit comments