diff options
| author | Conrad Ludgate <conradludgate@gmail.com> | 2023-09-11 09:26:05 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-11 09:26:05 +0100 |
| commit | f90c01f702f6a98b041f766b6a1d857bc1b9afef (patch) | |
| tree | 04a4755bd632abdcf398d0ce903163ed60a5a212 /atuin-client | |
| parent | Use `case` for Linux distro choice in `install.sh` (#1200) (diff) | |
| download | atuin-f90c01f702f6a98b041f766b6a1d857bc1b9afef.zip | |
replace chrono with time (#806)
* replace chrono with time
* Fix test chrono usage
---------
Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
Diffstat (limited to 'atuin-client')
| -rw-r--r-- | atuin-client/Cargo.toml | 3 | ||||
| -rw-r--r-- | atuin-client/src/api_client.rs | 11 | ||||
| -rw-r--r-- | atuin-client/src/database.rs | 63 | ||||
| -rw-r--r-- | atuin-client/src/encryption.rs | 54 | ||||
| -rw-r--r-- | atuin-client/src/history.rs | 33 | ||||
| -rw-r--r-- | atuin-client/src/history/builder.rs | 9 | ||||
| -rw-r--r-- | atuin-client/src/import/bash.rs | 13 | ||||
| -rw-r--r-- | atuin-client/src/import/fish.rs | 10 | ||||
| -rw-r--r-- | atuin-client/src/import/nu.rs | 5 | ||||
| -rw-r--r-- | atuin-client/src/import/nu_histdb.rs | 10 | ||||
| -rw-r--r-- | atuin-client/src/import/resh.rs | 14 | ||||
| -rw-r--r-- | atuin-client/src/import/zsh.rs | 37 | ||||
| -rw-r--r-- | atuin-client/src/import/zsh_histdb.rs | 6 | ||||
| -rw-r--r-- | atuin-client/src/settings.rs | 30 | ||||
| -rw-r--r-- | atuin-client/src/sync.rs | 14 |
15 files changed, 173 insertions, 139 deletions
diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml index 732b6a74..9e8a050d 100644 --- a/atuin-client/Cargo.toml +++ b/atuin-client/Cargo.toml @@ -3,6 +3,7 @@ name = "atuin-client" edition = "2021" description = "client library for atuin" +rust-version = { workspace = true } version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -20,7 +21,7 @@ atuin-common = { path = "../atuin-common", version = "16.0.0" } log = { workspace = true } base64 = { workspace = true } -chrono = { workspace = true } +time = { workspace = true } clap = { workspace = true } eyre = { workspace = true } directories = { workspace = true } diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs index 5ae1ed0a..d2ca339f 100644 --- a/atuin-client/src/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::env; -use chrono::Utc; use eyre::{bail, Result}; use reqwest::{ header::{HeaderMap, AUTHORIZATION, USER_AGENT}, @@ -17,6 +16,8 @@ use atuin_common::{ record::RecordIndex, }; use semver::Version; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; use crate::{history::History, sync::hash_str}; @@ -150,8 +151,8 @@ impl<'a> Client<'a> { pub async fn get_history( &self, - sync_ts: chrono::DateTime<Utc>, - history_ts: chrono::DateTime<Utc>, + sync_ts: OffsetDateTime, + history_ts: OffsetDateTime, host: Option<String>, ) -> Result<SyncHistoryResponse> { let host = host.unwrap_or_else(|| { @@ -165,8 +166,8 @@ impl<'a> Client<'a> { let url = format!( "{}/sync/history?sync_ts={}&history_ts={}&host={}", self.sync_addr, - urlencoding::encode(sync_ts.to_rfc3339().as_str()), - urlencoding::encode(history_ts.to_rfc3339().as_str()), + urlencoding::encode(sync_ts.format(&Rfc3339)?.as_str()), + urlencoding::encode(history_ts.format(&Rfc3339)?.as_str()), host, ); diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs index b69c7cb2..16a4a43f 100644 --- a/atuin-client/src/database.rs +++ b/atuin-client/src/database.rs @@ -6,7 +6,6 @@ use std::{ use async_trait::async_trait; use atuin_common::utils; -use chrono::{prelude::*, Utc}; use fs_err as fs; use itertools::Itertools; use lazy_static::lazy_static; @@ -17,6 +16,7 @@ use sqlx::{ sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow}, Result, Row, }; +use time::OffsetDateTime; use super::{ history::History, @@ -81,18 +81,14 @@ pub trait Database: Send + Sync + 'static { max: Option<usize>, unique: bool, ) -> Result<Vec<History>>; - async fn range( - &self, - from: chrono::DateTime<Utc>, - to: chrono::DateTime<Utc>, - ) -> Result<Vec<History>>; + async fn range(&self, from: OffsetDateTime, to: OffsetDateTime) -> Result<Vec<History>>; async fn update(&self, h: &History) -> Result<()>; async fn history_count(&self) -> Result<i64>; async fn first(&self) -> Result<History>; async fn last(&self) -> Result<History>; - async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>; + async fn before(&self, timestamp: OffsetDateTime, count: i64) -> Result<Vec<History>>; async fn delete(&self, mut h: History) -> Result<()>; async fn deleted(&self) -> Result<Vec<History>>; @@ -158,14 +154,14 @@ impl Sqlite { values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", ) .bind(h.id.as_str()) - .bind(h.timestamp.timestamp_nanos()) + .bind(h.timestamp.unix_timestamp_nanos() as i64) .bind(h.duration) .bind(h.exit) .bind(h.command.as_str()) .bind(h.cwd.as_str()) .bind(h.session.as_str()) .bind(h.hostname.as_str()) - .bind(h.deleted_at.map(|t|t.timestamp_nanos())) + .bind(h.deleted_at.map(|t|t.unix_timestamp_nanos() as i64)) .execute(&mut **tx) .await?; @@ -177,14 +173,19 @@ impl Sqlite { History::from_db() .id(row.get("id")) - .timestamp(Utc.timestamp_nanos(row.get("timestamp"))) + .timestamp( + OffsetDateTime::from_unix_timestamp_nanos(row.get::<i64, _>("timestamp") as i128) + .unwrap(), + ) .duration(row.get("duration")) .exit(row.get("exit")) .command(row.get("command")) .cwd(row.get("cwd")) .session(row.get("session")) .hostname(row.get("hostname")) - .deleted_at(deleted_at.map(|t| Utc.timestamp_nanos(t))) + .deleted_at( + deleted_at.and_then(|t| OffsetDateTime::from_unix_timestamp_nanos(t as i128).ok()), + ) .build() .into() } @@ -236,14 +237,14 @@ impl Database for Sqlite { where id = ?1", ) .bind(h.id.as_str()) - .bind(h.timestamp.timestamp_nanos()) + .bind(h.timestamp.unix_timestamp_nanos() as i64) .bind(h.duration) .bind(h.exit) .bind(h.command.as_str()) .bind(h.cwd.as_str()) .bind(h.session.as_str()) .bind(h.hostname.as_str()) - .bind(h.deleted_at.map(|t|t.timestamp_nanos())) + .bind(h.deleted_at.map(|t|t.unix_timestamp_nanos() as i64)) .execute(&self.pool) .await?; @@ -292,18 +293,14 @@ impl Database for Sqlite { Ok(res) } - async fn range( - &self, - from: chrono::DateTime<Utc>, - to: chrono::DateTime<Utc>, - ) -> Result<Vec<History>> { + async fn range(&self, from: OffsetDateTime, to: OffsetDateTime) -> Result<Vec<History>> { debug!("listing history from {:?} to {:?}", from, to); let res = sqlx::query( "select * from history where timestamp >= ?1 and timestamp <= ?2 order by timestamp asc", ) - .bind(from.timestamp_nanos()) - .bind(to.timestamp_nanos()) + .bind(from.unix_timestamp_nanos() as i64) + .bind(to.unix_timestamp_nanos() as i64) .map(Self::query_history) .fetch_all(&self.pool) .await?; @@ -332,11 +329,11 @@ impl Database for Sqlite { Ok(res) } - async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>> { + async fn before(&self, timestamp: OffsetDateTime, count: i64) -> Result<Vec<History>> { let res = sqlx::query( "select * from history where timestamp < ?1 order by timestamp desc limit ?2", ) - .bind(timestamp.timestamp_nanos()) + .bind(timestamp.unix_timestamp_nanos() as i64) .bind(count) .map(Self::query_history) .fetch_all(&self.pool) @@ -471,13 +468,23 @@ impl Database for Sqlite { .map(|exclude_cwd| sql.and_where_ne("cwd", quote(exclude_cwd))); filter_options.before.map(|before| { - interim::parse_date_string(before.as_str(), Utc::now(), interim::Dialect::Uk) - .map(|before| sql.and_where_lt("timestamp", quote(before.timestamp_nanos()))) + interim::parse_date_string( + before.as_str(), + OffsetDateTime::now_utc(), + interim::Dialect::Uk, + ) + .map(|before| { + sql.and_where_lt("timestamp", quote(before.unix_timestamp_nanos() as i64)) + }) }); filter_options.after.map(|after| { - interim::parse_date_string(after.as_str(), Utc::now(), interim::Dialect::Uk) - .map(|after| sql.and_where_gt("timestamp", quote(after.timestamp_nanos()))) + interim::parse_date_string( + after.as_str(), + OffsetDateTime::now_utc(), + interim::Dialect::Uk, + ) + .map(|after| sql.and_where_gt("timestamp", quote(after.unix_timestamp_nanos() as i64))) }); sql.and_where_is_null("deleted_at"); @@ -540,7 +547,7 @@ impl Database for Sqlite { // deleted_at doesn't mean the actual time that the user deleted it, // but the time that the system marks it as deleted async fn delete(&self, mut h: History) -> Result<()> { - let now = chrono::Utc::now(); + let now = OffsetDateTime::now_utc(); h.command = rand::thread_rng() .sample_iter(&Alphanumeric) .take(32) @@ -612,7 +619,7 @@ mod test { async fn new_history_item(db: &mut impl Database, cmd: &str) -> Result<()> { let mut captured: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(OffsetDateTime::now_utc()) .command(cmd) .cwd("/home/ellie") .build() diff --git a/atuin-client/src/encryption.rs b/atuin-client/src/encryption.rs index 056c56d7..f9f8bcb3 100644 --- a/atuin-client/src/encryption.rs +++ b/atuin-client/src/encryption.rs @@ -11,7 +11,6 @@ use std::{io::prelude::*, path::PathBuf}; use base64::prelude::{Engine, BASE64_STANDARD}; -use chrono::{DateTime, Utc}; pub use crypto_secretbox::Key; use crypto_secretbox::{ aead::{Nonce, OsRng}, @@ -21,6 +20,7 @@ use eyre::{bail, ensure, eyre, Context, Result}; use fs_err as fs; use rmp::{decode::Bytes, Marker}; use serde::{Deserialize, Serialize}; +use time::{format_description::well_known::Rfc3339, macros::format_description, OffsetDateTime}; use crate::{history::History, settings::Settings}; @@ -137,6 +137,28 @@ pub fn decrypt(mut encrypted_history: EncryptedHistory, key: &Key) -> Result<His Ok(history) } +fn format_rfc3339(ts: OffsetDateTime) -> Result<String> { + // horrible hack. chrono AutoSI limits to 0, 3, 6, or 9 decimal places for nanoseconds. + // time does not have this functionality. + static PARTIAL_RFC3339_0: &[time::format_description::FormatItem<'static>] = + format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z"); + static PARTIAL_RFC3339_3: &[time::format_description::FormatItem<'static>] = + format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"); + static PARTIAL_RFC3339_6: &[time::format_description::FormatItem<'static>] = + format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z"); + static PARTIAL_RFC3339_9: &[time::format_description::FormatItem<'static>] = + format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z"); + + let fmt = match ts.nanosecond() { + 0 => PARTIAL_RFC3339_0, + ns if ns % 1_000_000 == 0 => PARTIAL_RFC3339_3, + ns if ns % 1_000 == 0 => PARTIAL_RFC3339_6, + _ => PARTIAL_RFC3339_9, + }; + + Ok(ts.format(fmt)?) +} + fn encode(h: &History) -> Result<Vec<u8>> { use rmp::encode; @@ -145,11 +167,7 @@ fn encode(h: &History) -> Result<Vec<u8>> { encode::write_array_len(&mut output, 9)?; encode::write_str(&mut output, &h.id)?; - encode::write_str( - &mut output, - &(h.timestamp - .to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)), - )?; + encode::write_str(&mut output, &(format_rfc3339(h.timestamp)?))?; encode::write_sint(&mut output, h.duration)?; encode::write_sint(&mut output, h.exit)?; encode::write_str(&mut output, &h.command)?; @@ -157,10 +175,7 @@ fn encode(h: &History) -> Result<Vec<u8>> { encode::write_str(&mut output, &h.session)?; encode::write_str(&mut output, &h.hostname)?; match h.deleted_at { - Some(d) => encode::write_str( - &mut output, - &d.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true), - )?, + Some(d) => encode::write_str(&mut output, &format_rfc3339(d)?)?, None => encode::write_nil(&mut output)?, } @@ -220,7 +235,7 @@ fn decode(bytes: &[u8]) -> Result<History> { Ok(History { id: id.to_owned(), - timestamp: DateTime::parse_from_rfc3339(timestamp)?.with_timezone(&Utc), + timestamp: OffsetDateTime::parse(timestamp, &Rfc3339)?, duration, exit, command: command.to_owned(), @@ -228,9 +243,8 @@ fn decode(bytes: &[u8]) -> Result<History> { session: session.to_owned(), hostname: hostname.to_owned(), deleted_at: deleted_at - .map(DateTime::parse_from_rfc3339) - .transpose()? - .map(|dt| dt.with_timezone(&Utc)), + .map(|t| OffsetDateTime::parse(t, &Rfc3339)) + .transpose()?, }) } @@ -241,6 +255,8 @@ fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report { #[cfg(test)] mod test { use crypto_secretbox::{aead::OsRng, KeyInit, XSalsa20Poly1305}; + use pretty_assertions::assert_eq; + use time::{macros::datetime, OffsetDateTime}; use crate::history::History; @@ -253,7 +269,7 @@ mod test { let history = History::from_db() .id("1".into()) - .timestamp(chrono::Utc::now()) + .timestamp(OffsetDateTime::now_utc()) .command("ls".into()) .cwd("/home/ellie".into()) .exit(0) @@ -297,7 +313,7 @@ mod test { ]; let history = History { id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(), - timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(), + timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00), duration: 49206000, exit: 0, command: "git status".to_owned(), @@ -318,14 +334,14 @@ mod test { fn test_decode_deleted() { let history = History { id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(), - timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(), + timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00), duration: 49206000, exit: 0, command: "git status".to_owned(), cwd: "/Users/conrad.ludgate/Documents/code/atuin".to_owned(), session: "b97d9a306f274473a203d2eba41f9457".to_owned(), hostname: "fvfg936c0kpf:conrad.ludgate".to_owned(), - deleted_at: Some("2023-05-28T18:35:40.633872Z".parse().unwrap()), + deleted_at: Some(datetime!(2023-05-28 18:35:40.633872 +00:00)), }; let b = encode(&history).unwrap(); @@ -349,7 +365,7 @@ mod test { ]; let history = History { id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(), - timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(), + timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00), duration: 49206000, exit: 0, command: "git status".to_owned(), diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs index 4d084786..f4c0a8eb 100644 --- a/atuin-client/src/history.rs +++ b/atuin-client/src/history.rs @@ -1,11 +1,10 @@ use std::env; -use chrono::Utc; - use atuin_common::utils::uuid_v7; use regex::RegexSet; use crate::{secrets::SECRET_PATTERNS, settings::Settings}; +use time::OffsetDateTime; mod builder; @@ -29,7 +28,7 @@ pub struct History { /// Stored as `client_id` in the database. pub id: String, /// When the command was run. - pub timestamp: chrono::DateTime<Utc>, + pub timestamp: OffsetDateTime, /// How long the command took to run. pub duration: i64, /// The exit code of the command. @@ -43,20 +42,20 @@ pub struct History { /// The hostname of the machine the command was run on. pub hostname: String, /// Timestamp, which is set when the entry is deleted, allowing a soft delete. - pub deleted_at: Option<chrono::DateTime<Utc>>, + pub deleted_at: Option<OffsetDateTime>, } impl History { #[allow(clippy::too_many_arguments)] fn new( - timestamp: chrono::DateTime<Utc>, + timestamp: OffsetDateTime, command: String, cwd: String, exit: i64, duration: i64, session: Option<String>, hostname: Option<String>, - deleted_at: Option<chrono::DateTime<Utc>>, + deleted_at: Option<OffsetDateTime>, ) -> Self { let session = session .or_else(|| env::var("ATUIN_SESSION").ok()) @@ -91,7 +90,7 @@ impl History { /// use atuin_client::history::History; /// /// let history: History = History::import() - /// .timestamp(chrono::Utc::now()) + /// .timestamp(time::OffsetDateTime::now_utc()) /// .command("ls -la") /// .build() /// .into(); @@ -102,7 +101,7 @@ impl History { /// use atuin_client::history::History; /// /// let history: History = History::import() - /// .timestamp(chrono::Utc::now()) + /// .timestamp(time::OffsetDateTime::now_utc()) /// .command("ls -la") /// .cwd("/home/user") /// .exit(0) @@ -138,7 +137,7 @@ impl History { /// use atuin_client::history::History; /// /// let history: History = History::capture() - /// .timestamp(chrono::Utc::now()) + /// .timestamp(time::OffsetDateTime::now_utc()) /// .command("ls -la") /// .cwd("/home/user") /// .build() @@ -152,7 +151,7 @@ impl History { /// /// // this will not compile because `cwd` is missing /// let history: History = History::capture() - /// .timestamp(chrono::Utc::now()) + /// .timestamp(time::OffsetDateTime::now_utc()) /// .command("ls -la") /// .build() /// .into(); @@ -170,7 +169,7 @@ impl History { /// /// // this will not compile because `id` field is missing /// let history: History = History::from_db() - /// .timestamp(chrono::Utc::now()) + /// .timestamp(time::OffsetDateTime::now_utc()) /// .command("ls -la".to_string()) /// .cwd("/home/user".to_string()) /// .exit(0) @@ -216,35 +215,35 @@ mod tests { settings.history_filter = RegexSet::new(["^psql"]).unwrap(); let normal_command: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command("echo foo") .cwd("/") .build() .into(); let with_space: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command(" echo bar") .cwd("/") .build() .into(); let stripe_key: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop") .cwd("/") .build() .into(); let secret_dir: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command("echo ohno") .cwd("/supasecret") .build() .into(); let with_psql: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command("psql") .cwd("/supasecret") .build() @@ -263,7 +262,7 @@ mod tests { settings.secrets_filter = false; let stripe_key: History = History::capture() - .timestamp(chrono::Utc::now()) + .timestamp(time::OffsetDateTime::now_utc()) .command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop") .cwd("/") .build() diff --git a/atuin-client/src/history/builder.rs b/atuin-client/src/history/builder.rs index dc22b60e..27531c95 100644 --- a/atuin-client/src/history/builder.rs +++ b/atuin-client/src/history/builder.rs @@ -1,4 +1,3 @@ -use chrono::Utc; use typed_builder::TypedBuilder; use super::History; @@ -8,7 +7,7 @@ use super::History; /// The only two required fields are `timestamp` and `command`. #[derive(Debug, Clone, TypedBuilder)] pub struct HistoryImported { - timestamp: chrono::DateTime<Utc>, + timestamp: time::OffsetDateTime, #[builder(setter(into))] command: String, #[builder(default = "unknown".into(), setter(into))] @@ -45,7 +44,7 @@ impl From<HistoryImported> for History { /// the command is finished, such as `exit` or `duration`. #[derive(Debug, Clone, TypedBuilder)] pub struct HistoryCaptured { - timestamp: chrono::DateTime<Utc>, + timestamp: time::OffsetDateTime, #[builder(setter(into))] command: String, #[builder(setter(into))] @@ -73,14 +72,14 @@ impl From<HistoryCaptured> for History { #[derive(Debug, Clone, TypedBuilder)] pub struct HistoryFromDb { id: String, - timestamp: chrono::DateTime<Utc>, + timestamp: time::OffsetDateTime, command: String, cwd: String, exit: i64, duration: i64, session: String, hostname: String, - deleted_at: Option<chrono::DateTime<Utc>>, + deleted_at: Option<time::OffsetDateTime>, } impl From<HistoryFromDb> for History { diff --git a/atuin-client/src/import/bash.rs b/atuin-client/src/import/bash.rs index 25ede053..fe080a55 100644 --- a/atuin-client/src/import/bash.rs +++ b/atuin-client/src/import/bash.rs @@ -1,10 +1,10 @@ use std::{fs::File, io::Read, path::PathBuf, str}; use async_trait::async_trait; -use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use directories::UserDirs; use eyre::{eyre, Result}; use itertools::Itertools; +use time::{Duration, OffsetDateTime}; use super::{get_histpath, unix_byte_lines, Importer, Loader}; use crate::history::History; @@ -55,7 +55,7 @@ impl Importer for Bash { _ => None, }) // if no known timestamps, use now as base - .unwrap_or((lines.len(), Utc::now())); + .unwrap_or((lines.len(), OffsetDateTime::now_utc())); // if no timestamp is recorded, then use this increment to set an arbitrary timestamp // to preserve ordering @@ -99,7 +99,7 @@ enum LineType<'a> { Empty, /// A timestamp line start with a '#', followed immediately by an integer /// that represents seconds since UNIX epoch. - Timestamp(DateTime<Utc>), + Timestamp(OffsetDateTime), /// Anything else. Command(&'a str), } @@ -119,10 +119,9 @@ impl<'a> From<&'a [u8]> for LineType<'a> { } } -fn try_parse_line_as_timestamp(line: &str) -> Option<DateTime<Utc>> { +fn try_parse_line_as_timestamp(line: &str) -> Option<OffsetDateTime> { let seconds = line.strip_prefix('#')?.parse().ok()?; - let time = NaiveDateTime::from_timestamp(seconds, 0); - Some(DateTime::from_utc(time, Utc)) + OffsetDateTime::from_unix_timestamp(seconds).ok() } #[cfg(test)] @@ -183,7 +182,7 @@ cd ../ ["git reset", "git clean -dxf", "cd ../"], ); assert_equal( - loader.buf.iter().map(|h| h.timestamp.timestamp()), + loader.buf.iter().map(|h| h.timestamp.unix_timestamp()), [1672918999, 1672919006, 1672919020], ) } diff --git a/atuin-client/src/import/fish.rs b/atuin-client/src/import/fish.rs index 90ecabc3..82c59013 100644 --- a/atuin-client/src/import/fish.rs +++ b/atuin-client/src/import/fish.rs @@ -4,9 +4,9 @@ use std::{fs::File, io::Read, path::PathBuf}; use async_trait::async_trait; -use chrono::{prelude::*, Utc}; use directories::BaseDirs; use eyre::{eyre, Result}; +use time::OffsetDateTime; use super::{unix_byte_lines, Importer, Loader}; use crate::history::History; @@ -59,8 +59,8 @@ impl Importer for Fish { } async fn load(self, loader: &mut impl Loader) -> Result<()> { - let now = Utc::now(); - let mut time: Option<DateTime<Utc>> = None; + let now = OffsetDateTime::now_utc(); + let mut time: Option<OffsetDateTime> = None; let mut cmd: Option<String> = None; for b in unix_byte_lines(&self.bytes) { @@ -89,7 +89,7 @@ impl Importer for Fish { } else if let Some(t) = s.strip_prefix(" when: ") { // if t is not an int, just ignore this line if let Ok(t) = t.parse::<i64>() { - time = Some(Utc.timestamp(t, 0)); + time = Some(OffsetDateTime::from_unix_timestamp(t)?); } } else { // ... ignore paths lines @@ -164,7 +164,7 @@ ERROR ($timestamp:expr, $command:expr) => { let h = history.next().expect("missing entry in history"); assert_eq!(h.command.as_str(), $command); - assert_eq!(h.timestamp.timestamp(), $timestamp); + assert_eq!(h.timestamp.unix_timestamp(), $timestamp); }; } diff --git a/atuin-client/src/import/nu.rs b/atuin-client/src/import/nu.rs index 46600325..1131fac5 100644 --- a/atuin-client/src/import/nu.rs +++ b/atuin-client/src/import/nu.rs @@ -6,6 +6,7 @@ use std::{fs::File, io::Read, path::PathBuf}; use async_trait::async_trait; use directories::BaseDirs; use eyre::{eyre, Result}; +use time::OffsetDateTime; use super::{unix_byte_lines, Importer, Loader}; use crate::history::History; @@ -44,7 +45,7 @@ impl Importer for Nu { } async fn load(self, h: &mut impl Loader) -> Result<()> { - let now = chrono::Utc::now(); + let now = OffsetDateTime::now_utc(); let mut counter = 0; for b in unix_byte_lines(&self.bytes) { @@ -55,7 +56,7 @@ impl Importer for Nu { let cmd: String = s.replace("<\\n>", "\n"); - let offset = chrono::Duration::nanoseconds(counter); + let offset = time::Duration::nanoseconds(counter); counter += 1; let entry = History::import().timestamp(now - offset).command(cmd); diff --git a/atuin-client/src/import/nu_histdb.rs b/atuin-client/src/import/nu_histdb.rs index 34568d80..f0e8e95c 100644 --- a/atuin-client/src/import/nu_histdb.rs +++ b/atuin-client/src/import/nu_histdb.rs @@ -4,10 +4,10 @@ use std::path::PathBuf; use async_trait::async_trait; -use chrono::{prelude::*, Utc}; use directories::BaseDirs; use eyre::{eyre, Result}; use sqlx::{sqlite::SqlitePool, Pool}; +use time::{Duration, OffsetDateTime}; use super::Importer; use crate::history::History; @@ -31,10 +31,10 @@ impl From<HistDbEntry> for History { let ts_secs = histdb_item.start_timestamp / 1000; let ts_ns = (histdb_item.start_timestamp % 1000) * 1_000_000; let imported = History::import() - .timestamp(DateTime::from_utc( - NaiveDateTime::from_timestamp(ts_secs, ts_ns as u32), - Utc, - )) + .timestamp( + OffsetDateTime::from_unix_timestamp(ts_secs).unwrap() + + Duration::nanoseconds(ts_ns), + ) .command(String::from_utf8(histdb_item.command_line).unwrap()) .cwd(String::from_utf8(histdb_item.cwd).unwrap()) .exit(histdb_item.exit_status) diff --git a/atuin-client/src/import/resh.rs b/atuin-client/src/import/resh.rs index 3c5b799e..5475db51 100644 --- a/atuin-client/src/import/resh.rs +++ b/atuin-client/src/import/resh.rs @@ -1,12 +1,12 @@ use std::{fs::File, io::Read, path::PathBuf}; use async_trait::async_trait; -use chrono::{TimeZone, Utc}; use directories::UserDirs; use eyre::{eyre, Result}; use serde::Deserialize; use atuin_common::utils::uuid_v7; +use time::OffsetDateTime; use super::{get_histpath, unix_byte_lines, Importer, Loader}; use crate::history::History; @@ -110,16 +110,18 @@ impl Importer for Resh { #[allow(clippy::cast_sign_loss)] let timestamp = { let secs = entry.realtime_before.floor() as i64; - let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as u32; - Utc.timestamp(secs, nanosecs) + let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as i64; + OffsetDateTime::from_unix_timestamp(secs)? + time::Duration::nanoseconds(nanosecs) }; #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] let duration = { let secs = entry.realtime_after.floor() as i64; - let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as u32; - let difference = Utc.timestamp(secs, nanosecs) - timestamp; - difference.num_nanoseconds().unwrap_or(0) + let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as i64; + let base = OffsetDateTime::from_unix_timestamp(secs)? + + time::Duration::nanoseconds(nanosecs); + let difference = base - timestamp; + difference.whole_nanoseconds() as i64 }; let imported = History::import() diff --git a/atuin-client/src/import/zsh.rs b/atuin-client/src/import/zsh.rs index e98819e2..632caff9 100644 --- a/atuin-client/src/import/zsh.rs +++ b/atuin-client/src/import/zsh.rs @@ -4,9 +4,9 @@ use std::{fs::File, io::Read, path::PathBuf}; use async_trait::async_trait; -use chrono::{prelude::*, Utc}; use directories::UserDirs; use eyre::{eyre, Result}; +use time::OffsetDateTime; use super::{get_histpath, unix_byte_lines, Importer, Loader}; use crate::history::History; @@ -58,7 +58,7 @@ impl Importer for Zsh { } async fn load(self, h: &mut impl Loader) -> Result<()> { - let now = chrono::Utc::now(); + let now = OffsetDateTime::now_utc(); let mut line = String::new(); let mut counter = 0; @@ -79,7 +79,7 @@ impl Importer for Zsh { counter += 1; h.push(parse_extended(command, counter)).await?; } else { - let offset = chrono::Duration::seconds(counter); + let offset = time::Duration::seconds(counter); counter += 1; let imported = History::import() @@ -102,11 +102,10 @@ fn parse_extended(line: &str, counter: i64) -> History { let time = time .parse::<i64>() - .unwrap_or_else(|_| chrono::Utc::now().timestamp()); - - let offset = chrono::Duration::milliseconds(counter); - let time = Utc.timestamp(time, 0); - let time = time + offset; + .ok() + .and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok()) + .unwrap_or_else(OffsetDateTime::now_utc) + + time::Duration::milliseconds(counter); // use nanos, because why the hell not? we won't display them. let duration = duration.parse::<i64>().map_or(-1, |t| t * 1_000_000_000); @@ -121,8 +120,6 @@ fn parse_extended(line: &str, counter: i64) -> History { #[cfg(test)] mod test { - use chrono::prelude::*; - use chrono::Utc; use itertools::assert_equal; use crate::import::tests::TestLoader; @@ -135,25 +132,37 @@ mod test { assert_eq!(parsed.command, "cargo install atuin"); assert_eq!(parsed.duration, 0); - assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0)); + assert_eq!( + parsed.timestamp, + OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap() + ); let parsed = parse_extended("1613322469:10;cargo install atuin;cargo update", 0); assert_eq!(parsed.command, "cargo install atuin;cargo update"); assert_eq!(parsed.duration, 10_000_000_000); - assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0)); + assert_eq!( + parsed.timestamp, + OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap() + ); let parsed = parse_extended("1613322469:10;cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷", 0); assert_eq!(parsed.command, "cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷"); assert_eq!(parsed.duration, 10_000_000_000); - assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0)); + assert_eq!( + parsed.timestamp, + OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap() + ); let parsed = parse_extended("1613322469:10;cargo install \\n atuin\n", 0); assert_eq!(parsed.command, "cargo install \\n atuin"); assert_eq!(parsed.duration, 10_000_000_000); - assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0)); + assert_eq!( + parsed.timestamp, + OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap() + ); } #[tokio::test] diff --git a/atuin-client/src/import/zsh_histdb.rs b/atuin-client/src/import/zsh_histdb.rs index 78a7176b..37c70814 100644 --- a/atuin-client/src/import/zsh_histdb.rs +++ b/atuin-client/src/import/zsh_histdb.rs @@ -35,10 +35,10 @@ use std::path::{Path, PathBuf}; use async_trait::async_trait; -use chrono::{prelude::*, Utc}; use directories::UserDirs; use eyre::{eyre, Result}; use sqlx::{sqlite::SqlitePool, Pool}; +use time::PrimitiveDateTime; use super::Importer; use crate::history::History; @@ -52,7 +52,7 @@ pub struct HistDbEntryCount { #[derive(sqlx::FromRow, Debug)] pub struct HistDbEntry { pub id: i64, - pub start_time: NaiveDateTime, + pub start_time: PrimitiveDateTime, pub host: Vec<u8>, pub dir: Vec<u8>, pub argv: Vec<u8>, @@ -62,7 +62,7 @@ pub struct HistDbEntry { impl From<HistDbEntry> for History { fn from(histdb_item: HistDbEntry) -> Self { let imported = History::import() - .timestamp(DateTime::from_utc(histdb_item.start_time, Utc)) + .timestamp(histdb_item.start_time.assume_utc()) .command( String::from_utf8(histdb_item.argv) .unwrap_or_else(|_e| String::from("")) diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs index c68be0d5..ebc1a372 100644 --- a/atuin-client/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -1,11 +1,11 @@ use std::{ + convert::TryFrom, io::prelude::*, path::{Path, PathBuf}, str::FromStr, }; use atuin_common::record::HostId; -use chrono::{prelude::*, Utc}; use clap::ValueEnum; use config::{ builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat, @@ -16,6 +16,7 @@ use parse_duration::parse; use regex::RegexSet; use semver::Version; use serde::Deserialize; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use uuid::Uuid; pub const HISTORY_PAGE_SIZE: i64 = 100; @@ -207,21 +208,20 @@ impl Settings { } fn save_current_time(filename: &str) -> Result<()> { - Settings::save_to_data_dir(filename, Utc::now().to_rfc3339().as_str())?; + Settings::save_to_data_dir( + filename, + OffsetDateTime::now_utc().format(&Rfc3339)?.as_str(), + )?; Ok(()) } - fn load_time_from_file(filename: &str) -> Result<chrono::DateTime<Utc>> { + fn load_time_from_file(filename: &str) -> Result<OffsetDateTime> { let value = Settings::read_from_data_dir(filename); match value { - Some(v) => { - let time = chrono::DateTime::parse_from_rfc3339(v.as_str())?; - - Ok(time.with_timezone(&Utc)) - } - None => Ok(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), + Some(v) => Ok(OffsetDateTime::parse(v.as_str(), &Rfc3339)?), + None => Ok(OffsetDateTime::UNIX_EPOCH), } } @@ -233,11 +233,11 @@ impl Settings { Settings::save_current_time(LAST_VERSION_CHECK_FILENAME) } - pub fn last_sync() -> Result<chrono::DateTime<Utc>> { + pub fn last_sync() -> Result<OffsetDateTime> { Settings::load_time_from_file(LAST_SYNC_FILENAME) } - pub fn last_version_check() -> Result<chrono::DateTime<Utc>> { + pub fn last_version_check() -> Result<OffsetDateTime> { Settings::load_time_from_file(LAST_VERSION_CHECK_FILENAME) } @@ -265,8 +265,8 @@ impl Settings { match parse(self.sync_frequency.as_str()) { Ok(d) => { - let d = chrono::Duration::from_std(d).unwrap(); - Ok(Utc::now() - Settings::last_sync()? >= d) + let d = time::Duration::try_from(d).unwrap(); + Ok(OffsetDateTime::now_utc() - Settings::last_sync()? >= d) } Err(e) => Err(eyre!("failed to check sync: {}", e)), } @@ -274,10 +274,10 @@ impl Settings { fn needs_update_check(&self) -> Result<bool> { let last_check = Settings::last_version_check()?; - let diff = Utc::now() - last_check; + let diff = OffsetDateTime::now_utc() - last_check; // Check a max of once per hour - Ok(diff.num_hours() >= 1) + Ok(diff.whole_hours() >= 1) } async fn latest_version(&self) -> Result<Version> { diff --git a/atuin-client/src/sync.rs b/atuin-client/src/sync.rs index 6704ad53..439ed6d0 100644 --- a/atuin-client/src/sync.rs +++ b/atuin-client/src/sync.rs @@ -2,11 +2,11 @@ use std::collections::HashSet; use std::convert::TryInto; use std::iter::FromIterator; -use chrono::prelude::*; use eyre::Result; use atuin_common::api::AddHistoryRequest; use crypto_secretbox::Key; +use time::OffsetDateTime; use crate::{ api_client, @@ -52,12 +52,12 @@ async fn sync_download( let mut local_count = initial_local; let mut last_sync = if force { - Utc.timestamp_millis(0) + OffsetDateTime::UNIX_EPOCH } else { Settings::last_sync()? }; - let mut last_timestamp = Utc.timestamp_millis(0); + let mut last_timestamp = OffsetDateTime::UNIX_EPOCH; let host = if force { Some(String::from("")) } else { None }; @@ -74,7 +74,7 @@ async fn sync_download( .map(|h| decrypt(h, key).expect("failed to decrypt history! check your key")) .map(|mut h| { if remote_deleted.contains(h.id.as_str()) { - h.deleted_at = Some(chrono::Utc::now()); + h.deleted_at = Some(time::OffsetDateTime::now_utc()); h.command = String::from(""); } @@ -99,8 +99,8 @@ async fn sync_download( // be "lost" between syncs. In this case we need to rewind the sync // timestamps if page_last == last_timestamp { - last_timestamp = Utc.timestamp_millis(0); - last_sync -= chrono::Duration::hours(1); + last_timestamp = OffsetDateTime::UNIX_EPOCH; + last_sync -= time::Duration::hours(1); } else { last_timestamp = page_last; } @@ -142,7 +142,7 @@ async fn sync_upload( debug!("remote has {}, we have {}", remote_count, local_count); // first just try the most recent set - let mut cursor = Utc::now(); + let mut cursor = OffsetDateTime::now_utc(); while local_count > remote_count { let last = db.before(cursor, remote_status.page_size).await?; |
