]>
git.proxmox.com Git - proxmox-backup.git/blob - src/tools/systemd/time.rs
1 use std
::convert
::TryInto
;
4 use bitflags
::bitflags
;
6 pub use super::parse_time
::*;
7 use super::tm_editor
::*;
11 pub struct WeekDays
: u8 {
22 #[derive(Debug, Clone)]
23 pub enum DateTimeValue
{
30 // Test if the entry contains the value
31 pub fn contains(&self, value
: u32) -> bool
{
33 DateTimeValue
::Single(v
) => *v
== value
,
34 DateTimeValue
::Range(start
, end
) => value
>= *start
&& value
<= *end
,
35 DateTimeValue
::Repeated(start
, repetition
) => {
38 let offset
= value
- start
;
39 offset
% repetition
== 0
50 pub fn list_contains(list
: &[DateTimeValue
], value
: u32) -> bool
{
51 list
.iter().find(|spec
| spec
.contains(value
)).is_some()
54 // Find an return an entry greater than value
55 pub fn find_next(list
: &[DateTimeValue
], value
: u32) -> Option
<u32> {
56 let mut next
: Option
<u32> = None
;
57 let mut set_next
= |v
: u32| {
58 if let Some(n
) = next
{
59 if v
< n { next = Some(v); }
66 DateTimeValue
::Single(v
) => {
67 if *v
> value { set_next(*v); }
69 DateTimeValue
::Range(start
, end
) => {
74 if n
>= *start
&& n
<= *end
{
79 DateTimeValue
::Repeated(start
, repetition
) => {
82 } else if *repetition
> 0 {
83 set_next(start
+ ((value
- start
+ repetition
) / repetition
) * repetition
);
93 /// Calendar events may be used to refer to one or more points in time in a
94 /// single expression. They are designed after the systemd.time Calendar Events
95 /// specification, but are not guaranteed to be 100% compatible.
96 #[derive(Default, Clone, Debug)]
97 pub struct CalendarEvent
{
98 /// the days in a week this event should trigger
100 /// the second(s) this event should trigger
101 pub second
: Vec
<DateTimeValue
>, // todo: support float values
102 /// the minute(s) this event should trigger
103 pub minute
: Vec
<DateTimeValue
>,
104 /// the hour(s) this event should trigger
105 pub hour
: Vec
<DateTimeValue
>,
106 /// the day(s) in a month this event should trigger
107 pub day
: Vec
<DateTimeValue
>,
108 /// the month(s) in a year this event should trigger
109 pub month
: Vec
<DateTimeValue
>,
110 /// the years(s) this event should trigger
111 pub year
: Vec
<DateTimeValue
>,
114 #[derive(Default, Clone, Debug)]
115 pub struct TimeSpan
{
128 impl From
<TimeSpan
> for f64 {
129 fn from(ts
: TimeSpan
) -> Self {
130 (ts
.seconds
as f64) +
131 ((ts
.nsec
as f64) / 1_000_000_000.0) +
132 ((ts
.usec
as f64) / 1_000_000.0) +
133 ((ts
.msec
as f64) / 1_000.0) +
134 ((ts
.minutes
as f64) * 60.0) +
135 ((ts
.hours
as f64) * 3600.0) +
136 ((ts
.days
as f64) * 3600.0 * 24.0) +
137 ((ts
.weeks
as f64) * 3600.0 * 24.0 * 7.0) +
138 ((ts
.months
as f64) * 3600.0 * 24.0 * 30.44) +
139 ((ts
.years
as f64) * 3600.0 * 24.0 * 365.25)
144 pub fn verify_time_span
<'a
>(i
: &'a
str) -> Result
<(), Error
> {
149 pub fn verify_calendar_event(i
: &str) -> Result
<(), Error
> {
150 parse_calendar_event(i
)?
;
154 pub fn compute_next_event(
155 event
: &CalendarEvent
,
158 ) -> Result
<Option
<i64>, Error
> {
160 let last
= last
+ 1; // at least one second later
162 let all_days
= event
.days
.is_empty() || event
.days
.is_all();
164 let mut t
= TmEditor
::new(last
, utc
)?
;
169 // cancel after 1000 loops
176 if !event
.year
.is_empty() {
177 let year
: u32 = t
.year().try_into()?
;
178 if !DateTimeValue
::list_contains(&event
.year
, year
) {
179 if let Some(n
) = DateTimeValue
::find_next(&event
.year
, year
) {
180 t
.add_years((n
- year
).try_into()?
)?
;
183 // if we have no valid year, we cannot find a correct timestamp
189 if !event
.month
.is_empty() {
190 let month
: u32 = t
.month().try_into()?
;
191 if !DateTimeValue
::list_contains(&event
.month
, month
) {
192 if let Some(n
) = DateTimeValue
::find_next(&event
.month
, month
) {
193 t
.add_months((n
- month
).try_into()?
)?
;
195 // if we could not find valid month, retry next year
202 if !event
.day
.is_empty() {
203 let day
: u32 = t
.day().try_into()?
;
204 if !DateTimeValue
::list_contains(&event
.day
, day
) {
205 if let Some(n
) = DateTimeValue
::find_next(&event
.day
, day
) {
206 t
.add_days((n
- day
).try_into()?
)?
;
208 // if we could not find valid mday, retry next month
215 if !all_days
{ // match day first
216 let day_num
: u32 = t
.day_num().try_into()?
;
217 let day
= WeekDays
::from_bits(1<<day_num
).unwrap();
218 if !event
.days
.contains(day
) {
219 if let Some(n
) = ((day_num
+1)..7)
220 .find(|d
| event
.days
.contains(WeekDays
::from_bits(1<<d
).unwrap()))
223 t
.add_days((n
- day_num
).try_into()?
)?
;
226 t
.add_days((7 - day_num
).try_into()?
)?
;
233 if !event
.hour
.is_empty() {
234 let hour
= t
.hour().try_into()?
;
235 if !DateTimeValue
::list_contains(&event
.hour
, hour
) {
236 if let Some(n
) = DateTimeValue
::find_next(&event
.hour
, hour
) {
238 t
.set_time(n
.try_into()?
, 0, 0)?
;
248 if !event
.minute
.is_empty() {
249 let minute
= t
.min().try_into()?
;
250 if !DateTimeValue
::list_contains(&event
.minute
, minute
) {
251 if let Some(n
) = DateTimeValue
::find_next(&event
.minute
, minute
) {
253 t
.set_min_sec(n
.try_into()?
, 0)?
;
256 t
.set_time(t
.hour() + 1, 0, 0)?
;
263 if !event
.second
.is_empty() {
264 let second
= t
.sec().try_into()?
;
265 if !DateTimeValue
::list_contains(&event
.second
, second
) {
266 if let Some(n
) = DateTimeValue
::find_next(&event
.second
, second
) {
268 t
.set_sec(n
.try_into()?
)?
;
271 t
.set_min_sec(t
.min() + 1, 0)?
;
277 let next
= t
.into_epoch()?
;
278 return Ok(Some(next
))
288 use proxmox
::tools
::time
::*;
290 fn test_event(v
: &'
static str) -> Result
<(), Error
> {
291 match parse_calendar_event(v
) {
292 Ok(event
) => println
!("CalendarEvent '{}' => {:?}", v
, event
),
293 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
299 const fn make_test_time(mday
: i32, hour
: i32, min
: i32) -> libc
::time_t
{
300 (mday
*3600*24 + hour
*3600 + min
*60) as libc
::time_t
304 fn test_compute_next_event() -> Result
<(), Error
> {
306 let test_value
= |v
: &'
static str, last
: i64, expect
: i64| -> Result
<i64, Error
> {
307 let event
= match parse_calendar_event(v
) {
309 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
312 match compute_next_event(&event
, last
, true) {
315 println
!("next {:?} => {}", event
, next
);
317 bail
!("next {:?} failed\nnext: {:?}\nexpect: {:?}",
318 event
, gmtime(next
), gmtime(expect
));
321 Ok(None
) => bail
!("next {:?} failed to find a timestamp", event
),
322 Err(err
) => bail
!("compute next for '{}' failed - {}", v
, err
),
328 let test_never
= |v
: &'
static str, last
: i64| -> Result
<(), Error
> {
329 let event
= match parse_calendar_event(v
) {
331 Err(err
) => bail
!("parsing '{}' failed - {}", v
, err
),
334 match compute_next_event(&event
, last
, true)?
{
336 Some(next
) => bail
!("compute next for '{}' succeeded, but expected fail - result {}", v
, next
),
341 const HOUR
: i64 = 3600;
342 const DAY
: i64 = 3600*24;
344 const THURSDAY_00_00
: i64 = make_test_time(0, 0, 0);
345 const THURSDAY_15_00
: i64 = make_test_time(0, 15, 0);
347 const JUL_31_2020
: i64 = 1596153600; // Friday, 2020-07-31 00:00:00
348 const DEC_31_2020
: i64 = 1609372800; // Thursday, 2020-12-31 00:00:00
350 test_value("*:0", THURSDAY_00_00
, THURSDAY_00_00
+ HOUR
)?
;
351 test_value("*:*", THURSDAY_00_00
, THURSDAY_00_00
+ MIN
)?
;
352 test_value("*:*:*", THURSDAY_00_00
, THURSDAY_00_00
+ 1)?
;
353 test_value("*:3:5", THURSDAY_00_00
, THURSDAY_00_00
+ 3*MIN
+ 5)?
;
355 test_value("mon *:*", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
)?
;
356 test_value("mon 2:*", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
+ 2*HOUR
)?
;
357 test_value("mon 2:50", THURSDAY_00_00
, THURSDAY_00_00
+ 4*DAY
+ 2*HOUR
+ 50*MIN
)?
;
359 test_value("tue", THURSDAY_00_00
, THURSDAY_00_00
+ 5*DAY
)?
;
360 test_value("wed", THURSDAY_00_00
, THURSDAY_00_00
+ 6*DAY
)?
;
361 test_value("thu", THURSDAY_00_00
, THURSDAY_00_00
+ 7*DAY
)?
;
362 test_value("fri", THURSDAY_00_00
, THURSDAY_00_00
+ 1*DAY
)?
;
363 test_value("sat", THURSDAY_00_00
, THURSDAY_00_00
+ 2*DAY
)?
;
364 test_value("sun", THURSDAY_00_00
, THURSDAY_00_00
+ 3*DAY
)?
;
366 // test multiple values for a single field
367 // and test that the order does not matter
368 test_value("5,10:4,8", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
+ 4*MIN
)?
;
369 test_value("10,5:8,4", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
+ 4*MIN
)?
;
370 test_value("6,4..10:23,5/5", THURSDAY_00_00
, THURSDAY_00_00
+ 4*HOUR
+ 5*MIN
)?
;
371 test_value("4..10,6:5/5,23", THURSDAY_00_00
, THURSDAY_00_00
+ 4*HOUR
+ 5*MIN
)?
;
373 // test month wrapping
374 test_value("sat", JUL_31_2020
, JUL_31_2020
+ 1*DAY
)?
;
375 test_value("sun", JUL_31_2020
, JUL_31_2020
+ 2*DAY
)?
;
376 test_value("mon", JUL_31_2020
, JUL_31_2020
+ 3*DAY
)?
;
377 test_value("tue", JUL_31_2020
, JUL_31_2020
+ 4*DAY
)?
;
378 test_value("wed", JUL_31_2020
, JUL_31_2020
+ 5*DAY
)?
;
379 test_value("thu", JUL_31_2020
, JUL_31_2020
+ 6*DAY
)?
;
380 test_value("fri", JUL_31_2020
, JUL_31_2020
+ 7*DAY
)?
;
382 // test year wrapping
383 test_value("fri", DEC_31_2020
, DEC_31_2020
+ 1*DAY
)?
;
384 test_value("sat", DEC_31_2020
, DEC_31_2020
+ 2*DAY
)?
;
385 test_value("sun", DEC_31_2020
, DEC_31_2020
+ 3*DAY
)?
;
386 test_value("mon", DEC_31_2020
, DEC_31_2020
+ 4*DAY
)?
;
387 test_value("tue", DEC_31_2020
, DEC_31_2020
+ 5*DAY
)?
;
388 test_value("wed", DEC_31_2020
, DEC_31_2020
+ 6*DAY
)?
;
389 test_value("thu", DEC_31_2020
, DEC_31_2020
+ 7*DAY
)?
;
391 test_value("daily", THURSDAY_00_00
, THURSDAY_00_00
+ DAY
)?
;
392 test_value("daily", THURSDAY_00_00
+1, THURSDAY_00_00
+ DAY
)?
;
394 let n
= test_value("5/2:0", THURSDAY_00_00
, THURSDAY_00_00
+ 5*HOUR
)?
;
395 let n
= test_value("5/2:0", n
, THURSDAY_00_00
+ 7*HOUR
)?
;
396 let n
= test_value("5/2:0", n
, THURSDAY_00_00
+ 9*HOUR
)?
;
397 test_value("5/2:0", n
, THURSDAY_00_00
+ 11*HOUR
)?
;
399 let mut n
= test_value("*:*", THURSDAY_00_00
, THURSDAY_00_00
+ MIN
)?
;
401 n
= test_value("*:*", n
, THURSDAY_00_00
+ i
*MIN
)?
;
404 let mut n
= test_value("*:0", THURSDAY_00_00
, THURSDAY_00_00
+ HOUR
)?
;
406 n
= test_value("*:0", n
, THURSDAY_00_00
+ i
*HOUR
)?
;
409 let mut n
= test_value("1:0", THURSDAY_15_00
, THURSDAY_00_00
+ DAY
+ HOUR
)?
;
411 n
= test_value("1:0", n
, THURSDAY_00_00
+ i
*DAY
+ HOUR
)?
;
414 // test date functionality
416 test_value("2020-07-31", 0, JUL_31_2020
)?
;
417 test_value("02-28", 0, (31+27)*DAY
)?
;
418 test_value("02-29", 0, 2*365*DAY
+ (31+28)*DAY
)?
; // 1972-02-29
419 test_value("1965/5-01-01", -1, THURSDAY_00_00
)?
;
420 test_value("2020-7..9-2/2", JUL_31_2020
, JUL_31_2020
+ 2*DAY
)?
;
421 test_value("2020,2021-12-31", JUL_31_2020
, DEC_31_2020
)?
;
423 test_value("monthly", 0, 31*DAY
)?
;
424 test_value("quarterly", 0, (31+28+31)*DAY
)?
;
425 test_value("semiannually", 0, (31+28+31+30+31+30)*DAY
)?
;
426 test_value("yearly", 0, (365)*DAY
)?
;
428 test_never("2021-02-29", 0)?
;
429 test_never("02-30", 0)?
;
435 fn test_calendar_event_weekday() -> Result
<(), Error
> {
436 test_event("mon,wed..fri")?
;
437 test_event("fri..mon")?
;
441 test_event("monDay")?
;
443 test_event("Tuesday")?
;
445 test_event("wednesday")?
;
447 test_event("thursday")?
;
449 test_event("friday")?
;
451 test_event("saturday")?
;
453 test_event("sunday")?
;
455 test_event("mon..fri")?
;
456 test_event("mon,tue,fri")?
;
457 test_event("mon,tue..wednesday,fri..sat")?
;
463 fn test_time_span_parser() -> Result
<(), Error
> {
465 let test_value
= |ts_str
: &str, expect
: f64| -> Result
<(), Error
> {
466 let ts
= parse_time_span(ts_str
)?
;
467 assert_eq
!(f64::from(ts
), expect
, "{}", ts_str
);
471 test_value("2", 2.0)?
;
472 test_value("2s", 2.0)?
;
473 test_value("2sec", 2.0)?
;
474 test_value("2second", 2.0)?
;
475 test_value("2seconds", 2.0)?
;
477 test_value(" 2s 2 s 2", 6.0)?
;
479 test_value("1msec 1ms", 0.002)?
;
480 test_value("1usec 1us 1µs", 0.000_003)?
;
481 test_value("1nsec 1ns", 0.000_000_002)?
;
482 test_value("1minutes 1minute 1min 1m", 4.0*60.0)?
;
483 test_value("1hours 1hour 1hr 1h", 4.0*3600.0)?
;
484 test_value("1days 1day 1d", 3.0*86400.0)?
;
485 test_value("1weeks 1 week 1w", 3.0*86400.0*7.0)?
;
486 test_value("1months 1month 1M", 3.0*86400.0*30.44)?
;
487 test_value("1years 1year 1y", 3.0*86400.0*365.25)?
;
489 test_value("2h", 7200.0)?
;
490 test_value(" 2 h", 7200.0)?
;
491 test_value("2hours", 7200.0)?
;
492 test_value("48hr", 48.0*3600.0)?
;
493 test_value("1y 12month", 365.25*24.0*3600.0 + 12.0*30.44*24.0*3600.0)?
;
494 test_value("55s500ms", 55.5)?
;
495 test_value("300ms20s 5day", 5.0*24.0*3600.0 + 20.0 + 0.3)?
;