@@ -49,7 +49,7 @@ pub(crate) mod error;
4949use jiff:: Zoned ;
5050use primitive:: space;
5151use winnow:: {
52- combinator:: { alt, eof, terminated, trace} ,
52+ combinator:: { alt, eof, preceded , repeat_till , terminated, trace} ,
5353 error:: { AddContext , ContextError , ErrMode , StrContext , StrContextValue } ,
5454 stream:: Stream ,
5555 ModalResult , Parser ,
@@ -70,7 +70,8 @@ enum Item {
7070 Pure ( String ) ,
7171}
7272
73- /// Parse a date and time string based on a specific date.
73+ /// Parse a date and time string and build a `Zoned` object. The parsed result
74+ /// is resolved against the given base date and time.
7475pub ( crate ) fn parse_at_date < S : AsRef < str > + Clone > ( base : Zoned , input : S ) -> Result < Zoned , Error > {
7576 let input = input. as_ref ( ) . to_ascii_lowercase ( ) ;
7677 match parse ( & mut input. as_str ( ) ) {
@@ -79,11 +80,12 @@ pub(crate) fn parse_at_date<S: AsRef<str> + Clone>(base: Zoned, input: S) -> Res
7980 }
8081}
8182
82- /// Parse a date and time string based on the current local time.
83+ /// Parse a date and time string and build a `Zoned` object. The parsed result
84+ /// is resolved against the current local date and time.
8385pub ( crate ) fn parse_at_local < S : AsRef < str > + Clone > ( input : S ) -> Result < Zoned , Error > {
8486 let input = input. as_ref ( ) . to_ascii_lowercase ( ) ;
8587 match parse ( & mut input. as_str ( ) ) {
86- Ok ( builder) => builder. build ( ) ,
88+ Ok ( builder) => builder. build ( ) , // the builder uses current local date and time if no base is given.
8789 Err ( e) => Err ( e. into ( ) ) ,
8890 }
8991}
@@ -196,7 +198,7 @@ fn parse(input: &mut &str) -> ModalResult<DateTimeBuilder> {
196198fn parse_timestamp ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
197199 trace (
198200 "parse_timestamp" ,
199- terminated ( epoch:: parse. map ( Item :: Timestamp ) , eof) ,
201+ terminated ( epoch:: parse. map ( Item :: Timestamp ) , preceded ( space , eof) ) ,
200202 )
201203 . verify_map ( |ts : Item | {
202204 if let Item :: Timestamp ( ts) = ts {
@@ -210,59 +212,13 @@ fn parse_timestamp(input: &mut &str) -> ModalResult<DateTimeBuilder> {
210212
211213/// Parse a sequence of items.
212214fn parse_items ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
213- let mut builder = DateTimeBuilder :: new ( ) ;
214-
215- loop {
216- match parse_item. parse_next ( input) {
217- Ok ( item) => match item {
218- Item :: Timestamp ( ts) => {
219- builder = builder
220- . set_timestamp ( ts)
221- . map_err ( |e| expect_error ( input, e) ) ?;
222- }
223- Item :: DateTime ( dt) => {
224- builder = builder
225- . set_date ( dt. date )
226- . map_err ( |e| expect_error ( input, e) ) ?
227- . set_time ( dt. time )
228- . map_err ( |e| expect_error ( input, e) ) ?;
229- }
230- Item :: Date ( d) => {
231- builder = builder. set_date ( d) . map_err ( |e| expect_error ( input, e) ) ?;
232- }
233- Item :: Time ( t) => {
234- builder = builder. set_time ( t) . map_err ( |e| expect_error ( input, e) ) ?;
235- }
236- Item :: Weekday ( weekday) => {
237- builder = builder
238- . set_weekday ( weekday)
239- . map_err ( |e| expect_error ( input, e) ) ?;
240- }
241- Item :: TimeZone ( tz) => {
242- builder = builder
243- . set_timezone ( tz)
244- . map_err ( |e| expect_error ( input, e) ) ?;
245- }
246- Item :: Relative ( rel) => {
247- builder = builder
248- . push_relative ( rel)
249- . map_err ( |e| expect_error ( input, e) ) ?;
250- }
251- Item :: Pure ( pure) => {
252- builder = builder. set_pure ( pure) . map_err ( |e| expect_error ( input, e) ) ?;
253- }
254- } ,
255- Err ( ErrMode :: Backtrack ( _) ) => break ,
256- Err ( e) => return Err ( e) ,
257- }
258- }
259-
260- space. parse_next ( input) ?;
261- if !input. is_empty ( ) {
262- return Err ( expect_error ( input, "unexpected input" ) ) ;
263- }
215+ let ( items, _) : ( Vec < Item > , _ ) = trace (
216+ "parse_items" ,
217+ repeat_till ( 0 .., parse_item, preceded ( space, eof) ) ,
218+ )
219+ . parse_next ( input) ?;
264220
265- Ok ( builder )
221+ items . try_into ( ) . map_err ( |e| expect_error ( input , e ) )
266222}
267223
268224/// Parse an item.
@@ -346,6 +302,11 @@ mod tests {
346302 test_eq_fmt( "%Y-%m-%dT%H:%M:%S%:z" , "@1690466034" )
347303 ) ;
348304
305+ assert_eq ! (
306+ "2023-07-27T13:53:54+00:00" ,
307+ test_eq_fmt( "%Y-%m-%dT%H:%M:%S%:z" , " @1690466034 " )
308+ ) ;
309+
349310 // https://github.com/uutils/coreutils/issues/6398
350311 // TODO: make this work
351312 // assert_eq!("1111 1111 00", test_eq_fmt("%m%d %H%M %S", "11111111"));
@@ -371,6 +332,12 @@ mod tests {
371332 ) ;
372333 }
373334
335+ #[ test]
336+ fn empty ( ) {
337+ let result = parse ( & mut "" ) ;
338+ assert ! ( result. is_ok( ) ) ;
339+ }
340+
374341 #[ test]
375342 fn invalid ( ) {
376343 let result = parse ( & mut "2025-05-19 2024-05-20 06:14:49" ) ;
@@ -396,7 +363,6 @@ mod tests {
396363
397364 let result = parse ( & mut "2025-05-19 +00:00 +01:00" ) ;
398365 assert ! ( result. is_err( ) ) ;
399- assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
400366
401367 let result = parse ( & mut "m1y" ) ;
402368 assert ! ( result. is_err( ) ) ;
@@ -407,15 +373,12 @@ mod tests {
407373
408374 let result = parse ( & mut "2025-05-19 abcdef" ) ;
409375 assert ! ( result. is_err( ) ) ;
410- assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
411376
412377 let result = parse ( & mut "@1690466034 2025-05-19" ) ;
413378 assert ! ( result. is_err( ) ) ;
414- assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
415379
416380 let result = parse ( & mut "2025-05-19 @1690466034" ) ;
417381 assert ! ( result. is_err( ) ) ;
418- assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
419382
420383 // Pure number as year (too large).
421384 let result = parse ( & mut "jul 18 12:30 10000" ) ;
0 commit comments