From aa74a7f9a31c617e137c7c02e373a95daa61138e Mon Sep 17 00:00:00 2001 From: jude Date: Tue, 22 Nov 2022 20:41:07 +0000 Subject: [PATCH] Use timezones wherever possible. Replace uses of NaiveDateTime with DateTime. Use timezones in postman to update days correctly. Use chrono::Months to update months rather than using MySQL query. --- Cargo.lock | 34 +++++------ Cargo.toml | 4 +- postman/src/sender.rs | 105 ++++++++++++--------------------- src/commands/reminder_cmds.rs | 42 ++++++------- src/models/reminder/builder.rs | 10 ++-- src/models/reminder/mod.rs | 13 ++-- src/models/timer.rs | 4 +- 7 files changed, 92 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7754b0..fc8f196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -225,15 +225,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cfg-if" @@ -417,9 +417,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb3d1683412e9be6a15533314f00ec223c0762c522a3f77f048b265aab4470c" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422f23e724af1240ec469ea1e834d87a4b59ce2efe2c6a96256b0c47e2fd86aa" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] @@ -1911,9 +1911,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "poise" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c01d22dcda434b0dfe956c60f6ac9b0352c4c2f4af852afb3155a971cd306d" +checksum = "aee439543df35482730552e7c9ed0c45a5f1d521548e6c0249967c4ba8828f60" dependencies = [ "async-trait", "derivative", @@ -1930,9 +1930,9 @@ dependencies = [ [[package]] name = "poise_macros" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c" +checksum = "53d21213ff7aeef5ab69729a5cddfb351a84a9bf3dadf9f470032440d43746c2" dependencies = [ "darling", "proc-macro2", @@ -2133,7 +2133,7 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "reminder_rs" -version = "1.6.7" +version = "1.6.8" dependencies = [ "base64", "chrono", @@ -2518,9 +2518,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa 1.0.4", "ryu", diff --git a/Cargo.toml b/Cargo.toml index 4c9bd74..c51bcf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "reminder_rs" -version = "1.6.8" +version = "1.6.9" authors = ["jellywx "] edition = "2018" [dependencies] -poise = "0.3" +poise = "0.4" dotenv = "0.15" tokio = { version = "1", features = ["process", "full"] } reqwest = "0.11" diff --git a/postman/src/sender.rs b/postman/src/sender.rs index 9036a2f..71c6ef9 100644 --- a/postman/src/sender.rs +++ b/postman/src/sender.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Days, Duration}; +use chrono::{DateTime, Days, Duration, Months, TimeZone}; use chrono_tz::Tz; use lazy_static::lazy_static; use log::{error, info, warn}; @@ -62,18 +62,23 @@ pub fn substitute(string: &str) -> String { let format = caps.name("format").map(|m| m.as_str()); if let (Some(final_time), Some(format)) = (final_time, format) { - let dt = NaiveDateTime::from_timestamp(final_time, 0); - let now = Utc::now().naive_utc(); + match NaiveDateTime::from_timestamp_opt(final_time, 0) { + Some(dt) => { + let now = Utc::now().naive_utc(); - let difference = { - if now < dt { - dt - Utc::now().naive_utc() - } else { - Utc::now().naive_utc() - dt + let difference = { + if now < dt { + dt - Utc::now().naive_utc() + } else { + Utc::now().naive_utc() - dt + } + }; + + fmt_displacement(format, difference.num_seconds() as u64) } - }; - fmt_displacement(format, difference.num_seconds() as u64) + None => String::new(), + } } else { String::new() } @@ -243,10 +248,10 @@ pub struct Reminder { attachment: Option>, attachment_name: Option, - utc_time: NaiveDateTime, + utc_time: DateTime, timezone: String, restartable: bool, - expires: Option, + expires: Option>, interval_seconds: Option, interval_months: Option, @@ -330,9 +335,7 @@ WHERE async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) { let _ = sqlx::query!( - " -UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ? - ", + "UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ?", self.channel_id ) .execute(pool) @@ -341,44 +344,25 @@ UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ? async fn refresh(&self, pool: impl Executor<'_, Database = Database> + Copy) { if self.interval_seconds.is_some() || self.interval_months.is_some() { - let now = Utc::now().naive_local(); - let mut updated_reminder_time = self.utc_time; + let now = Utc::now(); + let mut updated_reminder_time = + self.utc_time.with_timezone(&self.timezone.parse().unwrap_or(Tz::UTC)); if let Some(interval) = self.interval_months { - match sqlx::query!( - // use the second date_add to force return value to datetime - "SELECT DATE_ADD(DATE_ADD(?, INTERVAL ? MONTH), INTERVAL 0 SECOND) AS new_time", - updated_reminder_time, - interval - ) - .fetch_one(pool) - .await - { - Ok(row) => match row.new_time { - Some(datetime) => { - updated_reminder_time = datetime; - } - None => { - warn!("Could not update interval by months: got NULL"); + updated_reminder_time = updated_reminder_time + .checked_add_months(Months::new(interval)) + .unwrap_or_else(|| { + warn!("Could not add months to a reminder"); - updated_reminder_time += Duration::days(30); - } - }, - - Err(e) => { - warn!("Could not update interval by months: {:?}", e); - - // naively fallback to adding 30 days - updated_reminder_time += Duration::days(30); - } - } + updated_reminder_time + }); } - fn increment_days( - now: NaiveDateTime, - mut new_time: NaiveDateTime, + fn increment_days( + now: DateTime, + mut new_time: DateTime, interval: u32, - ) -> Option { + ) -> Option> { while new_time < now { new_time = new_time.checked_add_days(Days::new((interval / 86400).into()))?; } @@ -403,16 +387,12 @@ UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ? } } - if self.expires.map_or(false, |expires| { - NaiveDateTime::from_timestamp(updated_reminder_time.timestamp(), 0) > expires - }) { + if self.expires.map_or(false, |expires| updated_reminder_time > expires) { self.force_delete(pool).await; } else { sqlx::query!( - " -UPDATE reminders SET `utc_time` = ? WHERE `id` = ? - ", - updated_reminder_time, + "UPDATE reminders SET `utc_time` = ? WHERE `id` = ?", + updated_reminder_time.with_timezone(&Utc), self.id ) .execute(pool) @@ -425,15 +405,10 @@ UPDATE reminders SET `utc_time` = ? WHERE `id` = ? } async fn force_delete(&self, pool: impl Executor<'_, Database = Database> + Copy) { - sqlx::query!( - " -DELETE FROM reminders WHERE `id` = ? - ", - self.id - ) - .execute(pool) - .await - .expect(&format!("Could not delete Reminder {}", self.id)); + sqlx::query!("DELETE FROM reminders WHERE `id` = ?", self.id) + .execute(pool) + .await + .expect(&format!("Could not delete Reminder {}", self.id)); } async fn pin_message>(&self, message_id: M, http: impl AsRef) { @@ -571,9 +546,7 @@ DELETE FROM reminders WHERE `id` = ? .map_or(true, |inner| inner >= Utc::now().naive_local())) { let _ = sqlx::query!( - " -UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ? - ", + "UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ?", self.channel_id ) .execute(pool) diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index 9790a0e..25e877a 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -1,10 +1,6 @@ -use std::{ - collections::HashSet, - string::ToString, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::{collections::HashSet, string::ToString}; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use chrono_tz::Tz; use num_integer::Integer; use poise::{ @@ -60,18 +56,27 @@ pub async fn pause( let parsed = natural_parser(&until, &timezone.to_string()).await; if let Some(timestamp) = parsed { - let dt = NaiveDateTime::from_timestamp(timestamp, 0); + match NaiveDateTime::from_timestamp_opt(timestamp, 0) { + Some(dt) => { + channel.paused = true; + channel.paused_until = Some(dt); - channel.paused = true; - channel.paused_until = Some(dt); + channel.commit_changes(&ctx.data().database).await; - channel.commit_changes(&ctx.data().database).await; + ctx.say(format!( + "Reminders in this channel have been silenced until ****", + timestamp + )) + .await?; + } - ctx.say(format!( - "Reminders in this channel have been silenced until ****", - timestamp - )) - .await?; + None => { + ctx.say(format!( + "Time processed could not be interpreted as `DateTime`. Please write the time as clearly as possible", + )) + .await?; + } + } } else { ctx.say( "Time could not be processed. Please write the time as clearly as possible", @@ -432,11 +437,8 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr reply } -fn time_difference(start_time: NaiveDateTime) -> String { - let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; - let now = NaiveDateTime::from_timestamp(unix_time, 0); - - let delta = (now - start_time).num_seconds(); +fn time_difference(start_time: DateTime) -> String { + let delta = (Utc::now() - start_time).num_seconds(); let (minutes, seconds) = delta.div_rem(&60); let (hours, minutes) = minutes.div_rem(&60); diff --git a/src/models/reminder/builder.rs b/src/models/reminder/builder.rs index 8a47db1..8420bd9 100644 --- a/src/models/reminder/builder.rs +++ b/src/models/reminder/builder.rs @@ -175,17 +175,15 @@ impl<'a> MultiReminderBuilder<'a> { } pub fn time>(mut self, time: T) -> Self { - self.utc_time = NaiveDateTime::from_timestamp(time.into(), 0); + if let Some(utc_time) = NaiveDateTime::from_timestamp_opt(time.into(), 0) { + self.utc_time = utc_time; + } self } pub fn expires>(mut self, time: Option) -> Self { - if let Some(t) = time { - self.expires = Some(NaiveDateTime::from_timestamp(t.into(), 0)); - } else { - self.expires = None; - } + self.expires = time.map(|t| NaiveDateTime::from_timestamp_opt(t.into(), 0)).flatten(); self } diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index aa000a2..423b586 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -6,7 +6,7 @@ pub mod look_flags; use std::hash::{Hash, Hasher}; -use chrono::{NaiveDateTime, TimeZone}; +use chrono::{DateTime, NaiveDateTime, Utc}; use chrono_tz::Tz; use poise::serenity_prelude::{ model::id::{ChannelId, GuildId, UserId}, @@ -24,7 +24,7 @@ pub struct Reminder { pub id: u32, pub uid: String, pub channel: u64, - pub utc_time: NaiveDateTime, + pub utc_time: DateTime, pub interval_seconds: Option, pub interval_months: Option, pub expires: Option, @@ -310,16 +310,15 @@ WHERE count + 1, self.display_content(), self.channel, - timezone.timestamp(self.utc_time.timestamp(), 0).format("%Y-%m-%d %H:%M:%S") + self.utc_time.with_timezone(timezone).format("%Y-%m-%d %H:%M:%S") ) } pub fn display(&self, flags: &LookFlags, timezone: &Tz) -> String { let time_display = match flags.time_display { - TimeDisplayType::Absolute => timezone - .timestamp(self.utc_time.timestamp(), 0) - .format("%Y-%m-%d %H:%M:%S") - .to_string(), + TimeDisplayType::Absolute => { + self.utc_time.with_timezone(timezone).format("%Y-%m-%d %H:%M:%S").to_string() + } TimeDisplayType::Relative => format!("", self.utc_time.timestamp()), }; diff --git a/src/models/timer.rs b/src/models/timer.rs index 080b0da..7802f2a 100644 --- a/src/models/timer.rs +++ b/src/models/timer.rs @@ -1,9 +1,9 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use sqlx::MySqlPool; pub struct Timer { pub name: String, - pub start_time: NaiveDateTime, + pub start_time: DateTime, pub owner: u64, }