This is an automated email from the ASF dual-hosted git repository.

tison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git


The following commit(s) were added to refs/heads/main by this push:
     new a925881  refactor: chrono to jiff (#634)
a925881 is described below

commit a925881ab574d65082a40a3bbe84fa0dc2850e1b
Author: tison <[email protected]>
AuthorDate: Mon Sep 29 15:00:01 2025 +0800

    refactor: chrono to jiff (#634)
    
    This closes #631
    
    Signed-off-by: tison <[email protected]>
---
 Cargo.toml                                         |  3 +-
 core/Cargo.toml                                    |  2 +-
 core/src/time.rs                                   | 96 +++++++---------------
 licenserc.toml                                     |  2 +-
 services/aliyun-oss/Cargo.toml                     |  2 +-
 services/aliyun-oss/src/credential.rs              |  6 +-
 services/aliyun-oss/src/sign_request.rs            | 22 ++---
 services/aws-v4/Cargo.toml                         |  2 +-
 services/aws-v4/src/credential.rs                  |  6 +-
 services/aws-v4/src/provide_credential/cognito.rs  |  6 +-
 services/aws-v4/src/provide_credential/imds.rs     | 10 +--
 services/aws-v4/src/provide_credential/process.rs  |  4 +-
 .../src/provide_credential/s3_express_session.rs   | 16 ++--
 services/aws-v4/src/provide_credential/sso.rs      | 10 +--
 services/aws-v4/src/sign_request.rs                | 12 +--
 services/azure-storage/Cargo.toml                  |  2 +-
 services/azure-storage/src/account_sas.rs          | 14 ++--
 services/azure-storage/src/credential.rs           |  8 +-
 .../src/provide_credential/azure_cli.rs            | 11 +--
 .../src/provide_credential/azure_pipelines.rs      | 15 ++--
 .../src/provide_credential/client_certificate.rs   | 17 ++--
 .../src/provide_credential/client_secret.rs        |  3 +-
 .../azure-storage/src/provide_credential/imds.rs   |  8 +-
 .../src/provide_credential/workload_identity.rs    |  5 +-
 services/azure-storage/src/sign_request.rs         | 14 ++--
 services/google/Cargo.toml                         |  2 +-
 services/google/src/credential.rs                  | 18 ++--
 .../src/provide_credential/authorized_user.rs      |  6 +-
 .../src/provide_credential/external_account.rs     | 14 ++--
 .../impersonated_service_account.rs                | 14 ++--
 .../google/src/provide_credential/vm_metadata.rs   |  3 +-
 services/google/src/sign_request.rs                | 10 +--
 services/huaweicloud-obs/Cargo.toml                |  2 +-
 services/huaweicloud-obs/src/sign_request.rs       | 37 ++++-----
 services/oracle/Cargo.toml                         |  4 +-
 services/oracle/src/credential.rs                  |  6 +-
 services/oracle/src/provide_credential/config.rs   |  3 +-
 .../oracle/src/provide_credential/config_file.rs   |  3 +-
 services/oracle/src/provide_credential/env.rs      |  3 +-
 services/tencent-cos/Cargo.toml                    |  2 +-
 services/tencent-cos/src/credential.rs             |  6 +-
 .../assume_role_with_web_identity.rs               |  2 +-
 services/tencent-cos/src/sign_request.rs           | 12 +--
 services/tencent-cos/tests/credential_chain.rs     |  4 +-
 44 files changed, 190 insertions(+), 257 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0feb155..90c7dbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,15 +32,14 @@ anyhow = "1"
 async-trait = "0.1"
 base64 = "0.22"
 bytes = "1"
-chrono = "0.4.35"
 criterion = { version = "0.7.0", features = ["async_tokio", "html_reports"] }
 dotenv = "0.15"
 env_logger = "0.11"
 form_urlencoded = "1"
 hex = "0.4"
 hmac = "0.12"
-home = "0.5"
 http = "1"
+jiff = "0.2"
 log = "0.4"
 macro_rules_attribute = "0.2.0"
 once_cell = "1"
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 124566c..e0f5037 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -32,11 +32,11 @@ anyhow.workspace = true
 async-trait.workspace = true
 base64.workspace = true
 bytes.workspace = true
-chrono.workspace = true
 form_urlencoded.workspace = true
 hex.workspace = true
 hmac.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 percent-encoding.workspace = true
 sha1.workspace = true
diff --git a/core/src/time.rs b/core/src/time.rs
index b8cb500..d2fd5f6 100644
--- a/core/src/time.rs
+++ b/core/src/time.rs
@@ -18,68 +18,25 @@
 //! Time related utils.
 
 use crate::Error;
-use chrono::format::Fixed;
-use chrono::format::Item;
-use chrono::format::Numeric;
-use chrono::format::Pad;
-use chrono::SecondsFormat;
-use chrono::Utc;
 
-/// DateTime is the alias for chrono::DateTime<Utc>.
-pub type DateTime = chrono::DateTime<Utc>;
+/// DateTime is the alias for `jiff::Timestamp`.
+pub type Timestamp = jiff::Timestamp;
 
 /// Create datetime of now.
-pub fn now() -> DateTime {
-    Utc::now()
+pub fn now() -> Timestamp {
+    Timestamp::now()
 }
 
-/// DATE is a time format like `20220301`
-const DATE: &[Item<'static>] = &[
-    Item::Numeric(Numeric::Year, Pad::Zero),
-    Item::Numeric(Numeric::Month, Pad::Zero),
-    Item::Numeric(Numeric::Day, Pad::Zero),
-];
-
 /// Format time into date: `20220301`
-pub fn format_date(t: DateTime) -> String {
-    t.format_with_items(DATE.iter()).to_string()
+pub fn format_date(t: Timestamp) -> String {
+    t.strftime("%Y%m%d").to_string()
 }
 
-/// ISO8601 is a time format like `20220313T072004Z`.
-const ISO8601: &[Item<'static>] = &[
-    Item::Numeric(Numeric::Year, Pad::Zero),
-    Item::Numeric(Numeric::Month, Pad::Zero),
-    Item::Numeric(Numeric::Day, Pad::Zero),
-    Item::Literal("T"),
-    Item::Numeric(Numeric::Hour, Pad::Zero),
-    Item::Numeric(Numeric::Minute, Pad::Zero),
-    Item::Numeric(Numeric::Second, Pad::Zero),
-    Item::Literal("Z"),
-];
-
 /// Format time into ISO8601: `20220313T072004Z`
-pub fn format_iso8601(t: DateTime) -> String {
-    t.format_with_items(ISO8601.iter()).to_string()
+pub fn format_iso8601(t: Timestamp) -> String {
+    t.strftime("%Y%m%dT%H%M%SZ").to_string()
 }
 
-/// HTTP_DATE is a time format like `Sun, 06 Nov 1994 08:49:37 GMT`.
-const HTTP_DATE: &[Item<'static>] = &[
-    Item::Fixed(Fixed::ShortWeekdayName),
-    Item::Literal(", "),
-    Item::Numeric(Numeric::Day, Pad::Zero),
-    Item::Literal(" "),
-    Item::Fixed(Fixed::ShortMonthName),
-    Item::Literal(" "),
-    Item::Numeric(Numeric::Year, Pad::Zero),
-    Item::Literal(" "),
-    Item::Numeric(Numeric::Hour, Pad::Zero),
-    Item::Literal(":"),
-    Item::Numeric(Numeric::Minute, Pad::Zero),
-    Item::Literal(":"),
-    Item::Numeric(Numeric::Second, Pad::Zero),
-    Item::Literal(" GMT"),
-];
-
 /// Format time into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
 ///
 /// ## Note
@@ -88,13 +45,13 @@ const HTTP_DATE: &[Item<'static>] = &[
 ///
 /// - Timezone is fixed to GMT.
 /// - Day must be 2 digit.
-pub fn format_http_date(t: DateTime) -> String {
-    t.format_with_items(HTTP_DATE.iter()).to_string()
+pub fn format_http_date(t: Timestamp) -> String {
+    t.strftime("%a, %d %b %Y %T GMT").to_string()
 }
 
 /// Format time into RFC3339: `2022-03-13T07:20:04Z`
-pub fn format_rfc3339(t: DateTime) -> String {
-    t.to_rfc3339_opts(SecondsFormat::Secs, true)
+pub fn format_rfc3339(t: Timestamp) -> String {
+    t.strftime("%FT%TZ").to_string()
 }
 
 /// Parse time from RFC3339.
@@ -104,22 +61,31 @@ pub fn format_rfc3339(t: DateTime) -> String {
 /// - `2022-03-13T07:20:04Z`
 /// - `2022-03-01T08:12:34+00:00`
 /// - `2022-03-01T08:12:34.00+00:00`
-pub fn parse_rfc3339(s: &str) -> crate::Result<DateTime> {
-    Ok(chrono::DateTime::parse_from_rfc3339(s)
-        .map_err(|err| {
-            Error::unexpected(format!("parse '{s}' into rfc3339 
failed")).with_source(err)
-        })?
-        .with_timezone(&Utc))
+pub fn parse_rfc3339(s: &str) -> crate::Result<Timestamp> {
+    s.parse().map_err(|err| {
+        Error::unexpected(format!("parse '{s}' into rfc3339 
failed")).with_source(err)
+    })
+}
+
+/// Parse time from RFC2822.
+///
+/// All of them are valid time:
+///
+/// - `Sat, 13 Jul 2024 15:09:59 -0400`
+/// - `Mon, 15 Aug 2022 16:50:12 GMT`
+pub fn parse_rfc2822(s: &str) -> crate::Result<Timestamp> {
+    let zoned = jiff::fmt::rfc2822::parse(s).map_err(|err| {
+        Error::unexpected(format!("parse '{s}' into rfc2822 
failed")).with_source(err)
+    })?;
+    Ok(zoned.timestamp())
 }
 
 #[cfg(test)]
 mod tests {
-    use chrono::TimeZone;
-
     use super::*;
 
-    fn test_time() -> DateTime {
-        Utc.with_ymd_and_hms(2022, 3, 1, 8, 12, 34).unwrap()
+    fn test_time() -> Timestamp {
+        "2022-03-01T08:12:34Z".parse().unwrap()
     }
 
     #[test]
diff --git a/licenserc.toml b/licenserc.toml
index 47a56e5..1937a22 100644
--- a/licenserc.toml
+++ b/licenserc.toml
@@ -17,4 +17,4 @@
 
 headerPath = "Apache-2.0-ASF.txt"
 
-includes = ['**/*.rs', '**/*.yml', '**/*.yaml', '**/*.toml']
\ No newline at end of file
+includes = ['**/*.rs', '**/*.yml', '**/*.yaml', '**/*.toml']
diff --git a/services/aliyun-oss/Cargo.toml b/services/aliyun-oss/Cargo.toml
index 5521684..ea71e8f 100644
--- a/services/aliyun-oss/Cargo.toml
+++ b/services/aliyun-oss/Cargo.toml
@@ -29,8 +29,8 @@ repository.workspace = true
 [dependencies]
 anyhow.workspace = true
 async-trait.workspace = true
-chrono.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 once_cell.workspace = true
 percent-encoding.workspace = true
diff --git a/services/aliyun-oss/src/credential.rs 
b/services/aliyun-oss/src/credential.rs
index d7c0229..9ec61ed 100644
--- a/services/aliyun-oss/src/credential.rs
+++ b/services/aliyun-oss/src/credential.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::time::{now, DateTime};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::utils::Redact;
 use reqsign_core::SigningCredential;
 use std::fmt::{Debug, Formatter};
@@ -30,7 +30,7 @@ pub struct Credential {
     /// Security token for aliyun services.
     pub security_token: Option<String>,
     /// Expiration time for this credential.
-    pub expires_in: Option<DateTime>,
+    pub expires_in: Option<Timestamp>,
 }
 
 impl Debug for Credential {
@@ -54,7 +54,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in 
bounds"))
+            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
         {
             return valid;
         }
diff --git a/services/aliyun-oss/src/sign_request.rs 
b/services/aliyun-oss/src/sign_request.rs
index 37b4adb..08df07b 100644
--- a/services/aliyun-oss/src/sign_request.rs
+++ b/services/aliyun-oss/src/sign_request.rs
@@ -22,7 +22,7 @@ use http::HeaderValue;
 use once_cell::sync::Lazy;
 use percent_encoding::utf8_percent_encode;
 use reqsign_core::hash::base64_hmac_sha1;
-use reqsign_core::time::{format_http_date, now, DateTime};
+use reqsign_core::time::{format_http_date, now, Timestamp};
 use reqsign_core::Result;
 use reqsign_core::{Context, SignRequest};
 use std::collections::HashSet;
@@ -35,7 +35,7 @@ const CONTENT_MD5: &str = "content-md5";
 #[derive(Debug)]
 pub struct RequestSigner {
     bucket: String,
-    time: Option<DateTime>,
+    time: Option<Timestamp>,
 }
 
 impl RequestSigner {
@@ -54,12 +54,12 @@ impl RequestSigner {
     /// We should always take current time to sign requests.
     /// Only use this function for testing.
     #[cfg(test)]
-    pub fn with_time(mut self, time: DateTime) -> Self {
+    pub fn with_time(mut self, time: Timestamp) -> Self {
         self.time = Some(time);
         self
     }
 
-    fn get_time(&self) -> DateTime {
+    fn get_time(&self) -> Timestamp {
         self.time.unwrap_or_else(now)
     }
 }
@@ -97,7 +97,7 @@ impl RequestSigner {
         &self,
         req: &mut http::request::Parts,
         cred: &Credential,
-        signing_time: DateTime,
+        signing_time: Timestamp,
     ) -> Result<()> {
         let string_to_sign = self.build_string_to_sign(req, cred, 
signing_time, None)?;
         let signature =
@@ -125,11 +125,11 @@ impl RequestSigner {
         &self,
         req: &mut http::request::Parts,
         cred: &Credential,
-        signing_time: DateTime,
+        signing_time: Timestamp,
         expires: Duration,
     ) -> Result<()> {
         let expiration_time = signing_time
-            + chrono::TimeDelta::from_std(expires).map_err(|e| {
+            + jiff::SignedDuration::try_from(expires).map_err(|e| {
                 reqsign_core::Error::request_invalid(format!("Invalid 
expiration duration: {e}"))
             })?;
         let string_to_sign = self.build_string_to_sign(req, cred, 
signing_time, Some(expires))?;
@@ -154,7 +154,7 @@ impl RequestSigner {
         query_pairs.push(("OSSAccessKeyId".to_string(), 
cred.access_key_id.clone()));
         query_pairs.push((
             "Expires".to_string(),
-            expiration_time.timestamp().to_string(),
+            expiration_time.as_second().to_string(),
         ));
         query_pairs.push((
             "Signature".to_string(),
@@ -200,7 +200,7 @@ impl RequestSigner {
         &self,
         req: &http::request::Parts,
         cred: &Credential,
-        signing_time: DateTime,
+        signing_time: Timestamp,
         expires: Option<Duration>,
     ) -> Result<String> {
         let mut s = String::new();
@@ -229,12 +229,12 @@ impl RequestSigner {
         match expires {
             Some(expires_duration) => {
                 let expiration_time = signing_time
-                    + 
chrono::TimeDelta::from_std(expires_duration).map_err(|e| {
+                    + 
jiff::SignedDuration::try_from(expires_duration).map_err(|e| {
                         reqsign_core::Error::request_invalid(format!(
                             "Invalid expiration duration: {e}"
                         ))
                     })?;
-                writeln!(&mut s, "{}", expiration_time.timestamp())?;
+                writeln!(&mut s, "{}", expiration_time.as_second())?;
             }
             None => {
                 writeln!(&mut s, "{}", format_http_date(signing_time))?;
diff --git a/services/aws-v4/Cargo.toml b/services/aws-v4/Cargo.toml
index 80df230..47acb30 100644
--- a/services/aws-v4/Cargo.toml
+++ b/services/aws-v4/Cargo.toml
@@ -34,9 +34,9 @@ name = "aws"
 anyhow.workspace = true
 async-trait.workspace = true
 bytes = "1.7.2"
-chrono.workspace = true
 form_urlencoded.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 percent-encoding.workspace = true
 quick-xml.workspace = true
diff --git a/services/aws-v4/src/credential.rs 
b/services/aws-v4/src/credential.rs
index 6e952e8..a0a1dbb 100644
--- a/services/aws-v4/src/credential.rs
+++ b/services/aws-v4/src/credential.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::time::{now, DateTime};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::utils::Redact;
 use reqsign_core::SigningCredential;
 use std::fmt::{Debug, Formatter};
@@ -30,7 +30,7 @@ pub struct Credential {
     /// Session token for aws services.
     pub session_token: Option<String>,
     /// Expiration time for this credential.
-    pub expires_in: Option<DateTime>,
+    pub expires_in: Option<Timestamp>,
 }
 
 impl Debug for Credential {
@@ -54,7 +54,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in 
bounds"))
+            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
         {
             return valid;
         }
diff --git a/services/aws-v4/src/provide_credential/cognito.rs 
b/services/aws-v4/src/provide_credential/cognito.rs
index 52ce42b..1de3730 100644
--- a/services/aws-v4/src/provide_credential/cognito.rs
+++ b/services/aws-v4/src/provide_credential/cognito.rs
@@ -17,9 +17,9 @@
 
 use crate::Credential;
 use async_trait::async_trait;
-use chrono::DateTime;
 use http::{Method, Request, StatusCode};
 use log::debug;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 use serde_json::json;
@@ -224,8 +224,8 @@ impl CognitoIdentityCredentialProvider {
             .map_err(|e| Error::unexpected(format!("failed to parse 
credentials response: {e}")))?;
 
         let creds = result.credentials;
-        let expires_in = DateTime::from_timestamp(creds.expiration, 0)
-            .ok_or_else(|| Error::unexpected("invalid expiration 
timestamp".to_string()))?;
+        let expires_in = Timestamp::from_second(creds.expiration)
+            .map_err(|e| Error::unexpected(format!("invalid expiration date: 
{e}")))?;
 
         Ok(Credential {
             access_key_id: creds.access_key_id,
diff --git a/services/aws-v4/src/provide_credential/imds.rs 
b/services/aws-v4/src/provide_credential/imds.rs
index 61db8b5..a25af58 100644
--- a/services/aws-v4/src/provide_credential/imds.rs
+++ b/services/aws-v4/src/provide_credential/imds.rs
@@ -21,7 +21,7 @@ use async_trait::async_trait;
 use bytes::Bytes;
 use http::header::CONTENT_LENGTH;
 use http::Method;
-use reqsign_core::time::{now, parse_rfc3339, DateTime};
+use reqsign_core::time::{now, parse_rfc3339, Timestamp};
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 use std::sync::{Arc, Mutex};
@@ -29,14 +29,14 @@ use std::sync::{Arc, Mutex};
 #[derive(Debug, Clone)]
 pub struct IMDSv2CredentialProvider {
     endpoint: Option<String>,
-    token: Arc<Mutex<(String, DateTime)>>,
+    token: Arc<Mutex<(String, Timestamp)>>,
 }
 
 impl Default for IMDSv2CredentialProvider {
     fn default() -> Self {
         Self {
             endpoint: None,
-            token: Arc::new(Mutex::new((String::new(), DateTime::default()))),
+            token: Arc::new(Mutex::new((String::new(), Timestamp::default()))),
         }
     }
 }
@@ -105,8 +105,8 @@ impl IMDSv2CredentialProvider {
         }
         let ec2_token = resp.into_body();
         // Set expires_in to 10 minutes to enforce re-read.
-        let expires_in = now() + 
chrono::TimeDelta::try_seconds(21600).expect("in bounds")
-            - chrono::TimeDelta::try_seconds(600).expect("in bounds");
+        let expires_in =
+            now() + jiff::SignedDuration::from_secs(21600) - 
jiff::SignedDuration::from_secs(600);
 
         {
             *self.token.lock().expect("lock poisoned") = (ec2_token.clone(), 
expires_in);
diff --git a/services/aws-v4/src/provide_credential/process.rs 
b/services/aws-v4/src/provide_credential/process.rs
index e672048..8782199 100644
--- a/services/aws-v4/src/provide_credential/process.rs
+++ b/services/aws-v4/src/provide_credential/process.rs
@@ -17,9 +17,9 @@
 
 use crate::Credential;
 use async_trait::async_trait;
-use chrono::{DateTime, Utc};
 use ini::Ini;
 use log::debug;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 
@@ -213,7 +213,7 @@ impl ProvideCredential for ProcessCredentialProvider {
 
         let expires_in =
             if let Some(exp_str) = &output.expiration {
-                Some(exp_str.parse::<DateTime<Utc>>().map_err(|e| {
+                Some(exp_str.parse::<Timestamp>().map_err(|e| {
                     Error::unexpected(format!("failed to parse expiration 
time: {e}"))
                 })?)
             } else {
diff --git a/services/aws-v4/src/provide_credential/s3_express_session.rs 
b/services/aws-v4/src/provide_credential/s3_express_session.rs
index fc79a3b..723b2bb 100644
--- a/services/aws-v4/src/provide_credential/s3_express_session.rs
+++ b/services/aws-v4/src/provide_credential/s3_express_session.rs
@@ -20,6 +20,7 @@ use async_trait::async_trait;
 use bytes::Bytes;
 use http::{header, Method, Request};
 use log::debug;
+use reqsign_core::time::parse_rfc3339;
 use reqsign_core::{Context, Error, ProvideCredential, Result, SignRequest};
 use serde::Deserialize;
 
@@ -160,13 +161,12 @@ impl S3ExpressSessionProvider {
 
         // Parse expiration time from ISO8601 format
         let expiration =
-            
chrono::DateTime::parse_from_rfc3339(&create_session_resp.credentials.expiration)
-                .map_err(|e| {
-                    Error::unexpected(format!(
-                        "Failed to parse expiration time '{}': {e}",
-                        create_session_resp.credentials.expiration
-                    ))
-                })?;
+            
parse_rfc3339(&create_session_resp.credentials.expiration).map_err(|e| {
+                Error::unexpected(format!(
+                    "Failed to parse expiration time '{}': {e}",
+                    create_session_resp.credentials.expiration
+                ))
+            })?;
 
         // Convert to Credential with expiration time
         let creds = create_session_resp.credentials;
@@ -174,7 +174,7 @@ impl S3ExpressSessionProvider {
             access_key_id: creds.access_key_id,
             secret_access_key: creds.secret_access_key,
             session_token: Some(creds.session_token),
-            expires_in: Some(expiration.into()),
+            expires_in: Some(expiration),
         })
     }
 
diff --git a/services/aws-v4/src/provide_credential/sso.rs 
b/services/aws-v4/src/provide_credential/sso.rs
index b000422..a0adaec 100644
--- a/services/aws-v4/src/provide_credential/sso.rs
+++ b/services/aws-v4/src/provide_credential/sso.rs
@@ -17,10 +17,10 @@
 
 use crate::Credential;
 use async_trait::async_trait;
-use chrono::{DateTime, Utc};
 use http::{Method, Request, StatusCode};
 use ini::Ini;
 use log::{debug, warn};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 
@@ -218,11 +218,11 @@ impl SSOCredentialProvider {
                 })?;
 
                 // Check if token is expired
-                let expires_at: DateTime<Utc> = 
token.expires_at.parse().map_err(|e| {
+                let expires_at = 
token.expires_at.parse::<Timestamp>().map_err(|e| {
                     Error::unexpected(format!("failed to parse expiration 
time: {e}"))
                 })?;
 
-                if expires_at <= Utc::now() {
+                if expires_at <= now() {
                     warn!("SSO token is expired");
                     return Ok(None);
                 }
@@ -286,8 +286,8 @@ impl SSOCredentialProvider {
             .map_err(|e| Error::unexpected(format!("failed to parse SSO 
credentials: {e}")))?;
 
         let role_creds = creds.role_credentials;
-        let expires_in = DateTime::from_timestamp_millis(role_creds.expiration)
-            .ok_or_else(|| Error::unexpected("invalid expiration 
timestamp".to_string()))?;
+        let expires_in = Timestamp::from_millisecond(role_creds.expiration)
+            .map_err(|e| Error::unexpected(format!("invalid expiration 
timestamp: {e}")))?;
 
         Ok(Credential {
             access_key_id: role_creds.access_key_id,
diff --git a/services/aws-v4/src/sign_request.rs 
b/services/aws-v4/src/sign_request.rs
index 2aa7e52..6b27ddf 100644
--- a/services/aws-v4/src/sign_request.rs
+++ b/services/aws-v4/src/sign_request.rs
@@ -26,7 +26,7 @@ use http::{header, HeaderValue};
 use log::debug;
 use percent_encoding::{percent_decode_str, utf8_percent_encode};
 use reqsign_core::hash::{hex_hmac_sha256, hex_sha256, hmac_sha256};
-use reqsign_core::time::{format_date, format_iso8601, now, DateTime};
+use reqsign_core::time::{format_date, format_iso8601, now, Timestamp};
 use reqsign_core::{Context, Result, SignRequest, SigningRequest};
 use std::fmt::Write;
 use std::time::Duration;
@@ -39,7 +39,7 @@ pub struct RequestSigner {
     service: String,
     region: String,
 
-    time: Option<DateTime>,
+    time: Option<Timestamp>,
 }
 
 impl RequestSigner {
@@ -60,7 +60,7 @@ impl RequestSigner {
     /// We should always take current time to sign requests.
     /// Only use this function for testing.
     #[cfg(test)]
-    pub fn with_time(mut self, time: DateTime) -> Self {
+    pub fn with_time(mut self, time: Timestamp) -> Self {
         self.time = Some(time);
         self
     }
@@ -233,7 +233,7 @@ fn canonicalize_header(
     ctx: &mut SigningRequest,
     cred: &Credential,
     expires_in: Option<Duration>,
-    now: DateTime,
+    now: Timestamp,
 ) -> Result<()> {
     // Header names and values need to be normalized according to Step 4 of 
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
     for (_, value) in ctx.headers.iter_mut() {
@@ -298,7 +298,7 @@ fn canonicalize_query(
     ctx: &mut SigningRequest,
     cred: &Credential,
     expires_in: Option<Duration>,
-    now: DateTime,
+    now: Timestamp,
     service: &str,
     region: &str,
 ) -> Result<()> {
@@ -351,7 +351,7 @@ fn canonicalize_query(
     Ok(())
 }
 
-fn generate_signing_key(secret: &str, time: DateTime, region: &str, service: 
&str) -> Vec<u8> {
+fn generate_signing_key(secret: &str, time: Timestamp, region: &str, service: 
&str) -> Vec<u8> {
     // Sign secret
     let secret = format!("AWS4{secret}");
     // Sign date
diff --git a/services/azure-storage/Cargo.toml 
b/services/azure-storage/Cargo.toml
index 788cb83..ca3cfe7 100644
--- a/services/azure-storage/Cargo.toml
+++ b/services/azure-storage/Cargo.toml
@@ -31,9 +31,9 @@ anyhow.workspace = true
 async-trait.workspace = true
 base64.workspace = true
 bytes.workspace = true
-chrono.workspace = true
 form_urlencoded.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 percent-encoding.workspace = true
 reqsign-core.workspace = true
diff --git a/services/azure-storage/src/account_sas.rs 
b/services/azure-storage/src/account_sas.rs
index bd04ee8..7eff18a 100644
--- a/services/azure-storage/src/account_sas.rs
+++ b/services/azure-storage/src/account_sas.rs
@@ -19,7 +19,7 @@ use reqsign_core::Result;
 
 use reqsign_core::hash;
 use reqsign_core::time;
-use reqsign_core::time::DateTime;
+use reqsign_core::time::Timestamp;
 
 /// The default parameters that make up a SAS token
 /// 
https://learn.microsoft.com/en-us/rest/api/storageservices/create-account-sas#specify-the-account-sas-parameters
@@ -35,15 +35,15 @@ pub struct AccountSharedAccessSignature {
     resource: String,
     resource_type: String,
     permissions: String,
-    expiry: DateTime,
-    start: Option<DateTime>,
+    expiry: Timestamp,
+    start: Option<Timestamp>,
     ip: Option<String>,
     protocol: Option<String>,
 }
 
 impl AccountSharedAccessSignature {
     /// Create a SAS token signer with default parameters
-    pub fn new(account: String, key: String, expiry: DateTime) -> Self {
+    pub fn new(account: String, key: String, expiry: Timestamp) -> Self {
         Self {
             account,
             key,
@@ -125,14 +125,14 @@ mod tests {
 
     use super::*;
 
-    fn test_time() -> DateTime {
-        DateTime::from_str("2022-03-01T08:12:34Z").unwrap()
+    fn test_time() -> Timestamp {
+        Timestamp::from_str("2022-03-01T08:12:34Z").unwrap()
     }
 
     #[test]
     fn test_can_generate_sas_token() {
         let key = hash::base64_encode("key".as_bytes());
-        let expiry = test_time() + 
chrono::TimeDelta::try_minutes(5).expect("in bounds");
+        let expiry = test_time() + jiff::SignedDuration::from_mins(5);
         let sign = AccountSharedAccessSignature::new("account".to_string(), 
key, expiry);
         let token_content = sign.token().expect("token decode failed");
         let token = token_content
diff --git a/services/azure-storage/src/credential.rs 
b/services/azure-storage/src/credential.rs
index abd1ec4..e824bc9 100644
--- a/services/azure-storage/src/credential.rs
+++ b/services/azure-storage/src/credential.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::time::{now, DateTime};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::utils::Redact;
 use reqsign_core::SigningCredential;
 use std::fmt::{Debug, Formatter};
@@ -40,7 +40,7 @@ pub enum Credential {
         /// Bearer token.
         token: String,
         /// Expiration time for this credential.
-        expires_in: Option<DateTime>,
+        expires_in: Option<Timestamp>,
     },
 }
 
@@ -82,7 +82,7 @@ impl SigningCredential for Credential {
                 }
                 // Check expiration for bearer tokens (take 20s as buffer to 
avoid edge cases)
                 if let Some(expires) = expires_in {
-                    *expires > now() + 
chrono::TimeDelta::try_seconds(20).expect("in bounds")
+                    *expires > now() + jiff::SignedDuration::from_secs(20)
                 } else {
                     true
                 }
@@ -108,7 +108,7 @@ impl Credential {
     }
 
     /// Create a new credential with bearer token authentication.
-    pub fn with_bearer_token(bearer_token: &str, expires_in: Option<DateTime>) 
-> Self {
+    pub fn with_bearer_token(bearer_token: &str, expires_in: 
Option<Timestamp>) -> Self {
         Self::BearerToken {
             token: bearer_token.to_string(),
             expires_in,
diff --git a/services/azure-storage/src/provide_credential/azure_cli.rs 
b/services/azure-storage/src/provide_credential/azure_cli.rs
index ce6dc82..69f112a 100644
--- a/services/azure-storage/src/provide_credential/azure_cli.rs
+++ b/services/azure-storage/src/provide_credential/azure_cli.rs
@@ -15,12 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::credential::Credential;
 use async_trait::async_trait;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential};
 use serde::Deserialize;
 
-use crate::credential::Credential;
-
 /// AzureCliCredentialProvider provides credentials from Azure CLI
 ///
 /// This provider reads tokens from Azure CLI's local storage or invokes
@@ -96,12 +96,13 @@ impl ProvideCredential for AzureCliCredentialProvider {
 
         // Calculate expiration time
         let expires_on = if let Some(timestamp) = token.expires_on_timestamp {
-            Some(chrono::DateTime::from_timestamp(timestamp, 0).unwrap())
+            Some(Timestamp::from_second(timestamp).unwrap())
         } else if let Some(expires_str) = token.expires_on {
             // Parse the string format "2023-10-31 21:59:10.000000"
-            chrono::NaiveDateTime::parse_from_str(&expires_str, "%Y-%m-%d 
%H:%M:%S%.f")
+            expires_str
+                .parse::<jiff::civil::DateTime>()
+                .and_then(|dt| jiff::tz::TimeZone::UTC.to_timestamp(dt))
                 .ok()
-                .map(|dt| chrono::DateTime::from_naive_utc_and_offset(dt, 
chrono::Utc))
         } else {
             None
         };
diff --git a/services/azure-storage/src/provide_credential/azure_pipelines.rs 
b/services/azure-storage/src/provide_credential/azure_pipelines.rs
index 6544528..a1849d0 100644
--- a/services/azure-storage/src/provide_credential/azure_pipelines.rs
+++ b/services/azure-storage/src/provide_credential/azure_pipelines.rs
@@ -17,12 +17,12 @@
 
 use std::collections::HashMap;
 
+use crate::credential::Credential;
 use async_trait::async_trait;
+use reqsign_core::time::now;
 use reqsign_core::{Context, ProvideCredential};
 use serde::Deserialize;
 
-use crate::credential::Credential;
-
 /// AzurePipelinesCredentialProvider provides credentials using Azure 
Pipelines workload identity
 ///
 /// This provider uses OIDC tokens from Azure Pipelines to authenticate with 
Azure AD
@@ -233,13 +233,8 @@ impl ProvideCredential for 
AzurePipelinesCredentialProvider {
             .await?;
 
         // Calculate expiration time
-        let expires_on = std::time::SystemTime::now()
-            
.checked_add(std::time::Duration::from_secs(token_response.expires_in))
-            .and_then(|t| {
-                t.duration_since(std::time::UNIX_EPOCH)
-                    .ok()
-                    .map(|d| chrono::DateTime::from_timestamp(d.as_secs() as 
i64, 0).unwrap())
-            });
+        let expires_in = 
std::time::Duration::from_secs(token_response.expires_in);
+        let expires_on = now().checked_add(expires_in).ok();
 
         Ok(Some(Credential::with_bearer_token(
             &token_response.access_token,
@@ -271,7 +266,7 @@ mod tests {
     async fn test_provide_credential_no_environment() {
         // When not in Azure Pipelines environment, should return None
         let provider = AzurePipelinesCredentialProvider::new();
-        let ctx = reqsign_core::Context::new()
+        let ctx = Context::new()
             .with_file_read(reqsign_file_read_tokio::TokioFileRead)
             
.with_http_send(reqsign_http_send_reqwest::ReqwestHttpSend::default());
 
diff --git 
a/services/azure-storage/src/provide_credential/client_certificate.rs 
b/services/azure-storage/src/provide_credential/client_certificate.rs
index 9e1d6a6..7268568 100644
--- a/services/azure-storage/src/provide_credential/client_certificate.rs
+++ b/services/azure-storage/src/provide_credential/client_certificate.rs
@@ -18,23 +18,23 @@
 use std::collections::HashMap;
 use std::time::{Duration, SystemTime, UNIX_EPOCH};
 
+use crate::credential::Credential;
 use async_trait::async_trait;
 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
 use base64::Engine;
 use jsonwebtoken::{Algorithm, EncodingKey, Header};
+use reqsign_core::time::now;
 use reqsign_core::{Context, ProvideCredential};
 use rsa::pkcs8::DecodePrivateKey;
 use rsa::RsaPrivateKey;
 use serde::{Deserialize, Serialize};
 use sha1::{Digest, Sha1};
 
-use crate::credential::Credential;
-
 /// Generate a unique JWT ID using timestamp and a pseudo-random component
 fn generate_jti(now: u64) -> String {
     // Use timestamp in nanoseconds + a hash of the timestamp for uniqueness
-    let nano_time = std::time::SystemTime::now()
-        .duration_since(std::time::UNIX_EPOCH)
+    let nano_time = SystemTime::now()
+        .duration_since(UNIX_EPOCH)
         .unwrap_or_default()
         .as_nanos();
 
@@ -315,13 +315,8 @@ impl ProvideCredential for 
ClientCertificateCredentialProvider {
             .await?;
 
         // Calculate expiration time
-        let expires_on = SystemTime::now()
-            .checked_add(Duration::from_secs(token_response.expires_in))
-            .and_then(|t| {
-                t.duration_since(UNIX_EPOCH)
-                    .ok()
-                    .map(|d| chrono::DateTime::from_timestamp(d.as_secs() as 
i64, 0).unwrap())
-            });
+        let expires_in = Duration::from_secs(token_response.expires_in);
+        let expires_on = now().checked_add(expires_in).ok();
 
         Ok(Some(Credential::with_bearer_token(
             &token_response.access_token,
diff --git a/services/azure-storage/src/provide_credential/client_secret.rs 
b/services/azure-storage/src/provide_credential/client_secret.rs
index 7294f7c..bde45cd 100644
--- a/services/azure-storage/src/provide_credential/client_secret.rs
+++ b/services/azure-storage/src/provide_credential/client_secret.rs
@@ -91,8 +91,7 @@ impl ProvideCredential for ClientSecretCredentialProvider {
         match token {
             Some(token_response) => {
                 let expires_on = reqsign_core::time::now()
-                    + chrono::TimeDelta::try_seconds(token_response.expires_in 
as i64)
-                        .unwrap_or_else(|| 
chrono::TimeDelta::try_minutes(10).expect("in bounds"));
+                    + 
jiff::SignedDuration::from_secs(token_response.expires_in as i64);
 
                 Ok(Some(Credential::with_bearer_token(
                     &token_response.access_token,
diff --git a/services/azure-storage/src/provide_credential/imds.rs 
b/services/azure-storage/src/provide_credential/imds.rs
index a09a41f..963b34f 100644
--- a/services/azure-storage/src/provide_credential/imds.rs
+++ b/services/azure-storage/src/provide_credential/imds.rs
@@ -17,6 +17,7 @@
 
 use crate::Credential;
 use async_trait::async_trait;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
 
 /// Load credential from Azure Instance Metadata Service (IMDS).
@@ -51,15 +52,16 @@ impl ProvideCredential for ImdsCredentialProvider {
         let token = get_access_token("https://storage.azure.com/";, ctx).await?;
 
         let expires_on = if token.expires_on.is_empty() {
-            reqsign_core::time::now() + 
chrono::TimeDelta::try_minutes(10).expect("in bounds")
+            reqsign_core::time::now() + jiff::SignedDuration::from_mins(10)
         } else {
             // Azure IMDS returns expires_on as Unix timestamp (seconds since 
epoch)
             let timestamp = token.expires_on.parse::<i64>().map_err(|e| {
                 reqsign_core::Error::unexpected("failed to parse expires_on 
timestamp")
                     .with_source(e)
             })?;
-            chrono::DateTime::from_timestamp(timestamp, 0)
-                .ok_or_else(|| reqsign_core::Error::unexpected("invalid 
expires_on timestamp"))?
+            Timestamp::from_second(timestamp).map_err(|e| {
+                reqsign_core::Error::unexpected(format!("invalid expires_on 
timestamp: {e}"))
+            })?
         };
 
         Ok(Some(Credential::with_bearer_token(
diff --git a/services/azure-storage/src/provide_credential/workload_identity.rs 
b/services/azure-storage/src/provide_credential/workload_identity.rs
index 17d0328..500186a 100644
--- a/services/azure-storage/src/provide_credential/workload_identity.rs
+++ b/services/azure-storage/src/provide_credential/workload_identity.rs
@@ -91,10 +91,7 @@ impl ProvideCredential for 
WorkloadIdentityCredentialProvider {
                                 .with_source(e)
                         })?
                     }
-                    None => {
-                        reqsign_core::time::now()
-                            + chrono::TimeDelta::try_minutes(10).expect("in 
bounds")
-                    }
+                    None => reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
                 };
 
                 Ok(Some(Credential::with_bearer_token(
diff --git a/services/azure-storage/src/sign_request.rs 
b/services/azure-storage/src/sign_request.rs
index 43eb071..c528a07 100644
--- a/services/azure-storage/src/sign_request.rs
+++ b/services/azure-storage/src/sign_request.rs
@@ -23,7 +23,7 @@ use http::{header, HeaderValue};
 use log::debug;
 use percent_encoding::percent_encode;
 use reqsign_core::hash::{base64_decode, base64_hmac_sha256};
-use reqsign_core::time::{format_http_date, now, DateTime};
+use reqsign_core::time::{format_http_date, now, Timestamp};
 use reqsign_core::{Context, Result, SignRequest, SigningMethod, 
SigningRequest};
 use std::fmt::Write;
 use std::time::Duration;
@@ -33,7 +33,7 @@ use std::time::Duration;
 /// - [Authorize with Shared 
Key](https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key)
 #[derive(Debug)]
 pub struct RequestSigner {
-    time: Option<DateTime>,
+    time: Option<Timestamp>,
 }
 
 impl RequestSigner {
@@ -49,7 +49,7 @@ impl RequestSigner {
     /// We should always take current time to sign requests.
     /// Only use this function for testing.
     #[cfg(test)]
-    pub fn with_time(mut self, time: DateTime) -> Self {
+    pub fn with_time(mut self, time: Timestamp) -> Self {
         self.time = Some(time);
         self
     }
@@ -134,7 +134,7 @@ impl SignRequest for RequestSigner {
                             account_name.clone(),
                             account_key.clone(),
                             now()
-                                + chrono::TimeDelta::from_std(d).map_err(|e| {
+                                + 
jiff::SignedDuration::try_from(d).map_err(|e| {
                                     reqsign_core::Error::unexpected("failed to 
convert duration")
                                         .with_source(e)
                                 })?,
@@ -214,7 +214,7 @@ impl SignRequest for RequestSigner {
 fn string_to_sign(
     ctx: &mut SigningRequest,
     account_name: &str,
-    now_time: DateTime,
+    now_time: Timestamp,
 ) -> Result<String> {
     let mut s = String::with_capacity(128);
 
@@ -374,7 +374,7 @@ fn string_to_sign(
 /// ## Reference
 ///
 /// - [Constructing the canonicalized headers 
string](https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key#constructing-the-canonicalized-headers-string)
-fn canonicalize_header(ctx: &mut SigningRequest, now_time: DateTime) -> 
Result<String> {
+fn canonicalize_header(ctx: &mut SigningRequest, now_time: Timestamp) -> 
Result<String> {
     ctx.headers.insert(
         X_MS_DATE,
         format_http_date(now_time).parse().map_err(|e| {
@@ -455,7 +455,7 @@ mod tests {
             .with_env(OsEnv);
         let cred = Credential::with_bearer_token(
             "token",
-            Some(now() + chrono::TimeDelta::try_hours(1).unwrap()),
+            Some(now() + jiff::SignedDuration::from_hours(1)),
         );
         let builder = RequestSigner::new();
 
diff --git a/services/google/Cargo.toml b/services/google/Cargo.toml
index cfb86d2..58d3864 100644
--- a/services/google/Cargo.toml
+++ b/services/google/Cargo.toml
@@ -28,8 +28,8 @@ repository.workspace = true
 
 [dependencies]
 async-trait.workspace = true
-chrono.workspace = true
 http.workspace = true
+jiff.workspace = true
 jsonwebtoken = "9.2"
 log.workspace = true
 percent-encoding.workspace = true
diff --git a/services/google/src/credential.rs 
b/services/google/src/credential.rs
index 7167c13..ace9f94 100644
--- a/services/google/src/credential.rs
+++ b/services/google/src/credential.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 use reqsign_core::{
-    time::now, time::DateTime, utils::Redact, Result, SigningCredential as 
KeyTrait,
+    time::now, time::Timestamp, utils::Redact, Result, SigningCredential as 
KeyTrait,
 };
 use std::fmt::{self, Debug};
 
@@ -183,7 +183,7 @@ pub struct Token {
     /// The access token.
     pub access_token: String,
     /// The expiration time of the token.
-    pub expires_at: Option<DateTime>,
+    pub expires_at: Option<Timestamp>,
 }
 
 impl Debug for Token {
@@ -204,7 +204,7 @@ impl KeyTrait for Token {
         match self.expires_at {
             Some(expires_at) => {
                 // Consider token invalid if it expires within 2 minutes
-                let buffer = chrono::TimeDelta::try_seconds(2 * 60).expect("in 
bounds");
+                let buffer = jiff::SignedDuration::from_mins(2);
                 now() < expires_at - buffer
             }
             None => true, // No expiration means always valid
@@ -337,15 +337,15 @@ mod tests {
         assert!(token.is_valid());
 
         // Token with future expiration
-        token.expires_at = Some(now() + 
chrono::TimeDelta::try_hours(1).unwrap());
+        token.expires_at = Some(now() + jiff::SignedDuration::from_hours(1));
         assert!(token.is_valid());
 
         // Token that expires within 2 minutes
-        token.expires_at = Some(now() + 
chrono::TimeDelta::try_seconds(30).unwrap());
+        token.expires_at = Some(now() + jiff::SignedDuration::from_secs(30));
         assert!(!token.is_valid());
 
         // Expired token
-        token.expires_at = Some(now() - 
chrono::TimeDelta::try_hours(1).unwrap());
+        token.expires_at = Some(now() - jiff::SignedDuration::from_hours(1));
         assert!(!token.is_valid());
 
         // Empty access token
@@ -417,7 +417,7 @@ mod tests {
         // Valid token only
         let cred = Credential::with_token(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() + chrono::TimeDelta::try_hours(1).unwrap()),
+            expires_at: Some(now() + jiff::SignedDuration::from_hours(1)),
         });
         assert!(cred.is_valid());
         assert!(!cred.has_service_account());
@@ -439,7 +439,7 @@ mod tests {
         });
         cred.token = Some(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() + chrono::TimeDelta::try_hours(1).unwrap()),
+            expires_at: Some(now() + jiff::SignedDuration::from_hours(1)),
         });
         assert!(cred.is_valid());
         assert!(cred.has_service_account());
@@ -452,7 +452,7 @@ mod tests {
         });
         cred.token = Some(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() - chrono::TimeDelta::try_hours(1).unwrap()),
+            expires_at: Some(now() - jiff::SignedDuration::from_hours(1)),
         });
         assert!(cred.is_valid()); // Still valid because of service account
         assert!(!cred.has_valid_token());
diff --git a/services/google/src/provide_credential/authorized_user.rs 
b/services/google/src/provide_credential/authorized_user.rs
index 88b130a..e510e5e 100644
--- a/services/google/src/provide_credential/authorized_user.rs
+++ b/services/google/src/provide_credential/authorized_user.rs
@@ -94,9 +94,9 @@ impl ProvideCredential for AuthorizedUserCredentialProvider {
                 reqsign_core::Error::unexpected("failed to parse token 
response").with_source(e)
             })?;
 
-        let expires_at = token_resp.expires_in.map(|expires_in| {
-            now() + chrono::TimeDelta::try_seconds(expires_in as 
i64).expect("in bounds")
-        });
+        let expires_at = token_resp
+            .expires_in
+            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
 
         let token = Token {
             access_token: token_resp.access_token,
diff --git a/services/google/src/provide_credential/external_account.rs 
b/services/google/src/provide_credential/external_account.rs
index 150a920..4cd7c2a 100644
--- a/services/google/src/provide_credential/external_account.rs
+++ b/services/google/src/provide_credential/external_account.rs
@@ -21,9 +21,9 @@ use http::header::{ACCEPT, CONTENT_TYPE};
 use log::{debug, error};
 use serde::{Deserialize, Serialize};
 
-use reqsign_core::{time::now, Context, ProvideCredential, Result};
-
 use crate::credential::{external_account, Credential, ExternalAccount, Token};
+use reqsign_core::time::parse_rfc3339;
+use reqsign_core::{time::now, Context, ProvideCredential, Result};
 
 /// The maximum impersonated token lifetime allowed, 1 hour.
 const MAX_LIFETIME: Duration = Duration::from_secs(3600);
@@ -176,9 +176,9 @@ impl ExternalAccountCredentialProvider {
             reqsign_core::Error::unexpected("failed to parse STS 
response").with_source(e)
         })?;
 
-        let expires_at = token_resp.expires_in.map(|expires_in| {
-            now() + chrono::TimeDelta::try_seconds(expires_in as 
i64).expect("in bounds")
-        });
+        let expires_at = token_resp
+            .expires_in
+            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -247,9 +247,7 @@ impl ExternalAccountCredentialProvider {
             })?;
 
         // Parse expire time from RFC3339 format
-        let expires_at = 
chrono::DateTime::parse_from_rfc3339(&token_resp.expire_time)
-            .ok()
-            .map(|dt| dt.with_timezone(&chrono::Utc));
+        let expires_at = parse_rfc3339(&token_resp.expire_time).ok();
 
         Ok(Some(Token {
             access_token: token_resp.access_token,
diff --git 
a/services/google/src/provide_credential/impersonated_service_account.rs 
b/services/google/src/provide_credential/impersonated_service_account.rs
index a1e53b6..5760488 100644
--- a/services/google/src/provide_credential/impersonated_service_account.rs
+++ b/services/google/src/provide_credential/impersonated_service_account.rs
@@ -21,9 +21,9 @@ use http::header::CONTENT_TYPE;
 use log::{debug, error};
 use serde::{Deserialize, Serialize};
 
-use reqsign_core::{time::now, Context, ProvideCredential, Result};
-
 use crate::credential::{Credential, ImpersonatedServiceAccount, Token};
+use reqsign_core::time::parse_rfc3339;
+use reqsign_core::{time::now, Context, ProvideCredential, Result};
 
 /// The maximum impersonated token lifetime allowed, 1 hour.
 const MAX_LIFETIME: Duration = Duration::from_secs(3600);
@@ -135,9 +135,9 @@ impl ImpersonatedServiceAccountCredentialProvider {
                 reqsign_core::Error::unexpected("failed to parse token 
response").with_source(e)
             })?;
 
-        let expires_at = token_resp.expires_in.map(|expires_in| {
-            now() + chrono::TimeDelta::try_seconds(expires_in as 
i64).expect("in bounds")
-        });
+        let expires_at = token_resp
+            .expires_in
+            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -200,9 +200,7 @@ impl ImpersonatedServiceAccountCredentialProvider {
             })?;
 
         // Parse expire time from RFC3339 format
-        let expires_at = 
chrono::DateTime::parse_from_rfc3339(&token_resp.expire_time)
-            .ok()
-            .map(|dt| dt.with_timezone(&chrono::Utc));
+        let expires_at = parse_rfc3339(&token_resp.expire_time).ok();
 
         Ok(Token {
             access_token: token_resp.access_token,
diff --git a/services/google/src/provide_credential/vm_metadata.rs 
b/services/google/src/provide_credential/vm_metadata.rs
index bd09ccd..48b2421 100644
--- a/services/google/src/provide_credential/vm_metadata.rs
+++ b/services/google/src/provide_credential/vm_metadata.rs
@@ -106,8 +106,7 @@ impl ProvideCredential for VmMetadataCredentialProvider {
                     .with_source(e)
             })?;
 
-        let expires_at = now()
-            + chrono::TimeDelta::try_seconds(token_resp.expires_in as 
i64).expect("in bounds");
+        let expires_at = now() + 
jiff::SignedDuration::from_secs(token_resp.expires_in as i64);
 
         let token = Token {
             access_token: token_resp.access_token,
diff --git a/services/google/src/sign_request.rs 
b/services/google/src/sign_request.rs
index fa4e2f9..b2d58a4 100644
--- a/services/google/src/sign_request.rs
+++ b/services/google/src/sign_request.rs
@@ -47,7 +47,7 @@ struct Claims {
 
 impl Claims {
     fn new(client_email: &str, scope: &str) -> Self {
-        let current = now().timestamp() as u64;
+        let current = now().as_second() as u64;
 
         Claims {
             iss: client_email.to_string(),
@@ -155,9 +155,9 @@ impl RequestSigner {
             reqsign_core::Error::unexpected("failed to parse token 
response").with_source(e)
         })?;
 
-        let expires_at = token_resp.expires_in.map(|expires_in| {
-            now() + chrono::TimeDelta::try_seconds(expires_in as 
i64).expect("in bounds")
-        });
+        let expires_at = token_resp
+            .expires_in
+            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -373,7 +373,7 @@ fn canonicalize_query(
     req: &mut SigningRequest,
     method: SigningMethod,
     cred: &ServiceAccount,
-    now: DateTime,
+    now: Timestamp,
     service: &str,
     region: &str,
 ) -> Result<()> {
diff --git a/services/huaweicloud-obs/Cargo.toml 
b/services/huaweicloud-obs/Cargo.toml
index 6842eb2..3f0c464 100644
--- a/services/huaweicloud-obs/Cargo.toml
+++ b/services/huaweicloud-obs/Cargo.toml
@@ -29,8 +29,8 @@ repository.workspace = true
 [dependencies]
 anyhow.workspace = true
 async-trait.workspace = true
-chrono.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 once_cell.workspace = true
 percent-encoding.workspace = true
diff --git a/services/huaweicloud-obs/src/sign_request.rs 
b/services/huaweicloud-obs/src/sign_request.rs
index 6176956..893c0a1 100644
--- a/services/huaweicloud-obs/src/sign_request.rs
+++ b/services/huaweicloud-obs/src/sign_request.rs
@@ -34,7 +34,7 @@ use super::credential::Credential;
 use reqsign_core::hash::base64_hmac_sha1;
 use reqsign_core::time::format_http_date;
 use reqsign_core::time::now;
-use reqsign_core::time::DateTime;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{SignRequest, SigningMethod, SigningRequest};
 
 /// RequestSigner that implement Huawei Cloud Object Storage Service 
Authorization.
@@ -43,7 +43,7 @@ use reqsign_core::{SignRequest, SigningMethod, 
SigningRequest};
 #[derive(Debug)]
 pub struct RequestSigner {
     bucket: String,
-    time: Option<DateTime>,
+    time: Option<Timestamp>,
 }
 
 impl RequestSigner {
@@ -62,7 +62,7 @@ impl RequestSigner {
     /// We should always take current time to sign requests.
     /// Only use this function for testing.
     #[cfg(test)]
-    pub fn with_time(mut self, time: DateTime) -> Self {
+    pub fn with_time(mut self, time: Timestamp) -> Self {
         self.time = Some(time);
         self
     }
@@ -110,8 +110,8 @@ impl SignRequest for RequestSigner {
                 ctx.query_push("AccessKeyId", &k.access_key_id);
                 ctx.query_push(
                     "Expires",
-                    (now + chrono::TimeDelta::from_std(expire).unwrap())
-                        .timestamp()
+                    (now + jiff::SignedDuration::try_from(expire).unwrap())
+                        .as_second()
                         .to_string(),
                 );
                 ctx.query_push(
@@ -144,7 +144,7 @@ impl SignRequest for RequestSigner {
 fn string_to_sign(
     ctx: &mut SigningRequest,
     cred: &Credential,
-    now: DateTime,
+    now: Timestamp,
     method: SigningMethod,
     bucket: &str,
 ) -> Result<String> {
@@ -169,7 +169,7 @@ fn string_to_sign(
             writeln!(
                 &mut s,
                 "{}",
-                (now + 
chrono::TimeDelta::from_std(expires).unwrap()).timestamp()
+                (now + 
jiff::SignedDuration::try_from(expires).unwrap()).as_second()
             )?;
         }
     }
@@ -303,9 +303,9 @@ static SUBRESOURCES: Lazy<HashSet<&'static str>> = 
Lazy::new(|| {
 mod tests {
     use std::str::FromStr;
 
-    use chrono::Utc;
     use http::header::HeaderName;
     use http::Uri;
+    use reqsign_core::time::parse_rfc2822;
     use reqsign_core::Result;
     use reqsign_core::{Context, OsEnv, Signer};
     use reqsign_file_read_tokio::TokioFileRead;
@@ -317,11 +317,8 @@ mod tests {
     #[tokio::test]
     async fn test_sign() -> Result<()> {
         let loader = StaticCredentialProvider::new("access_key", "123456");
-        let builder = RequestSigner::new("bucket").with_time(
-            chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 
GMT")
-                .unwrap()
-                .with_timezone(&Utc),
-        );
+        let builder =
+            RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
 
         let ctx = Context::new()
             .with_file_read(TokioFileRead)
@@ -359,11 +356,8 @@ mod tests {
     #[tokio::test]
     async fn test_sign_with_subresource() -> Result<()> {
         let loader = StaticCredentialProvider::new("access_key", "123456");
-        let builder = RequestSigner::new("bucket").with_time(
-            chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 
GMT")
-                .unwrap()
-                .with_timezone(&Utc),
-        );
+        let builder =
+            RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
 
         let ctx = Context::new()
             .with_file_read(TokioFileRead)
@@ -403,11 +397,8 @@ mod tests {
     #[tokio::test]
     async fn test_sign_list_objects() -> Result<()> {
         let loader = StaticCredentialProvider::new("access_key", "123456");
-        let builder = RequestSigner::new("bucket").with_time(
-            chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 
GMT")
-                .unwrap()
-                .with_timezone(&Utc),
-        );
+        let builder =
+            RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
 
         let ctx = Context::new()
             .with_file_read(TokioFileRead)
diff --git a/services/oracle/Cargo.toml b/services/oracle/Cargo.toml
index 651bb28..8531cea 100644
--- a/services/oracle/Cargo.toml
+++ b/services/oracle/Cargo.toml
@@ -30,14 +30,12 @@ repository.workspace = true
 anyhow.workspace = true
 async-trait.workspace = true
 base64.workspace = true
-chrono.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 reqsign-core.workspace = true
 rsa.workspace = true
 rust-ini.workspace = true
-serde.workspace = true
-
 
 [dev-dependencies]
 env_logger = "0.11"
diff --git a/services/oracle/src/credential.rs 
b/services/oracle/src/credential.rs
index 64d95c6..eb9731c 100644
--- a/services/oracle/src/credential.rs
+++ b/services/oracle/src/credential.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::time::{now, DateTime};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::utils::Redact;
 use reqsign_core::SigningCredential;
 use std::fmt::{Debug, Formatter};
@@ -32,7 +32,7 @@ pub struct Credential {
     /// Fingerprint of the API Key.
     pub fingerprint: String,
     /// Expiration time for this credential.
-    pub expires_in: Option<DateTime>,
+    pub expires_in: Option<Timestamp>,
 }
 
 impl Debug for Credential {
@@ -59,7 +59,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in 
bounds"))
+            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
         {
             return valid;
         }
diff --git a/services/oracle/src/provide_credential/config.rs 
b/services/oracle/src/provide_credential/config.rs
index cec03da..71d8c5f 100644
--- a/services/oracle/src/provide_credential/config.rs
+++ b/services/oracle/src/provide_credential/config.rs
@@ -63,8 +63,7 @@ impl ProvideCredential for ConfigCredentialProvider {
                     fingerprint: fingerprint.clone(),
                     // Set expires_in to 10 minutes to enforce re-read
                     expires_in: Some(
-                        reqsign_core::time::now()
-                            + chrono::TimeDelta::try_minutes(10).expect("in 
bounds"),
+                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
                     ),
                 }))
             }
diff --git a/services/oracle/src/provide_credential/config_file.rs 
b/services/oracle/src/provide_credential/config_file.rs
index 2ea4f2f..8b7ebea 100644
--- a/services/oracle/src/provide_credential/config_file.rs
+++ b/services/oracle/src/provide_credential/config_file.rs
@@ -109,8 +109,7 @@ impl ProvideCredential for ConfigFileCredentialProvider {
                     key_file: expanded_key_file,
                     fingerprint: fingerprint.to_string(),
                     expires_in: Some(
-                        reqsign_core::time::now()
-                            + chrono::TimeDelta::try_minutes(10).expect("in 
bounds"),
+                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
                     ),
                 }))
             }
diff --git a/services/oracle/src/provide_credential/env.rs 
b/services/oracle/src/provide_credential/env.rs
index f92dbd8..582828d 100644
--- a/services/oracle/src/provide_credential/env.rs
+++ b/services/oracle/src/provide_credential/env.rs
@@ -65,8 +65,7 @@ impl ProvideCredential for EnvCredentialProvider {
                     key_file: expanded_key_file,
                     fingerprint: fingerprint.clone(),
                     expires_in: Some(
-                        reqsign_core::time::now()
-                            + chrono::TimeDelta::try_minutes(10).expect("in 
bounds"),
+                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
                     ),
                 }))
             }
diff --git a/services/tencent-cos/Cargo.toml b/services/tencent-cos/Cargo.toml
index 298fd38..f513dcc 100644
--- a/services/tencent-cos/Cargo.toml
+++ b/services/tencent-cos/Cargo.toml
@@ -30,8 +30,8 @@ repository.workspace = true
 [dependencies]
 anyhow.workspace = true
 async-trait.workspace = true
-chrono.workspace = true
 http.workspace = true
+jiff.workspace = true
 log.workspace = true
 percent-encoding.workspace = true
 reqsign-core.workspace = true
diff --git a/services/tencent-cos/src/credential.rs 
b/services/tencent-cos/src/credential.rs
index cb5a63a..cb317b0 100644
--- a/services/tencent-cos/src/credential.rs
+++ b/services/tencent-cos/src/credential.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::time::{now, DateTime};
+use reqsign_core::time::{now, Timestamp};
 use reqsign_core::utils::Redact;
 use reqsign_core::SigningCredential;
 use std::fmt::{Debug, Formatter};
@@ -30,7 +30,7 @@ pub struct Credential {
     /// Security token for temporary credentials
     pub security_token: Option<String>,
     /// Expiration time for this credential
-    pub expires_in: Option<DateTime>,
+    pub expires_in: Option<Timestamp>,
 }
 
 impl Debug for Credential {
@@ -52,7 +52,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in 
bounds"))
+            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
         {
             return valid;
         }
diff --git 
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs 
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
index c0ee0dc..fb00487 100644
--- 
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
+++ 
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
@@ -106,7 +106,7 @@ impl ProvideCredential for 
AssumeRoleWithWebIdentityCredentialProvider {
             .header(CONTENT_LENGTH, bs.len())
             .header("X-TC-Action", "AssumeRoleWithWebIdentity")
             .header("X-TC-Region", &region)
-            .header("X-TC-Timestamp", now().timestamp())
+            .header("X-TC-Timestamp", now().as_second())
             .header("X-TC-Version", "2018-08-13")
             .body(bs.into())?;
 
diff --git a/services/tencent-cos/src/sign_request.rs 
b/services/tencent-cos/src/sign_request.rs
index 89c9034..779a1c7 100644
--- a/services/tencent-cos/src/sign_request.rs
+++ b/services/tencent-cos/src/sign_request.rs
@@ -23,7 +23,7 @@ use http::request::Parts;
 use log::debug;
 use percent_encoding::{percent_decode_str, utf8_percent_encode};
 use reqsign_core::hash::{hex_hmac_sha1, hex_sha1};
-use reqsign_core::time::{format_http_date, now, DateTime};
+use reqsign_core::time::{format_http_date, now, Timestamp};
 use reqsign_core::{Context, Result, SignRequest, SigningRequest};
 use std::time::Duration;
 
@@ -32,7 +32,7 @@ use std::time::Duration;
 /// - [Tencent COS 
Signature](https://cloud.tencent.com/document/product/436/7778)
 #[derive(Debug, Default)]
 pub struct RequestSigner {
-    time: Option<DateTime>,
+    time: Option<Timestamp>,
 }
 
 impl RequestSigner {
@@ -48,7 +48,7 @@ impl RequestSigner {
     /// We should always take current time to sign requests.
     /// Only use this function for testing.
     #[cfg(test)]
-    pub fn with_time(mut self, time: DateTime) -> Self {
+    pub fn with_time(mut self, time: Timestamp) -> Self {
         self.time = Some(time);
         self
     }
@@ -116,13 +116,13 @@ impl SignRequest for RequestSigner {
 fn build_signature(
     ctx: &mut SigningRequest,
     cred: &Credential,
-    now: DateTime,
+    now: Timestamp,
     expires: Duration,
 ) -> String {
     let key_time = format!(
         "{};{}",
-        now.timestamp(),
-        (now + chrono::TimeDelta::from_std(expires).unwrap()).timestamp()
+        now.as_second(),
+        (now + jiff::SignedDuration::try_from(expires).unwrap()).as_second()
     );
 
     let sign_key = hex_hmac_sha1(cred.secret_key.as_bytes(), 
key_time.as_bytes());
diff --git a/services/tencent-cos/tests/credential_chain.rs 
b/services/tencent-cos/tests/credential_chain.rs
index e381ae2..6645714 100644
--- a/services/tencent-cos/tests/credential_chain.rs
+++ b/services/tencent-cos/tests/credential_chain.rs
@@ -199,9 +199,7 @@ impl ProvideCredential for SecurityTokenProvider {
             secret_id: "temp_id".to_string(),
             secret_key: "temp_key".to_string(),
             security_token: Some("security_token".to_string()),
-            expires_in: Some(
-                reqsign_core::time::now() + 
chrono::TimeDelta::try_hours(1).expect("in bounds"),
-            ),
+            expires_in: Some(reqsign_core::time::now() + 
jiff::SignedDuration::from_hours(1)),
         }))
     }
 }

Reply via email to