diff --git a/src/tools/systemd/time.rs b/src/tools/systemd/time.rs index 58d9dd21..ce66e5e1 100644 --- a/src/tools/systemd/time.rs +++ b/src/tools/systemd/time.rs @@ -4,6 +4,8 @@ use anyhow::{bail, Error}; use lazy_static::lazy_static; use bitflags::bitflags; +use proxmox::tools::time::*; + use nom::{ error::{context, ParseError, VerboseError}, bytes::complete::{tag, take_while1}, @@ -472,6 +474,163 @@ pub fn verify_calendar_event(i: &str) -> Result<(), Error> { Ok(()) } + +fn is_leap_year(year: libc::c_int) -> bool { + if year % 4 != 0 { return false; } + if year % 100 != 0 { return true; } + if year % 400 != 0 { return false; } + return true; +} + +fn days_in_month(mon: libc::c_int, year: libc::c_int) -> libc::c_int { + + let mon = mon % 12; + + static MAP: &[libc::c_int] = &[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + if mon == 1 && is_leap_year(year) { return 29; } + + MAP[mon as usize] +} + +fn wrap_time(t: &mut libc::tm) { + + // sec: 0..59 + if t.tm_sec >= 60 { + t.tm_min += t.tm_sec / 60; + t.tm_sec %= 60; + } + + // min: 0..59 + if t.tm_min >= 60 { + t.tm_hour += t.tm_min / 60; + t.tm_min %= 60; + } + + // hour: 0..23 + if t.tm_hour >= 24 { + t.tm_mday += t.tm_hour / 24; + t.tm_wday += t.tm_hour / 24; + t.tm_hour %= 24; + } + + // Translate to 0..($days_in_mon-1) + t.tm_mday -= 1; + loop { + let days_in_mon = days_in_month(t.tm_mon, t.tm_year); + if t.tm_mday < days_in_mon { break; } + // Wrap one month + t.tm_mday -= days_in_mon; + t.tm_wday += 7 - (days_in_mon % 7); + t.tm_mon += 1; + } + + // Translate back to 1..$days_in_mon + t.tm_mday += 1; + + // mon: 0..11 + if t.tm_mon >= 12 { + t.tm_year += t.tm_mon / 12; + t.tm_mon %= 12; + } + + t.tm_wday %= 7; +} + +fn time_add_days(t: &mut libc::tm, days: libc::c_int) { + t.tm_mday += days; + t.tm_wday += days; + wrap_time(t); +} + +pub fn compute_next_event( + event: &CalendarEvent, + last: i64, + utc: bool, +) -> Result { + + let last = last + 60; // at least one minute later + + let all_days = event.days.is_empty() || event.days.is_all(); + + let mut t = if utc { gmtime(last)? } else { localtime(last)? }; + t.tm_sec = 0; // we're not interested in seconds, actually + t.tm_year += 1900; // real years for clarity + + let mut count = 0; + + loop { + if count > 1000 { // should not happen + bail!("unable to compute next calendar event"); + } else { + count += 1; + } + + if !all_days { // match day first + // Note: tm_wday (0-6, Sunday = 0) => convert to Sunday = 6 + let day_num = (t.tm_wday + 6) % 7; + let day = WeekDays::from_bits(1< libc::time_t { + (mday*3600*24 + hour*3600 + min*60) as libc::time_t + } + + #[test] + fn test_compute_next_event() -> Result<(), Error> { + + let test_value = |v: &'static str, last: i64, expect: i64| -> Result { + let event = match parse_calendar_event(v) { + Ok(event) => event, + Err(err) => bail!("parsing '{}' failed - {}", v, err), + }; + + match compute_next_event(&event, last, true) { + Ok(next) => { + if next == expect { + println!("next {:?} => {}", event, next); + } else { + bail!("next {:?} failed\nnext: {:?}\nexpect: {:?}", + event, gmtime(next), gmtime(expect)); + } + } + Err(err) => bail!("compute next for '{}' failed - {}", v, err), + } + + Ok(expect) + }; + + const MIN: i64 = 60; + const HOUR: i64 = 3600; + const DAY: i64 = 3600*24; + + const THURSDAY_00_00: i64 = make_test_time(0, 0, 0); + const THURSDAY_15_00: i64 = make_test_time(0, 15, 0); + + test_value("*:*", THURSDAY_00_00, THURSDAY_00_00 + MIN)?; + + test_value("mon *:*", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY)?; + test_value("mon 2:*", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY + 2*HOUR)?; + test_value("mon 2:50", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY + 2*HOUR + 50*MIN)?; + + let mut n = test_value("*:*", THURSDAY_00_00, THURSDAY_00_00 + MIN)?; + for i in 2..100 { + n = test_value("*:*", n, THURSDAY_00_00 + i*MIN)?; + } + + let mut n = test_value("*:0", THURSDAY_00_00, THURSDAY_00_00 + HOUR)?; + for i in 2..100 { + n = test_value("*:0", n, THURSDAY_00_00 + i*HOUR)?; + } + + let mut n = test_value("1:0", THURSDAY_15_00, THURSDAY_00_00 + DAY + HOUR)?; + for i in 2..100 { + n = test_value("1:0", n, THURSDAY_00_00 + i*DAY + HOUR)?; + } + + Ok(()) + } + #[test] fn test_calendar_event_weekday() -> Result<(), Error> { test_event("mon,wed..fri")?;