Use timezones wherever possible.

Replace uses of NaiveDateTime with DateTime<Utc>. Use timezones in postman to update days correctly. Use chrono::Months to update months rather than using MySQL query.
This commit is contained in:
jude 2022-11-22 20:41:07 +00:00
parent 08e4c6cb57
commit aa74a7f9a3
7 changed files with 92 additions and 120 deletions

34
Cargo.lock generated
View File

@ -56,9 +56,9 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.19" version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -225,15 +225,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.2.1" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.76" version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -417,9 +417,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebb3d1683412e9be6a15533314f00ec223c0762c522a3f77f048b265aab4470c" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@ -427,9 +427,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.13" version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422f23e724af1240ec469ea1e834d87a4b59ce2efe2c6a96256b0c47e2fd86aa" checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -1911,9 +1911,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]] [[package]]
name = "poise" name = "poise"
version = "0.3.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c01d22dcda434b0dfe956c60f6ac9b0352c4c2f4af852afb3155a971cd306d" checksum = "aee439543df35482730552e7c9ed0c45a5f1d521548e6c0249967c4ba8828f60"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"derivative", "derivative",
@ -1930,9 +1930,9 @@ dependencies = [
[[package]] [[package]]
name = "poise_macros" name = "poise_macros"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c" checksum = "53d21213ff7aeef5ab69729a5cddfb351a84a9bf3dadf9f470032440d43746c2"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
@ -2133,7 +2133,7 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "reminder_rs" name = "reminder_rs"
version = "1.6.7" version = "1.6.8"
dependencies = [ dependencies = [
"base64", "base64",
"chrono", "chrono",
@ -2518,9 +2518,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.88" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [ dependencies = [
"itoa 1.0.4", "itoa 1.0.4",
"ryu", "ryu",

View File

@ -1,11 +1,11 @@
[package] [package]
name = "reminder_rs" name = "reminder_rs"
version = "1.6.8" version = "1.6.9"
authors = ["jellywx <judesouthworth@pm.me>"] authors = ["jellywx <judesouthworth@pm.me>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
poise = "0.3" poise = "0.4"
dotenv = "0.15" dotenv = "0.15"
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
reqwest = "0.11" reqwest = "0.11"

View File

@ -1,4 +1,4 @@
use chrono::{DateTime, Days, Duration}; use chrono::{DateTime, Days, Duration, Months, TimeZone};
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};
@ -62,18 +62,23 @@ pub fn substitute(string: &str) -> String {
let format = caps.name("format").map(|m| m.as_str()); let format = caps.name("format").map(|m| m.as_str());
if let (Some(final_time), Some(format)) = (final_time, format) { if let (Some(final_time), Some(format)) = (final_time, format) {
let dt = NaiveDateTime::from_timestamp(final_time, 0); match NaiveDateTime::from_timestamp_opt(final_time, 0) {
let now = Utc::now().naive_utc(); Some(dt) => {
let now = Utc::now().naive_utc();
let difference = { let difference = {
if now < dt { if now < dt {
dt - Utc::now().naive_utc() dt - Utc::now().naive_utc()
} else { } else {
Utc::now().naive_utc() - dt 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 { } else {
String::new() String::new()
} }
@ -243,10 +248,10 @@ pub struct Reminder {
attachment: Option<Vec<u8>>, attachment: Option<Vec<u8>>,
attachment_name: Option<String>, attachment_name: Option<String>,
utc_time: NaiveDateTime, utc_time: DateTime<Utc>,
timezone: String, timezone: String,
restartable: bool, restartable: bool,
expires: Option<NaiveDateTime>, expires: Option<DateTime<Utc>>,
interval_seconds: Option<u32>, interval_seconds: Option<u32>,
interval_months: Option<u32>, interval_months: Option<u32>,
@ -330,9 +335,7 @@ WHERE
async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) {
let _ = sqlx::query!( 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 self.channel_id
) )
.execute(pool) .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) { async fn refresh(&self, pool: impl Executor<'_, Database = Database> + Copy) {
if self.interval_seconds.is_some() || self.interval_months.is_some() { if self.interval_seconds.is_some() || self.interval_months.is_some() {
let now = Utc::now().naive_local(); let now = Utc::now();
let mut updated_reminder_time = self.utc_time; let mut updated_reminder_time =
self.utc_time.with_timezone(&self.timezone.parse().unwrap_or(Tz::UTC));
if let Some(interval) = self.interval_months { if let Some(interval) = self.interval_months {
match sqlx::query!( updated_reminder_time = updated_reminder_time
// use the second date_add to force return value to datetime .checked_add_months(Months::new(interval))
"SELECT DATE_ADD(DATE_ADD(?, INTERVAL ? MONTH), INTERVAL 0 SECOND) AS new_time", .unwrap_or_else(|| {
updated_reminder_time, warn!("Could not add months to a reminder");
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 += Duration::days(30); updated_reminder_time
} });
},
Err(e) => {
warn!("Could not update interval by months: {:?}", e);
// naively fallback to adding 30 days
updated_reminder_time += Duration::days(30);
}
}
} }
fn increment_days( fn increment_days<T: TimeZone>(
now: NaiveDateTime, now: DateTime<Utc>,
mut new_time: NaiveDateTime, mut new_time: DateTime<T>,
interval: u32, interval: u32,
) -> Option<NaiveDateTime> { ) -> Option<DateTime<T>> {
while new_time < now { while new_time < now {
new_time = new_time.checked_add_days(Days::new((interval / 86400).into()))?; 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| { if self.expires.map_or(false, |expires| updated_reminder_time > expires) {
NaiveDateTime::from_timestamp(updated_reminder_time.timestamp(), 0) > expires
}) {
self.force_delete(pool).await; self.force_delete(pool).await;
} else { } else {
sqlx::query!( sqlx::query!(
" "UPDATE reminders SET `utc_time` = ? WHERE `id` = ?",
UPDATE reminders SET `utc_time` = ? WHERE `id` = ? updated_reminder_time.with_timezone(&Utc),
",
updated_reminder_time,
self.id self.id
) )
.execute(pool) .execute(pool)
@ -425,15 +405,10 @@ UPDATE reminders SET `utc_time` = ? WHERE `id` = ?
} }
async fn force_delete(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn force_delete(&self, pool: impl Executor<'_, Database = Database> + Copy) {
sqlx::query!( sqlx::query!("DELETE FROM reminders WHERE `id` = ?", self.id)
" .execute(pool)
DELETE FROM reminders WHERE `id` = ? .await
", .expect(&format!("Could not delete Reminder {}", self.id));
self.id
)
.execute(pool)
.await
.expect(&format!("Could not delete Reminder {}", self.id));
} }
async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) { async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) {
@ -571,9 +546,7 @@ DELETE FROM reminders WHERE `id` = ?
.map_or(true, |inner| inner >= Utc::now().naive_local())) .map_or(true, |inner| inner >= Utc::now().naive_local()))
{ {
let _ = sqlx::query!( 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 self.channel_id
) )
.execute(pool) .execute(pool)

View File

@ -1,10 +1,6 @@
use std::{ use std::{collections::HashSet, string::ToString};
collections::HashSet,
string::ToString,
time::{SystemTime, UNIX_EPOCH},
};
use chrono::NaiveDateTime; use chrono::{DateTime, NaiveDateTime, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use num_integer::Integer; use num_integer::Integer;
use poise::{ use poise::{
@ -60,18 +56,27 @@ pub async fn pause(
let parsed = natural_parser(&until, &timezone.to_string()).await; let parsed = natural_parser(&until, &timezone.to_string()).await;
if let Some(timestamp) = parsed { 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.commit_changes(&ctx.data().database).await;
channel.paused_until = Some(dt);
channel.commit_changes(&ctx.data().database).await; ctx.say(format!(
"Reminders in this channel have been silenced until **<t:{}:D>**",
timestamp
))
.await?;
}
ctx.say(format!( None => {
"Reminders in this channel have been silenced until **<t:{}:D>**", ctx.say(format!(
timestamp "Time processed could not be interpreted as `DateTime`. Please write the time as clearly as possible",
)) ))
.await?; .await?;
}
}
} else { } else {
ctx.say( ctx.say(
"Time could not be processed. Please write the time as clearly as possible", "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 reply
} }
fn time_difference(start_time: NaiveDateTime) -> String { fn time_difference(start_time: DateTime<Utc>) -> String {
let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let delta = (Utc::now() - start_time).num_seconds();
let now = NaiveDateTime::from_timestamp(unix_time, 0);
let delta = (now - start_time).num_seconds();
let (minutes, seconds) = delta.div_rem(&60); let (minutes, seconds) = delta.div_rem(&60);
let (hours, minutes) = minutes.div_rem(&60); let (hours, minutes) = minutes.div_rem(&60);

View File

@ -175,17 +175,15 @@ impl<'a> MultiReminderBuilder<'a> {
} }
pub fn time<T: Into<i64>>(mut self, time: T) -> Self { pub fn time<T: Into<i64>>(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 self
} }
pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self { pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self {
if let Some(t) = time { self.expires = time.map(|t| NaiveDateTime::from_timestamp_opt(t.into(), 0)).flatten();
self.expires = Some(NaiveDateTime::from_timestamp(t.into(), 0));
} else {
self.expires = None;
}
self self
} }

View File

@ -6,7 +6,7 @@ pub mod look_flags;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use chrono::{NaiveDateTime, TimeZone}; use chrono::{DateTime, NaiveDateTime, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use poise::serenity_prelude::{ use poise::serenity_prelude::{
model::id::{ChannelId, GuildId, UserId}, model::id::{ChannelId, GuildId, UserId},
@ -24,7 +24,7 @@ pub struct Reminder {
pub id: u32, pub id: u32,
pub uid: String, pub uid: String,
pub channel: u64, pub channel: u64,
pub utc_time: NaiveDateTime, pub utc_time: DateTime<Utc>,
pub interval_seconds: Option<u32>, pub interval_seconds: Option<u32>,
pub interval_months: Option<u32>, pub interval_months: Option<u32>,
pub expires: Option<NaiveDateTime>, pub expires: Option<NaiveDateTime>,
@ -310,16 +310,15 @@ WHERE
count + 1, count + 1,
self.display_content(), self.display_content(),
self.channel, 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 { pub fn display(&self, flags: &LookFlags, timezone: &Tz) -> String {
let time_display = match flags.time_display { let time_display = match flags.time_display {
TimeDisplayType::Absolute => timezone TimeDisplayType::Absolute => {
.timestamp(self.utc_time.timestamp(), 0) self.utc_time.with_timezone(timezone).format("%Y-%m-%d %H:%M:%S").to_string()
.format("%Y-%m-%d %H:%M:%S") }
.to_string(),
TimeDisplayType::Relative => format!("<t:{}:R>", self.utc_time.timestamp()), TimeDisplayType::Relative => format!("<t:{}:R>", self.utc_time.timestamp()),
}; };

View File

@ -1,9 +1,9 @@
use chrono::NaiveDateTime; use chrono::{DateTime, Utc};
use sqlx::MySqlPool; use sqlx::MySqlPool;
pub struct Timer { pub struct Timer {
pub name: String, pub name: String,
pub start_time: NaiveDateTime, pub start_time: DateTime<Utc>,
pub owner: u64, pub owner: u64,
} }