diff --git a/Cargo.lock b/Cargo.lock index 65771f90..d3c3f188 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,7 @@ dependencies = [ "hex", "indoc", "itertools", + "jiff", "lazy_static", "libssh2-sys", "lru 0.16.3", @@ -834,7 +835,6 @@ dependencies = [ "tempfile", "thiserror 2.0.17", "thousands", - "time", "tokio", "tracing", "tracing-appender", @@ -1029,7 +1029,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -1840,6 +1839,47 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2193,6 +2233,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 8ec94617..063416f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,12 +61,7 @@ strum_macros = "0.26" tempfile = "3" thiserror = "2.0" thousands = "0.2.0" -time = { version = "0.3.47", features = [ - "local-offset", - "macros", - "serde", - "serde-human-readable", -] } +jiff = { version = "0.2.19", features = ["serde"] } tokio = { version = "1.43", features = ["full", "test-util", "tracing"] } tracing = "0.1" tracing-appender = "0.2" @@ -93,7 +88,7 @@ version = "0.1.5" [dependencies.tracing-subscriber] version = "0.3.20" -features = ["env-filter", "fmt", "json", "local-time", "time"] +features = ["env-filter", "fmt", "json", "local-time"] [dev-dependencies] assert_cmd = "2.1.2" diff --git a/src/band.rs b/src/band.rs index 9a0ab5ec..dfd7b5f0 100644 --- a/src/band.rs +++ b/src/band.rs @@ -26,8 +26,8 @@ use std::sync::Arc; use crate::transport::Transport; use itertools::Itertools; +use jiff::Timestamp; use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; use tracing::{debug, trace, warn}; use crate::jsonio::{read_json, write_json}; @@ -113,10 +113,10 @@ pub struct Info { pub is_closed: bool, /// Time Conserve started writing this band. - pub start_time: OffsetDateTime, + pub start_time: Timestamp, /// Time this band was completed, if it is complete. - pub end_time: Option, + pub end_time: Option, /// Number of hunks present in the index, if that is known. pub index_hunk_count: Option, @@ -153,7 +153,7 @@ impl Band { Some("23.2.0".to_owned()) }; let head = Head { - start_time: OffsetDateTime::now_utc().unix_timestamp(), + start_time: Timestamp::now().as_second(), band_format_version, format_flags: format_flags.into(), }; @@ -171,7 +171,7 @@ impl Band { &self.transport, BAND_TAIL_FILENAME, &Tail { - end_time: OffsetDateTime::now_utc().unix_timestamp(), + end_time: Timestamp::now().as_second(), index_hunk_count: Some(index_hunk_count), }, ) @@ -267,18 +267,14 @@ impl Band { pub async fn get_info(&self) -> Result { let tail_option: Option = read_json(&self.transport, BAND_TAIL_FILENAME).await?; let start_time = - OffsetDateTime::from_unix_timestamp(self.head.start_time).map_err(|_| { - Error::InvalidMetadata { - details: format!("Invalid band start timestamp {:?}", self.head.start_time), - } + Timestamp::from_second(self.head.start_time).map_err(|_| Error::InvalidMetadata { + details: format!("Invalid band start timestamp {:?}", self.head.start_time), })?; let end_time = tail_option .as_ref() .map(|tail| { - OffsetDateTime::from_unix_timestamp(tail.end_time).map_err(|_| { - Error::InvalidMetadata { - details: format!("Invalid band end timestamp {:?}", tail.end_time), - } + Timestamp::from_second(tail.end_time).map_err(|_| Error::InvalidMetadata { + details: format!("Invalid band end timestamp {:?}", tail.end_time), }) }) .transpose()?; @@ -352,10 +348,14 @@ mod tests { assert_eq!(info.id.to_string(), "b0000"); assert!(info.is_closed); assert_eq!(info.index_hunk_count, Some(0)); - let dur = info.end_time.expect("info has an end_time") - info.start_time; + let dur = info + .end_time + .expect("info has an end_time") + .since(info.start_time) + .unwrap(); // Test should have taken (much) less than 5s between starting and finishing // the band. (It might fail if you set a breakpoint right there.) - assert!(dur < Duration::from_secs(5)); + assert!(dur.total(jiff::Unit::Second).unwrap() < 5.0); } #[tokio::test] diff --git a/src/bin/conserve.rs b/src/bin/conserve.rs index ce5cdd7f..684d35a2 100644 --- a/src/bin/conserve.rs +++ b/src/bin/conserve.rs @@ -23,7 +23,6 @@ use std::time::Instant; use clap::builder::{Styles, styling}; use clap::{Parser, Subcommand}; use conserve::change::Change; -use time::UtcOffset; #[allow(unused_imports)] use tracing::{Level, debug, error, info, trace, warn}; @@ -31,9 +30,9 @@ use crate::transport::Transport; use conserve::termui::{TermUiMonitor, TraceTimeStyle, enable_tracing}; use conserve::*; -/// Local timezone offset, calculated once at startup, to avoid issues about +/// Local timezone, calculated once at startup, to avoid issues about /// looking at the environment once multiple threads are running. -static LOCAL_OFFSET: RwLock = RwLock::new(UtcOffset::UTC); +static LOCAL_TZ: RwLock = RwLock::new(jiff::tz::TimeZone::UTC); #[mutants::skip] // only visual effects, not worth testing fn clap_styles() -> Styles { @@ -633,7 +632,7 @@ impl Command { let timezone = if *utc { None } else { - Some(*LOCAL_OFFSET.read().unwrap()) + Some(LOCAL_TZ.read().unwrap().clone()) }; let archive = Archive::open(Transport::new(archive).await?).await?; let options = ShowVersionsOptions { @@ -716,10 +715,9 @@ fn make_change_callback( } fn main() -> Result { - // Before anything else, get the local time offset, to avoid `time-rs` - // problems with loading it when threads are running. - *LOCAL_OFFSET.write().unwrap() = - UtcOffset::current_local_offset().expect("get local time offset"); + // Before anything else, get the local timezone, to avoid issues + // with loading it when threads are running. + *LOCAL_TZ.write().unwrap() = jiff::tz::TimeZone::system(); let args = Args::parse(); let start_time = Instant::now(); let console_level = if args.debug { diff --git a/src/change.rs b/src/change.rs index bc6af0af..02f8c251 100644 --- a/src/change.rs +++ b/src/change.rs @@ -15,8 +15,8 @@ use std::fmt; +use jiff::Timestamp; use serde::Serialize; -use time::OffsetDateTime; use crate::{Apath, EntryTrait, Kind, Owner, Result, UnixMode}; @@ -138,7 +138,7 @@ pub struct EntryMetadata { // TODO: Eventually unify with EntryValue or Entry? #[serde(flatten)] pub kind: KindMetadata, - pub mtime: OffsetDateTime, + pub mtime: Timestamp, #[serde(flatten)] pub owner: Owner, pub unix_mode: UnixMode, diff --git a/src/entry.rs b/src/entry.rs index 24afd604..db279a6f 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -16,8 +16,8 @@ use std::fmt::Debug; +use jiff::Timestamp; use serde::Serialize; -use time::OffsetDateTime; use crate::*; @@ -29,7 +29,7 @@ use crate::*; pub trait EntryTrait: Debug { fn apath(&self) -> &Apath; fn kind(&self) -> Kind; - fn mtime(&self) -> OffsetDateTime; + fn mtime(&self) -> Timestamp; fn size(&self) -> Option; fn symlink_target(&self) -> Option<&str>; fn unix_mode(&self) -> UnixMode; diff --git a/src/index/entry.rs b/src/index/entry.rs index f2551752..4d58b981 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -11,15 +11,14 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. +use jiff::Timestamp; use serde_json::json; -use time::OffsetDateTime; use crate::apath::Apath; use crate::entry::EntryTrait; use crate::kind::Kind; use crate::owner::Owner; use crate::unix_mode::UnixMode; -use crate::unix_time::FromUnixAndNanos; use crate::{blockdir, source}; /// Description of one archived file. @@ -83,8 +82,8 @@ impl EntryTrait for IndexEntry { } #[inline] - fn mtime(&self) -> OffsetDateTime { - OffsetDateTime::from_unix_seconds_and_nanos(self.mtime, self.mtime_nanos) + fn mtime(&self) -> Timestamp { + Timestamp::new(self.mtime, self.mtime_nanos as i32).expect("valid timestamp") } /// Size of the file, if it is a file. None for directories and symlinks. @@ -142,8 +141,8 @@ impl IndexEntry { kind: source.kind(), addrs: Vec::new(), target: source.symlink_target().map(|t| t.to_owned()), - mtime: mtime.unix_timestamp(), - mtime_nanos: mtime.nanosecond(), + mtime: mtime.as_second(), + mtime_nanos: mtime.subsec_nanosecond() as u32, unix_mode: source.unix_mode(), owner: source.owner().to_owned(), } diff --git a/src/restore.rs b/src/restore.rs index fbbc717d..748d0993 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use filetime::set_file_handle_times; #[cfg(unix)] use filetime::set_symlink_file_times; -use time::OffsetDateTime; +use jiff::Timestamp; use tracing::{instrument, trace}; use crate::blockdir::BlockDir; @@ -29,7 +29,7 @@ use crate::counters::Counter; use crate::index::entry::IndexEntry; use crate::io::{directory_is_empty, ensure_dir_exists}; use crate::monitor::Monitor; -use crate::unix_time::ToFileTime; +use crate::unix_time::timestamp_to_file_time; use crate::*; /// Description of how to restore a tree. @@ -176,7 +176,7 @@ fn restore_dir(apath: &Apath, restore_path: &Path, options: &RestoreOptions) -> struct DirDeferral { path: PathBuf, unix_mode: UnixMode, - mtime: OffsetDateTime, + mtime: Timestamp, owner: Owner, } @@ -200,7 +200,7 @@ fn apply_deferrals(deferrals: &[DirDeferral], monitor: Arc) -> Resu source, }); } - if let Err(source) = filetime::set_file_mtime(path, (*mtime).to_file_time()) { + if let Err(source) = filetime::set_file_mtime(path, timestamp_to_file_time(mtime)) { monitor.error(Error::RestoreModificationTime { path: path.clone(), source, @@ -247,7 +247,7 @@ async fn restore_file( source, })?; - let mtime = Some(source_entry.mtime().to_file_time()); + let mtime = Some(timestamp_to_file_time(&source_entry.mtime())); set_file_handle_times(&out, mtime, mtime).map_err(|source| Error::RestoreModificationTime { path: path.clone(), source, @@ -291,7 +291,7 @@ fn restore_symlink(path: &Path, entry: &IndexEntry) -> Result<()> { source, }); } - let mtime = entry.mtime().to_file_time(); + let mtime = timestamp_to_file_time(&entry.mtime()); if let Err(source) = set_symlink_file_times(path, mtime, mtime) { return Err(Error::RestoreModificationTime { path: path.to_owned(), diff --git a/src/show.rs b/src/show.rs index 650fdcd2..bb935a83 100644 --- a/src/show.rs +++ b/src/show.rs @@ -20,8 +20,6 @@ use std::borrow::Cow; use std::io::{BufWriter, Write}; use std::sync::Arc; -use time::UtcOffset; -use time::format_description::well_known::Rfc3339; use tracing::error; use crate::index::entry::IndexEntry; @@ -42,7 +40,7 @@ pub struct ShowVersionsOptions { /// Show how much time the backup took, or "incomplete" if it never finished. pub backup_duration: bool, /// Show times in this zone. - pub timezone: Option, + pub timezone: Option, } /// Print a list of versions, one per line, on stdout. @@ -78,24 +76,28 @@ pub async fn show_versions( }; if options.start_time { - let mut start_time = info.start_time; - if let Some(timezone) = options.timezone { - start_time = start_time.to_offset(timezone); - } + let start_time_str = if let Some(timezone) = options.timezone.as_ref() { + info.start_time.to_zoned(timezone.clone()).to_string() + } else { + info.start_time.to_string() + }; l.push(format!( "{date:<25}", // "yyyy-mm-ddThh:mm:ss+oooo" => 25 - date = start_time.format(&Rfc3339).unwrap(), + date = start_time_str, )); } if options.backup_duration { let duration_str: Cow = if info.is_closed { if let Some(end_time) = info.end_time { - let duration = end_time - info.start_time; - if let Ok(duration) = duration.try_into() { - duration_to_hms(duration).into() - } else { - Cow::Borrowed("negative") + let span = end_time.since(info.start_time).unwrap(); + // Convert jiff::Span to std::time::Duration + match span.total(jiff::Unit::Nanosecond) { + Ok(total_nanos) if total_nanos >= 0.0 => { + let duration = std::time::Duration::from_nanos(total_nanos as u64); + duration_to_hms(duration).into() + } + _ => Cow::Borrowed("negative"), } } else { Cow::Borrowed("unknown") diff --git a/src/source.rs b/src/source.rs index 68533b20..37c6aa8c 100644 --- a/src/source.rs +++ b/src/source.rs @@ -23,6 +23,7 @@ use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::sync::Arc; +use jiff::Timestamp; use tracing::{error, warn}; use crate::counters::Counter; @@ -92,10 +93,8 @@ fn entry_from_fs_metadata( source_path: &Path, metadata: &fs::Metadata, ) -> Result { - let mtime = metadata - .modified() - .expect("Failed to get file mtime") - .into(); + let mtime = Timestamp::try_from(metadata.modified().expect("Failed to get file mtime")) + .expect("File mtime converts to Timestamp"); let kind_meta = if metadata.is_file() { KindMeta::File { size: metadata.len(), diff --git a/src/source/entry.rs b/src/source/entry.rs index 3b709152..77e2565c 100644 --- a/src/source/entry.rs +++ b/src/source/entry.rs @@ -1,5 +1,5 @@ +use jiff::Timestamp; use serde::{self, Serialize}; -use time::OffsetDateTime; use crate::{Apath, EntryTrait, Kind, Owner, UnixMode, entry::KindMeta}; @@ -13,7 +13,7 @@ pub struct Entry { pub(crate) kind_meta: KindMeta, /// Modification time. - pub(crate) mtime: OffsetDateTime, + pub(crate) mtime: Timestamp, pub(crate) unix_mode: UnixMode, #[serde(flatten)] pub(crate) owner: Owner, @@ -28,7 +28,7 @@ impl EntryTrait for Entry { Kind::from(&self.kind_meta) } - fn mtime(&self) -> OffsetDateTime { + fn mtime(&self) -> Timestamp { self.mtime } diff --git a/src/transport.rs b/src/transport.rs index 224cd520..47f4c177 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -19,7 +19,7 @@ use std::sync::{Arc, Mutex}; use std::{fmt, result}; use bytes::Bytes; -use time::OffsetDateTime; +use jiff::Timestamp; use url::Url; use crate::*; @@ -307,7 +307,7 @@ pub struct Metadata { pub kind: Kind, /// Last modified time. - pub modified: OffsetDateTime, + pub modified: Timestamp, } impl Metadata { diff --git a/src/transport/local.rs b/src/transport/local.rs index d03f5299..35cbd2c7 100644 --- a/src/transport/local.rs +++ b/src/transport/local.rs @@ -18,6 +18,7 @@ use std::{io, path}; use async_trait::async_trait; use bytes::Bytes; +use jiff::Timestamp; use tempfile::TempDir; use tokio::sync::Semaphore; use tracing::{error, trace, warn}; @@ -143,10 +144,17 @@ impl super::Protocol for Protocol { let fsmeta = tokio::fs::metadata(&path) .await .map_err(|err| Error::io_error(&path, err))?; - let modified = fsmeta - .modified() - .map_err(|err| Error::io_error(&path, err))? - .into(); + let modified = Timestamp::try_from( + fsmeta + .modified() + .map_err(|err| Error::io_error(&path, err))?, + ) + .map_err(|err| { + Error::io_error( + &path, + std::io::Error::new(std::io::ErrorKind::InvalidData, err), + ) + })?; Ok(Metadata { len: fsmeta.len(), kind: fsmeta.file_type().into(), @@ -212,12 +220,10 @@ async fn collect_tokio_dir_entry(dir_entry: tokio::fs::DirEntry) -> Option OffsetDateTime::now_utc()); + assert!( + metadata + .modified + .checked_add(jiff::Span::new().seconds(60)) + .unwrap() + > jiff::Timestamp::now() + ); assert!( transport .metadata("nopoem") diff --git a/src/transport/s3.rs b/src/transport/s3.rs index 8d2960fc..a9786658 100644 --- a/src/transport/s3.rs +++ b/src/transport/s3.rs @@ -43,6 +43,7 @@ use aws_types::SdkConfig; use aws_types::region::Region; use base64::Engine; use bytes::Bytes; +use jiff::Timestamp; use tracing::{debug, error, trace}; use url::Url; @@ -341,7 +342,8 @@ impl super::Protocol for Protocol { Ok(Metadata { kind: Kind::File, len, - modified: modified.into(), + modified: Timestamp::try_from(modified) + .expect("S3 last_modified converts to Timestamp"), }) } Err(err) => { diff --git a/src/transport/sftp.rs b/src/transport/sftp.rs index 4ffa34ee..d8cbb6fb 100644 --- a/src/transport/sftp.rs +++ b/src/transport/sftp.rs @@ -10,8 +10,8 @@ use std::sync::Arc; use async_trait::async_trait; use bytes::Bytes; +use jiff::Timestamp; use ssh2::Sftp; -use time::OffsetDateTime; use tokio::task::spawn_blocking; use tracing::{error, info, trace, warn}; use url::Url; @@ -208,7 +208,7 @@ impl super::Protocol for Protocol { url: Some(self.join_url(relpath)), } })?; - let modified = OffsetDateTime::from_unix_timestamp(modified as i64).map_err(|err| { + let modified = Timestamp::from_second(modified as i64).map_err(|err| { warn!("Invalid mtime for {full_path:?}"); super::Error { kind: ErrorKind::Other, diff --git a/src/unix_time.rs b/src/unix_time.rs index 8f172562..a72eef30 100644 --- a/src/unix_time.rs +++ b/src/unix_time.rs @@ -13,41 +13,12 @@ //! Times relative to the Unix epoch. //! -//! In particular, glue between [filetime] and [time]. +//! In particular, glue between [filetime] and [jiff]. use filetime::FileTime; -use time::OffsetDateTime; +use jiff::Timestamp; -pub(crate) trait FromUnixAndNanos { - fn from_unix_seconds_and_nanos(unix_seconds: i64, nanoseconds: u32) -> Self; -} - -impl FromUnixAndNanos for OffsetDateTime { - fn from_unix_seconds_and_nanos(unix_seconds: i64, nanoseconds: u32) -> Self { - OffsetDateTime::from_unix_timestamp(unix_seconds) - .unwrap() - .replace_nanosecond(nanoseconds) - .unwrap() - } -} - -#[allow(unused)] // really unused at present, but might be useful -pub(crate) trait ToOffsetDateTime { - fn to_offset_date_time(&self) -> OffsetDateTime; -} - -impl ToOffsetDateTime for FileTime { - fn to_offset_date_time(&self) -> OffsetDateTime { - OffsetDateTime::from_unix_seconds_and_nanos(self.unix_seconds(), self.nanoseconds()) - } -} - -pub(crate) trait ToFileTime { - fn to_file_time(&self) -> FileTime; -} - -impl ToFileTime for OffsetDateTime { - fn to_file_time(&self) -> FileTime { - FileTime::from_unix_time(self.unix_timestamp(), self.nanosecond()) - } +/// Helper to convert a Timestamp to a FileTime. +pub(crate) fn timestamp_to_file_time(timestamp: &Timestamp) -> FileTime { + FileTime::from_unix_time(timestamp.as_second(), timestamp.subsec_nanosecond() as u32) } diff --git a/tests/old_archives.rs b/tests/old_archives.rs index 45a3da1c..5ab748e8 100644 --- a/tests/old_archives.rs +++ b/tests/old_archives.rs @@ -22,11 +22,11 @@ use assert_fs::TempDir; use assert_fs::prelude::*; use conserve::counters::Counter; use conserve::monitor::test::TestMonitor; +use jiff::Timestamp; use predicates::prelude::*; use pretty_assertions::assert_eq; use conserve::*; -use time::OffsetDateTime; mod util; use util::{copy_testdata_archive, testdata_archive_path}; @@ -167,25 +167,27 @@ async fn restore_old_archive() { // Check that mtimes are restored. The sub-second times are not tested // because their behavior might vary depending on the local filesystem. - let file_mtime = OffsetDateTime::from( + let file_mtime = Timestamp::try_from( metadata(dest.child("hello").path()) .unwrap() .modified() .unwrap(), - ); + ) + .unwrap(); assert_eq!( - file_mtime.unix_timestamp(), + file_mtime.as_second(), 1592266523, "mtime not restored correctly" ); - let dir_mtime = OffsetDateTime::from( + let dir_mtime = Timestamp::try_from( metadata(dest.child("subdir").path()) .unwrap() .modified() .unwrap(), - ); - assert_eq!(dir_mtime.unix_timestamp(), 1592266523); + ) + .unwrap(); + assert_eq!(dir_mtime.as_second(), 1592266523); } } diff --git a/tests/s3_integration.rs b/tests/s3_integration.rs index 9a6b3520..d4f75225 100644 --- a/tests/s3_integration.rs +++ b/tests/s3_integration.rs @@ -28,9 +28,8 @@ use aws_sdk_s3::types::{ ExpirationStatus, LifecycleExpiration, LifecycleRule, LifecycleRuleFilter, }; use indoc::indoc; +use jiff::Timestamp; use rand::Rng; -use time::OffsetDateTime; -use time::macros::format_description; use tokio::runtime::Runtime; struct TempBucket { @@ -52,9 +51,12 @@ impl TempBucket { let mut rng = rand::rng(); let bucket_name = format!( "conserve-s3-integration-{time}-{rand:x}", - time = OffsetDateTime::now_utc() - .format(format_description!("[year][month][day]-[hour][minute]")) - .expect("Format time"), + time = Timestamp::now() + .to_string() + .chars() + .filter(|c| c.is_alphanumeric() || *c == '-') + .take(16) // Take first 16 characters + .collect::(), rand = rng.random::() ); let app_name = AppName::new(format!(