@@ -45,7 +45,7 @@ use builder::DateTimeBuilder;
4545use chrono:: { DateTime , FixedOffset } ;
4646use primitive:: space;
4747use winnow:: {
48- combinator:: { alt, trace} ,
48+ combinator:: { alt, eof , terminated , trace} ,
4949 error:: { AddContext , ContextError , ErrMode , StrContext , StrContextValue } ,
5050 stream:: Stream ,
5151 ModalResult , Parser ,
@@ -65,39 +65,49 @@ pub enum Item {
6565 TimeZone ( time:: Offset ) ,
6666}
6767
68+ fn expect_error ( input : & mut & str , reason : & ' static str ) -> ErrMode < ContextError > {
69+ ErrMode :: Cut ( ContextError :: new ( ) ) . add_context (
70+ input,
71+ & input. checkpoint ( ) ,
72+ StrContext :: Expected ( StrContextValue :: Description ( reason) ) ,
73+ )
74+ }
75+
6876/// Parse an item.
69- /// TODO: timestamp values are exclusive with other items. See
70- /// https://github.com/uutils/parse_datetime/issues/156
71- pub fn parse_one ( input : & mut & str ) -> ModalResult < Item > {
77+ ///
78+ /// Grammar:
79+ ///
80+ /// ```ebnf
81+ /// item = combined | date | time | relative | weekday | timezone | year ;
82+ /// ```
83+ fn parse_item ( input : & mut & str ) -> ModalResult < Item > {
7284 trace (
73- "parse_one " ,
85+ "parse_item " ,
7486 alt ( (
7587 combined:: parse. map ( Item :: DateTime ) ,
7688 date:: parse. map ( Item :: Date ) ,
7789 time:: parse. map ( Item :: Time ) ,
7890 relative:: parse. map ( Item :: Relative ) ,
7991 weekday:: parse. map ( Item :: Weekday ) ,
80- epoch:: parse. map ( Item :: Timestamp ) ,
8192 timezone:: parse. map ( Item :: TimeZone ) ,
8293 date:: year. map ( Item :: Year ) ,
8394 ) ) ,
8495 )
8596 . parse_next ( input)
8697}
8798
88- fn expect_error ( input : & mut & str , reason : & ' static str ) -> ErrMode < ContextError > {
89- ErrMode :: Cut ( ContextError :: new ( ) ) . add_context (
90- input,
91- & input. checkpoint ( ) ,
92- StrContext :: Expected ( StrContextValue :: Description ( reason) ) ,
93- )
94- }
95-
96- pub fn parse ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
99+ /// Parse a sequence of items.
100+ ///
101+ /// Grammar:
102+ ///
103+ /// ```ebnf
104+ /// items = item, { space, item } ;
105+ /// ```
106+ fn parse_items ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
97107 let mut builder = DateTimeBuilder :: new ( ) ;
98108
99109 loop {
100- match parse_one . parse_next ( input) {
110+ match parse_item . parse_next ( input) {
101111 Ok ( item) => match item {
102112 Item :: Timestamp ( ts) => {
103113 builder = builder
@@ -147,6 +157,38 @@ pub fn parse(input: &mut &str) -> ModalResult<DateTimeBuilder> {
147157 Ok ( builder)
148158}
149159
160+ /// Parse a timestamp.
161+ ///
162+ /// From the GNU docs:
163+ ///
164+ /// (Timestamp) Such a number cannot be combined with any other date item, as it
165+ /// specifies a complete timestamp.
166+ fn parse_timestamp ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
167+ trace (
168+ "parse_timestamp" ,
169+ terminated ( epoch:: parse. map ( Item :: Timestamp ) , eof) ,
170+ )
171+ . verify_map ( |ts : Item | {
172+ if let Item :: Timestamp ( ts) = ts {
173+ DateTimeBuilder :: new ( ) . set_timestamp ( ts) . ok ( )
174+ } else {
175+ None
176+ }
177+ } )
178+ . parse_next ( input)
179+ }
180+
181+ /// Parse a date and time string.
182+ ///
183+ /// Grammar:
184+ ///
185+ /// ```ebnf
186+ /// date_time = timestamp | items ;
187+ /// ```
188+ pub ( crate ) fn parse ( input : & mut & str ) -> ModalResult < DateTimeBuilder > {
189+ trace ( "parse" , alt ( ( parse_timestamp, parse_items) ) ) . parse_next ( input)
190+ }
191+
150192pub ( crate ) fn at_date (
151193 builder : DateTimeBuilder ,
152194 base : DateTime < FixedOffset > ,
@@ -287,6 +329,14 @@ mod tests {
287329 let result = parse ( & mut "2025-05-19 abcdef" ) ;
288330 assert ! ( result. is_err( ) ) ;
289331 assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
332+
333+ let result = parse ( & mut "@1690466034 2025-05-19" ) ;
334+ assert ! ( result. is_err( ) ) ;
335+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
336+
337+ let result = parse ( & mut "2025-05-19 @1690466034" ) ;
338+ assert ! ( result. is_err( ) ) ;
339+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
290340 }
291341
292342 #[ test]
0 commit comments