aboutsummaryrefslogtreecommitdiffstats
path: root/atuin-client
diff options
context:
space:
mode:
authorConrad Ludgate <conradludgate@gmail.com>2023-09-11 09:26:05 +0100
committerGitHub <noreply@github.com>2023-09-11 09:26:05 +0100
commitf90c01f702f6a98b041f766b6a1d857bc1b9afef (patch)
tree04a4755bd632abdcf398d0ce903163ed60a5a212 /atuin-client
parentUse `case` for Linux distro choice in `install.sh` (#1200) (diff)
downloadatuin-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.toml3
-rw-r--r--atuin-client/src/api_client.rs11
-rw-r--r--atuin-client/src/database.rs63
-rw-r--r--atuin-client/src/encryption.rs54
-rw-r--r--atuin-client/src/history.rs33
-rw-r--r--atuin-client/src/history/builder.rs9
-rw-r--r--atuin-client/src/import/bash.rs13
-rw-r--r--atuin-client/src/import/fish.rs10
-rw-r--r--atuin-client/src/import/nu.rs5
-rw-r--r--atuin-client/src/import/nu_histdb.rs10
-rw-r--r--atuin-client/src/import/resh.rs14
-rw-r--r--atuin-client/src/import/zsh.rs37
-rw-r--r--atuin-client/src/import/zsh_histdb.rs6
-rw-r--r--atuin-client/src/settings.rs30
-rw-r--r--atuin-client/src/sync.rs14
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?;