1
- use chrono:: { Datelike , Duration , NaiveDate , NaiveDateTime , NaiveTime , Timelike } ;
1
+ use chrono:: { Duration , NaiveDate , NaiveDateTime , NaiveTime } ;
2
2
use once_cell:: sync:: Lazy ;
3
3
use regex:: Regex ;
4
- use ropey:: RopeSlice ;
5
-
6
- use std:: borrow:: Cow ;
7
- use std:: cmp;
8
4
use std:: fmt:: Write ;
9
5
10
- use super :: Increment ;
11
- use crate :: { Range , Tendril } ;
6
+ /// Increment a Date or DateTime
7
+ ///
8
+ /// If just a Date is selected the day will be incremented.
9
+ /// If a DateTime is selected the second will be incremented.
10
+ pub fn increment ( selected_text : & str , amount : i64 ) -> Option < String > {
11
+ if selected_text. is_empty ( ) {
12
+ return None ;
13
+ }
12
14
13
- #[ derive( Debug , PartialEq , Eq ) ]
14
- pub struct DateTimeIncrementor {
15
- date_time : NaiveDateTime ,
16
- range : Range ,
17
- fmt : & ' static str ,
18
- field : DateField ,
19
- }
15
+ FORMATS . iter ( ) . find_map ( |format| {
16
+ let captures = format. regex . captures ( selected_text) ?;
17
+ if captures. len ( ) - 1 != format. fields . len ( ) {
18
+ return None ;
19
+ }
20
20
21
- impl DateTimeIncrementor {
22
- pub fn from_range ( text : RopeSlice , range : Range ) -> Option < DateTimeIncrementor > {
23
- let range = if range. is_empty ( ) {
24
- if range. anchor < text. len_chars ( ) {
25
- // Treat empty range as a cursor range.
26
- range. put_cursor ( text, range. anchor + 1 , true )
27
- } else {
28
- // The range is empty and at the end of the text.
29
- return None ;
21
+ let date_time = captures. get ( 0 ) ?;
22
+ let has_date = format. fields . iter ( ) . any ( |f| f. unit . is_date ( ) ) ;
23
+ let has_time = format. fields . iter ( ) . any ( |f| f. unit . is_time ( ) ) ;
24
+ let date_time = & selected_text[ date_time. start ( ) ..date_time. end ( ) ] ;
25
+ match ( has_date, has_time) {
26
+ ( true , true ) => {
27
+ let date_time = NaiveDateTime :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?;
28
+ Some (
29
+ date_time
30
+ . checked_add_signed ( Duration :: minutes ( amount) ) ?
31
+ . format ( format. fmt )
32
+ . to_string ( ) ,
33
+ )
30
34
}
31
- } else {
32
- range
33
- } ;
34
-
35
- FORMATS . iter ( ) . find_map ( |format| {
36
- let from = range. from ( ) . saturating_sub ( format. max_len ) ;
37
- let to = ( range. from ( ) + format. max_len ) . min ( text. len_chars ( ) ) ;
38
-
39
- let ( from_in_text, to_in_text) = ( range. from ( ) - from, range. to ( ) - from) ;
40
- let text: Cow < str > = text. slice ( from..to) . into ( ) ;
41
-
42
- let captures = format. regex . captures ( & text) ?;
43
- if captures. len ( ) - 1 != format. fields . len ( ) {
44
- return None ;
35
+ ( true , false ) => {
36
+ let date = NaiveDate :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?;
37
+ Some (
38
+ date. checked_add_signed ( Duration :: days ( amount) ) ?
39
+ . format ( format. fmt )
40
+ . to_string ( ) ,
41
+ )
45
42
}
46
-
47
- let date_time = captures. get ( 0 ) ?;
48
- let offset = range. from ( ) - from_in_text;
49
- let range = Range :: new ( date_time. start ( ) + offset, date_time. end ( ) + offset) ;
50
-
51
- let field = captures
52
- . iter ( )
53
- . skip ( 1 )
54
- . enumerate ( )
55
- . find_map ( |( i, capture) | {
56
- let capture = capture?;
57
- let capture_range = capture. range ( ) ;
58
-
59
- if capture_range. contains ( & from_in_text)
60
- && capture_range. contains ( & ( to_in_text - 1 ) )
61
- {
62
- Some ( format. fields [ i] )
63
- } else {
64
- None
65
- }
66
- } ) ?;
67
-
68
- let has_date = format. fields . iter ( ) . any ( |f| f. unit . is_date ( ) ) ;
69
- let has_time = format. fields . iter ( ) . any ( |f| f. unit . is_time ( ) ) ;
70
-
71
- let date_time = & text[ date_time. start ( ) ..date_time. end ( ) ] ;
72
- let date_time = match ( has_date, has_time) {
73
- ( true , true ) => NaiveDateTime :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?,
74
- ( true , false ) => {
75
- let date = NaiveDate :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?;
76
-
77
- date. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( )
78
- }
79
- ( false , true ) => {
80
- let time = NaiveTime :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?;
81
-
82
- NaiveDate :: from_ymd_opt ( 0 , 1 , 1 ) . unwrap ( ) . and_time ( time)
83
- }
84
- ( false , false ) => return None ,
85
- } ;
86
-
87
- Some ( DateTimeIncrementor {
88
- date_time,
89
- range,
90
- fmt : format. fmt ,
91
- field,
92
- } )
93
- } )
94
- }
95
- }
96
-
97
- impl Increment for DateTimeIncrementor {
98
- fn increment ( & self , amount : i64 ) -> ( Range , Tendril ) {
99
- let date_time = match self . field . unit {
100
- DateUnit :: Years => add_years ( self . date_time , amount) ,
101
- DateUnit :: Months => add_months ( self . date_time , amount) ,
102
- DateUnit :: Days => add_duration ( self . date_time , Duration :: days ( amount) ) ,
103
- DateUnit :: Hours => add_duration ( self . date_time , Duration :: hours ( amount) ) ,
104
- DateUnit :: Minutes => add_duration ( self . date_time , Duration :: minutes ( amount) ) ,
105
- DateUnit :: Seconds => add_duration ( self . date_time , Duration :: seconds ( amount) ) ,
106
- DateUnit :: AmPm => toggle_am_pm ( self . date_time ) ,
43
+ ( false , true ) => {
44
+ let time = NaiveTime :: parse_from_str ( date_time, format. fmt ) . ok ( ) ?;
45
+ let ( adjusted_time, _) = time. overflowing_add_signed ( Duration :: minutes ( amount) ) ;
46
+ Some ( adjusted_time. format ( format. fmt ) . to_string ( ) )
47
+ }
48
+ ( false , false ) => None ,
107
49
}
108
- . unwrap_or ( self . date_time ) ;
109
-
110
- ( self . range , date_time. format ( self . fmt ) . to_string ( ) . into ( ) )
111
- }
50
+ } )
112
51
}
113
52
114
53
static FORMATS : Lazy < Vec < Format > > = Lazy :: new ( || {
@@ -144,7 +83,7 @@ impl Format {
144
83
fn new ( fmt : & ' static str ) -> Self {
145
84
let mut remaining = fmt;
146
85
let mut fields = Vec :: new ( ) ;
147
- let mut regex = String :: new ( ) ;
86
+ let mut regex = "^" . to_string ( ) ;
148
87
let mut max_len = 0 ;
149
88
150
89
while let Some ( i) = remaining. find ( '%' ) {
@@ -166,6 +105,7 @@ impl Format {
166
105
write ! ( regex, "({})" , field. regex) . unwrap ( ) ;
167
106
remaining = & after[ spec_len..] ;
168
107
}
108
+ regex += "$" ;
169
109
170
110
let regex = Regex :: new ( & regex) . unwrap ( ) ;
171
111
@@ -305,155 +245,47 @@ impl DateUnit {
305
245
}
306
246
}
307
247
308
- fn ndays_in_month ( year : i32 , month : u32 ) -> u32 {
309
- // The first day of the next month...
310
- let ( y, m) = if month == 12 {
311
- ( year + 1 , 1 )
312
- } else {
313
- ( year, month + 1 )
314
- } ;
315
- let d = NaiveDate :: from_ymd_opt ( y, m, 1 ) . unwrap ( ) ;
316
-
317
- // ...is preceded by the last day of the original month.
318
- d. pred_opt ( ) . unwrap ( ) . day ( )
319
- }
320
-
321
- fn add_months ( date_time : NaiveDateTime , amount : i64 ) -> Option < NaiveDateTime > {
322
- let month = ( date_time. month0 ( ) as i64 ) . checked_add ( amount) ?;
323
- let year = date_time. year ( ) + i32:: try_from ( month / 12 ) . ok ( ) ?;
324
- let year = if month. is_negative ( ) { year - 1 } else { year } ;
325
-
326
- // Normalize month
327
- let month = month % 12 ;
328
- let month = if month. is_negative ( ) {
329
- month + 12
330
- } else {
331
- month
332
- } as u32
333
- + 1 ;
334
-
335
- let day = cmp:: min ( date_time. day ( ) , ndays_in_month ( year, month) ) ;
336
-
337
- NaiveDate :: from_ymd_opt ( year, month, day) . map ( |date| date. and_time ( date_time. time ( ) ) )
338
- }
339
-
340
- fn add_years ( date_time : NaiveDateTime , amount : i64 ) -> Option < NaiveDateTime > {
341
- let year = i32:: try_from ( ( date_time. year ( ) as i64 ) . checked_add ( amount) ?) . ok ( ) ?;
342
- let ndays = ndays_in_month ( year, date_time. month ( ) ) ;
343
-
344
- if date_time. day ( ) > ndays {
345
- NaiveDate :: from_ymd_opt ( year, date_time. month ( ) , ndays)
346
- . and_then ( |date| date. succ_opt ( ) . map ( |date| date. and_time ( date_time. time ( ) ) ) )
347
- } else {
348
- date_time. with_year ( year)
349
- }
350
- }
351
-
352
- fn add_duration ( date_time : NaiveDateTime , duration : Duration ) -> Option < NaiveDateTime > {
353
- date_time. checked_add_signed ( duration)
354
- }
355
-
356
- fn toggle_am_pm ( date_time : NaiveDateTime ) -> Option < NaiveDateTime > {
357
- if date_time. hour ( ) < 12 {
358
- add_duration ( date_time, Duration :: hours ( 12 ) )
359
- } else {
360
- add_duration ( date_time, Duration :: hours ( -12 ) )
361
- }
362
- }
363
-
364
248
#[ cfg( test) ]
365
249
mod test {
366
250
use super :: * ;
367
- use crate :: Rope ;
368
251
369
252
#[ test]
370
253
fn test_increment_date_times ( ) {
371
254
let tests = [
372
255
// (original, cursor, amount, expected)
373
- ( "2020-02-28" , 0 , 1 , "2021-02-28" ) ,
374
- ( "2020-02-29" , 0 , 1 , "2021-03-01" ) ,
375
- ( "2020-01-31" , 5 , 1 , "2020-02-29" ) ,
376
- ( "2020-01-20" , 5 , 1 , "2020-02-20" ) ,
377
- ( "2021-01-01" , 5 , -1 , "2020-12-01" ) ,
378
- ( "2021-01-31" , 5 , -2 , "2020-11-30" ) ,
379
- ( "2020-02-28" , 8 , 1 , "2020-02-29" ) ,
380
- ( "2021-02-28" , 8 , 1 , "2021-03-01" ) ,
381
- ( "2021-02-28" , 0 , -1 , "2020-02-28" ) ,
382
- ( "2021-03-01" , 0 , -1 , "2020-03-01" ) ,
383
- ( "2020-02-29" , 5 , -1 , "2020-01-29" ) ,
384
- ( "2020-02-20" , 5 , -1 , "2020-01-20" ) ,
385
- ( "2020-02-29" , 8 , -1 , "2020-02-28" ) ,
386
- ( "2021-03-01" , 8 , -1 , "2021-02-28" ) ,
387
- ( "1980/12/21" , 8 , 100 , "1981/03/31" ) ,
388
- ( "1980/12/21" , 8 , -100 , "1980/09/12" ) ,
389
- ( "1980/12/21" , 8 , 1000 , "1983/09/17" ) ,
390
- ( "1980/12/21" , 8 , -1000 , "1978/03/27" ) ,
391
- ( "2021-11-24 07:12:23" , 0 , 1 , "2022-11-24 07:12:23" ) ,
392
- ( "2021-11-24 07:12:23" , 5 , 1 , "2021-12-24 07:12:23" ) ,
393
- ( "2021-11-24 07:12:23" , 8 , 1 , "2021-11-25 07:12:23" ) ,
394
- ( "2021-11-24 07:12:23" , 11 , 1 , "2021-11-24 08:12:23" ) ,
395
- ( "2021-11-24 07:12:23" , 14 , 1 , "2021-11-24 07:13:23" ) ,
396
- ( "2021-11-24 07:12:23" , 17 , 1 , "2021-11-24 07:12:24" ) ,
397
- ( "2021/11/24 07:12:23" , 0 , 1 , "2022/11/24 07:12:23" ) ,
398
- ( "2021/11/24 07:12:23" , 5 , 1 , "2021/12/24 07:12:23" ) ,
399
- ( "2021/11/24 07:12:23" , 8 , 1 , "2021/11/25 07:12:23" ) ,
400
- ( "2021/11/24 07:12:23" , 11 , 1 , "2021/11/24 08:12:23" ) ,
401
- ( "2021/11/24 07:12:23" , 14 , 1 , "2021/11/24 07:13:23" ) ,
402
- ( "2021/11/24 07:12:23" , 17 , 1 , "2021/11/24 07:12:24" ) ,
403
- ( "2021-11-24 07:12" , 0 , 1 , "2022-11-24 07:12" ) ,
404
- ( "2021-11-24 07:12" , 5 , 1 , "2021-12-24 07:12" ) ,
405
- ( "2021-11-24 07:12" , 8 , 1 , "2021-11-25 07:12" ) ,
406
- ( "2021-11-24 07:12" , 11 , 1 , "2021-11-24 08:12" ) ,
407
- ( "2021-11-24 07:12" , 14 , 1 , "2021-11-24 07:13" ) ,
408
- ( "2021/11/24 07:12" , 0 , 1 , "2022/11/24 07:12" ) ,
409
- ( "2021/11/24 07:12" , 5 , 1 , "2021/12/24 07:12" ) ,
410
- ( "2021/11/24 07:12" , 8 , 1 , "2021/11/25 07:12" ) ,
411
- ( "2021/11/24 07:12" , 11 , 1 , "2021/11/24 08:12" ) ,
412
- ( "2021/11/24 07:12" , 14 , 1 , "2021/11/24 07:13" ) ,
413
- ( "Wed Nov 24 2021" , 0 , 1 , "Thu Nov 25 2021" ) ,
414
- ( "Wed Nov 24 2021" , 4 , 1 , "Fri Dec 24 2021" ) ,
415
- ( "Wed Nov 24 2021" , 8 , 1 , "Thu Nov 25 2021" ) ,
416
- ( "Wed Nov 24 2021" , 11 , 1 , "Thu Nov 24 2022" ) ,
417
- ( "24-Nov-2021" , 0 , 1 , "25-Nov-2021" ) ,
418
- ( "24-Nov-2021" , 3 , 1 , "24-Dec-2021" ) ,
419
- ( "24-Nov-2021" , 7 , 1 , "24-Nov-2022" ) ,
420
- ( "2021 Nov 24" , 0 , 1 , "2022 Nov 24" ) ,
421
- ( "2021 Nov 24" , 5 , 1 , "2021 Dec 24" ) ,
422
- ( "2021 Nov 24" , 9 , 1 , "2021 Nov 25" ) ,
423
- ( "Nov 24, 2021" , 0 , 1 , "Dec 24, 2021" ) ,
424
- ( "Nov 24, 2021" , 4 , 1 , "Nov 25, 2021" ) ,
425
- ( "Nov 24, 2021" , 8 , 1 , "Nov 24, 2022" ) ,
426
- ( "7:21:53 am" , 0 , 1 , "8:21:53 am" ) ,
427
- ( "7:21:53 am" , 3 , 1 , "7:22:53 am" ) ,
428
- ( "7:21:53 am" , 5 , 1 , "7:21:54 am" ) ,
429
- ( "7:21:53 am" , 8 , 1 , "7:21:53 pm" ) ,
430
- ( "7:21:53 AM" , 0 , 1 , "8:21:53 AM" ) ,
431
- ( "7:21:53 AM" , 3 , 1 , "7:22:53 AM" ) ,
432
- ( "7:21:53 AM" , 5 , 1 , "7:21:54 AM" ) ,
433
- ( "7:21:53 AM" , 8 , 1 , "7:21:53 PM" ) ,
434
- ( "7:21 am" , 0 , 1 , "8:21 am" ) ,
435
- ( "7:21 am" , 3 , 1 , "7:22 am" ) ,
436
- ( "7:21 am" , 5 , 1 , "7:21 pm" ) ,
437
- ( "7:21 AM" , 0 , 1 , "8:21 AM" ) ,
438
- ( "7:21 AM" , 3 , 1 , "7:22 AM" ) ,
439
- ( "7:21 AM" , 5 , 1 , "7:21 PM" ) ,
440
- ( "23:24:23" , 1 , 1 , "00:24:23" ) ,
441
- ( "23:24:23" , 3 , 1 , "23:25:23" ) ,
442
- ( "23:24:23" , 6 , 1 , "23:24:24" ) ,
443
- ( "23:24" , 1 , 1 , "00:24" ) ,
444
- ( "23:24" , 3 , 1 , "23:25" ) ,
256
+ ( "2020-02-28" , 1 , "2020-02-29" ) ,
257
+ ( "2020-02-29" , 1 , "2020-03-01" ) ,
258
+ ( "2020-01-31" , 1 , "2020-02-01" ) ,
259
+ ( "2020-01-20" , 1 , "2020-01-21" ) ,
260
+ ( "2021-01-01" , -1 , "2020-12-31" ) ,
261
+ ( "2021-01-31" , -2 , "2021-01-29" ) ,
262
+ ( "2020-02-28" , 1 , "2020-02-29" ) ,
263
+ ( "2021-02-28" , 1 , "2021-03-01" ) ,
264
+ ( "2021-03-01" , -1 , "2021-02-28" ) ,
265
+ ( "2020-02-29" , -1 , "2020-02-28" ) ,
266
+ ( "2020-02-20" , -1 , "2020-02-19" ) ,
267
+ ( "2021-03-01" , -1 , "2021-02-28" ) ,
268
+ ( "1980/12/21" , 100 , "1981/03/31" ) ,
269
+ ( "1980/12/21" , -100 , "1980/09/12" ) ,
270
+ ( "1980/12/21" , 1000 , "1983/09/17" ) ,
271
+ ( "1980/12/21" , -1000 , "1978/03/27" ) ,
272
+ ( "2021-11-24 07:12:23" , 1 , "2021-11-24 07:13:23" ) ,
273
+ ( "2021-11-24 07:12" , 1 , "2021-11-24 07:13" ) ,
274
+ ( "Wed Nov 24 2021" , 1 , "Thu Nov 25 2021" ) ,
275
+ ( "24-Nov-2021" , 1 , "25-Nov-2021" ) ,
276
+ ( "2021 Nov 24" , 1 , "2021 Nov 25" ) ,
277
+ ( "Nov 24, 2021" , 1 , "Nov 25, 2021" ) ,
278
+ ( "7:21:53 am" , 1 , "7:22:53 am" ) ,
279
+ ( "7:21:53 AM" , 1 , "7:22:53 AM" ) ,
280
+ ( "7:21 am" , 1 , "7:22 am" ) ,
281
+ ( "23:24:23" , 1 , "23:25:23" ) ,
282
+ ( "23:24" , 1 , "23:25" ) ,
283
+ ( "23:59" , 1 , "00:00" ) ,
284
+ ( "23:59:59" , 1 , "00:00:59" ) ,
445
285
] ;
446
286
447
- for ( original, cursor, amount, expected) in tests {
448
- let rope = Rope :: from_str ( original) ;
449
- let range = Range :: new ( cursor, cursor + 1 ) ;
450
- assert_eq ! (
451
- DateTimeIncrementor :: from_range( rope. slice( ..) , range)
452
- . unwrap( )
453
- . increment( amount)
454
- . 1 ,
455
- Tendril :: from( expected)
456
- ) ;
287
+ for ( original, amount, expected) in tests {
288
+ assert_eq ! ( increment( original, amount) . unwrap( ) , expected) ;
457
289
}
458
290
}
459
291
@@ -482,10 +314,7 @@ mod test {
482
314
] ;
483
315
484
316
for invalid in tests {
485
- let rope = Rope :: from_str ( invalid) ;
486
- let range = Range :: new ( 0 , 1 ) ;
487
-
488
- assert_eq ! ( DateTimeIncrementor :: from_range( rope. slice( ..) , range) , None )
317
+ assert_eq ! ( increment( invalid, 1 ) , None )
489
318
}
490
319
}
491
320
}
0 commit comments