@@ -45,7 +45,7 @@ use builder::DateTimeBuilder;
45
45
use chrono:: { DateTime , FixedOffset } ;
46
46
use primitive:: space;
47
47
use winnow:: {
48
- combinator:: { alt, trace} ,
48
+ combinator:: { alt, eof , terminated , trace} ,
49
49
error:: { AddContext , ContextError , ErrMode , StrContext , StrContextValue } ,
50
50
stream:: Stream ,
51
51
ModalResult , Parser ,
@@ -65,39 +65,49 @@ pub enum Item {
65
65
TimeZone ( time:: Offset ) ,
66
66
}
67
67
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
+
68
76
/// 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 > {
72
84
trace (
73
- "parse_one " ,
85
+ "parse_item " ,
74
86
alt ( (
75
87
combined:: parse. map ( Item :: DateTime ) ,
76
88
date:: parse. map ( Item :: Date ) ,
77
89
time:: parse. map ( Item :: Time ) ,
78
90
relative:: parse. map ( Item :: Relative ) ,
79
91
weekday:: parse. map ( Item :: Weekday ) ,
80
- epoch:: parse. map ( Item :: Timestamp ) ,
81
92
timezone:: parse. map ( Item :: TimeZone ) ,
82
93
date:: year. map ( Item :: Year ) ,
83
94
) ) ,
84
95
)
85
96
. parse_next ( input)
86
97
}
87
98
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 > {
97
107
let mut builder = DateTimeBuilder :: new ( ) ;
98
108
99
109
loop {
100
- match parse_one . parse_next ( input) {
110
+ match parse_item . parse_next ( input) {
101
111
Ok ( item) => match item {
102
112
Item :: Timestamp ( ts) => {
103
113
builder = builder
@@ -147,6 +157,38 @@ pub fn parse(input: &mut &str) -> ModalResult<DateTimeBuilder> {
147
157
Ok ( builder)
148
158
}
149
159
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
+
150
192
pub ( crate ) fn at_date (
151
193
builder : DateTimeBuilder ,
152
194
base : DateTime < FixedOffset > ,
@@ -287,6 +329,14 @@ mod tests {
287
329
let result = parse ( & mut "2025-05-19 abcdef" ) ;
288
330
assert ! ( result. is_err( ) ) ;
289
331
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" ) ) ;
290
340
}
291
341
292
342
#[ test]
0 commit comments