@@ -33,6 +33,7 @@ mod ordinal;
33
33
mod relative;
34
34
mod time;
35
35
mod weekday;
36
+
36
37
mod epoch {
37
38
use winnow:: { combinator:: preceded, ModalResult , Parser } ;
38
39
@@ -41,6 +42,7 @@ mod epoch {
41
42
s ( preceded ( "@" , dec_int) ) . parse_next ( input)
42
43
}
43
44
}
45
+
44
46
mod timezone {
45
47
use super :: time;
46
48
use winnow:: ModalResult ;
@@ -53,12 +55,11 @@ mod timezone {
53
55
use chrono:: NaiveDate ;
54
56
use chrono:: { DateTime , Datelike , FixedOffset , TimeZone , Timelike } ;
55
57
56
- use winnow:: error:: { StrContext , StrContextValue } ;
57
58
use winnow:: {
58
59
ascii:: { digit1, multispace0} ,
59
60
combinator:: { alt, delimited, not, opt, peek, preceded, repeat, separated, trace} ,
60
- error:: { ContextError , ErrMode , ParserError } ,
61
- stream:: AsChar ,
61
+ error:: { AddContext , ContextError , ErrMode , ParserError , StrContext , StrContextValue } ,
62
+ stream:: { AsChar , Stream } ,
62
63
token:: { none_of, one_of, take_while} ,
63
64
ModalResult , Parser ,
64
65
} ;
@@ -145,9 +146,9 @@ where
145
146
/// following two forms:
146
147
///
147
148
/// - 0
148
- /// - [+-][1-9][0-9]*
149
+ /// - [+-]? [1-9][0-9]*
149
150
///
150
- /// Inputs like [+-]0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
151
+ /// Inputs like [+-]? 0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
151
152
/// custom implementation to support such zero-prefixed integers.
152
153
fn dec_int < ' a , E > ( input : & mut & ' a str ) -> winnow:: Result < i32 , E >
153
154
where
@@ -175,6 +176,23 @@ where
175
176
. parse_next ( input)
176
177
}
177
178
179
+ /// Parse a float number.
180
+ ///
181
+ /// Rationale for not using `winnow::ascii::float`: the `float` parser provided
182
+ /// by winnow accepts E-notation numbers (e.g., `1.23e4`), whereas GNU date
183
+ /// rejects such numbers. To remain compatible with GNU date, we provide a
184
+ /// custom implementation that only accepts inputs like [+-]?[0-9]+(\.[0-9]+)?.
185
+ fn float < ' a , E > ( input : & mut & ' a str ) -> winnow:: Result < f64 , E >
186
+ where
187
+ E : ParserError < & ' a str > ,
188
+ {
189
+ ( opt ( one_of ( [ '+' , '-' ] ) ) , digit1, opt ( preceded ( '.' , digit1) ) )
190
+ . void ( )
191
+ . take ( )
192
+ . verify_map ( |s : & str | s. parse ( ) . ok ( ) )
193
+ . parse_next ( input)
194
+ }
195
+
178
196
// Parse an item
179
197
pub fn parse_one ( input : & mut & str ) -> ModalResult < Item > {
180
198
trace (
@@ -193,6 +211,14 @@ pub fn parse_one(input: &mut &str) -> ModalResult<Item> {
193
211
. parse_next ( input)
194
212
}
195
213
214
+ fn expect_error ( input : & mut & str , reason : & ' static str ) -> ErrMode < ContextError > {
215
+ ErrMode :: Cut ( ContextError :: new ( ) ) . add_context (
216
+ input,
217
+ & input. checkpoint ( ) ,
218
+ StrContext :: Expected ( StrContextValue :: Description ( reason) ) ,
219
+ )
220
+ }
221
+
196
222
pub fn parse ( input : & mut & str ) -> ModalResult < Vec < Item > > {
197
223
let mut items = Vec :: new ( ) ;
198
224
let mut date_seen = false ;
@@ -206,13 +232,10 @@ pub fn parse(input: &mut &str) -> ModalResult<Vec<Item>> {
206
232
match item {
207
233
Item :: DateTime ( ref dt) => {
208
234
if date_seen || time_seen {
209
- let mut ctx_err = ContextError :: new ( ) ;
210
- ctx_err. push ( StrContext :: Expected (
211
- winnow:: error:: StrContextValue :: Description (
212
- "date or time cannot appear more than once" ,
213
- ) ,
235
+ return Err ( expect_error (
236
+ input,
237
+ "date or time cannot appear more than once" ,
214
238
) ) ;
215
- return Err ( ErrMode :: Backtrack ( ctx_err) ) ;
216
239
}
217
240
218
241
date_seen = true ;
@@ -223,45 +246,35 @@ pub fn parse(input: &mut &str) -> ModalResult<Vec<Item>> {
223
246
}
224
247
Item :: Date ( ref d) => {
225
248
if date_seen {
226
- let mut ctx_err = ContextError :: new ( ) ;
227
- ctx_err. push ( StrContext :: Expected ( StrContextValue :: Description (
228
- "date cannot appear more than once" ,
229
- ) ) ) ;
230
- return Err ( ErrMode :: Backtrack ( ctx_err) ) ;
249
+ return Err ( expect_error ( input, "date cannot appear more than once" ) ) ;
231
250
}
232
251
233
252
date_seen = true ;
234
253
if d. year . is_some ( ) {
235
254
year_seen = true ;
236
255
}
237
256
}
238
- Item :: Time ( _ ) => {
257
+ Item :: Time ( ref t ) => {
239
258
if time_seen {
240
- let mut ctx_err = ContextError :: new ( ) ;
241
- ctx_err. push ( StrContext :: Expected ( StrContextValue :: Description (
242
- "time cannot appear more than once" ,
243
- ) ) ) ;
244
- return Err ( ErrMode :: Backtrack ( ctx_err) ) ;
259
+ return Err ( expect_error ( input, "time cannot appear more than once" ) ) ;
245
260
}
246
261
time_seen = true ;
262
+ if t. offset . is_some ( ) {
263
+ tz_seen = true ;
264
+ }
247
265
}
248
266
Item :: Year ( _) => {
249
267
if year_seen {
250
- let mut ctx_err = ContextError :: new ( ) ;
251
- ctx_err. push ( StrContext :: Expected ( StrContextValue :: Description (
252
- "year cannot appear more than once" ,
253
- ) ) ) ;
254
- return Err ( ErrMode :: Backtrack ( ctx_err) ) ;
268
+ return Err ( expect_error ( input, "year cannot appear more than once" ) ) ;
255
269
}
256
270
year_seen = true ;
257
271
}
258
272
Item :: TimeZone ( _) => {
259
273
if tz_seen {
260
- let mut ctx_err = ContextError :: new ( ) ;
261
- ctx_err . push ( StrContext :: Expected ( StrContextValue :: Description (
274
+ return Err ( expect_error (
275
+ input ,
262
276
"timezone cannot appear more than once" ,
263
- ) ) ) ;
264
- return Err ( ErrMode :: Backtrack ( ctx_err) ) ;
277
+ ) ) ;
265
278
}
266
279
tz_seen = true ;
267
280
}
@@ -276,7 +289,7 @@ pub fn parse(input: &mut &str) -> ModalResult<Vec<Item>> {
276
289
277
290
space. parse_next ( input) ?;
278
291
if !input. is_empty ( ) {
279
- return Err ( ErrMode :: Backtrack ( ContextError :: new ( ) ) ) ;
292
+ return Err ( expect_error ( input , "unexpected input" ) ) ;
280
293
}
281
294
282
295
Ok ( items)
@@ -540,4 +553,46 @@ mod tests {
540
553
test_eq_fmt( "%Y-%m-%d %H:%M:%S %:z" , "Jul 17 06:14:49 2024 BRT" ) ,
541
554
) ;
542
555
}
556
+
557
+ #[ test]
558
+ fn invalid ( ) {
559
+ let result = parse ( & mut "2025-05-19 2024-05-20 06:14:49" ) ;
560
+ assert ! ( result. is_err( ) ) ;
561
+ assert ! ( result
562
+ . unwrap_err( )
563
+ . to_string( )
564
+ . contains( "date or time cannot appear more than once" ) ) ;
565
+
566
+ let result = parse ( & mut "2025-05-19 2024-05-20" ) ;
567
+ assert ! ( result. is_err( ) ) ;
568
+ assert ! ( result
569
+ . unwrap_err( )
570
+ . to_string( )
571
+ . contains( "date cannot appear more than once" ) ) ;
572
+
573
+ let result = parse ( & mut "06:14:49 06:14:49" ) ;
574
+ assert ! ( result. is_err( ) ) ;
575
+ assert ! ( result
576
+ . unwrap_err( )
577
+ . to_string( )
578
+ . contains( "time cannot appear more than once" ) ) ;
579
+
580
+ let result = parse ( & mut "2025-05-19 2024" ) ;
581
+ assert ! ( result. is_err( ) ) ;
582
+ assert ! ( result
583
+ . unwrap_err( )
584
+ . to_string( )
585
+ . contains( "year cannot appear more than once" ) ) ;
586
+
587
+ let result = parse ( & mut "2025-05-19 +00:00 +01:00" ) ;
588
+ assert ! ( result. is_err( ) ) ;
589
+ assert ! ( result
590
+ . unwrap_err( )
591
+ . to_string( )
592
+ . contains( "timezone cannot appear more than once" ) ) ;
593
+
594
+ let result = parse ( & mut "2025-05-19 abcdef" ) ;
595
+ assert ! ( result. is_err( ) ) ;
596
+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "unexpected input" ) ) ;
597
+ }
543
598
}
0 commit comments