diff --git a/CHANGELOG.md b/CHANGELOG.md index 1464ea7..287c658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Procedure when bumping the version number: ## Unreleased +### Changed + +- **(breaking)** Switched to `jiff` from `time` + ## v0.5.1 - 2024-05-20 ### Added diff --git a/Cargo.toml b/Cargo.toml index fb59b0d..2d45180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,10 @@ async-trait = { version = "0.1.80", optional = true } caseless = "0.2.1" cookie = { version = "0.18.1", optional = true } futures-util = { version = "0.3.30", default-features = false, features = ["sink"] } +jiff = { version = "0.1.15", features = ["serde"] } log = "0.4.21" serde = { version = "1.0.202", features = ["derive"] } serde_json = "1.0.117" -time = { version = "0.3.36", features = ["serde"] } tokio = { version = "1.37.0", features = ["time", "sync", "macros", "rt"] } tokio-stream = "0.1.15" tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-native-roots"] } diff --git a/examples/testbot_commands.rs b/examples/testbot_commands.rs index 3a07c19..bacab89 100644 --- a/examples/testbot_commands.rs +++ b/examples/testbot_commands.rs @@ -12,8 +12,8 @@ use euphoxide::bot::commands::Commands; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::bot::instances::Instances; use euphoxide::conn; +use jiff::Timestamp; use log::error; -use time::OffsetDateTime; use tokio::sync::mpsc; const HELP: &str = "I'm an example bot for https://github.com/Garmelon/euphoxide"; @@ -74,7 +74,7 @@ impl ClapCommand for Test { struct Bot { commands: Arc>, - start_time: OffsetDateTime, + start_time: Timestamp, stop: bool, } @@ -85,7 +85,7 @@ impl HasDescriptions for Bot { } impl HasStartTime for Bot { - fn start_time(&self) -> OffsetDateTime { + fn start_time(&self) -> Timestamp { self.start_time } } @@ -107,7 +107,7 @@ async fn main() { let mut bot = Bot { commands: cmds.clone(), - start_time: OffsetDateTime::now_utc(), + start_time: Timestamp::now(), stop: false, }; diff --git a/examples/testbot_instance.rs b/examples/testbot_instance.rs index 5b932b3..c1c2895 100644 --- a/examples/testbot_instance.rs +++ b/examples/testbot_instance.rs @@ -3,46 +3,14 @@ use euphoxide::api::packet::ParsedPacket; use euphoxide::api::{Data, Nick, Send}; +use euphoxide::bot::botrulez; use euphoxide::bot::instance::{ConnSnapshot, Event, ServerConfig}; -use time::OffsetDateTime; +use jiff::Timestamp; use tokio::sync::mpsc; const NICK: &str = "TestBot"; const HELP: &str = "I'm an example bot for https://github.com/Garmelon/euphoxide"; -fn format_delta(delta: time::Duration) -> String { - const MINUTE: u64 = 60; - const HOUR: u64 = MINUTE * 60; - const DAY: u64 = HOUR * 24; - - let mut seconds: u64 = delta.whole_seconds().try_into().unwrap(); - let mut parts = vec![]; - - let days = seconds / DAY; - if days > 0 { - parts.push(format!("{days}d")); - seconds -= days * DAY; - } - - let hours = seconds / HOUR; - if hours > 0 { - parts.push(format!("{hours}h")); - seconds -= hours * HOUR; - } - - let mins = seconds / MINUTE; - if mins > 0 { - parts.push(format!("{mins}m")); - seconds -= mins * MINUTE; - } - - if parts.is_empty() || seconds > 0 { - parts.push(format!("{seconds}s")); - } - - parts.join(" ") -} - async fn on_packet(packet: ParsedPacket, snapshot: ConnSnapshot) -> Result<(), ()> { let data = match packet.content { Ok(data) => data, @@ -107,8 +75,11 @@ async fn on_packet(packet: ParsedPacket, snapshot: ConnSnapshot) -> Result<(), ( reply = Some(HELP.to_string()); } else if content == format!("!uptime @{NICK}") { if let Some(joined) = snapshot.state.joined() { - let delta = OffsetDateTime::now_utc() - joined.since; - reply = Some(format!("/me has been up for {}", format_delta(delta))); + let delta = Timestamp::now() - joined.since; + reply = Some(format!( + "/me has been up for {}", + botrulez::format_duration(delta) + )); } } else if content == "!test" { reply = Some("Test successful!".to_string()); diff --git a/examples/testbot_instances.rs b/examples/testbot_instances.rs index a8a6848..d44cbe7 100644 --- a/examples/testbot_instances.rs +++ b/examples/testbot_instances.rs @@ -3,47 +3,15 @@ use euphoxide::api::packet::ParsedPacket; use euphoxide::api::{Data, Nick, Send}; +use euphoxide::bot::botrulez; use euphoxide::bot::instance::{ConnSnapshot, Event, ServerConfig}; use euphoxide::bot::instances::Instances; -use time::OffsetDateTime; +use jiff::Timestamp; use tokio::sync::mpsc; const NICK: &str = "TestBot"; const HELP: &str = "I'm an example bot for https://github.com/Garmelon/euphoxide"; -fn format_delta(delta: time::Duration) -> String { - const MINUTE: u64 = 60; - const HOUR: u64 = MINUTE * 60; - const DAY: u64 = HOUR * 24; - - let mut seconds: u64 = delta.whole_seconds().try_into().unwrap(); - let mut parts = vec![]; - - let days = seconds / DAY; - if days > 0 { - parts.push(format!("{days}d")); - seconds -= days * DAY; - } - - let hours = seconds / HOUR; - if hours > 0 { - parts.push(format!("{hours}h")); - seconds -= hours * HOUR; - } - - let mins = seconds / MINUTE; - if mins > 0 { - parts.push(format!("{mins}m")); - seconds -= mins * MINUTE; - } - - if parts.is_empty() || seconds > 0 { - parts.push(format!("{seconds}s")); - } - - parts.join(" ") -} - async fn on_packet(packet: ParsedPacket, snapshot: ConnSnapshot) -> Result<(), ()> { let data = match packet.content { Ok(data) => data, @@ -108,8 +76,11 @@ async fn on_packet(packet: ParsedPacket, snapshot: ConnSnapshot) -> Result<(), ( reply = Some(HELP.to_string()); } else if content == format!("!uptime @{NICK}") { if let Some(joined) = snapshot.state.joined() { - let delta = OffsetDateTime::now_utc() - joined.since; - reply = Some(format!("/me has been up for {}", format_delta(delta))); + let delta = Timestamp::now() - joined.since; + reply = Some(format!( + "/me has been up for {}", + botrulez::format_duration(delta) + )); } } else if content == "!test" { reply = Some("Test successful!".to_string()); diff --git a/examples/testbot_manual.rs b/examples/testbot_manual.rs index c059004..d6535a4 100644 --- a/examples/testbot_manual.rs +++ b/examples/testbot_manual.rs @@ -6,8 +6,9 @@ use std::time::Duration; use euphoxide::api::packet::ParsedPacket; use euphoxide::api::{Data, Nick, Send}; +use euphoxide::bot::botrulez; use euphoxide::conn::{Conn, ConnTx, State}; -use time::OffsetDateTime; +use jiff::Timestamp; const TIMEOUT: Duration = Duration::from_secs(10); const DOMAIN: &str = "euphoria.leet.nu"; @@ -15,39 +16,6 @@ const ROOM: &str = "test"; const NICK: &str = "TestBot"; const HELP: &str = "I'm an example bot for https://github.com/Garmelon/euphoxide"; -fn format_delta(delta: time::Duration) -> String { - const MINUTE: u64 = 60; - const HOUR: u64 = MINUTE * 60; - const DAY: u64 = HOUR * 24; - - let mut seconds: u64 = delta.whole_seconds().try_into().unwrap(); - let mut parts = vec![]; - - let days = seconds / DAY; - if days > 0 { - parts.push(format!("{days}d")); - seconds -= days * DAY; - } - - let hours = seconds / HOUR; - if hours > 0 { - parts.push(format!("{hours}h")); - seconds -= hours * HOUR; - } - - let mins = seconds / MINUTE; - if mins > 0 { - parts.push(format!("{mins}m")); - seconds -= mins * MINUTE; - } - - if parts.is_empty() || seconds > 0 { - parts.push(format!("{seconds}s")); - } - - parts.join(" ") -} - async fn on_packet(packet: ParsedPacket, conn_tx: &ConnTx, state: &State) -> Result<(), ()> { let data = match packet.content { Ok(data) => data, @@ -112,8 +80,11 @@ async fn on_packet(packet: ParsedPacket, conn_tx: &ConnTx, state: &State) -> Res reply = Some(HELP.to_string()); } else if content == format!("!uptime @{NICK}") { if let Some(joined) = state.joined() { - let delta = OffsetDateTime::now_utc() - joined.since; - reply = Some(format!("/me has been up for {}", format_delta(delta))); + let delta = Timestamp::now() - joined.since; + reply = Some(format!( + "/me has been up for {}", + botrulez::format_duration(delta) + )); } } else if content == "!test" { reply = Some("Test successful!".to_string()); diff --git a/src/api/types.rs b/src/api/types.rs index d1a393d..8f438e9 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -10,9 +10,9 @@ use std::num::ParseIntError; use std::str::FromStr; use std::{error, fmt}; +use jiff::Timestamp; use serde::{de, ser, Deserialize, Serialize}; use serde_json::Value; -use time::{OffsetDateTime, UtcOffset}; /// Describes an account and its preferred name. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -403,19 +403,15 @@ impl<'de> Deserialize<'de> for Snowflake { /// Time is specified as a signed 64-bit integer, giving the number of seconds /// since the Unix Epoch. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct Time(#[serde(with = "time::serde::timestamp")] pub OffsetDateTime); +pub struct Time(#[serde(with = "jiff::fmt::serde::timestamp::second::required")] pub Timestamp); impl Time { - pub fn new(time: OffsetDateTime) -> Self { - let time = time - .to_offset(UtcOffset::UTC) - .replace_millisecond(0) - .unwrap(); + pub fn new(time: Timestamp) -> Self { Self(time) } pub fn now() -> Self { - Self::new(OffsetDateTime::now_utc()) + Self::new(Timestamp::now()) } } diff --git a/src/bot/botrulez/uptime.rs b/src/bot/botrulez/uptime.rs index b4465e5..1c98ff6 100644 --- a/src/bot/botrulez/uptime.rs +++ b/src/bot/botrulez/uptime.rs @@ -1,24 +1,21 @@ use async_trait::async_trait; use clap::Parser; -use time::macros::format_description; -use time::{Duration, OffsetDateTime, UtcOffset}; +use jiff::{Span, Timestamp}; use crate::api::Message; use crate::bot::command::{ClapCommand, Command, Context}; use crate::conn; -pub fn format_time(t: OffsetDateTime) -> String { - let t = t.to_offset(UtcOffset::UTC); - let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second] UTC"); - t.format(format).unwrap() +pub fn format_time(t: Timestamp) -> String { + t.strftime("%Y-&m-%d %H:%M:%S UTC").to_string() } -pub fn format_duration(d: Duration) -> String { +pub fn format_duration(d: Span) -> String { let d_abs = d.abs(); - let days = d_abs.whole_days(); - let hours = d_abs.whole_hours() % 24; - let mins = d_abs.whole_minutes() % 60; - let secs = d_abs.whole_seconds() % 60; + let days = d_abs.get_days(); + let hours = d_abs.get_hours() % 24; + let mins = d_abs.get_minutes() % 60; + let secs = d_abs.get_seconds() % 60; let mut segments = vec![]; if days > 0 { @@ -48,13 +45,13 @@ pub fn format_duration(d: Duration) -> String { pub struct Uptime; pub trait HasStartTime { - fn start_time(&self) -> OffsetDateTime; + fn start_time(&self) -> Timestamp; } impl Uptime { fn formulate_reply(&self, ctx: &Context, bot: &B, connected: bool) -> String { let start = bot.start_time(); - let now = OffsetDateTime::now_utc(); + let now = Timestamp::now(); let mut reply = format!( "/me has been up since {} ({})", diff --git a/src/conn.rs b/src/conn.rs index 92d083c..8d6820c 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -6,8 +6,8 @@ use std::future::Future; use std::time::{Duration, Instant}; use std::{error, fmt, result}; -use ::time::OffsetDateTime; use futures_util::SinkExt; +use jiff::Timestamp; use log::debug; use tokio::net::TcpStream; use tokio::select; @@ -75,7 +75,7 @@ pub type Result = result::Result; #[derive(Debug, Clone)] pub struct Joining { - pub since: OffsetDateTime, + pub since: Timestamp, pub hello: Option, pub snapshot: Option, pub bounce: Option, @@ -84,7 +84,7 @@ pub struct Joining { impl Joining { fn new() -> Self { Self { - since: OffsetDateTime::now_utc(), + since: Timestamp::now(), hello: None, snapshot: None, bounce: None, @@ -122,7 +122,7 @@ impl Joining { .map(|s| (s.session_id.clone(), SessionInfo::Full(s))) .collect::>(); Some(Joined { - since: OffsetDateTime::now_utc(), + since: Timestamp::now(), session, account: hello.account.clone(), listing, @@ -164,7 +164,7 @@ impl SessionInfo { #[derive(Debug, Clone)] pub struct Joined { - pub since: OffsetDateTime, + pub since: Timestamp, pub session: SessionView, pub account: Option, pub listing: HashMap, @@ -522,10 +522,10 @@ impl Conn { self.disconnect().await?; } - let now = OffsetDateTime::now_utc(); + let now = Timestamp::now(); // Send new ws ping - let ws_payload = now.unix_timestamp_nanos().to_be_bytes().to_vec(); + let ws_payload = now.as_millisecond().to_be_bytes().to_vec(); self.last_ws_ping_payload = Some(ws_payload.clone()); self.last_ws_ping_replied_to = false; self.ws.send(tungstenite::Message::Ping(ws_payload)).await?;