28
28
29
29
mod combined;
30
30
mod date;
31
+ mod number;
31
32
mod ordinal;
32
33
mod relative;
33
34
mod time;
34
35
mod time_zone;
35
36
mod weekday;
36
- mod number { }
37
+ mod epoch {
38
+ use winnow:: { ascii:: dec_uint, combinator:: preceded, PResult , Parser } ;
39
+
40
+ use super :: s;
41
+ pub fn parse ( input : & mut & str ) -> PResult < u32 > {
42
+ s ( preceded ( "@" , dec_uint) ) . parse_next ( input)
43
+ }
44
+ }
45
+ mod timezone {
46
+ use super :: s;
47
+ use winnow:: { PResult , Parser as _} ;
48
+
49
+ pub ( crate ) fn parse ( input : & mut & str ) -> PResult < String > {
50
+ s ( "gmt" ) . parse_next ( input) . map ( |x| x. to_string ( ) )
51
+ }
52
+ }
53
+
54
+ use chrono:: { DateTime , Datelike , Local , TimeZone , Timelike , Utc } ;
37
55
38
56
use winnow:: {
39
57
ascii:: multispace0,
40
58
combinator:: { alt, delimited, not, peek, preceded, repeat, separated, terminated} ,
41
- error:: ParserError ,
59
+ error:: { ParseError , ParserError } ,
42
60
stream:: AsChar ,
43
61
token:: { none_of, take_while} ,
44
62
PResult , Parser ,
45
63
} ;
46
64
47
65
#[ derive( PartialEq , Debug ) ]
48
66
pub enum Item {
67
+ Timestamp ( u32 ) ,
68
+ Year ( u32 ) ,
49
69
DateTime ( combined:: DateTime ) ,
50
70
Date ( date:: Date ) ,
51
71
Time ( time:: Time ) ,
52
72
Weekday ( weekday:: Weekday ) ,
53
73
Relative ( relative:: Relative ) ,
54
- TimeZone ( ( ) ) ,
74
+ Unknown ( String ) ,
75
+ TimeZone ( String ) ,
55
76
}
56
77
57
78
/// Allow spaces and comments before a parser
@@ -115,32 +136,118 @@ where
115
136
. parse_next ( input)
116
137
}
117
138
118
- /// Parse an item
139
+ // Parse an item
119
140
pub fn parse_one ( input : & mut & str ) -> PResult < Item > {
120
141
alt ( (
121
142
combined:: parse. map ( Item :: DateTime ) ,
122
143
date:: parse. map ( Item :: Date ) ,
123
144
time:: parse. map ( Item :: Time ) ,
124
145
relative:: parse. map ( Item :: Relative ) ,
125
146
weekday:: parse. map ( Item :: Weekday ) ,
126
- // time_zone::parse.map(Item::TimeZone),
147
+ epoch:: parse. map ( Item :: Timestamp ) ,
148
+ date:: year. map ( Item :: Year ) ,
149
+ timezone:: parse. map ( Item :: TimeZone ) ,
127
150
) )
128
151
. parse_next ( input)
129
152
}
130
153
131
- pub fn parse ( input : & mut & str ) -> Option < Vec < Item > > {
132
- terminated ( repeat ( 0 .., parse_one) , space) . parse ( input) . ok ( )
154
+ pub fn parse < ' a > (
155
+ input : & ' a mut & str ,
156
+ ) -> Result < Vec < Item > , ParseError < & ' a str , winnow:: error:: ContextError > > {
157
+ terminated ( repeat ( 0 .., parse_one) , space) . parse ( input)
158
+ }
159
+
160
+ pub fn to_date ( date : Vec < Item > ) -> DateTime < Utc > {
161
+ let mut d = chrono:: Utc :: now ( ) ;
162
+ eprintln ! ( "now {d:?}" ) ;
163
+ for item in date {
164
+ match item {
165
+ Item :: Unknown ( _) => { }
166
+ Item :: Timestamp ( ts) => d = chrono:: Utc . timestamp_opt ( ts. into ( ) , 0 ) . unwrap ( ) ,
167
+ Item :: Date ( date:: Date { day, month, year } ) => {
168
+ d = d. with_day ( day) . unwrap_or ( d) ;
169
+ d = d. with_month ( month) . unwrap_or ( d) ;
170
+ d = year
171
+ // converts i32 to u32 safely
172
+ . and_then ( |year : u32 | <u32 as TryInto < i32 > >:: try_into ( year) . ok ( ) )
173
+ . and_then ( |year| d. with_year ( year) )
174
+ . unwrap_or ( d) ;
175
+ }
176
+ Item :: DateTime ( combined:: DateTime {
177
+ date : date:: Date { day, month, year } ,
178
+ time :
179
+ time:: Time {
180
+ hour,
181
+ minute,
182
+ second,
183
+ offset : _,
184
+ } ,
185
+ ..
186
+ } ) => {
187
+ d = d. with_day ( day) . unwrap_or ( d) ;
188
+ d = d. with_month ( month) . unwrap_or ( d) ;
189
+ d = year
190
+ // converts i32 to u32 safely
191
+ . and_then ( |year : u32 | <u32 as TryInto < i32 > >:: try_into ( year) . ok ( ) )
192
+ . and_then ( |year| d. with_year ( year) )
193
+ . unwrap_or ( d) ;
194
+ d = d. with_hour ( hour) . unwrap_or ( d) ;
195
+ d = d. with_minute ( minute) . unwrap_or ( d) ;
196
+ d = d. with_second ( second as u32 ) . unwrap_or ( d) ;
197
+ }
198
+ Item :: Year ( year) => d = d. with_year ( year as i32 ) . unwrap_or ( d) ,
199
+ Item :: Time ( time:: Time {
200
+ hour,
201
+ minute,
202
+ second,
203
+ offset : _,
204
+ } ) => {
205
+ d = d. with_hour ( hour) . unwrap_or ( d) ;
206
+ d = d. with_minute ( minute) . unwrap_or ( d) ;
207
+ d = d. with_second ( second as u32 ) . unwrap_or ( d) ;
208
+ // TODO, use the timezone
209
+ }
210
+ Item :: Weekday ( weekday:: Weekday { .. } ) => {
211
+ todo ! ( ) ;
212
+ }
213
+ Item :: Relative ( relative:: Relative :: Years ( x) ) => d = d. with_year ( x) . unwrap_or ( d) ,
214
+ Item :: Relative ( relative:: Relative :: Months ( x) ) => {
215
+ d = d. with_month ( x as u32 ) . unwrap_or ( d)
216
+ }
217
+ Item :: Relative ( relative:: Relative :: Days ( x) ) => d += chrono:: Duration :: days ( x. into ( ) ) ,
218
+ Item :: Relative ( relative:: Relative :: Hours ( x) ) => d += chrono:: Duration :: hours ( x. into ( ) ) ,
219
+ Item :: Relative ( relative:: Relative :: Minutes ( x) ) => {
220
+ d += chrono:: Duration :: minutes ( x. into ( ) ) ;
221
+ }
222
+ // Seconds are special because they can be given as a float
223
+ Item :: Relative ( relative:: Relative :: Seconds ( x) ) => {
224
+ d += chrono:: Duration :: seconds ( x as i64 ) ;
225
+ }
226
+ Item :: TimeZone ( s) => { }
227
+ }
228
+ }
229
+ d
133
230
}
134
231
135
232
#[ cfg( test) ]
136
233
mod tests {
137
- use super :: { date:: Date , parse, time:: Time , Item } ;
234
+ use super :: { date:: Date , parse, time:: Time , to_date, Item } ;
235
+
236
+ fn test_eq_fmt ( fmt : & str , input : & str ) -> String {
237
+ let input = input. to_ascii_lowercase ( ) ;
238
+ parse ( & mut input. as_str ( ) )
239
+ . map ( to_date)
240
+ . map_err ( |e| eprintln ! ( "TEST FAILED AT:\n {}" , anyhow:: format_err!( "{e}" ) ) )
241
+ . expect ( "parsing failed during tests" )
242
+ . format ( fmt)
243
+ . to_string ( )
244
+ }
138
245
139
246
#[ test]
140
247
fn date_and_time ( ) {
141
248
assert_eq ! (
142
249
parse( & mut " 10:10 2022-12-12 " ) ,
143
- Some ( vec![
250
+ Ok ( vec![
144
251
Item :: Time ( Time {
145
252
hour: 10 ,
146
253
minute: 10 ,
@@ -153,6 +260,42 @@ mod tests {
153
260
year: Some ( 2022 )
154
261
} )
155
262
] )
156
- )
263
+ ) ;
264
+
265
+ // TODO: fix these issues
266
+
267
+ // format, expected output, input
268
+ assert_eq ! ( "2024-01-02" , test_eq_fmt( "%Y-%m-%d" , "2024-01-02" ) ) ;
269
+
270
+ // https://github.com/uutils/coreutils/issues/6662
271
+ assert_eq ! ( "2005-01-02" , test_eq_fmt( "%Y-%m-%d" , "2005-01-01 +1 day" ) ) ;
272
+
273
+ // https://github.com/uutils/coreutils/issues/6644
274
+ assert_eq ! ( "Jul 16" , test_eq_fmt( "%b %d" , "Jul 16" ) ) ;
275
+ assert_eq ! ( "0718061449" , test_eq_fmt( "%m%d%H%M%S" , "Jul 18 06:14:49" ) ) ;
276
+ assert_eq ! (
277
+ "07182024061449" ,
278
+ test_eq_fmt( "%m%d%Y%H%M%S" , "Jul 18, 2024 06:14:49" )
279
+ ) ;
280
+ //assert_eq!("07182024061449", test_eq_fmt("%m%d%Y%H%M%S", "Jul 18 -2024 06:14:49"));
281
+ assert_eq ! (
282
+ "07182024061449" ,
283
+ test_eq_fmt( "%m%d%Y%H%M%S" , "Jul 18 06:14:49 2024" )
284
+ ) ;
285
+ //assert_eq!("07182024061449", test_eq_fmt("%m%d%Y%H%M%S", "Jul 18 06:14:49 2024 GMT"));
286
+
287
+ // https://github.com/uutils/coreutils/issues/5177
288
+ assert_eq ! (
289
+ "2023-07-27T13:53:54+00:00" ,
290
+ test_eq_fmt( "%+" , "@1690466034" )
291
+ ) ;
292
+
293
+ // https://github.com/uutils/coreutils/issues/6398
294
+ assert_eq ! ( "1111 1111 00" , test_eq_fmt( "%m%d %H%M %S" , "11111111" ) ) ;
295
+
296
+ assert_eq ! (
297
+ "2024-07-17 06:14:49 UTC" ,
298
+ test_eq_fmt( "%Y-%m-%d %H:%M:%S %Z" , "Jul 17 06:14:49 2024 GMT" ) ,
299
+ ) ;
157
300
}
158
301
}
0 commit comments