Support daily intervals

Add new database column for interval_days. Update humantime to return days as a separate field.
This commit is contained in:
jude 2022-12-10 15:32:49 +00:00
parent aa74a7f9a3
commit a49a849917
6 changed files with 145 additions and 56 deletions

2
Cargo.lock generated
View File

@ -2133,7 +2133,7 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "reminder_rs" name = "reminder_rs"
version = "1.6.8" version = "1.6.9"
dependencies = [ dependencies = [
"base64", "base64",
"chrono", "chrono",

View File

@ -0,0 +1,3 @@
USE reminders;
ALTER TABLE reminders.reminders ADD COLUMN `interval_days` INT UNSIGNED DEFAULT NULL;

View File

@ -1,4 +1,4 @@
use chrono::{DateTime, Days, Duration, Months, TimeZone}; use chrono::{DateTime, Days, Duration, Months};
use chrono_tz::Tz; use chrono_tz::Tz;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{error, info, warn}; use log::{error, info, warn};
@ -253,6 +253,7 @@ pub struct Reminder {
restartable: bool, restartable: bool,
expires: Option<DateTime<Utc>>, expires: Option<DateTime<Utc>>,
interval_seconds: Option<u32>, interval_seconds: Option<u32>,
interval_days: Option<u32>,
interval_months: Option<u32>, interval_months: Option<u32>,
avatar: Option<String>, avatar: Option<String>,
@ -286,6 +287,7 @@ SELECT
reminders.`restartable` AS restartable, reminders.`restartable` AS restartable,
reminders.`expires` AS 'expires', reminders.`expires` AS 'expires',
reminders.`interval_seconds` AS 'interval_seconds', reminders.`interval_seconds` AS 'interval_seconds',
reminders.`interval_days` AS 'interval_days',
reminders.`interval_months` AS 'interval_months', reminders.`interval_months` AS 'interval_months',
reminders.`avatar` AS avatar, reminders.`avatar` AS avatar,
@ -348,6 +350,7 @@ WHERE
let mut updated_reminder_time = let mut updated_reminder_time =
self.utc_time.with_timezone(&self.timezone.parse().unwrap_or(Tz::UTC)); self.utc_time.with_timezone(&self.timezone.parse().unwrap_or(Tz::UTC));
while updated_reminder_time < now {
if let Some(interval) = self.interval_months { if let Some(interval) = self.interval_months {
updated_reminder_time = updated_reminder_time updated_reminder_time = updated_reminder_time
.checked_add_months(Months::new(interval)) .checked_add_months(Months::new(interval))
@ -358,32 +361,19 @@ WHERE
}); });
} }
fn increment_days<T: TimeZone>( if let Some(interval) = self.interval_days {
now: DateTime<Utc>, updated_reminder_time = updated_reminder_time
mut new_time: DateTime<T>, .checked_add_days(Days::new(interval as u64))
interval: u32, .unwrap_or_else(|| {
) -> Option<DateTime<T>> { warn!("Could not add days to a reminder");
while new_time < now {
new_time = new_time.checked_add_days(Days::new((interval / 86400).into()))?;
}
Some(new_time) updated_reminder_time
});
} }
if let Some(interval) = self.interval_seconds { if let Some(interval) = self.interval_seconds {
if interval.div_rem(&86400).1 == 0 {
updated_reminder_time = updated_reminder_time =
match increment_days(now, updated_reminder_time, interval) { updated_reminder_time + Duration::seconds(interval as i64);
Some(d) => d,
None => {
warn!("Failed to update days on a reminder.");
updated_reminder_time
}
}
}
while updated_reminder_time < now {
updated_reminder_time += Duration::seconds(interval as i64);
} }
} }

View File

@ -110,13 +110,14 @@ impl OverflowOp for u64 {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct Interval { pub struct Interval {
pub month: u64, pub month: u64,
pub day: u64,
pub sec: u64, pub sec: u64,
} }
struct Parser<'a> { struct Parser<'a> {
iter: Chars<'a>, iter: Chars<'a>,
src: &'a str, src: &'a str,
current: (u64, u64, u64), current: (u64, u64, u64, u64),
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
@ -140,17 +141,17 @@ impl<'a> Parser<'a> {
Ok(None) Ok(None)
} }
fn parse_unit(&mut self, n: u64, start: usize, end: usize) -> Result<(), Error> { fn parse_unit(&mut self, n: u64, start: usize, end: usize) -> Result<(), Error> {
let (mut month, mut sec, nsec) = match &self.src[start..end] { let (mut month, mut day, mut sec, nsec) = match &self.src[start..end] {
"nanos" | "nsec" | "ns" => (0u64, 0u64, n), "nanos" | "nsec" | "ns" => (0, 0u64, 0u64, n),
"usec" | "us" => (0, 0u64, n.mul(1000)?), "usec" | "us" => (0, 0, 0u64, n.mul(1000)?),
"millis" | "msec" | "ms" => (0, 0u64, n.mul(1_000_000)?), "millis" | "msec" | "ms" => (0, 0, 0u64, n.mul(1_000_000)?),
"seconds" | "second" | "secs" | "sec" | "s" => (0, n, 0), "seconds" | "second" | "secs" | "sec" | "s" => (0, 0, n, 0),
"minutes" | "minute" | "min" | "mins" | "m" => (0, n.mul(60)?, 0), "minutes" | "minute" | "min" | "mins" | "m" => (0, 0, n.mul(60)?, 0),
"hours" | "hour" | "hr" | "hrs" | "h" => (0, n.mul(3600)?, 0), "hours" | "hour" | "hr" | "hrs" | "h" => (0, 0, n.mul(3600)?, 0),
"days" | "day" | "d" => (0, n.mul(86400)?, 0), "days" | "day" | "d" => (0, n, 0, 0),
"weeks" | "week" | "w" => (0, n.mul(86400 * 7)?, 0), "weeks" | "week" | "w" => (0, n.mul(7)?, 0, 0),
"months" | "month" | "M" => (n, 0, 0), "months" | "month" | "M" => (n, 0, 0, 0),
"years" | "year" | "y" => (12, 0, 0), "years" | "year" | "y" => (n.mul(12)?, 0, 0, 0),
_ => { _ => {
return Err(Error::UnknownUnit { return Err(Error::UnknownUnit {
start, start,
@ -160,15 +161,16 @@ impl<'a> Parser<'a> {
}); });
} }
}; };
let mut nsec = self.current.2 + nsec; let mut nsec = self.current.3 + nsec;
if nsec > 1_000_000_000 { if nsec > 1_000_000_000 {
sec += nsec / 1_000_000_000; sec += nsec / 1_000_000_000;
nsec %= 1_000_000_000; nsec %= 1_000_000_000;
} }
sec += self.current.1; sec += self.current.2;
day += self.current.1;
month += self.current.0; month += self.current.0;
self.current = (month, sec, nsec); self.current = (month, day, sec, nsec);
Ok(()) Ok(())
} }
@ -215,7 +217,13 @@ impl<'a> Parser<'a> {
self.parse_unit(n, start, off)?; self.parse_unit(n, start, off)?;
n = match self.parse_first_char()? { n = match self.parse_first_char()? {
Some(n) => n, Some(n) => n,
None => return Ok(Interval { month: self.current.0, sec: self.current.1 }), None => {
return Ok(Interval {
month: self.current.0,
day: self.current.1,
sec: self.current.2,
})
}
}; };
} }
} }
@ -247,5 +255,73 @@ impl<'a> Parser<'a> {
/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000))); /// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
/// ``` /// ```
pub fn parse_duration(s: &str) -> Result<Interval, Error> { pub fn parse_duration(s: &str) -> Result<Interval, Error> {
Parser { iter: s.chars(), src: s, current: (0, 0, 0) }.parse() Parser { iter: s.chars(), src: s, current: (0, 0, 0, 0) }.parse()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_seconds() {
let interval = parse_duration("10 seconds").unwrap();
assert_eq!(interval.sec, 10);
assert_eq!(interval.day, 0);
assert_eq!(interval.month, 0);
}
#[test]
fn parse_minutes() {
let interval = parse_duration("10 minutes").unwrap();
assert_eq!(interval.sec, 600);
assert_eq!(interval.day, 0);
assert_eq!(interval.month, 0);
}
#[test]
fn parse_hours() {
let interval = parse_duration("10 hours").unwrap();
assert_eq!(interval.sec, 36_000);
assert_eq!(interval.day, 0);
assert_eq!(interval.month, 0);
}
#[test]
fn parse_days() {
let interval = parse_duration("10 days").unwrap();
assert_eq!(interval.sec, 0);
assert_eq!(interval.day, 10);
assert_eq!(interval.month, 0);
}
#[test]
fn parse_weeks() {
let interval = parse_duration("10 weeks").unwrap();
assert_eq!(interval.sec, 0);
assert_eq!(interval.day, 70);
assert_eq!(interval.month, 0);
}
#[test]
fn parse_months() {
let interval = parse_duration("10 months").unwrap();
assert_eq!(interval.sec, 0);
assert_eq!(interval.day, 0);
assert_eq!(interval.month, 10);
}
#[test]
fn parse_years() {
let interval = parse_duration("10 years").unwrap();
assert_eq!(interval.sec, 0);
assert_eq!(interval.day, 0);
assert_eq!(interval.month, 120);
}
} }

View File

@ -53,7 +53,8 @@ pub struct ReminderBuilder {
channel: u32, channel: u32,
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
timezone: String, timezone: String,
interval_secs: Option<i64>, interval_seconds: Option<i64>,
interval_days: Option<i64>,
interval_months: Option<i64>, interval_months: Option<i64>,
expires: Option<NaiveDateTime>, expires: Option<NaiveDateTime>,
content: String, content: String,
@ -87,6 +88,7 @@ INSERT INTO reminders (
`utc_time`, `utc_time`,
`timezone`, `timezone`,
`interval_seconds`, `interval_seconds`,
`interval_days`,
`interval_months`, `interval_months`,
`expires`, `expires`,
`content`, `content`,
@ -106,6 +108,7 @@ INSERT INTO reminders (
?, ?,
?, ?,
?, ?,
?,
? ?
) )
", ",
@ -113,7 +116,8 @@ INSERT INTO reminders (
self.channel, self.channel,
utc_time, utc_time,
self.timezone, self.timezone,
self.interval_secs, self.interval_seconds,
self.interval_days,
self.interval_months, self.interval_months,
self.expires, self.expires,
self.content, self.content,
@ -210,9 +214,14 @@ impl<'a> MultiReminderBuilder<'a> {
let mut ok_locs = HashSet::new(); let mut ok_locs = HashSet::new();
if self.interval.map_or(false, |i| ((i.sec + i.month * 30 * DAY) as i64) < *MIN_INTERVAL) { if self
.interval
.map_or(false, |i| ((i.sec + i.day * DAY + i.month * 30 * DAY) as i64) < *MIN_INTERVAL)
{
errors.insert(ReminderError::ShortInterval); errors.insert(ReminderError::ShortInterval);
} else if self.interval.map_or(false, |i| ((i.sec + i.month * 30 * DAY) as i64) > *MAX_TIME) } else if self
.interval
.map_or(false, |i| ((i.sec + i.day * DAY + i.month * 30 * DAY) as i64) > *MAX_TIME)
{ {
errors.insert(ReminderError::LongInterval); errors.insert(ReminderError::LongInterval);
} else { } else {
@ -300,7 +309,8 @@ impl<'a> MultiReminderBuilder<'a> {
channel: c, channel: c,
utc_time: self.utc_time, utc_time: self.utc_time,
timezone: self.timezone.to_string(), timezone: self.timezone.to_string(),
interval_secs: self.interval.map(|i| i.sec as i64), interval_seconds: self.interval.map(|i| i.sec as i64),
interval_days: self.interval.map(|i| i.day as i64),
interval_months: self.interval.map(|i| i.month as i64), interval_months: self.interval.map(|i| i.month as i64),
expires: self.expires, expires: self.expires,
content: self.content.content.clone(), content: self.content.content.clone(),

View File

@ -26,6 +26,7 @@ pub struct Reminder {
pub channel: u64, pub channel: u64,
pub utc_time: DateTime<Utc>, pub utc_time: DateTime<Utc>,
pub interval_seconds: Option<u32>, pub interval_seconds: Option<u32>,
pub interval_days: Option<u32>,
pub interval_months: Option<u32>, pub interval_months: Option<u32>,
pub expires: Option<NaiveDateTime>, pub expires: Option<NaiveDateTime>,
pub enabled: bool, pub enabled: bool,
@ -59,6 +60,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -95,6 +97,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -138,6 +141,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -195,6 +199,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -228,6 +233,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -262,6 +268,7 @@ SELECT
channels.channel, channels.channel,
reminders.utc_time, reminders.utc_time,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.expires, reminders.expires,
reminders.enabled, reminders.enabled,
@ -323,7 +330,10 @@ WHERE
TimeDisplayType::Relative => format!("<t:{}:R>", self.utc_time.timestamp()), TimeDisplayType::Relative => format!("<t:{}:R>", self.utc_time.timestamp()),
}; };
if self.interval_seconds.is_some() || self.interval_months.is_some() { if self.interval_seconds.is_some()
|| self.interval_days.is_some()
|| self.interval_months.is_some()
{
format!( format!(
"'{}' *occurs next at* **{}**, repeating (set by {})\n", "'{}' *occurs next at* **{}**, repeating (set by {})\n",
self.display_content(), self.display_content(),