[pve-devel] [RFC proxmox-backup] avoid chrono dependency

Dietmar Maurer dietmar at proxmox.com
Sun Sep 13 16:42:26 CEST 2020


depend on proxmox 0.3.7
---
 Cargo.toml                                 |   3 +-
 examples/download-speed.rs                 |   4 +-
 examples/upload-speed.rs                   |   2 +-
 src/api2/admin/datastore.rs                |  28 ++---
 src/api2/backup.rs                         |   2 +-
 src/api2/node/time.rs                      |  10 +-
 src/api2/reader.rs                         |   5 +-
 src/backup/backup_info.rs                  |  56 ++++------
 src/backup/catalog.rs                      |  11 +-
 src/backup/crypt_config.rs                 |   5 +-
 src/backup/datastore.rs                    |   3 +-
 src/backup/fixed_index.rs                  |  15 +--
 src/backup/key_derivation.rs               |  17 ++-
 src/backup/manifest.rs                     |   2 +-
 src/backup/prune.rs                        |  29 +++--
 src/bin/proxmox-backup-client.rs           |  58 +++++-----
 src/bin/proxmox_backup_client/benchmark.rs |   3 +-
 src/bin/proxmox_backup_client/key.rs       |   5 +-
 src/client/backup_reader.rs                |   5 +-
 src/client/backup_writer.rs                |   5 +-
 src/client/http_client.rs                  |   5 +-
 src/client/pull.rs                         |   2 +-
 src/pxar/tools.rs                          |  10 +-
 src/server/upid.rs                         |   3 +-
 src/server/worker_task.rs                  |  11 +-
 src/tools/file_logger.rs                   |   7 +-
 src/tools/format.rs                        |  10 +-
 src/tools/systemd.rs                       |   1 -
 src/tools/systemd/time.rs                  |   5 +-
 src/tools/systemd/tm_editor.rs             | 119 ---------------------
 30 files changed, 146 insertions(+), 295 deletions(-)
 delete mode 100644 src/tools/systemd/tm_editor.rs

diff --git a/Cargo.toml b/Cargo.toml
index 0625ffb..17d9bb8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,6 @@ apt-pkg-native = "0.3.1" # custom patched version
 base64 = "0.12"
 bitflags = "1.2.1"
 bytes = "0.5"
-chrono = "0.4" # Date and time library for Rust
 crc32fast = "1"
 endian_trait = { version = "0.6", features = ["arrays"] }
 anyhow = "1.0"
@@ -39,7 +38,7 @@ pam-sys = "0.5"
 percent-encoding = "2.1"
 pin-utils = "0.1.0"
 pathpatterns = "0.1.2"
-proxmox = { version = "0.3.5", features = [ "sortable-macro", "api-macro", "websocket" ] }
+proxmox = { version = "0.3.7", features = [ "sortable-macro", "api-macro", "websocket" ] }
 #proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] }
 #proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro", "websocket" ] }
 proxmox-fuse = "0.1.0"
diff --git a/examples/download-speed.rs b/examples/download-speed.rs
index 694c55d..0ec6cb6 100644
--- a/examples/download-speed.rs
+++ b/examples/download-speed.rs
@@ -2,8 +2,6 @@ use std::io::Write;
 
 use anyhow::{Error};
 
-use chrono::{DateTime, Utc};
-
 use proxmox_backup::api2::types::Userid;
 use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader};
 
@@ -36,7 +34,7 @@ async fn run() -> Result<(), Error> {
 
     let client = HttpClient::new(host, username, options)?;
 
-    let backup_time = "2019-06-28T10:49:48Z".parse::<DateTime<Utc>>()?;
+    let backup_time = proxmox::tools::time::parse_rfc3339("2019-06-28T10:49:48Z")?;
 
     let client = BackupReader::start(client, None, "store2", "host", "elsa", backup_time, true)
         .await?;
diff --git a/examples/upload-speed.rs b/examples/upload-speed.rs
index d67e9da..6be29b0 100644
--- a/examples/upload-speed.rs
+++ b/examples/upload-speed.rs
@@ -16,7 +16,7 @@ async fn upload_speed() -> Result<f64, Error> {
 
     let client = HttpClient::new(host, username, options)?;
 
-    let backup_time = chrono::Utc::now();
+    let backup_time = proxmox::tools::time::time()?;
 
     let client = BackupWriter::start(client, None, datastore, "host", "speedtest", backup_time, false, true).await?;
 
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index be2796d..acfc265 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -172,7 +172,7 @@ fn list_groups(
         let result_item = GroupListItem {
             backup_type: group.backup_type().to_string(),
             backup_id: group.backup_id().to_string(),
-            last_backup: info.backup_dir.backup_time().timestamp(),
+            last_backup: info.backup_dir.backup_time(),
             backup_count: list.len() as u64,
             files: info.files.clone(),
             owner: Some(owner),
@@ -230,7 +230,7 @@ pub fn list_snapshot_files(
 
     let datastore = DataStore::lookup_datastore(&store)?;
 
-    let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
+    let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
 
     let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ)) != 0;
     if !allowed { check_backup_owner(&datastore, snapshot.group(), &userid)?; }
@@ -280,7 +280,7 @@ fn delete_snapshot(
     let user_info = CachedUserInfo::new()?;
     let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
 
-    let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
+    let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
 
     let datastore = DataStore::lookup_datastore(&store)?;
 
@@ -403,7 +403,7 @@ pub fn list_snapshots (
         let result_item = SnapshotListItem {
             backup_type: group.backup_type().to_string(),
             backup_id: group.backup_id().to_string(),
-            backup_time: info.backup_dir.backup_time().timestamp(),
+            backup_time: info.backup_dir.backup_time(),
             comment,
             verification,
             files,
@@ -490,7 +490,7 @@ pub fn verify(
     match (backup_type, backup_id, backup_time) {
         (Some(backup_type), Some(backup_id), Some(backup_time)) => {
             worker_id = format!("{}_{}_{}_{:08X}", store, backup_type, backup_id, backup_time);
-            let dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+            let dir = BackupDir::new(backup_type, backup_id, backup_time);
             backup_dir = Some(dir);
         }
         (Some(backup_type), Some(backup_id), None) => {
@@ -673,7 +673,7 @@ fn prune(
             prune_result.push(json!({
                 "backup-type": group.backup_type(),
                 "backup-id": group.backup_id(),
-                "backup-time": backup_time.timestamp(),
+                "backup-time": backup_time,
                 "keep": keep,
             }));
         }
@@ -714,7 +714,7 @@ fn prune(
             prune_result.push(json!({
                 "backup-type": group.backup_type(),
                 "backup-id": group.backup_id(),
-                "backup-time": backup_time.timestamp(),
+                "backup-time": backup_time,
                 "keep": keep,
             }));
 
@@ -897,7 +897,7 @@ fn download_file(
         let backup_id = tools::required_string_param(&param, "backup-id")?;
         let backup_time = tools::required_integer_param(&param, "backup-time")?;
 
-        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
         let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
         if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
@@ -970,7 +970,7 @@ fn download_file_decoded(
         let backup_id = tools::required_string_param(&param, "backup-id")?;
         let backup_time = tools::required_integer_param(&param, "backup-time")?;
 
-        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
         let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
         if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
@@ -1083,7 +1083,7 @@ fn upload_backup_log(
         let backup_id = tools::required_string_param(&param, "backup-id")?;
         let backup_time = tools::required_integer_param(&param, "backup-time")?;
 
-        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
         let userid: Userid = rpcenv.get_user().unwrap().parse()?;
         check_backup_owner(&datastore, backup_dir.group(), &userid)?;
@@ -1159,7 +1159,7 @@ fn catalog(
     let user_info = CachedUserInfo::new()?;
     let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
 
-    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
     let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
     if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
@@ -1276,7 +1276,7 @@ fn pxar_file_download(
         let backup_id = tools::required_string_param(&param, "backup-id")?;
         let backup_time = tools::required_integer_param(&param, "backup-time")?;
 
-        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
         let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
         if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
@@ -1417,7 +1417,7 @@ fn get_notes(
     let user_info = CachedUserInfo::new()?;
     let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
 
-    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
     let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
     if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
@@ -1470,7 +1470,7 @@ fn set_notes(
     let user_info = CachedUserInfo::new()?;
     let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
 
-    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+    let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
 
     let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
     if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
diff --git a/src/api2/backup.rs b/src/api2/backup.rs
index 9420b14..c00f9be 100644
--- a/src/api2/backup.rs
+++ b/src/api2/backup.rs
@@ -114,7 +114,7 @@ async move {
     }
 
     let last_backup = BackupInfo::last_backup(&datastore.base_path(), &backup_group, true).unwrap_or(None);
-    let backup_dir = BackupDir::new_with_group(backup_group.clone(), backup_time)?;
+    let backup_dir = BackupDir::new_with_group(backup_group.clone(), backup_time);
 
     let _last_guard = if let Some(last) = &last_backup {
         if backup_dir.backup_time() <= last.backup_dir.backup_time() {
diff --git a/src/api2/node/time.rs b/src/api2/node/time.rs
index a5afa40..49fe873 100644
--- a/src/api2/node/time.rs
+++ b/src/api2/node/time.rs
@@ -1,4 +1,3 @@
-use chrono::prelude::*;
 use anyhow::{bail, format_err, Error};
 use serde_json::{json, Value};
 
@@ -57,10 +56,11 @@ fn read_etc_localtime() -> Result<String, Error> {
 )]
 /// Read server time and time zone settings.
 fn get_time(_param: Value) -> Result<Value, Error> {
-    let datetime = Local::now();
-    let offset = datetime.offset();
-    let time = datetime.timestamp();
-    let localtime = time + (offset.fix().local_minus_utc() as i64);
+    let time = proxmox::tools::time::time()?;
+    let tm = proxmox::tools::time::localtime(time)?;
+    let offset = tm.tm_gmtoff;
+
+    let localtime = time + offset;
 
     Ok(json!({
         "timezone": read_etc_localtime()?,
diff --git a/src/api2/reader.rs b/src/api2/reader.rs
index 5252d2e..c3dfa1a 100644
--- a/src/api2/reader.rs
+++ b/src/api2/reader.rs
@@ -1,4 +1,3 @@
-//use chrono::{Local, TimeZone};
 use anyhow::{bail, format_err, Error};
 use futures::*;
 use hyper::header::{self, HeaderValue, UPGRADE};
@@ -83,12 +82,12 @@ fn upgrade_to_backup_reader_protocol(
 
         let env_type = rpcenv.env_type();
 
-        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+        let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
         let path = datastore.base_path();
 
         //let files = BackupInfo::list_files(&path, &backup_dir)?;
 
-        let worker_id = format!("{}_{}_{}_{:08X}", store, backup_type, backup_id, backup_dir.backup_time().timestamp());
+        let worker_id = format!("{}_{}_{}_{:08X}", store, backup_type, backup_id, backup_dir.backup_time());
 
         WorkerTask::spawn("reader", Some(worker_id), userid.clone(), true, move |worker| {
             let mut env = ReaderEnvironment::new(
diff --git a/src/backup/backup_info.rs b/src/backup/backup_info.rs
index 023625f..63e8ee9 100644
--- a/src/backup/backup_info.rs
+++ b/src/backup/backup_info.rs
@@ -2,11 +2,8 @@ use crate::tools;
 
 use anyhow::{bail, format_err, Error};
 use regex::Regex;
-use std::convert::TryFrom;
 use std::os::unix::io::RawFd;
 
-use chrono::{DateTime, LocalResult, TimeZone, SecondsFormat, Utc};
-
 use std::path::{PathBuf, Path};
 use lazy_static::lazy_static;
 
@@ -106,8 +103,8 @@ impl BackupGroup {
         tools::scandir(libc::AT_FDCWD, &path, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| {
             if file_type != nix::dir::Type::Directory { return Ok(()); }
 
-            let dt = backup_time.parse::<DateTime<Utc>>()?;
-            let backup_dir = BackupDir::new(self.backup_type.clone(), self.backup_id.clone(), dt.timestamp())?;
+            let timestamp = proxmox::tools::time::parse_rfc3339(backup_time)?;
+            let backup_dir = BackupDir::new(self.backup_type.clone(), self.backup_id.clone(), timestamp);
             let files = list_backup_files(l2_fd, backup_time)?;
 
             list.push(BackupInfo { backup_dir, files });
@@ -117,7 +114,7 @@ impl BackupGroup {
         Ok(list)
     }
 
-    pub fn last_successful_backup(&self,  base_path: &Path) -> Result<Option<DateTime<Utc>>, Error> {
+    pub fn last_successful_backup(&self,  base_path: &Path) -> Result<Option<i64>, Error> {
 
         let mut last = None;
 
@@ -143,11 +140,11 @@ impl BackupGroup {
                 }
             }
 
-            let dt = backup_time.parse::<DateTime<Utc>>()?;
-            if let Some(last_dt) = last {
-                if dt > last_dt { last = Some(dt); }
+            let timestamp = proxmox::tools::time::parse_rfc3339(backup_time)?;
+            if let Some(last_timestamp) = last {
+                if timestamp > last_timestamp { last = Some(timestamp); }
             } else {
-                last = Some(dt);
+                last = Some(timestamp);
             }
 
             Ok(())
@@ -204,34 +201,29 @@ pub struct BackupDir {
     /// Backup group
     group: BackupGroup,
     /// Backup timestamp
-    backup_time: DateTime<Utc>,
+    backup_time: i64,
 }
 
 impl BackupDir {
 
-    pub fn new<T, U>(backup_type: T, backup_id: U, timestamp: i64) -> Result<Self, Error>
+    pub fn new<T, U>(backup_type: T, backup_id: U, backup_time: i64) -> Self
     where
         T: Into<String>,
         U: Into<String>,
     {
         let group = BackupGroup::new(backup_type.into(), backup_id.into());
-        BackupDir::new_with_group(group, timestamp)
+        BackupDir::new_with_group(group, backup_time)
     }
 
-    pub fn new_with_group(group: BackupGroup, timestamp: i64) -> Result<Self, Error> {
-        let backup_time = match Utc.timestamp_opt(timestamp, 0) {
-            LocalResult::Single(time) => time,
-            _ => bail!("can't create BackupDir with invalid backup time {}", timestamp),
-        };
-
-        Ok(Self { group, backup_time })
+    pub fn new_with_group(group: BackupGroup, backup_time: i64) -> Self {
+        Self { group, backup_time }
     }
 
     pub fn group(&self) -> &BackupGroup {
         &self.group
     }
 
-    pub fn backup_time(&self) -> DateTime<Utc> {
+    pub fn backup_time(&self) -> i64 {
         self.backup_time
     }
 
@@ -244,8 +236,9 @@ impl BackupDir {
         relative_path
     }
 
-    pub fn backup_time_to_string(backup_time: DateTime<Utc>) -> String {
-        backup_time.to_rfc3339_opts(SecondsFormat::Secs, true)
+    pub fn backup_time_to_string(backup_time: i64) -> String {
+        // fixme: can this fail? (avoid unwrap)
+        proxmox::tools::time::epoch_to_rfc3339_utc(backup_time).unwrap()
     }
 }
 
@@ -260,8 +253,8 @@ impl std::str::FromStr for BackupDir {
             .ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?;
 
         let group = BackupGroup::new(cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str());
-        let backup_time = cap.get(3).unwrap().as_str().parse::<DateTime<Utc>>()?;
-        BackupDir::try_from((group, backup_time.timestamp()))
+        let backup_time = proxmox::tools::time::parse_rfc3339(cap.get(3).unwrap().as_str())?;
+        Ok(BackupDir { group, backup_time })
     }
 }
 
@@ -274,14 +267,6 @@ impl std::fmt::Display for BackupDir {
     }
 }
 
-impl TryFrom<(BackupGroup, i64)> for BackupDir {
-    type Error = Error;
-
-    fn try_from((group, timestamp): (BackupGroup, i64)) -> Result<Self, Error> {
-        BackupDir::new_with_group(group, timestamp)
-    }
-}
-
 /// Detailed Backup Information, lists files inside a BackupDir
 #[derive(Debug, Clone)]
 pub struct BackupInfo {
@@ -339,8 +324,9 @@ impl BackupInfo {
                 tools::scandir(l1_fd, backup_id, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| {
                     if file_type != nix::dir::Type::Directory { return Ok(()); }
 
-                    let dt = backup_time.parse::<DateTime<Utc>>()?;
-                    let backup_dir = BackupDir::new(backup_type, backup_id, dt.timestamp())?;
+                    let timestamp = proxmox::tools::time::parse_rfc3339(backup_time)?;
+
+                    let backup_dir = BackupDir::new(backup_type, backup_id, timestamp);
 
                     let files = list_backup_files(l2_fd, backup_time)?;
 
diff --git a/src/backup/catalog.rs b/src/backup/catalog.rs
index 85a3262..a1610b6 100644
--- a/src/backup/catalog.rs
+++ b/src/backup/catalog.rs
@@ -5,7 +5,6 @@ use std::io::{Read, Write, Seek, SeekFrom};
 use std::os::unix::ffi::OsStrExt;
 
 use anyhow::{bail, format_err, Error};
-use chrono::offset::{TimeZone, Local, LocalResult};
 
 use pathpatterns::{MatchList, MatchType};
 use proxmox::tools::io::ReadExt;
@@ -533,10 +532,12 @@ impl <R: Read + Seek> CatalogReader<R> {
                     self.dump_dir(&path, pos)?;
                 }
                 CatalogEntryType::File => {
-                    let mtime_string = match Local.timestamp_opt(mtime as i64, 0) {
-                        LocalResult::Single(time) => time.to_rfc3339_opts(chrono::SecondsFormat::Secs, false),
-                        _ => (mtime as i64).to_string(),
-                    };
+                    let mut mtime_string = mtime.to_string();
+                    if let Ok(localtime) = proxmox::tools::time::localtime(mtime as i64) {
+                        if let Ok(s) = proxmox::tools::time::strftime("%FT%TZ", &localtime) {
+                            mtime_string = s;
+                        }
+                    }
 
                     println!(
                         "{} {:?} {} {}",
diff --git a/src/backup/crypt_config.rs b/src/backup/crypt_config.rs
index c30fb5f..d2660f3 100644
--- a/src/backup/crypt_config.rs
+++ b/src/backup/crypt_config.rs
@@ -10,7 +10,6 @@
 use std::io::Write;
 
 use anyhow::{bail, Error};
-use chrono::{Local, DateTime};
 use openssl::hash::MessageDigest;
 use openssl::pkcs5::pbkdf2_hmac;
 use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
@@ -216,10 +215,10 @@ impl CryptConfig {
     pub fn generate_rsa_encoded_key(
         &self,
         rsa: openssl::rsa::Rsa<openssl::pkey::Public>,
-        created: DateTime<Local>,
+        created: i64,
     ) -> Result<Vec<u8>, Error> {
 
-        let modified = Local::now();
+        let modified = proxmox::tools::time::time()?;
         let key_config = super::KeyConfig { kdf: None, created, modified, data: self.enc_key.to_vec() };
         let data = serde_json::to_string(&key_config)?.as_bytes().to_vec();
 
diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs
index ebe4748..46a5069 100644
--- a/src/backup/datastore.rs
+++ b/src/backup/datastore.rs
@@ -6,7 +6,6 @@ use std::convert::TryFrom;
 
 use anyhow::{bail, format_err, Error};
 use lazy_static::lazy_static;
-use chrono::{DateTime, Utc};
 use serde_json::Value;
 
 use proxmox::tools::fs::{replace_file, CreateOptions};
@@ -242,7 +241,7 @@ impl DataStore {
     /// Returns the time of the last successful backup
     ///
     /// Or None if there is no backup in the group (or the group dir does not exist).
-    pub fn last_successful_backup(&self, backup_group: &BackupGroup) -> Result<Option<DateTime<Utc>>, Error> {
+    pub fn last_successful_backup(&self, backup_group: &BackupGroup) -> Result<Option<i64>, Error> {
         let base_path = self.base_path();
         let mut group_path = base_path.clone();
         group_path.push(backup_group.group_path());
diff --git a/src/backup/fixed_index.rs b/src/backup/fixed_index.rs
index 309f450..e1b47ad 100644
--- a/src/backup/fixed_index.rs
+++ b/src/backup/fixed_index.rs
@@ -6,7 +6,6 @@ use super::chunk_store::*;
 use super::{IndexFile, ChunkReadInfo};
 use crate::tools::{self, epoch_now_u64};
 
-use chrono::{Local, LocalResult, TimeZone};
 use std::fs::File;
 use std::io::Write;
 use std::os::unix::io::AsRawFd;
@@ -148,13 +147,15 @@ impl FixedIndexReader {
     pub fn print_info(&self) {
         println!("Size: {}", self.size);
         println!("ChunkSize: {}", self.chunk_size);
-        println!(
-            "CTime: {}",
-            match Local.timestamp_opt(self.ctime as i64, 0) {
-                LocalResult::Single(ctime) => ctime.format("%c").to_string(),
-                _ => (self.ctime as i64).to_string(),
+
+        let mut ctime_str = self.ctime.to_string();
+        if let Ok(localtime) = proxmox::tools::time::localtime(self.ctime as i64) {
+            if let Ok(s) = proxmox::tools::time::strftime("%c", &localtime) {
+                ctime_str = s;
             }
-        );
+        }
+
+        println!("CTime: {}", ctime_str);
         println!("UUID: {:?}", self.uuid);
     }
 }
diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs
index ac27da6..6362047 100644
--- a/src/backup/key_derivation.rs
+++ b/src/backup/key_derivation.rs
@@ -1,7 +1,6 @@
 use anyhow::{bail, format_err, Context, Error};
 
 use serde::{Deserialize, Serialize};
-use chrono::{Local, DateTime};
 
 use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
 use proxmox::try_block;
@@ -61,10 +60,10 @@ impl KeyDerivationConfig {
 #[derive(Deserialize, Serialize, Debug)]
 pub struct KeyConfig {
     pub kdf: Option<KeyDerivationConfig>,
-    #[serde(with = "proxmox::tools::serde::date_time_as_rfc3339")]
-    pub created: DateTime<Local>,
-    #[serde(with = "proxmox::tools::serde::date_time_as_rfc3339")]
-    pub modified: DateTime<Local>,
+    #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
+    pub created: i64,
+    #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
+    pub modified: i64,
     #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
     pub data: Vec<u8>,
  }
@@ -136,7 +135,7 @@ pub fn encrypt_key_with_passphrase(
     enc_data.extend_from_slice(&tag);
     enc_data.extend_from_slice(&encrypted_key);
 
-    let created = Local::now();
+    let created = proxmox::tools::time::time()?;
 
     Ok(KeyConfig {
         kdf: Some(kdf),
@@ -149,7 +148,7 @@ pub fn encrypt_key_with_passphrase(
 pub fn load_and_decrypt_key(
     path: &std::path::Path,
     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
-) -> Result<([u8;32], DateTime<Local>), Error> {
+) -> Result<([u8;32], i64), Error> {
     do_load_and_decrypt_key(path, passphrase)
         .with_context(|| format!("failed to load decryption key from {:?}", path))
 }
@@ -157,14 +156,14 @@ pub fn load_and_decrypt_key(
 fn do_load_and_decrypt_key(
     path: &std::path::Path,
     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
-) -> Result<([u8;32], DateTime<Local>), Error> {
+) -> Result<([u8;32], i64), Error> {
     decrypt_key(&file_get_contents(&path)?, passphrase)
 }
 
 pub fn decrypt_key(
     mut keydata: &[u8],
     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
-) -> Result<([u8;32], DateTime<Local>), Error> {
+) -> Result<([u8;32], i64), Error> {
     let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
 
     let raw_data = key_config.data;
diff --git a/src/backup/manifest.rs b/src/backup/manifest.rs
index ed5e2a2..609cc99 100644
--- a/src/backup/manifest.rs
+++ b/src/backup/manifest.rs
@@ -103,7 +103,7 @@ impl BackupManifest {
         Self {
             backup_type: snapshot.group().backup_type().into(),
             backup_id: snapshot.group().backup_id().into(),
-            backup_time: snapshot.backup_time().timestamp(),
+            backup_time: snapshot.backup_time(),
             files: Vec::new(),
             unprotected: json!({}),
             signature: None,
diff --git a/src/backup/prune.rs b/src/backup/prune.rs
index f7a87c5..ce60129 100644
--- a/src/backup/prune.rs
+++ b/src/backup/prune.rs
@@ -2,13 +2,11 @@ use anyhow::{Error};
 use std::collections::{HashMap, HashSet};
 use std::path::PathBuf;
 
-use chrono::{DateTime, Timelike, Datelike, Local};
-
 use super::{BackupDir, BackupInfo};
 
 enum PruneMark { Keep, KeepPartial, Remove }
 
-fn mark_selections<F: Fn(DateTime<Local>, &BackupInfo) -> String> (
+fn mark_selections<F: Fn(libc::tm, &BackupInfo) -> String> (
     mark: &mut HashMap<PathBuf, PruneMark>,
     list: &Vec<BackupInfo>,
     keep: usize,
@@ -21,7 +19,8 @@ fn mark_selections<F: Fn(DateTime<Local>, &BackupInfo) -> String> (
     for info in list {
         let backup_id = info.backup_dir.relative_path();
         if let Some(PruneMark::Keep) = mark.get(&backup_id) {
-            let local_time = info.backup_dir.backup_time().with_timezone(&Local);
+            // fixme: avoid unwrap here?
+            let local_time = proxmox::tools::time::localtime(info.backup_dir.backup_time()).unwrap();
             let sel_id: String = select_id(local_time, &info);
             already_included.insert(sel_id);
         }
@@ -30,7 +29,8 @@ fn mark_selections<F: Fn(DateTime<Local>, &BackupInfo) -> String> (
     for info in list {
         let backup_id = info.backup_dir.relative_path();
         if let Some(_) = mark.get(&backup_id) { continue; }
-        let local_time = info.backup_dir.backup_time().with_timezone(&Local);
+        // fixme: avoid unwrap here?
+        let local_time = proxmox::tools::time::localtime(info.backup_dir.backup_time()).unwrap();
         let sel_id: String = select_id(local_time, &info);
 
         if already_included.contains(&sel_id) { continue; }
@@ -181,6 +181,7 @@ pub fn compute_prune_info(
 
     remove_incomplete_snapshots(&mut mark, &list);
 
+    // fixme: avoid unwrap
     if let Some(keep_last) = options.keep_last {
         mark_selections(&mut mark, &list, keep_last as usize, |_local_time, info| {
             BackupDir::backup_time_to_string(info.backup_dir.backup_time())
@@ -189,36 +190,34 @@ pub fn compute_prune_info(
 
     if let Some(keep_hourly) = options.keep_hourly {
         mark_selections(&mut mark, &list, keep_hourly as usize, |local_time, _info| {
-            format!("{}/{}/{}/{}", local_time.year(), local_time.month(),
-                    local_time.day(), local_time.hour())
+            proxmox::tools::time::strftime("%Y/%m/%d/%H", &local_time).unwrap()
         });
     }
 
     if let Some(keep_daily) = options.keep_daily {
         mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| {
-            format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day())
+            proxmox::tools::time::strftime("%Y/%m/%d", &local_time).unwrap()
         });
     }
 
     if let Some(keep_weekly) = options.keep_weekly {
         mark_selections(&mut mark, &list, keep_weekly as usize, |local_time, _info| {
-            let iso_week = local_time.iso_week();
-            let week = iso_week.week();
-            // Note: This year number might not match the calendar year number.
-            let iso_week_year = iso_week.year();
-            format!("{}/{}", iso_week_year, week)
+            // Note: Use iso-week year/week here. This year number
+            // might not match the calendar year number.
+            proxmox::tools::time::strftime("%G/%V", &local_time).unwrap()
         });
     }
 
     if let Some(keep_monthly) = options.keep_monthly {
         mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| {
-            format!("{}/{}", local_time.year(), local_time.month())
+            proxmox::tools::time::strftime("%Y/%m", &local_time).unwrap()
         });
     }
 
     if let Some(keep_yearly) = options.keep_yearly {
         mark_selections(&mut mark, &list, keep_yearly as usize, |local_time, _info| {
-            format!("{}/{}", local_time.year(), local_time.year())
+            // fixme: why "%Y/%Y"
+            proxmox::tools::time::strftime("%Y/%Y", &local_time).unwrap()
         });
     }
 
diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs
index fa13203..bd488b4 100644
--- a/src/bin/proxmox-backup-client.rs
+++ b/src/bin/proxmox-backup-client.rs
@@ -8,7 +8,6 @@ use std::sync::{Arc, Mutex};
 use std::task::Context;
 
 use anyhow::{bail, format_err, Error};
-use chrono::{Local, LocalResult, DateTime, Utc, TimeZone};
 use futures::future::FutureExt;
 use futures::stream::{StreamExt, TryStreamExt};
 use serde_json::{json, Value};
@@ -246,7 +245,7 @@ pub async fn api_datastore_latest_snapshot(
     client: &HttpClient,
     store: &str,
     group: BackupGroup,
-) -> Result<(String, String, DateTime<Utc>), Error> {
+) -> Result<(String, String, i64), Error> {
 
     let list = api_datastore_list_snapshots(client, store, Some(group.clone())).await?;
     let mut list: Vec<SnapshotListItem> = serde_json::from_value(list)?;
@@ -257,11 +256,7 @@ pub async fn api_datastore_latest_snapshot(
 
     list.sort_unstable_by(|a, b| b.backup_time.cmp(&a.backup_time));
 
-    let backup_time = match Utc.timestamp_opt(list[0].backup_time, 0) {
-        LocalResult::Single(time) => time,
-        _ => bail!("last snapshot of backup group {:?} has invalid timestmap {}.",
-                   group.group_path(), list[0].backup_time),
-    };
+    let backup_time = list[0].backup_time;
 
     Ok((group.backup_type().to_owned(), group.backup_id().to_owned(), backup_time))
 }
@@ -377,7 +372,7 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> {
 
     let render_last_backup = |_v: &Value, record: &Value| -> Result<String, Error> {
         let item: GroupListItem = serde_json::from_value(record.to_owned())?;
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.last_backup)?;
+        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.last_backup);
         Ok(snapshot.relative_path().to_str().unwrap().to_owned())
     };
 
@@ -448,7 +443,7 @@ async fn list_snapshots(param: Value) -> Result<Value, Error> {
 
     let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
         let item: SnapshotListItem = serde_json::from_value(record.to_owned())?;
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
+        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time);
         Ok(snapshot.relative_path().to_str().unwrap().to_owned())
     };
 
@@ -506,7 +501,7 @@ async fn forget_snapshots(param: Value) -> Result<Value, Error> {
     let result = client.delete(&path, Some(json!({
         "backup-type": snapshot.group().backup_type(),
         "backup-id": snapshot.group().backup_id(),
-        "backup-time": snapshot.backup_time().timestamp(),
+        "backup-time": snapshot.backup_time(),
     }))).await?;
 
     record_repository(&repo);
@@ -643,7 +638,7 @@ async fn list_snapshot_files(param: Value) -> Result<Value, Error> {
     let mut result = client.get(&path, Some(json!({
         "backup-type": snapshot.group().backup_type(),
         "backup-id": snapshot.group().backup_id(),
-        "backup-time": snapshot.backup_time().timestamp(),
+        "backup-time": snapshot.backup_time(),
     }))).await?;
 
     record_repository(&repo);
@@ -990,15 +985,9 @@ async fn create_backup(
         }
     }
 
-    let backup_time = match backup_time_opt {
-        Some(timestamp) => {
-            match Utc.timestamp_opt(timestamp, 0) {
-                LocalResult::Single(time) => time,
-                _ => bail!("Invalid backup-time parameter: {}", timestamp),
-            }
-        },
-        _ => Utc::now(),
-    };
+    let backup_time = backup_time_opt.unwrap_or_else(|| {
+        proxmox::tools::time::time().unwrap()
+    });
 
     let client = connect(repo.host(), repo.user())?;
     record_repository(&repo);
@@ -1007,9 +996,12 @@ async fn create_backup(
 
     println!("Client name: {}", proxmox::tools::nodename());
 
-    let start_time = Local::now();
+    let start_time = std::time::Instant::now();
 
-    println!("Starting protocol: {}", start_time.to_rfc3339_opts(chrono::SecondsFormat::Secs, false));
+    { // Note:: limit scope, because libc::tm is not Send
+        let localtime = proxmox::tools::time::localtime(proxmox::tools::time::time()?)?;
+        println!("Starting backup protocol: {}", proxmox::tools::time::strftime("%c", &localtime)?);
+    }
 
     let (crypt_config, rsa_encrypted_key) = match keydata {
         None => (None, None),
@@ -1047,7 +1039,7 @@ async fn create_backup(
         None
     };
 
-    let snapshot = BackupDir::new(backup_type, backup_id, backup_time.timestamp())?;
+    let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
     let mut manifest = BackupManifest::new(snapshot);
 
     let mut catalog = None;
@@ -1162,11 +1154,12 @@ async fn create_backup(
 
     client.finish().await?;
 
-    let end_time = Local::now();
-    let elapsed = end_time.signed_duration_since(start_time);
-    println!("Duration: {}", elapsed);
+    let end_time = std::time::Instant::now();
+    let elapsed = end_time.duration_since(start_time);
+    println!("Duration: {:.2}s", elapsed.as_secs_f64());
 
-    println!("End Time: {}", end_time.to_rfc3339_opts(chrono::SecondsFormat::Secs, false));
+    let localtime = proxmox::tools::time::localtime(proxmox::tools::time::time()?)?;
+    println!("End Time: {}",  proxmox::tools::time::strftime("%c", &localtime)?);
 
     Ok(Value::Null)
 }
@@ -1504,7 +1497,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> {
     let args = json!({
         "backup-type": snapshot.group().backup_type(),
         "backup-id":  snapshot.group().backup_id(),
-        "backup-time": snapshot.backup_time().timestamp(),
+        "backup-time": snapshot.backup_time(),
     });
 
     let body = hyper::Body::from(raw_data);
@@ -1572,7 +1565,7 @@ async fn prune_async(mut param: Value) -> Result<Value, Error> {
 
     let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
         let item: PruneListItem = serde_json::from_value(record.to_owned())?;
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
+        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time);
         Ok(snapshot.relative_path().to_str().unwrap().to_owned())
     };
 
@@ -1764,9 +1757,8 @@ async fn complete_backup_snapshot_do(param: &HashMap<String, String>) -> Vec<Str
             if let (Some(backup_id), Some(backup_type), Some(backup_time)) =
                 (item["backup-id"].as_str(), item["backup-type"].as_str(), item["backup-time"].as_i64())
             {
-                if let Ok(snapshot) = BackupDir::new(backup_type, backup_id, backup_time) {
-                    result.push(snapshot.relative_path().to_str().unwrap().to_owned());
-                }
+                let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
+                result.push(snapshot.relative_path().to_str().unwrap().to_owned());
             }
         }
     }
@@ -1800,7 +1792,7 @@ async fn complete_server_file_name_do(param: &HashMap<String, String>) -> Vec<St
     let query = tools::json_object_to_query(json!({
         "backup-type": snapshot.group().backup_type(),
         "backup-id": snapshot.group().backup_id(),
-        "backup-time": snapshot.backup_time().timestamp(),
+        "backup-time": snapshot.backup_time(),
     })).unwrap();
 
     let path = format!("api2/json/admin/datastore/{}/files?{}", repo.store(), query);
diff --git a/src/bin/proxmox_backup_client/benchmark.rs b/src/bin/proxmox_backup_client/benchmark.rs
index 9830c18..215da50 100644
--- a/src/bin/proxmox_backup_client/benchmark.rs
+++ b/src/bin/proxmox_backup_client/benchmark.rs
@@ -3,7 +3,6 @@ use std::sync::Arc;
 
 use anyhow::{Error};
 use serde_json::Value;
-use chrono::Utc;
 use serde::Serialize;
 
 use proxmox::api::{ApiMethod, RpcEnvironment};
@@ -212,7 +211,7 @@ async fn test_upload_speed(
     verbose: bool,
 ) -> Result<(), Error> {
 
-    let backup_time = Utc::now();
+    let backup_time = proxmox::tools::time::time()?;
 
     let client = connect(repo.host(), repo.user())?;
     record_repository(&repo);
diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs
index 30afa4e..14b4767 100644
--- a/src/bin/proxmox_backup_client/key.rs
+++ b/src/bin/proxmox_backup_client/key.rs
@@ -1,7 +1,6 @@
 use std::path::PathBuf;
 
 use anyhow::{bail, format_err, Error};
-use chrono::Local;
 use serde::{Deserialize, Serialize};
 
 use proxmox::api::api;
@@ -112,7 +111,7 @@ fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
 
     match kdf {
         Kdf::None => {
-            let created = Local::now();
+            let created = proxmox::tools::time::time()?;
 
             store_key_config(
                 &path,
@@ -180,7 +179,7 @@ fn change_passphrase(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error
 
     match kdf {
         Kdf::None => {
-            let modified = Local::now();
+            let modified = proxmox::tools::time::time()?;
 
             store_key_config(
                 &path,
diff --git a/src/client/backup_reader.rs b/src/client/backup_reader.rs
index d418571..5f002e2 100644
--- a/src/client/backup_reader.rs
+++ b/src/client/backup_reader.rs
@@ -4,7 +4,6 @@ use std::fs::File;
 use std::sync::Arc;
 use std::os::unix::fs::OpenOptionsExt;
 
-use chrono::{DateTime, Utc};
 use futures::future::AbortHandle;
 use serde_json::{json, Value};
 
@@ -41,14 +40,14 @@ impl BackupReader {
         datastore: &str,
         backup_type: &str,
         backup_id: &str,
-        backup_time: DateTime<Utc>,
+        backup_time: i64,
         debug: bool,
     ) -> Result<Arc<BackupReader>, Error> {
 
         let param = json!({
             "backup-type": backup_type,
             "backup-id": backup_id,
-            "backup-time": backup_time.timestamp(),
+            "backup-time": backup_time,
             "store": datastore,
             "debug": debug,
         });
diff --git a/src/client/backup_writer.rs b/src/client/backup_writer.rs
index 64c3cf2..e071911 100644
--- a/src/client/backup_writer.rs
+++ b/src/client/backup_writer.rs
@@ -4,7 +4,6 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 
 use anyhow::{bail, format_err, Error};
-use chrono::{DateTime, Utc};
 use futures::*;
 use futures::stream::Stream;
 use futures::future::AbortHandle;
@@ -51,7 +50,7 @@ impl BackupWriter {
         datastore: &str,
         backup_type: &str,
         backup_id: &str,
-        backup_time: DateTime<Utc>,
+        backup_time: i64,
         debug: bool,
         benchmark: bool
     ) -> Result<Arc<BackupWriter>, Error> {
@@ -59,7 +58,7 @@ impl BackupWriter {
         let param = json!({
             "backup-type": backup_type,
             "backup-id": backup_id,
-            "backup-time": backup_time.timestamp(),
+            "backup-time": backup_time,
             "store": datastore,
             "debug": debug,
             "benchmark": benchmark
diff --git a/src/client/http_client.rs b/src/client/http_client.rs
index ae3704d..f1b8287 100644
--- a/src/client/http_client.rs
+++ b/src/client/http_client.rs
@@ -2,7 +2,6 @@ use std::io::Write;
 use std::task::{Context, Poll};
 use std::sync::{Arc, Mutex};
 
-use chrono::Utc;
 use anyhow::{bail, format_err, Error};
 use futures::*;
 use http::Uri;
@@ -199,7 +198,7 @@ fn store_ticket_info(prefix: &str, server: &str, username: &str, ticket: &str, t
 
     let mut data = file_get_json(&path, Some(json!({})))?;
 
-    let now = Utc::now().timestamp();
+    let now = proxmox::tools::time::time()?;
 
     data[server][username] = json!({ "timestamp": now, "ticket": ticket, "token": token});
 
@@ -230,7 +229,7 @@ fn load_ticket_info(prefix: &str, server: &str, userid: &Userid) -> Option<(Stri
     // usually /run/user/<uid>/...
     let path = base.place_runtime_file("tickets").ok()?;
     let data = file_get_json(&path, None).ok()?;
-    let now = Utc::now().timestamp();
+    let now = proxmox::tools::time::time().ok()?;
     let ticket_lifetime = tools::ticket::TICKET_LIFETIME - 60;
     let uinfo = data[server][userid.as_str()].as_object()?;
     let timestamp = uinfo["timestamp"].as_i64()?;
diff --git a/src/client/pull.rs b/src/client/pull.rs
index ab7e989..2428051 100644
--- a/src/client/pull.rs
+++ b/src/client/pull.rs
@@ -347,7 +347,7 @@ pub async fn pull_group(
     let mut remote_snapshots = std::collections::HashSet::new();
 
     for item in list {
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
+        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time);
 
         // in-progress backups can't be synced
         if let None = item.size {
diff --git a/src/pxar/tools.rs b/src/pxar/tools.rs
index 2eed0f8..9be9148 100644
--- a/src/pxar/tools.rs
+++ b/src/pxar/tools.rs
@@ -115,12 +115,12 @@ fn mode_string(entry: &Entry) -> String {
 }
 
 fn format_mtime(mtime: &StatxTimestamp) -> String {
-    use chrono::offset::TimeZone;
-
-    match chrono::Local.timestamp_opt(mtime.secs, mtime.nanos) {
-        chrono::LocalResult::Single(mtime) => mtime.format("%Y-%m-%d %H:%M:%S").to_string(),
-        _ => format!("{}.{}", mtime.secs, mtime.nanos),
+    if let Ok(localtime) = proxmox::tools::time::localtime(mtime.secs) {
+        if let Ok(s) = proxmox::tools::time::strftime("%Y-%m-%d %H:%M:%S", &localtime) {
+            return s;
+        }
     }
+    format!("{}.{}", mtime.secs, mtime.nanos)
 }
 
 pub fn format_single_line_entry(entry: &Entry) -> String {
diff --git a/src/server/upid.rs b/src/server/upid.rs
index 9fc5085..d0f0d16 100644
--- a/src/server/upid.rs
+++ b/src/server/upid.rs
@@ -1,7 +1,6 @@
 use std::sync::atomic::{AtomicUsize, Ordering};
 
 use anyhow::{bail, Error};
-use chrono::Local;
 
 use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema};
 use proxmox::const_regex;
@@ -89,7 +88,7 @@ impl UPID {
         Ok(UPID {
             pid,
             pstart: procfs::PidStat::read_from_pid(nix::unistd::Pid::from_raw(pid))?.starttime,
-            starttime: Local::now().timestamp(),
+            starttime: proxmox::tools::time::time()?,
             task_id,
             worker_type: worker_type.to_owned(),
             worker_id,
diff --git a/src/server/worker_task.rs b/src/server/worker_task.rs
index 28e62ba..21e407f 100644
--- a/src/server/worker_task.rs
+++ b/src/server/worker_task.rs
@@ -5,7 +5,6 @@ use std::panic::UnwindSafe;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::{Arc, Mutex};
 
-use chrono::Local;
 use anyhow::{bail, format_err, Error};
 use futures::*;
 use lazy_static::lazy_static;
@@ -231,9 +230,7 @@ pub fn upid_read_status(upid: &UPID) -> Result<TaskState, Error> {
 
     let mut iter = last_line.splitn(2, ": ");
     if let Some(time_str) = iter.next() {
-        if let Ok(endtime) = chrono::DateTime::parse_from_rfc3339(time_str) {
-            let endtime = endtime.timestamp();
-
+        if let Ok(endtime) = proxmox::tools::time::parse_rfc3339(time_str) {
             if let Some(rest) = iter.next().and_then(|rest| rest.strip_prefix("TASK ")) {
                 if let Ok(state) = TaskState::from_endtime_and_message(endtime, rest) {
                     status = state;
@@ -364,8 +361,9 @@ fn update_active_workers(new_upid: Option<&UPID>) -> Result<Vec<TaskListInfo>, E
                     },
                     None => {
                         println!("Detected stopped UPID {}", upid_str);
+                        let now = proxmox::tools::time::time()?;
                         let status = upid_read_status(&upid)
-                            .unwrap_or_else(|_| TaskState::Unknown { endtime: Local::now().timestamp() });
+                            .unwrap_or_else(|_| TaskState::Unknown { endtime: now });
                         finish_list.push(TaskListInfo {
                             upid, upid_str, state: Some(status)
                         });
@@ -589,7 +587,8 @@ impl WorkerTask {
     pub fn create_state(&self, result: &Result<(), Error>) -> TaskState {
         let warn_count = self.data.lock().unwrap().warn_count;
 
-        let endtime = Local::now().timestamp();
+        // fixme: avoid unwrap
+        let endtime = proxmox::tools::time::time().unwrap();
 
         if let Err(err) = result {
             TaskState::Error { message: err.to_string(), endtime }
diff --git a/src/tools/file_logger.rs b/src/tools/file_logger.rs
index c0fcab7..c8aae5e 100644
--- a/src/tools/file_logger.rs
+++ b/src/tools/file_logger.rs
@@ -1,5 +1,4 @@
 use anyhow::{Error};
-use chrono::Local;
 use std::io::Write;
 
 /// Log messages with timestamps into files
@@ -56,7 +55,11 @@ impl FileLogger {
             stdout.write_all(b"\n").unwrap();
         }
 
-        let line = format!("{}: {}\n", Local::now().to_rfc3339(), msg);
+        // fixme: avoid unwrap
+        let now = proxmox::tools::time::time().unwrap();
+        let rfc3339 = proxmox::tools::time::epoch_to_rfc3339(now).unwrap();
+
+        let line = format!("{}: {}\n", rfc3339, msg);
         self.file.write_all(line.as_bytes()).unwrap();
     }
 }
diff --git a/src/tools/format.rs b/src/tools/format.rs
index 1e593ff..f09a922 100644
--- a/src/tools/format.rs
+++ b/src/tools/format.rs
@@ -1,6 +1,5 @@
 use anyhow::{Error};
 use serde_json::Value;
-use chrono::{Local, TimeZone, LocalResult};
 
 pub fn strip_server_file_expenstion(name: &str) -> String {
 
@@ -25,10 +24,13 @@ pub fn render_epoch(value: &Value, _record: &Value) -> Result<String, Error> {
     if value.is_null() { return Ok(String::new()); }
     let text = match value.as_i64() {
         Some(epoch) => {
-            match Local.timestamp_opt(epoch, 0) {
-                LocalResult::Single(epoch) => epoch.format("%c").to_string(),
-                _ => epoch.to_string(),
+            let mut epoch_string = epoch.to_string();
+            if let Ok(localtime) = proxmox::tools::time::localtime(epoch as i64) {
+                if let Ok(s) = proxmox::tools::time::strftime("%c", &localtime) {
+                    epoch_string = s;
+                }
             }
+            epoch_string
         },
         None => {
             value.to_string()
diff --git a/src/tools/systemd.rs b/src/tools/systemd.rs
index 9a6439d..8f0a66d 100644
--- a/src/tools/systemd.rs
+++ b/src/tools/systemd.rs
@@ -2,7 +2,6 @@ pub mod types;
 pub mod config;
 
 mod parse_time;
-pub mod tm_editor;
 pub mod time;
 
 use anyhow::{bail, Error};
diff --git a/src/tools/systemd/time.rs b/src/tools/systemd/time.rs
index f76731f..1961d4b 100644
--- a/src/tools/systemd/time.rs
+++ b/src/tools/systemd/time.rs
@@ -3,8 +3,9 @@ use std::convert::TryInto;
 use anyhow::Error;
 use bitflags::bitflags;
 
+use proxmox::tools::time::TmEditor;
+
 pub use super::parse_time::*;
-use super::tm_editor::*;
 
 bitflags!{
     #[derive(Default)]
@@ -161,7 +162,7 @@ pub fn compute_next_event(
 
     let all_days = event.days.is_empty() || event.days.is_all();
 
-    let mut t = TmEditor::new(last, utc)?;
+    let mut t = TmEditor::with_epoch(last, utc)?;
 
     let mut count = 0;
 
diff --git a/src/tools/systemd/tm_editor.rs b/src/tools/systemd/tm_editor.rs
deleted file mode 100644
index 770bb28..0000000
--- a/src/tools/systemd/tm_editor.rs
+++ /dev/null
@@ -1,119 +0,0 @@
-use anyhow::Error;
-
-use proxmox::tools::time::*;
-
-pub struct TmEditor {
-    utc: bool,
-    t: libc::tm,
-}
-
-impl TmEditor {
-
-    pub fn new(epoch: i64, utc: bool) -> Result<Self, Error> {
-        let t = if utc { gmtime(epoch)? } else { localtime(epoch)? };
-        Ok(Self { utc, t })
-    }
-
-    pub fn into_epoch(mut self) -> Result<i64, Error> {
-        let epoch = if self.utc { timegm(&mut self.t)? } else { timelocal(&mut self.t)? };
-        Ok(epoch)
-    }
-
-    /// increases the year by 'years' and resets all smaller fields to their minimum
-    pub fn add_years(&mut self, years: libc::c_int) -> Result<(), Error> {
-        if years == 0 { return Ok(()); }
-        self.t.tm_mon = 0;
-        self.t.tm_mday = 1;
-        self.t.tm_hour = 0;
-        self.t.tm_min = 0;
-        self.t.tm_sec = 0;
-        self.t.tm_year += years;
-        self.normalize_time()
-    }
-
-    /// increases the month by 'months' and resets all smaller fields to their minimum
-    pub fn add_months(&mut self, months: libc::c_int) -> Result<(), Error> {
-        if months == 0 { return Ok(()); }
-        self.t.tm_mday = 1;
-        self.t.tm_hour = 0;
-        self.t.tm_min = 0;
-        self.t.tm_sec = 0;
-        self.t.tm_mon += months;
-        self.normalize_time()
-    }
-
-    /// increases the day by 'days' and resets all smaller fields to their minimum
-    pub fn add_days(&mut self, days: libc::c_int) -> Result<(), Error> {
-        if days == 0 { return Ok(()); }
-        self.t.tm_hour = 0;
-        self.t.tm_min = 0;
-        self.t.tm_sec = 0;
-        self.t.tm_mday += days;
-        self.normalize_time()
-    }
-
-    pub fn year(&self) -> libc::c_int { self.t.tm_year + 1900 } // see man mktime
-    pub fn month(&self) -> libc::c_int { self.t.tm_mon + 1 }
-    pub fn day(&self) -> libc::c_int { self.t.tm_mday }
-    pub fn hour(&self) -> libc::c_int { self.t.tm_hour }
-    pub fn min(&self) -> libc::c_int { self.t.tm_min }
-    pub fn sec(&self) -> libc::c_int { self.t.tm_sec }
-
-    // Note: tm_wday (0-6, Sunday = 0) => convert to Sunday = 6
-    pub fn day_num(&self) -> libc::c_int {
-        (self.t.tm_wday + 6) % 7
-    }
-
-    pub fn set_time(&mut self, hour: libc::c_int, min: libc::c_int, sec: libc::c_int) -> Result<(), Error> {
-        self.t.tm_hour = hour;
-        self.t.tm_min = min;
-        self.t.tm_sec = sec;
-        self.normalize_time()
-    }
-
-    pub fn set_min_sec(&mut self, min: libc::c_int, sec: libc::c_int) -> Result<(), Error> {
-        self.t.tm_min = min;
-        self.t.tm_sec = sec;
-        self.normalize_time()
-    }
-
-    fn normalize_time(&mut self) -> Result<(), Error> {
-        // libc normalizes it for us
-        if self.utc {
-            timegm(&mut self.t)?;
-        } else {
-            timelocal(&mut self.t)?;
-        }
-        Ok(())
-    }
-
-    pub fn set_sec(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_sec = v;
-        self.normalize_time()
-    }
-
-    pub fn set_min(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_min = v;
-        self.normalize_time()
-    }
-
-    pub fn set_hour(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_hour = v;
-        self.normalize_time()
-    }
-
-    pub fn set_mday(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_mday = v;
-        self.normalize_time()
-    }
-
-    pub fn set_mon(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_mon = v - 1;
-        self.normalize_time()
-    }
-
-    pub fn set_year(&mut self, v: libc::c_int) -> Result<(), Error> {
-        self.t.tm_year = v - 1900;
-        self.normalize_time()
-    }
-}
-- 
2.20.1




More information about the pve-devel mailing list