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:
parent
08e4c6cb57
commit
aa74a7f9a3
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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,7 +62,8 @@ 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) {
|
||||||
|
Some(dt) => {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
|
|
||||||
let difference = {
|
let difference = {
|
||||||
@ -74,6 +75,10 @@ pub fn substitute(string: &str) -> String {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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,12 +405,7 @@ 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)
|
||||||
"
|
|
||||||
DELETE FROM reminders WHERE `id` = ?
|
|
||||||
",
|
|
||||||
self.id
|
|
||||||
)
|
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
.expect(&format!("Could not delete Reminder {}", self.id));
|
.expect(&format!("Could not delete Reminder {}", self.id));
|
||||||
@ -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)
|
||||||
|
@ -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,8 +56,8 @@ 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 = true;
|
||||||
channel.paused_until = Some(dt);
|
channel.paused_until = Some(dt);
|
||||||
|
|
||||||
@ -72,6 +68,15 @@ pub async fn pause(
|
|||||||
timestamp
|
timestamp
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
ctx.say(format!(
|
||||||
|
"Time processed could not be interpreted as `DateTime`. Please write the time as clearly as possible",
|
||||||
|
))
|
||||||
|
.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);
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user