1 use std
::convert
::TryInto
;
4 use bitflags
::bitflags
;
8 use crate::{parse_calendar_event, parse_time_span}
;
11 /// Defines one or more days of a week.
13 pub struct WeekDays
: u8 {
24 #[derive(Debug, Clone)]
25 pub(crate) enum DateTimeValue
{
32 // Test if the entry contains the value
33 pub fn contains(&self, value
: u32) -> bool
{
35 DateTimeValue
::Single(v
) => *v
== value
,
36 DateTimeValue
::Range(start
, end
) => value
>= *start
&& value
<= *end
,
37 DateTimeValue
::Repeated(start
, repetition
) => {
40 let offset
= value
- start
;
41 offset
% repetition
== 0
52 pub fn list_contains(list
: &[DateTimeValue
], value
: u32) -> bool
{
53 list
.iter().any(|spec
| spec
.contains(value
))
56 // Find an return an entry greater than value
57 pub fn find_next(list
: &[DateTimeValue
], value
: u32) -> Option
<u32> {
58 let mut next
: Option
<u32> = None
;
59 let mut set_next
= |v
: u32| {
60 if let Some(n
) = next
{
61 if v
< n { next = Some(v); }
68 DateTimeValue
::Single(v
) => {
69 if *v
> value { set_next(*v); }
71 DateTimeValue
::Range(start
, end
) => {
76 if n
>= *start
&& n
<= *end
{
81 DateTimeValue
::Repeated(start
, repetition
) => {
84 } else if *repetition
> 0 {
85 set_next(start
+ ((value
- start
+ repetition
) / repetition
) * repetition
);
95 /// Calendar events may be used to refer to one or more points in time in a
96 /// single expression. They are designed after the systemd.time Calendar Events
97 /// specification, but are not guaranteed to be 100% compatible.
98 #[derive(Default, Clone, Debug)]
99 pub struct CalendarEvent
{
100 /// the days in a week this event should trigger
101 pub(crate) days
: WeekDays
,
102 /// the second(s) this event should trigger
103 pub(crate) second
: Vec
<DateTimeValue
>, // todo: support float values
104 /// the minute(s) this event should trigger
105 pub(crate) minute
: Vec
<DateTimeValue
>,
106 /// the hour(s) this event should trigger
107 pub(crate) hour
: Vec
<DateTimeValue
>,
108 /// the day(s) in a month this event should trigger
109 pub(crate) day
: Vec
<DateTimeValue
>,
110 /// the month(s) in a year this event should trigger
111 pub(crate) month
: Vec
<DateTimeValue
>,
112 /// the years(s) this event should trigger
113 pub(crate) year
: Vec
<DateTimeValue
>,
116 /// A time spans defines a time duration
117 #[derive(Default, Clone, Debug)]
118 pub struct TimeSpan
{
131 impl From
<TimeSpan
> for f64 {
132 fn from(ts
: TimeSpan
) -> Self {
133 (ts
.seconds
as f64) +
134 ((ts
.nsec
as f64) / 1_000_000_000.0) +
135 ((ts
.usec
as f64) / 1_000_000.0) +
136 ((ts
.msec
as f64) / 1_000.0) +
137 ((ts
.minutes
as f64) * 60.0) +
138 ((ts
.hours
as f64) * 3600.0) +
139 ((ts
.days
as f64) * 3600.0 * 24.0) +
140 ((ts
.weeks
as f64) * 3600.0 * 24.0 * 7.0) +
141 ((ts
.months
as f64) * 3600.0 * 24.0 * 30.44) +
142 ((ts
.years
as f64) * 3600.0 * 24.0 * 365.25)
146 impl From
<std
::time
::Duration
> for TimeSpan
{
147 fn from(duration
: std
::time
::Duration
) -> Self {
148 let mut duration
= duration
.as_nanos();
149 let nsec
= (duration
% 1000) as u64;
151 let usec
= (duration
% 1000) as u64;
153 let msec
= (duration
% 1000) as u64;
155 let seconds
= (duration
% 60) as u64;
157 let minutes
= (duration
% 60) as u64;
159 let hours
= (duration
% 24) as u64;
161 let years
= (duration
as f64 / 365.25) as u64;
162 let ydays
= (duration
as f64 % 365.25) as u64;
163 let months
= (ydays
as f64 / 30.44) as u64;
164 let mdays
= (ydays
as f64 % 30.44) as u64;
165 let weeks
= mdays
/ 7;
166 let days
= mdays
% 7;
182 impl std
::fmt
::Display
for TimeSpan
{
183 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> Result
<(), std
::fmt
::Error
> {
184 let mut first
= true;
185 { // block scope for mutable borrows
186 let mut do_write
= |v
: u64, unit
: &str| -> Result
<(), std
::fmt
::Error
> {
191 write
!(f
, "{}{}", v
, unit
)
194 do_write(self.years
, "y")?
;
197 do_write(self.months
, "m")?
;
200 do_write(self.weeks
, "w")?
;
203 do_write(self.days
, "d")?
;
206 do_write(self.hours
, "h")?
;
208 if self.minutes
> 0 {
209 do_write(self.minutes
, "min")?
;
215 let seconds
= self.seconds
as f64 + (self.msec
as f64 / 1000.0);
217 if seconds
>= 1.0 || !first
{
218 write
!(f
, "{:.0}s", seconds
)?
;
220 write
!(f
, "{:.1}s", seconds
)?
;
229 /// Verify the format of the [TimeSpan]
230 pub fn verify_time_span(i
: &str) -> Result
<(), Error
> {
235 /// Verify the format of the [CalendarEvent]
236 pub fn verify_calendar_event(i
: &str) -> Result
<(), Error
> {
237 parse_calendar_event(i
)?
;
241 /// Compute the next event
242 pub fn compute_next_event(
243 event
: &CalendarEvent
,
246 ) -> Result
<Option
<i64>, Error
> {
248 let last
= last
+ 1; // at least one second later
250 let all_days
= event
.days
.is_empty() || event
.days
.is_all();
252 let mut t
= TmEditor
::with_epoch(last
, utc
)?
;
257 // cancel after 1000 loops
264 if !event
.year
.is_empty() {
265 let year
: u32 = t
.year().try_into()?
;
266 if !DateTimeValue
::list_contains(&event
.year
, year
) {
267 if let Some(n
) = DateTimeValue
::find_next(&event
.year
, year
) {
268 t
.add_years((n
- year
).try_into()?
)?
;
271 // if we have no valid year, we cannot find a correct timestamp
277 if !event
.month
.is_empty() {
278 let month
: u32 = t
.month().try_into()?
;
279 if !DateTimeValue
::list_contains(&event
.month
, month
) {
280 if let Some(n
) = DateTimeValue
::find_next(&event
.month
, month
) {
281 t
.add_months((n
- month
).try_into()?
)?
;
283 // if we could not find valid month, retry next year
290 if !event
.day
.is_empty() {
291 let day
: u32 = t
.day().try_into()?
;
292 if !DateTimeValue
::list_contains(&event
.day
, day
) {
293 if let Some(n
) = DateTimeValue
::find_next(&event
.day
, day
) {
294 t
.add_days((n
- day
).try_into()?
)?
;
296 // if we could not find valid mday, retry next month
303 if !all_days
{ // match day first
304 let day_num
: u32 = t
.day_num().try_into()?
;
305 let day
= WeekDays
::from_bits(1<<day_num
).unwrap();
306 if !event
.days
.contains(day
) {
307 if let Some(n
) = ((day_num
+1)..7)
308 .find(|d
| event
.days
.contains(WeekDays
::from_bits(1<<d
).unwrap()))
311 t
.add_days((n
- day_num
).try_into()?
)?
;
314 t
.add_days((7 - day_num
).try_into()?
)?
;
321 if !event
.hour
.is_empty() {
322 let hour
= t
.hour().try_into()?
;
323 if !DateTimeValue
::list_contains(&event
.hour
, hour
) {
324 if let Some(n
) = DateTimeValue
::find_next(&event
.hour
, hour
) {
326 t
.set_time(n
.try_into()?
, 0, 0)?
;
336 if !event
.minute
.is_empty() {
337 let minute
= t
.min().try_into()?
;
338 if !DateTimeValue
::list_contains(&event
.minute
, minute
) {
339 if let Some(n
) = DateTimeValue
::find_next(&event
.minute
, minute
) {
341 t
.set_min_sec(n
.try_into()?
, 0)?
;
344 t
.set_time(t
.hour() + 1, 0, 0)?
;
351 if !event
.second
.is_empty() {
352 let second
= t
.sec().try_into()?
;
353 if !DateTimeValue
::list_contains(&event
.second
, second
) {
354 if let Some(n
) = DateTimeValue
::find_next(&event
.second
, second
) {
356 t
.set_sec(n
.try_into()?
)?
;
359 t
.set_min_sec(t
.min() + 1, 0)?
;
365 let next
= t
.into_epoch()?
;
366 return Ok(Some(next
))
377 fn test_event(v
: &'
static str) -> Result
<(), Error
> {
378 match parse_calendar_event(v
) {
379 Ok(event
) => println
!("CalendarEvent '{}' => {:?}", v
, event
),
380 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
386 const fn make_test_time(mday
: i32, hour
: i32, min
: i32) -> i64 {
387 (mday
*3600*24 + hour
*3600 + min
*60) as i64
391 fn test_compute_next_event() -> Result
<(), Error
> {
393 let test_value
= |v
: &'
static str, last
: i64, expect
: i64| -> Result
<i64, Error
> {
394 let event
= match parse_calendar_event(v
) {
396 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
399 match compute_next_event(&event
, last
, true) {
402 println
!("next {:?} => {}", event
, next
);
405 "next {:?} failed\nnext: {:?}\nexpect: {:?}",
408 crate::gmtime(expect
),
412 Ok(None
) => bail
!("next {:?} failed to find a timestamp", event
),
413 Err(err
) => bail
!("compute next for '{}' failed - {}", v
, err
),
419 let test_never
= |v
: &'
static str, last
: i64| -> Result
<(), Error
> {
420 let event
= match parse_calendar_event(v
) {
422 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
425 match compute_next_event(&event
, last
, true)?
{
427 Some(next
) => bail
!("compute next for '{}' succeeded, but expected fail - result {}", v
, next
),
432 const HOUR
: i64 = 3600;
433 const DAY
: i64 = 3600*24;
435 const THURSDAY_00_00
: i64 = make_test_time(0, 0, 0);
436 const THURSDAY_15_00
: i64 = make_test_time(0, 15, 0);
438 const JUL_31_2020
: i64 = 1596153600; // Friday, 2020-07-31 00:00:00
439 const DEC_31_2020
: i64 = 1609372800; // Thursday, 2020-12-31 00:00:00
441 test_value("*:0", THURSDAY_00_00
, THURSDAY_00_00
+ HOUR
)?
;
442 test_value("*:*", THURSDAY_00_00
, THURSDAY_00_00
+ MIN
)?
;
443 test_value("*:*:*", THURSDAY_00_00
, THURSDAY_00_00
+ 1)?
;
444 test_value("*:3:5", THURSDAY_00_00
, THURSDAY_00_00
+ 3*MIN
+ 5)?
;
446 test_value("mon *:*", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
)?
;
447 test_value("mon 2:*", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
+ 2*HOUR
)?
;
448 test_value("mon 2:50", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
+ 2*HOUR
+ 50*MIN
)?
;
450 test_value("tue", THURSDAY_00_00
, THURSDAY_00_00
+ 5*DAY
)?
;
451 test_value("wed", THURSDAY_00_00
, THURSDAY_00_00
+ 6*DAY
)?
;
452 test_value("thu", THURSDAY_00_00
, THURSDAY_00_00
+ 7*DAY
)?
;
453 test_value("fri", THURSDAY_00_00
, THURSDAY_00_00
+ 1*DAY
)?
;
454 test_value("sat", THURSDAY_00_00
, THURSDAY_00_00
+ 2*DAY
)?
;
455 test_value("sun", THURSDAY_00_00
, THURSDAY_00_00
+ 3*DAY
)?
;
457 // test multiple values for a single field
458 // and test that the order does not matter
459 test_value("5,10:4,8", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
+ 4*MIN
)?
;
460 test_value("10,5:8,4", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
+ 4*MIN
)?
;
461 test_value("6,4..10:23,5/5", THURSDAY_00_00
, THURSDAY_00_00
+ 4*HOUR
+ 5*MIN
)?
;
462 test_value("4..10,6:5/5,23", THURSDAY_00_00
, THURSDAY_00_00
+ 4*HOUR
+ 5*MIN
)?
;
464 // test month wrapping
465 test_value("sat", JUL_31_2020
, JUL_31_2020
+ 1*DAY
)?
;
466 test_value("sun", JUL_31_2020
, JUL_31_2020
+ 2*DAY
)?
;
467 test_value("mon", JUL_31_2020
, JUL_31_2020
+ 3*DAY
)?
;
468 test_value("tue", JUL_31_2020
, JUL_31_2020
+ 4*DAY
)?
;
469 test_value("wed", JUL_31_2020
, JUL_31_2020
+ 5*DAY
)?
;
470 test_value("thu", JUL_31_2020
, JUL_31_2020
+ 6*DAY
)?
;
471 test_value("fri", JUL_31_2020
, JUL_31_2020
+ 7*DAY
)?
;
473 // test year wrapping
474 test_value("fri", DEC_31_2020
, DEC_31_2020
+ 1*DAY
)?
;
475 test_value("sat", DEC_31_2020
, DEC_31_2020
+ 2*DAY
)?
;
476 test_value("sun", DEC_31_2020
, DEC_31_2020
+ 3*DAY
)?
;
477 test_value("mon", DEC_31_2020
, DEC_31_2020
+ 4*DAY
)?
;
478 test_value("tue", DEC_31_2020
, DEC_31_2020
+ 5*DAY
)?
;
479 test_value("wed", DEC_31_2020
, DEC_31_2020
+ 6*DAY
)?
;
480 test_value("thu", DEC_31_2020
, DEC_31_2020
+ 7*DAY
)?
;
482 test_value("daily", THURSDAY_00_00
, THURSDAY_00_00
+ DAY
)?
;
483 test_value("daily", THURSDAY_00_00
+1, THURSDAY_00_00
+ DAY
)?
;
485 let n
= test_value("5/2:0", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
)?
;
486 let n
= test_value("5/2:0", n
, THURSDAY_00_00
+ 7*HOUR
)?
;
487 let n
= test_value("5/2:0", n
, THURSDAY_00_00
+ 9*HOUR
)?
;
488 test_value("5/2:0", n
, THURSDAY_00_00
+ 11*HOUR
)?
;
490 let mut n
= test_value("*:*", THURSDAY_00_00
, THURSDAY_00_00
+ MIN
)?
;
492 n
= test_value("*:*", n
, THURSDAY_00_00
+ i
*MIN
)?
;
495 let mut n
= test_value("*:0", THURSDAY_00_00
, THURSDAY_00_00
+ HOUR
)?
;
497 n
= test_value("*:0", n
, THURSDAY_00_00
+ i
*HOUR
)?
;
500 let mut n
= test_value("1:0", THURSDAY_15_00
, THURSDAY_00_00
+ DAY
+ HOUR
)?
;
502 n
= test_value("1:0", n
, THURSDAY_00_00
+ i
*DAY
+ HOUR
)?
;
505 // test date functionality
507 test_value("2020-07-31", 0, JUL_31_2020
)?
;
508 test_value("02-28", 0, (31+27)*DAY
)?
;
509 test_value("02-29", 0, 2*365*DAY
+ (31+28)*DAY
)?
; // 1972-02-29
510 test_value("1965/5-01-01", -1, THURSDAY_00_00
)?
;
511 test_value("2020-7..9-2/2", JUL_31_2020
, JUL_31_2020
+ 2*DAY
)?
;
512 test_value("2020,2021-12-31", JUL_31_2020
, DEC_31_2020
)?
;
514 test_value("monthly", 0, 31*DAY
)?
;
515 test_value("quarterly", 0, (31+28+31)*DAY
)?
;
516 test_value("semiannually", 0, (31+28+31+30+31+30)*DAY
)?
;
517 test_value("yearly", 0, (365)*DAY
)?
;
519 test_never("2021-02-29", 0)?
;
520 test_never("02-30", 0)?
;
526 fn test_calendar_event_weekday() -> Result
<(), Error
> {
527 test_event("mon,wed..fri")?
;
528 test_event("fri..mon")?
;
532 test_event("monDay")?
;
534 test_event("Tuesday")?
;
536 test_event("wednesday")?
;
538 test_event("thursday")?
;
540 test_event("friday")?
;
542 test_event("saturday")?
;
544 test_event("sunday")?
;
546 test_event("mon..fri")?
;
547 test_event("mon,tue,fri")?
;
548 test_event("mon,tue..wednesday,fri..sat")?
;
554 fn test_time_span_parser() -> Result
<(), Error
> {
556 let test_value
= |ts_str
: &str, expect
: f64| -> Result
<(), Error
> {
557 let ts
= parse_time_span(ts_str
)?
;
558 assert_eq
!(f64::from(ts
), expect
, "{}", ts_str
);
562 test_value("2", 2.0)?
;
563 test_value("2s", 2.0)?
;
564 test_value("2sec", 2.0)?
;
565 test_value("2second", 2.0)?
;
566 test_value("2seconds", 2.0)?
;
568 test_value(" 2s 2 s 2", 6.0)?
;
570 test_value("1msec 1ms", 0.002)?
;
571 test_value("1usec 1us 1µs", 0.000_003)?
;
572 test_value("1nsec 1ns", 0.000_000_002)?
;
573 test_value("1minutes 1minute 1min 1m", 4.0*60.0)?
;
574 test_value("1hours 1hour 1hr 1h", 4.0*3600.0)?
;
575 test_value("1days 1day 1d", 3.0*86400.0)?
;
576 test_value("1weeks 1 week 1w", 3.0*86400.0*7.0)?
;
577 test_value("1months 1month 1M", 3.0*86400.0*30.44)?
;
578 test_value("1years 1year 1y", 3.0*86400.0*365.25)?
;
580 test_value("2h", 7200.0)?
;
581 test_value(" 2 h", 7200.0)?
;
582 test_value("2hours", 7200.0)?
;
583 test_value("48hr", 48.0*3600.0)?
;
584 test_value("1y 12month", 365.25*24.0*3600.0 + 12.0*30.44*24.0*3600.0)?
;
585 test_value("55s500ms", 55.5)?
;
586 test_value("300ms20s 5day", 5.0*24.0*3600.0 + 20.0 + 0.3)?
;