Support daily intervals
Add new database column for interval_days. Update humantime to return days as a separate field.
This commit is contained in:
parent
aa74a7f9a3
commit
a49a849917
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
3
migration/05-reminder_variable_intervals_2.sql
Normal file
3
migration/05-reminder_variable_intervals_2.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
USE reminders;
|
||||||
|
|
||||||
|
ALTER TABLE reminders.reminders ADD COLUMN `interval_days` INT UNSIGNED DEFAULT NULL;
|
@ -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,42 +350,30 @@ 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));
|
||||||
|
|
||||||
if let Some(interval) = self.interval_months {
|
while updated_reminder_time < now {
|
||||||
updated_reminder_time = updated_reminder_time
|
if let Some(interval) = self.interval_months {
|
||||||
.checked_add_months(Months::new(interval))
|
updated_reminder_time = updated_reminder_time
|
||||||
.unwrap_or_else(|| {
|
.checked_add_months(Months::new(interval))
|
||||||
warn!("Could not add months to a reminder");
|
.unwrap_or_else(|| {
|
||||||
|
warn!("Could not add months to a reminder");
|
||||||
|
|
||||||
updated_reminder_time
|
updated_reminder_time
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
fn increment_days<T: TimeZone>(
|
|
||||||
now: DateTime<Utc>,
|
|
||||||
mut new_time: DateTime<T>,
|
|
||||||
interval: u32,
|
|
||||||
) -> Option<DateTime<T>> {
|
|
||||||
while new_time < now {
|
|
||||||
new_time = new_time.checked_add_days(Days::new((interval / 86400).into()))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(new_time)
|
if let Some(interval) = self.interval_days {
|
||||||
}
|
updated_reminder_time = updated_reminder_time
|
||||||
|
.checked_add_days(Days::new(interval as u64))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!("Could not add days to a reminder");
|
||||||
|
|
||||||
if let Some(interval) = self.interval_seconds {
|
updated_reminder_time
|
||||||
if interval.div_rem(&86400).1 == 0 {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(interval) = self.interval_seconds {
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user