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 873fac1  chore: bump major version and encapsulate Timestamp (#643)
873fac1 is described below

commit 873fac195c1b678fa0f47f59d26f32e9015e419a
Author: tison <[email protected]>
AuthorDate: Fri Oct 3 21:40:53 2025 +0800

    chore: bump major version and encapsulate Timestamp (#643)
    
    Signed-off-by: tison <[email protected]>
---
 Cargo.toml                                         |  22 +-
 context/command-execute-tokio/Cargo.toml           |   2 +-
 context/file-read-tokio/Cargo.toml                 |   2 +-
 context/http-send-reqwest/Cargo.toml               |   2 +-
 core/Cargo.toml                                    |   2 +-
 core/src/time.rs                                   | 234 ++++++++++++++++-----
 reqsign/Cargo.toml                                 |   2 +-
 services/aliyun-oss/Cargo.toml                     |   3 +-
 services/aliyun-oss/src/credential.rs              |   5 +-
 .../provide_credential/assume_role_with_oidc.rs    |   6 +-
 services/aliyun-oss/src/sign_request.rs            |  20 +-
 services/aws-v4/Cargo.toml                         |   3 +-
 services/aws-v4/src/credential.rs                  |   5 +-
 .../aws-v4/src/provide_credential/assume_role.rs   |   3 +-
 .../assume_role_with_web_identity.rs               |   3 +-
 services/aws-v4/src/provide_credential/imds.rs     |  10 +-
 services/aws-v4/src/provide_credential/process.rs  |  14 +-
 .../src/provide_credential/s3_express_session.rs   |  10 +-
 services/aws-v4/src/provide_credential/sso.rs      |   9 +-
 services/aws-v4/src/sign_request.rs                |  41 ++--
 services/azure-storage/Cargo.toml                  |   3 +-
 services/azure-storage/src/account_sas.rs          |  15 +-
 services/azure-storage/src/credential.rs           |   5 +-
 .../src/provide_credential/azure_cli.rs            |   8 +-
 .../src/provide_credential/azure_pipelines.rs      |  12 +-
 .../src/provide_credential/client_certificate.rs   |   7 +-
 .../src/provide_credential/client_secret.rs        |   6 +-
 .../azure-storage/src/provide_credential/imds.rs   |   3 +-
 .../src/provide_credential/workload_identity.rs    |  14 +-
 services/azure-storage/src/sign_request.rs         |  16 +-
 services/google/Cargo.toml                         |   3 +-
 services/google/src/credential.rs                  |  21 +-
 .../src/provide_credential/authorized_user.rs      |  13 +-
 .../src/provide_credential/external_account.rs     |  10 +-
 .../impersonated_service_account.rs                |  10 +-
 .../google/src/provide_credential/vm_metadata.rs   |  14 +-
 services/google/src/sign_request.rs                |  16 +-
 services/huaweicloud-obs/Cargo.toml                |   3 +-
 services/huaweicloud-obs/src/sign_request.rs       |  36 ++--
 services/oracle/Cargo.toml                         |   3 +-
 services/oracle/src/credential.rs                  |   5 +-
 services/oracle/src/provide_credential/config.rs   |   6 +-
 .../oracle/src/provide_credential/config_file.rs   |   6 +-
 services/oracle/src/provide_credential/env.rs      |   6 +-
 services/oracle/src/sign_request.rs                |  15 +-
 services/tencent-cos/Cargo.toml                    |   3 +-
 services/tencent-cos/src/credential.rs             |   5 +-
 .../assume_role_with_web_identity.rs               |   6 +-
 services/tencent-cos/src/sign_request.rs           |  15 +-
 services/tencent-cos/tests/credential_chain.rs     |   4 +-
 50 files changed, 379 insertions(+), 308 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index f8f218d..df08d4f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,17 +27,17 @@ rust-version = "1.85.0"
 
 [workspace.dependencies]
 # Workspace dependencies
-reqsign-aliyun-oss = { version = "1.1.0", path = "services/aliyun-oss" }
-reqsign-aws-v4 = { version = "1.1.0", path = "services/aws-v4" }
-reqsign-azure-storage = { version = "1.1.0", path = "services/azure-storage" }
-reqsign-command-execute-tokio = { version = "1.1.0", path = 
"context/command-execute-tokio" }
-reqsign-core = { version = "1.1.0", path = "core" }
-reqsign-file-read-tokio = { version = "1.1.0", path = 
"context/file-read-tokio" }
-reqsign-google = { version = "1.1.0", path = "services/google" }
-reqsign-http-send-reqwest = { version = "1.1.0", path = 
"context/http-send-reqwest" }
-reqsign-huaweicloud-obs = { version = "1.1.0", path = 
"services/huaweicloud-obs" }
-reqsign-oracle = { version = "1.1.0", path = "services/oracle" }
-reqsign-tencent-cos = { version = "1.1.0", path = "services/tencent-cos" }
+reqsign-aliyun-oss = { version = "2.0.0", path = "services/aliyun-oss" }
+reqsign-aws-v4 = { version = "2.0.0", path = "services/aws-v4" }
+reqsign-azure-storage = { version = "2.0.0", path = "services/azure-storage" }
+reqsign-command-execute-tokio = { version = "2.0.0", path = 
"context/command-execute-tokio" }
+reqsign-core = { version = "2.0.0", path = "core" }
+reqsign-file-read-tokio = { version = "2.0.0", path = 
"context/file-read-tokio" }
+reqsign-google = { version = "2.0.0", path = "services/google" }
+reqsign-http-send-reqwest = { version = "2.0.0", path = 
"context/http-send-reqwest" }
+reqsign-huaweicloud-obs = { version = "2.0.0", path = 
"services/huaweicloud-obs" }
+reqsign-oracle = { version = "2.0.0", path = "services/oracle" }
+reqsign-tencent-cos = { version = "2.0.0", path = "services/tencent-cos" }
 
 # Crates.io dependencies
 anyhow = "1"
diff --git a/context/command-execute-tokio/Cargo.toml 
b/context/command-execute-tokio/Cargo.toml
index c4981dc..44cca44 100644
--- a/context/command-execute-tokio/Cargo.toml
+++ b/context/command-execute-tokio/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-command-execute-tokio"
-version = "1.1.0"
+version = "2.0.0"
 
 categories = ["asynchronous"]
 description = "Tokio-based command execution implementation for reqsign"
diff --git a/context/file-read-tokio/Cargo.toml 
b/context/file-read-tokio/Cargo.toml
index 417e6a0..b8599c4 100644
--- a/context/file-read-tokio/Cargo.toml
+++ b/context/file-read-tokio/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-file-read-tokio"
-version = "1.1.0"
+version = "2.0.0"
 
 categories = ["asynchronous"]
 description = "Tokio-based file reader implementation for reqsign"
diff --git a/context/http-send-reqwest/Cargo.toml 
b/context/http-send-reqwest/Cargo.toml
index 9b361b5..f215dd6 100644
--- a/context/http-send-reqwest/Cargo.toml
+++ b/context/http-send-reqwest/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-http-send-reqwest"
-version = "1.1.0"
+version = "2.0.0"
 
 categories = ["asynchronous"]
 description = "Reqwest-based HTTP client implementation for reqsign."
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 117e7e0..8f7c127 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-core"
-version = "1.1.0"
+version = "2.0.0"
 
 categories = ["command-line-utilities", "web-programming"]
 description = "Signing API requests without effort."
diff --git a/core/src/time.rs b/core/src/time.rs
index d2fd5f6..d538aa5 100644
--- a/core/src/time.rs
+++ b/core/src/time.rs
@@ -18,66 +18,196 @@
 //! Time related utils.
 
 use crate::Error;
+use std::fmt;
+use std::ops::{Add, AddAssign, Sub, SubAssign};
+use std::str::FromStr;
+use std::time::{Duration, SystemTime};
 
-/// DateTime is the alias for `jiff::Timestamp`.
-pub type Timestamp = jiff::Timestamp;
+/// An instant in time represented as the number of nanoseconds since the Unix 
epoch.
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Timestamp(jiff::Timestamp);
 
-/// Create datetime of now.
-pub fn now() -> Timestamp {
-    Timestamp::now()
+impl FromStr for Timestamp {
+    type Err = Error;
+
+    /// Parse a timestamp by the default [`DateTimeParser`].
+    ///
+    /// All of them are valid time:
+    ///
+    /// - `2022-03-13T07:20:04Z`
+    /// - `2022-03-01T08:12:34+00:00`
+    /// - `2022-03-01T08:12:34.00+00:00`
+    /// - `2022-07-08T02:14:07+02:00[Europe/Paris]`
+    ///
+    /// [`DateTimeParser`]: jiff::fmt::temporal::DateTimeParser
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.parse() {
+            Ok(t) => Ok(Timestamp(t)),
+            Err(err) => Err(
+                Error::unexpected(format!("parse '{s}' into timestamp 
failed")).with_source(err),
+            ),
+        }
+    }
 }
 
-/// Format time into date: `20220301`
-pub fn format_date(t: Timestamp) -> String {
-    t.strftime("%Y%m%d").to_string()
+impl fmt::Display for Timestamp {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
 }
 
-/// Format time into ISO8601: `20220313T072004Z`
-pub fn format_iso8601(t: Timestamp) -> String {
-    t.strftime("%Y%m%dT%H%M%SZ").to_string()
+impl Timestamp {
+    /// Create the timestamp of now.
+    pub fn now() -> Self {
+        Self(jiff::Timestamp::now())
+    }
+
+    /// Format the timestamp into date: `20220301`
+    pub fn format_date(self) -> String {
+        self.0.strftime("%Y%m%d").to_string()
+    }
+
+    /// Format the timestamp into ISO8601: `20220313T072004Z`
+    pub fn format_iso8601(self) -> String {
+        self.0.strftime("%Y%m%dT%H%M%SZ").to_string()
+    }
+
+    /// Format the timestamp into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
+    ///
+    /// ## Note
+    ///
+    /// HTTP date is slightly different from RFC2822.
+    ///
+    /// - Timezone is fixed to GMT.
+    /// - Day must be 2 digit.
+    pub fn format_http_date(self) -> String {
+        self.0.strftime("%a, %d %b %Y %T GMT").to_string()
+    }
+
+    /// Format the timestamp into RFC3339 in Zulu: `2022-03-13T07:20:04Z`
+    pub fn format_rfc3339_zulu(self) -> String {
+        self.0.strftime("%FT%TZ").to_string()
+    }
+
+    /// Returns this timestamp as a number of seconds since the Unix epoch.
+    ///
+    /// This only returns the number of whole seconds. That is, if there are
+    /// any fractional seconds in this timestamp, then they are truncated.
+    pub fn as_second(self) -> i64 {
+        self.0.as_second()
+    }
+
+    /// Returns the fractional second component of this timestamp in units of
+    /// nanoseconds.
+    ///
+    /// It is guaranteed that this will never return a value that is greater
+    /// than 1 second (or less than -1 second).
+    pub fn subsec_nanosecond(self) -> i32 {
+        self.0.subsec_nanosecond()
+    }
+
+    /// Convert to `SystemTime`.
+    pub fn as_system_time(self) -> SystemTime {
+        SystemTime::from(self.0)
+    }
+
+    /// Creates a new instant in time from the number of milliseconds elapsed
+    /// since the Unix epoch.
+    ///
+    /// When `millisecond` is negative, it corresponds to an instant in time
+    /// before the Unix epoch. A smaller number corresponds to an instant in
+    /// time further into the past.
+    pub fn from_millisecond(millis: i64) -> crate::Result<Self> {
+        match jiff::Timestamp::from_millisecond(millis) {
+            Ok(t) => Ok(Timestamp(t)),
+            Err(err) => Err(Error::unexpected(format!(
+                "convert '{millis}' milliseconds into timestamp failed"
+            ))
+            .with_source(err)),
+        }
+    }
+
+    /// Creates a new instant in time from the number of seconds elapsed since
+    /// the Unix epoch.
+    ///
+    /// When `second` is negative, it corresponds to an instant in time before
+    /// the Unix epoch. A smaller number corresponds to an instant in time
+    /// further into the past.
+    pub fn from_second(second: i64) -> crate::Result<Self> {
+        match jiff::Timestamp::from_second(second) {
+            Ok(t) => Ok(Timestamp(t)),
+            Err(err) => Err(Error::unexpected(format!(
+                "convert '{second}' seconds into timestamp failed"
+            ))
+            .with_source(err)),
+        }
+    }
+
+    /// Parse a timestamp 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> {
+        match jiff::fmt::rfc2822::parse(s) {
+            Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
+            Err(err) => {
+                Err(Error::unexpected(format!("parse '{s}' into rfc2822 
failed")).with_source(err))
+            }
+        }
+    }
+
+    /// Parse the string format "2023-10-31 21:59:10.000000".
+    pub fn parse_datetime_utc(s: &str) -> crate::Result<Timestamp> {
+        let dt = s.parse::<jiff::civil::DateTime>().map_err(|err| {
+            Error::unexpected(format!("parse '{s}' into datetime 
failed")).with_source(err)
+        })?;
+
+        let ts = jiff::tz::TimeZone::UTC.to_timestamp(dt).map_err(|err| {
+            Error::unexpected(format!("convert '{s}' into timestamp 
failed")).with_source(err)
+        })?;
+
+        Ok(Timestamp(ts))
+    }
 }
 
-/// Format time into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
-///
-/// ## Note
-///
-/// HTTP date is slightly different from RFC2822.
-///
-/// - Timezone is fixed to GMT.
-/// - Day must be 2 digit.
-pub fn format_http_date(t: Timestamp) -> String {
-    t.strftime("%a, %d %b %Y %T GMT").to_string()
+impl Add<Duration> for Timestamp {
+    type Output = Timestamp;
+
+    fn add(self, rhs: Duration) -> Timestamp {
+        let ts = self
+            .0
+            .checked_add(rhs)
+            .expect("adding unsigned duration to timestamp overflowed");
+
+        Timestamp(ts)
+    }
 }
 
-/// Format time into RFC3339: `2022-03-13T07:20:04Z`
-pub fn format_rfc3339(t: Timestamp) -> String {
-    t.strftime("%FT%TZ").to_string()
+impl AddAssign<Duration> for Timestamp {
+    fn add_assign(&mut self, rhs: Duration) {
+        *self = *self + rhs
+    }
 }
 
-/// Parse time from RFC3339.
-///
-/// All of them are valid time:
-///
-/// - `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<Timestamp> {
-    s.parse().map_err(|err| {
-        Error::unexpected(format!("parse '{s}' into rfc3339 
failed")).with_source(err)
-    })
+impl Sub<Duration> for Timestamp {
+    type Output = Timestamp;
+
+    fn sub(self, rhs: Duration) -> Timestamp {
+        let ts = self
+            .0
+            .checked_sub(rhs)
+            .expect("subtracting unsigned duration from timestamp overflowed");
+
+        Timestamp(ts)
+    }
 }
 
-/// 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())
+impl SubAssign<Duration> for Timestamp {
+    fn sub_assign(&mut self, rhs: Duration) {
+        *self = *self - rhs
+    }
 }
 
 #[cfg(test)]
@@ -85,31 +215,31 @@ mod tests {
     use super::*;
 
     fn test_time() -> Timestamp {
-        "2022-03-01T08:12:34Z".parse().unwrap()
+        Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
     }
 
     #[test]
     fn test_format_date() {
         let t = test_time();
-        assert_eq!("20220301", format_date(t))
+        assert_eq!("20220301", t.format_date())
     }
 
     #[test]
     fn test_format_ios8601() {
         let t = test_time();
-        assert_eq!("20220301T081234Z", format_iso8601(t))
+        assert_eq!("20220301T081234Z", t.format_iso8601())
     }
 
     #[test]
     fn test_format_http_date() {
         let t = test_time();
-        assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", format_http_date(t))
+        assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
     }
 
     #[test]
     fn test_format_rfc3339() {
         let t = test_time();
-        assert_eq!("2022-03-01T08:12:34Z", format_rfc3339(t))
+        assert_eq!("2022-03-01T08:12:34Z", t.format_rfc3339_zulu())
     }
 
     #[test]
@@ -121,7 +251,7 @@ mod tests {
             "2022-03-01T08:12:34+00:00",
             "2022-03-01T08:12:34.00+00:00",
         ] {
-            assert_eq!(t, parse_rfc3339(v).expect("must be valid time"));
+            assert_eq!(t, v.parse().expect("must be valid time"));
         }
     }
 }
diff --git a/reqsign/Cargo.toml b/reqsign/Cargo.toml
index 34c1fba..0816666 100644
--- a/reqsign/Cargo.toml
+++ b/reqsign/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign"
-version = "0.17.1"
+version = "0.18.0"
 
 categories = ["authentication", "web-programming::http-client"]
 description = "Signing HTTP requests for AWS, Azure, Google, Huawei, Aliyun, 
Tencent and Oracle services"
diff --git a/services/aliyun-oss/Cargo.toml b/services/aliyun-oss/Cargo.toml
index fbbc124..09e5b20 100644
--- a/services/aliyun-oss/Cargo.toml
+++ b/services/aliyun-oss/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-aliyun-oss"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Aliyun OSS signing implementation for reqsign."
 
@@ -30,7 +30,6 @@ rust-version.workspace = true
 anyhow = { workspace = true }
 async-trait = { workspace = true }
 http = { workspace = true }
-jiff = { workspace = true }
 log = { workspace = true }
 percent-encoding = { workspace = true }
 reqsign-core = { workspace = true }
diff --git a/services/aliyun-oss/src/credential.rs 
b/services/aliyun-oss/src/credential.rs
index 2c66229..495ce51 100644
--- a/services/aliyun-oss/src/credential.rs
+++ b/services/aliyun-oss/src/credential.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_core::SigningCredential;
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::utils::Redact;
 use std::fmt::{Debug, Formatter};
+use std::time::Duration;
 
 /// Credential that holds the access_key and secret_key.
 #[derive(Default, Clone)]
@@ -54,7 +55,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
+            .map(|v| v > Timestamp::now() + Duration::from_secs(120))
         {
             return valid;
         }
diff --git 
a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs 
b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
index e928d6d..cedb3a3 100644
--- a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
+++ b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
@@ -18,7 +18,7 @@
 use crate::{Credential, constants::*};
 use async_trait::async_trait;
 use reqsign_core::Result;
-use reqsign_core::time::{format_rfc3339, now, parse_rfc3339};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential};
 use serde::Deserialize;
 
@@ -87,7 +87,7 @@ impl ProvideCredential for 
AssumeRoleWithOidcCredentialProvider {
             provider_arn,
             role_arn,
             role_session_name,
-            format_rfc3339(now()),
+            Timestamp::now().format_rfc3339_zulu(),
             token
         );
 
@@ -119,7 +119,7 @@ impl ProvideCredential for 
AssumeRoleWithOidcCredentialProvider {
             access_key_id: resp_cred.access_key_id,
             access_key_secret: resp_cred.access_key_secret,
             security_token: Some(resp_cred.security_token),
-            expires_in: Some(parse_rfc3339(&resp_cred.expiration)?),
+            expires_in: Some(resp_cred.expiration.parse()?),
         };
 
         Ok(Some(cred))
diff --git a/services/aliyun-oss/src/sign_request.rs 
b/services/aliyun-oss/src/sign_request.rs
index e159439..cdc4802 100644
--- a/services/aliyun-oss/src/sign_request.rs
+++ b/services/aliyun-oss/src/sign_request.rs
@@ -22,7 +22,7 @@ use http::header::{AUTHORIZATION, CONTENT_TYPE, DATE};
 use percent_encoding::utf8_percent_encode;
 use reqsign_core::Result;
 use reqsign_core::hash::base64_hmac_sha1;
-use reqsign_core::time::{Timestamp, format_http_date, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, SignRequest};
 use std::collections::HashSet;
 use std::fmt::Write;
@@ -60,7 +60,7 @@ impl RequestSigner {
     }
 
     fn get_time(&self) -> Timestamp {
-        self.time.unwrap_or_else(now)
+        self.time.unwrap_or_else(Timestamp::now)
     }
 }
 
@@ -105,7 +105,7 @@ impl RequestSigner {
 
         // Add date header
         req.headers
-            .insert(DATE, format_http_date(signing_time).parse()?);
+            .insert(DATE, Timestamp::format_http_date(signing_time).parse()?);
 
         // Add security token if present
         if let Some(token) = &cred.security_token {
@@ -128,10 +128,7 @@ impl RequestSigner {
         signing_time: Timestamp,
         expires: Duration,
     ) -> Result<()> {
-        let expiration_time = signing_time
-            + jiff::SignedDuration::try_from(expires).map_err(|e| {
-                reqsign_core::Error::request_invalid(format!("Invalid 
expiration duration: {e}"))
-            })?;
+        let expiration_time = signing_time + expires;
         let string_to_sign = self.build_string_to_sign(req, cred, 
signing_time, Some(expires))?;
         let signature =
             base64_hmac_sha1(cred.access_key_secret.as_bytes(), 
string_to_sign.as_bytes());
@@ -228,16 +225,11 @@ impl RequestSigner {
         // Date or Expires
         match expires {
             Some(expires_duration) => {
-                let expiration_time = signing_time
-                    + 
jiff::SignedDuration::try_from(expires_duration).map_err(|e| {
-                        reqsign_core::Error::request_invalid(format!(
-                            "Invalid expiration duration: {e}"
-                        ))
-                    })?;
+                let expiration_time = signing_time + expires_duration;
                 writeln!(&mut s, "{}", expiration_time.as_second())?;
             }
             None => {
-                writeln!(&mut s, "{}", format_http_date(signing_time))?;
+                writeln!(&mut s, "{}", signing_time.format_http_date())?;
             }
         }
 
diff --git a/services/aws-v4/Cargo.toml b/services/aws-v4/Cargo.toml
index 84b1af9..e576e33 100644
--- a/services/aws-v4/Cargo.toml
+++ b/services/aws-v4/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-aws-v4"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "AWS SigV4 signing implementation for reqsign."
 
@@ -36,7 +36,6 @@ async-trait = { workspace = true }
 bytes = "1.7.2"
 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 edb3806..b2468ea 100644
--- a/services/aws-v4/src/credential.rs
+++ b/services/aws-v4/src/credential.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_core::SigningCredential;
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::utils::Redact;
 use std::fmt::{Debug, Formatter};
+use std::time::Duration;
 
 /// Credential that holds the access_key and secret_key.
 #[derive(Default, Clone)]
@@ -54,7 +55,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
+            .map(|v| v > Timestamp::now() + Duration::from_secs(120))
         {
             return valid;
         }
diff --git a/services/aws-v4/src/provide_credential/assume_role.rs 
b/services/aws-v4/src/provide_credential/assume_role.rs
index a738a9b..6b6a616 100644
--- a/services/aws-v4/src/provide_credential/assume_role.rs
+++ b/services/aws-v4/src/provide_credential/assume_role.rs
@@ -22,7 +22,6 @@ use crate::provide_credential::utils::{parse_sts_error, 
sts_endpoint};
 use async_trait::async_trait;
 use bytes::Bytes;
 use quick_xml::de;
-use reqsign_core::time::parse_rfc3339;
 use reqsign_core::{Context, Error, ProvideCredential, Result, Signer};
 use serde::Deserialize;
 use std::fmt::Write;
@@ -238,7 +237,7 @@ impl ProvideCredential for AssumeRoleCredentialProvider {
             access_key_id: resp_cred.access_key_id,
             secret_access_key: resp_cred.secret_access_key,
             session_token: Some(resp_cred.session_token),
-            expires_in: Some(parse_rfc3339(&resp_cred.expiration).map_err(|e| {
+            expires_in: Some(resp_cred.expiration.parse().map_err(|e| {
                 Error::unexpected("failed to parse AssumeRole credential 
expiration")
                     .with_source(e)
                     .with_context(format!("expiration_value: {}", 
resp_cred.expiration))
diff --git 
a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs 
b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
index 87c9933..196892b 100644
--- a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
+++ b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
@@ -20,7 +20,6 @@ use crate::provide_credential::utils::{parse_sts_error, 
sts_endpoint};
 use async_trait::async_trait;
 use bytes::Bytes;
 use quick_xml::de;
-use reqsign_core::time::parse_rfc3339;
 use reqsign_core::{Context, Error, ProvideCredential, Result, utils::Redact};
 use serde::Deserialize;
 use std::fmt::{Debug, Formatter};
@@ -211,7 +210,7 @@ impl ProvideCredential for 
AssumeRoleWithWebIdentityCredentialProvider {
             access_key_id: resp_cred.access_key_id,
             secret_access_key: resp_cred.secret_access_key,
             session_token: Some(resp_cred.session_token),
-            expires_in: Some(parse_rfc3339(&resp_cred.expiration).map_err(|e| {
+            expires_in: Some(resp_cred.expiration.parse().map_err(|e| {
                 Error::unexpected("failed to parse web identity credential 
expiration")
                     .with_source(e)
                     .with_context(format!("expiration_value: {}", 
resp_cred.expiration))
diff --git a/services/aws-v4/src/provide_credential/imds.rs 
b/services/aws-v4/src/provide_credential/imds.rs
index 01eedc9..1ec8bd2 100644
--- a/services/aws-v4/src/provide_credential/imds.rs
+++ b/services/aws-v4/src/provide_credential/imds.rs
@@ -21,10 +21,11 @@ use async_trait::async_trait;
 use bytes::Bytes;
 use http::Method;
 use http::header::CONTENT_LENGTH;
-use reqsign_core::time::{Timestamp, now, parse_rfc3339};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 use std::sync::{Arc, Mutex};
+use std::time::Duration;
 
 #[derive(Debug, Clone)]
 pub struct IMDSv2CredentialProvider {
@@ -68,7 +69,7 @@ impl IMDSv2CredentialProvider {
     async fn load_ec2_metadata_token(&self, ctx: &Context) -> Result<String> {
         {
             let (token, expires_in) = self.token.lock().expect("lock 
poisoned").clone();
-            if expires_in > now() {
+            if expires_in > Timestamp::now() {
                 return Ok(token);
             }
         }
@@ -105,8 +106,7 @@ impl IMDSv2CredentialProvider {
         }
         let ec2_token = resp.into_body();
         // Set expires_in to 10 minutes to enforce re-read.
-        let expires_in =
-            now() + jiff::SignedDuration::from_secs(21600) - 
jiff::SignedDuration::from_secs(600);
+        let expires_in = Timestamp::now() + Duration::from_secs(21600) - 
Duration::from_secs(600);
 
         {
             *self.token.lock().expect("lock poisoned") = (ec2_token.clone(), 
expires_in);
@@ -244,7 +244,7 @@ impl ProvideCredential for IMDSv2CredentialProvider {
             access_key_id: resp.access_key_id,
             secret_access_key: resp.secret_access_key,
             session_token: Some(resp.token),
-            expires_in: Some(parse_rfc3339(&resp.expiration).map_err(|e| {
+            expires_in: Some(resp.expiration.parse().map_err(|e| {
                 Error::unexpected("failed to parse IMDS credential expiration 
time")
                     .with_source(e)
                     .with_context(format!("expiration_value: {}", 
resp.expiration))
diff --git a/services/aws-v4/src/provide_credential/process.rs 
b/services/aws-v4/src/provide_credential/process.rs
index 8782199..d3383d1 100644
--- a/services/aws-v4/src/provide_credential/process.rs
+++ b/services/aws-v4/src/provide_credential/process.rs
@@ -19,7 +19,6 @@ use crate::Credential;
 use async_trait::async_trait;
 use ini::Ini;
 use log::debug;
-use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 
@@ -210,16 +209,9 @@ impl ProvideCredential for ProcessCredentialProvider {
         };
 
         let output = self.execute_process(ctx, &command).await?;
-
-        let expires_in =
-            if let Some(exp_str) = &output.expiration {
-                Some(exp_str.parse::<Timestamp>().map_err(|e| {
-                    Error::unexpected(format!("failed to parse expiration 
time: {e}"))
-                })?)
-            } else {
-                None
-            };
-
+        let expires_in = output
+            .expiration
+            .and_then(|expires_in| expires_in.parse().ok());
         Ok(Some(Credential {
             access_key_id: output.access_key_id,
             secret_access_key: output.secret_access_key,
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 c1c5e79..31706f2 100644
--- a/services/aws-v4/src/provide_credential/s3_express_session.rs
+++ b/services/aws-v4/src/provide_credential/s3_express_session.rs
@@ -20,7 +20,6 @@ use async_trait::async_trait;
 use bytes::Bytes;
 use http::{Method, Request, header};
 use log::debug;
-use reqsign_core::time::parse_rfc3339;
 use reqsign_core::{Context, Error, ProvideCredential, Result, SignRequest};
 use serde::Deserialize;
 
@@ -160,10 +159,13 @@ impl S3ExpressSessionProvider {
             })?;
 
         // Parse expiration time from ISO8601 format
-        let expiration =
-            
parse_rfc3339(&create_session_resp.credentials.expiration).map_err(|e| {
+        let expiration = create_session_resp
+            .credentials
+            .expiration
+            .parse()
+            .map_err(|e| {
                 Error::unexpected(format!(
-                    "Failed to parse expiration time '{}': {e}",
+                    "failed to parse expiration time '{}': {e}",
                     create_session_resp.credentials.expiration
                 ))
             })?;
diff --git a/services/aws-v4/src/provide_credential/sso.rs 
b/services/aws-v4/src/provide_credential/sso.rs
index 04f9ece..3237f2c 100644
--- a/services/aws-v4/src/provide_credential/sso.rs
+++ b/services/aws-v4/src/provide_credential/sso.rs
@@ -20,7 +20,7 @@ use async_trait::async_trait;
 use http::{Method, Request, StatusCode};
 use ini::Ini;
 use log::{debug, warn};
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Error, ProvideCredential, Result};
 use serde::Deserialize;
 
@@ -218,11 +218,8 @@ impl SSOCredentialProvider {
                 })?;
 
                 // Check if token is expired
-                let expires_at = 
token.expires_at.parse::<Timestamp>().map_err(|e| {
-                    Error::unexpected(format!("failed to parse expiration 
time: {e}"))
-                })?;
-
-                if expires_at <= now() {
+                let expires_at = token.expires_at.parse::<Timestamp>()?;
+                if expires_at <= Timestamp::now() {
                     warn!("SSO token is expired");
                     return Ok(None);
                 }
diff --git a/services/aws-v4/src/sign_request.rs 
b/services/aws-v4/src/sign_request.rs
index 8b9e734..10c583f 100644
--- a/services/aws-v4/src/sign_request.rs
+++ b/services/aws-v4/src/sign_request.rs
@@ -26,7 +26,7 @@ use http::{HeaderValue, header};
 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::{Timestamp, format_date, format_iso8601, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Result, SignRequest, SigningRequest};
 use std::fmt::Write;
 use std::time::Duration;
@@ -77,7 +77,7 @@ impl SignRequest for RequestSigner {
         credential: Option<&Self::Credential>,
         expires_in: Option<Duration>,
     ) -> Result<()> {
-        let now = self.time.unwrap_or_else(now);
+        let now = self.time.unwrap_or_else(Timestamp::now);
         let mut signed_req = SigningRequest::build(req)?;
 
         let Some(cred) = credential else {
@@ -102,7 +102,7 @@ impl SignRequest for RequestSigner {
         // Scope: "20220313/<region>/<service>/aws4_request"
         let scope = format!(
             "{}/{}/{}/aws4_request",
-            format_date(now),
+            now.format_date(),
             self.region,
             self.service
         );
@@ -119,7 +119,7 @@ impl SignRequest for RequestSigner {
             writeln!(f, "AWS4-HMAC-SHA256").map_err(|e| {
                 reqsign_core::Error::unexpected(format!("failed to write 
algorithm: {e}"))
             })?;
-            writeln!(f, "{}", format_iso8601(now)).map_err(|e| {
+            writeln!(f, "{}", now.format_iso8601()).map_err(|e| {
                 reqsign_core::Error::unexpected(format!("failed to write 
timestamp: {e}"))
             })?;
             writeln!(f, "{}", &scope).map_err(|e| {
@@ -255,7 +255,7 @@ fn canonicalize_header(
     if expires_in.is_none() {
         // Insert DATE header if not present.
         if ctx.headers.get(X_AMZ_DATE).is_none() {
-            let date_header = 
HeaderValue::try_from(format_iso8601(now)).map_err(|e| {
+            let date_header = 
HeaderValue::try_from(now.format_iso8601()).map_err(|e| {
                 reqsign_core::Error::unexpected(format!("failed to create date 
header: {e}"))
             })?;
             ctx.headers.insert(X_AMZ_DATE, date_header);
@@ -310,12 +310,12 @@ fn canonicalize_query(
             format!(
                 "{}/{}/{}/{}/aws4_request",
                 cred.access_key_id,
-                format_date(now),
+                now.format_date(),
                 region,
                 service
             ),
         ));
-        ctx.query.push(("X-Amz-Date".into(), format_iso8601(now)));
+        ctx.query.push(("X-Amz-Date".into(), now.format_iso8601()));
         ctx.query
             .push(("X-Amz-Expires".into(), expire.as_secs().to_string()));
         ctx.query.push((
@@ -355,20 +355,17 @@ fn generate_signing_key(secret: &str, time: Timestamp, 
region: &str, service: &s
     // Sign secret
     let secret = format!("AWS4{secret}");
     // Sign date
-    let sign_date = hmac_sha256(secret.as_bytes(), 
format_date(time).as_bytes());
+    let sign_date = hmac_sha256(secret.as_bytes(), 
time.format_date().as_bytes());
     // Sign region
     let sign_region = hmac_sha256(sign_date.as_slice(), region.as_bytes());
     // Sign service
     let sign_service = hmac_sha256(sign_region.as_slice(), service.as_bytes());
     // Sign request
-
     hmac_sha256(sign_service.as_slice(), "aws4_request".as_bytes())
 }
 
 #[cfg(test)]
 mod tests {
-    use std::time::SystemTime;
-
     use super::*;
     use crate::provide_credential::StaticCredentialProvider;
     use anyhow::Result;
@@ -487,7 +484,7 @@ mod tests {
             .expect("url must be valid");
 
         req.headers_mut().insert(
-            http::header::CONTENT_LENGTH,
+            header::CONTENT_LENGTH,
             HeaderValue::from_str(&content.len().to_string()).expect("must be 
valid"),
         );
 
@@ -599,7 +596,7 @@ mod tests {
             req.uri().path(),
             req.uri().query(),
         );
-        let now = now();
+        let now = Timestamp::now();
 
         let mut ss = SigningSettings::default();
         ss.percent_encoding_mode = PercentEncodingMode::Double;
@@ -616,7 +613,7 @@ mod tests {
             .identity(&id)
             .region("test")
             .name("s3")
-            .time(SystemTime::from(now))
+            .time(now.as_system_time())
             .settings(ss)
             .build()
             .expect("signing params must be valid");
@@ -674,13 +671,13 @@ mod tests {
             req.uri().path(),
             req.uri().query(),
         );
-        let now = now();
+        let now = Timestamp::now();
 
         let mut ss = SigningSettings::default();
         ss.percent_encoding_mode = PercentEncodingMode::Double;
         ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
         ss.signature_location = SignatureLocation::QueryParams;
-        ss.expires_in = Some(std::time::Duration::from_secs(3600));
+        ss.expires_in = Some(Duration::from_secs(3600));
         let id = Credentials::new(
             "access_key_id",
             "secret_access_key",
@@ -693,7 +690,7 @@ mod tests {
             .identity(&id)
             .region("test")
             .name("s3")
-            .time(SystemTime::from(now))
+            .time(now.as_system_time())
             .settings(ss)
             .build()
             .expect("signing params must be valid");
@@ -756,7 +753,7 @@ mod tests {
             req.uri().path(),
             req.uri().query(),
         );
-        let now = now();
+        let now = Timestamp::now();
 
         let mut ss = SigningSettings::default();
         ss.percent_encoding_mode = PercentEncodingMode::Double;
@@ -773,7 +770,7 @@ mod tests {
             .identity(&id)
             .region("test")
             .name("s3")
-            .time(SystemTime::from(now))
+            .time(now.as_system_time())
             .settings(ss)
             .build()
             .expect("signing params must be valid");
@@ -834,13 +831,13 @@ mod tests {
             req.uri().path(),
             req.uri().query(),
         );
-        let now = now();
+        let now = Timestamp::now();
 
         let mut ss = SigningSettings::default();
         ss.percent_encoding_mode = PercentEncodingMode::Double;
         ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
         ss.signature_location = SignatureLocation::QueryParams;
-        ss.expires_in = Some(std::time::Duration::from_secs(3600));
+        ss.expires_in = Some(Duration::from_secs(3600));
         let id = Credentials::new(
             "access_key_id",
             "secret_access_key",
@@ -854,7 +851,7 @@ mod tests {
             .region("test")
             // .security_token("security_token")
             .name("s3")
-            .time(SystemTime::from(now))
+            .time(now.as_system_time())
             .settings(ss)
             .build()
             .expect("signing params must be valid");
diff --git a/services/azure-storage/Cargo.toml 
b/services/azure-storage/Cargo.toml
index 210807e..edde2b3 100644
--- a/services/azure-storage/Cargo.toml
+++ b/services/azure-storage/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-azure-storage"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Azure Storage signing implementation for reqsign."
 
@@ -33,7 +33,6 @@ base64 = { workspace = true }
 bytes = { 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 e7c79fc..916c336 100644
--- a/services/azure-storage/src/account_sas.rs
+++ b/services/azure-storage/src/account_sas.rs
@@ -18,7 +18,6 @@
 use reqsign_core::Result;
 
 use reqsign_core::hash;
-use reqsign_core::time;
 use reqsign_core::time::Timestamp;
 
 /// The default parameters that make up a SAS token
@@ -68,8 +67,8 @@ impl AccountSharedAccessSignature {
             self.resource_type,
             self.start
                 .as_ref()
-                .map_or("".to_string(), |v| 
urlencoded(time::format_rfc3339(*v))),
-            time::format_rfc3339(self.expiry),
+                .map_or("".to_string(), |v| 
urlencoded(v.format_rfc3339_zulu())),
+            self.expiry.format_rfc3339_zulu(),
             self.ip.clone().unwrap_or_default(),
             self.protocol
                 .as_ref()
@@ -93,13 +92,13 @@ impl AccountSharedAccessSignature {
             ("srt".to_string(), self.resource_type.to_string()),
             (
                 "se".to_string(),
-                urlencoded(time::format_rfc3339(self.expiry)),
+                urlencoded(self.expiry.format_rfc3339_zulu()),
             ),
             ("sp".to_string(), self.permissions.to_string()),
         ];
 
         if let Some(start) = &self.start {
-            elements.push(("st".to_string(), 
urlencoded(time::format_rfc3339(*start))))
+            elements.push(("st".to_string(), 
urlencoded(start.format_rfc3339_zulu())))
         }
         if let Some(ip) = &self.ip {
             elements.push(("sip".to_string(), ip.to_string()))
@@ -121,9 +120,9 @@ fn urlencoded(s: String) -> String {
 
 #[cfg(test)]
 mod tests {
-    use std::str::FromStr;
-
     use super::*;
+    use std::str::FromStr;
+    use std::time::Duration;
 
     fn test_time() -> Timestamp {
         Timestamp::from_str("2022-03-01T08:12:34Z").unwrap()
@@ -132,7 +131,7 @@ mod tests {
     #[test]
     fn test_can_generate_sas_token() {
         let key = hash::base64_encode("key".as_bytes());
-        let expiry = test_time() + jiff::SignedDuration::from_mins(5);
+        let expiry = test_time() + Duration::from_secs(300);
         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 faaa4c4..ae64b13 100644
--- a/services/azure-storage/src/credential.rs
+++ b/services/azure-storage/src/credential.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_core::SigningCredential;
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::utils::Redact;
 use std::fmt::{Debug, Formatter};
+use std::time::Duration;
 
 /// Credential enum for different Azure Storage authentication methods.
 #[derive(Clone)]
@@ -82,7 +83,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() + jiff::SignedDuration::from_secs(20)
+                    *expires > Timestamp::now() + Duration::from_secs(20)
                 } else {
                     true
                 }
diff --git a/services/azure-storage/src/provide_credential/azure_cli.rs 
b/services/azure-storage/src/provide_credential/azure_cli.rs
index 69f112a..abe7d84 100644
--- a/services/azure-storage/src/provide_credential/azure_cli.rs
+++ b/services/azure-storage/src/provide_credential/azure_cli.rs
@@ -96,13 +96,9 @@ impl ProvideCredential for AzureCliCredentialProvider {
 
         // Calculate expiration time
         let expires_on = if let Some(timestamp) = token.expires_on_timestamp {
-            Some(Timestamp::from_second(timestamp).unwrap())
+            Timestamp::from_second(timestamp).ok()
         } else if let Some(expires_str) = token.expires_on {
-            // Parse the string format "2023-10-31 21:59:10.000000"
-            expires_str
-                .parse::<jiff::civil::DateTime>()
-                .and_then(|dt| jiff::tz::TimeZone::UTC.to_timestamp(dt))
-                .ok()
+            Timestamp::parse_datetime_utc(&expires_str).ok()
         } 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 a1849d0..fef7912 100644
--- a/services/azure-storage/src/provide_credential/azure_pipelines.rs
+++ b/services/azure-storage/src/provide_credential/azure_pipelines.rs
@@ -15,13 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::collections::HashMap;
-
 use crate::credential::Credential;
 use async_trait::async_trait;
-use reqsign_core::time::now;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential};
 use serde::Deserialize;
+use std::collections::HashMap;
+use std::time::Duration;
 
 /// AzurePipelinesCredentialProvider provides credentials using Azure 
Pipelines workload identity
 ///
@@ -233,12 +233,12 @@ impl ProvideCredential for 
AzurePipelinesCredentialProvider {
             .await?;
 
         // Calculate expiration time
-        let expires_in = 
std::time::Duration::from_secs(token_response.expires_in);
-        let expires_on = now().checked_add(expires_in).ok();
+        let expires_in = Duration::from_secs(token_response.expires_in);
+        let expires_on = Timestamp::now() + expires_in;
 
         Ok(Some(Credential::with_bearer_token(
             &token_response.access_token,
-            expires_on,
+            Some(expires_on),
         )))
     }
 }
diff --git 
a/services/azure-storage/src/provide_credential/client_certificate.rs 
b/services/azure-storage/src/provide_credential/client_certificate.rs
index 97bff76..3bbb4f1 100644
--- a/services/azure-storage/src/provide_credential/client_certificate.rs
+++ b/services/azure-storage/src/provide_credential/client_certificate.rs
@@ -23,7 +23,7 @@ use async_trait::async_trait;
 use base64::Engine;
 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
 use jsonwebtoken::{Algorithm, EncodingKey, Header};
-use reqsign_core::time::now;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential};
 use rsa::RsaPrivateKey;
 use rsa::pkcs8::DecodePrivateKey;
@@ -315,12 +315,11 @@ impl ProvideCredential for 
ClientCertificateCredentialProvider {
             .await?;
 
         // Calculate expiration time
-        let expires_in = Duration::from_secs(token_response.expires_in);
-        let expires_on = now().checked_add(expires_in).ok();
+        let expires_on = Timestamp::now() + 
Duration::from_secs(token_response.expires_in);
 
         Ok(Some(Credential::with_bearer_token(
             &token_response.access_token,
-            expires_on,
+            Some(expires_on),
         )))
     }
 }
diff --git a/services/azure-storage/src/provide_credential/client_secret.rs 
b/services/azure-storage/src/provide_credential/client_secret.rs
index bde45cd..b46922d 100644
--- a/services/azure-storage/src/provide_credential/client_secret.rs
+++ b/services/azure-storage/src/provide_credential/client_secret.rs
@@ -17,7 +17,9 @@
 
 use crate::Credential;
 use async_trait::async_trait;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
+use std::time::Duration;
 
 /// Load credential from Azure Client Secret.
 ///
@@ -90,9 +92,7 @@ impl ProvideCredential for ClientSecretCredentialProvider {
 
         match token {
             Some(token_response) => {
-                let expires_on = reqsign_core::time::now()
-                    + 
jiff::SignedDuration::from_secs(token_response.expires_in as i64);
-
+                let expires_on = Timestamp::now() + 
Duration::from_secs(token_response.expires_in);
                 Ok(Some(Credential::with_bearer_token(
                     &token_response.access_token,
                     Some(expires_on),
diff --git a/services/azure-storage/src/provide_credential/imds.rs 
b/services/azure-storage/src/provide_credential/imds.rs
index 963b34f..5c24959 100644
--- a/services/azure-storage/src/provide_credential/imds.rs
+++ b/services/azure-storage/src/provide_credential/imds.rs
@@ -19,6 +19,7 @@ use crate::Credential;
 use async_trait::async_trait;
 use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
+use std::time::Duration;
 
 /// Load credential from Azure Instance Metadata Service (IMDS).
 ///
@@ -52,7 +53,7 @@ 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() + jiff::SignedDuration::from_mins(10)
+            Timestamp::now() + Duration::from_secs(600)
         } else {
             // Azure IMDS returns expires_on as Unix timestamp (seconds since 
epoch)
             let timestamp = token.expires_on.parse::<i64>().map_err(|e| {
diff --git a/services/azure-storage/src/provide_credential/workload_identity.rs 
b/services/azure-storage/src/provide_credential/workload_identity.rs
index 500186a..eeea220 100644
--- a/services/azure-storage/src/provide_credential/workload_identity.rs
+++ b/services/azure-storage/src/provide_credential/workload_identity.rs
@@ -17,7 +17,9 @@
 
 use crate::Credential;
 use async_trait::async_trait;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
+use std::time::Duration;
 
 /// Load credential from Azure Workload Identity.
 ///
@@ -85,13 +87,11 @@ impl ProvideCredential for 
WorkloadIdentityCredentialProvider {
         match token {
             Some(token_response) => {
                 let expires_on = match token_response.expires_on {
-                    Some(expires_on) => {
-                        
reqsign_core::time::parse_rfc3339(&expires_on).map_err(|e| {
-                            reqsign_core::Error::unexpected("failed to parse 
expires_on time")
-                                .with_source(e)
-                        })?
-                    }
-                    None => reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
+                    Some(expires_on) => expires_on.parse().map_err(|e| {
+                        reqsign_core::Error::unexpected("failed to parse 
expires_on time")
+                            .with_source(e)
+                    })?,
+                    None => Timestamp::now() + Duration::from_secs(600),
                 };
 
                 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 aceb460..98563f0 100644
--- a/services/azure-storage/src/sign_request.rs
+++ b/services/azure-storage/src/sign_request.rs
@@ -23,7 +23,7 @@ use http::{HeaderValue, header};
 use log::debug;
 use percent_encoding::percent_encode;
 use reqsign_core::hash::{base64_decode, base64_hmac_sha256};
-use reqsign_core::time::{Timestamp, format_http_date, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Result, SignRequest, SigningMethod, 
SigningRequest};
 use std::fmt::Write;
 use std::time::Duration;
@@ -103,7 +103,7 @@ impl SignRequest for RequestSigner {
                     SigningMethod::Header => {
                         ctx.headers.insert(
                             X_MS_DATE,
-                            format_http_date(now()).parse().map_err(|e| {
+                            
Timestamp::now().format_http_date().parse().map_err(|e| {
                                 reqsign_core::Error::unexpected("failed to 
parse date header")
                                     .with_source(e)
                             })?,
@@ -133,11 +133,7 @@ impl SignRequest for RequestSigner {
                         let signer = 
crate::account_sas::AccountSharedAccessSignature::new(
                             account_name.clone(),
                             account_key.clone(),
-                            now()
-                                + 
jiff::SignedDuration::try_from(d).map_err(|e| {
-                                    reqsign_core::Error::unexpected("failed to 
convert duration")
-                                        .with_source(e)
-                                })?,
+                            Timestamp::now() + d,
                         );
                         let signer_token = signer.token().map_err(|e| {
                             reqsign_core::Error::unexpected("failed to 
generate account SAS token")
@@ -148,7 +144,7 @@ impl SignRequest for RequestSigner {
                         });
                     }
                     SigningMethod::Header => {
-                        let now_time = self.time.unwrap_or_else(now);
+                        let now_time = 
self.time.unwrap_or_else(Timestamp::now);
                         let string_to_sign = string_to_sign(&mut ctx, 
account_name, now_time)?;
                         let decode_content = 
base64_decode(account_key).map_err(|e| {
                             reqsign_core::Error::unexpected("failed to decode 
account key")
@@ -377,7 +373,7 @@ fn string_to_sign(
 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| {
+        now_time.format_http_date().parse().map_err(|e| {
             reqsign_core::Error::unexpected("failed to parse x-ms-date 
header").with_source(e)
         })?,
     );
@@ -462,7 +458,7 @@ mod tests {
             .with_env(OsEnv);
         let cred = Credential::with_bearer_token(
             "token",
-            Some(now() + jiff::SignedDuration::from_hours(1)),
+            Some(Timestamp::now() + Duration::from_secs(3600)),
         );
         let builder = RequestSigner::new();
 
diff --git a/services/google/Cargo.toml b/services/google/Cargo.toml
index 44e8a45..339d1fd 100644
--- a/services/google/Cargo.toml
+++ b/services/google/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-google"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Goole Cloud Platform signing implementation for reqsign."
 
@@ -29,7 +29,6 @@ rust-version.workspace = true
 [dependencies]
 async-trait = { 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 700ead0..81291ca 100644
--- a/services/google/src/credential.rs
+++ b/services/google/src/credential.rs
@@ -15,10 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use reqsign_core::{
-    Result, SigningCredential as KeyTrait, time::Timestamp, time::now, 
utils::Redact,
-};
+use reqsign_core::{Result, SigningCredential as KeyTrait, time::Timestamp, 
utils::Redact};
 use std::fmt::{self, Debug};
+use std::time::Duration;
 
 /// ServiceAccount holds the client email and private key for service account 
authentication.
 #[derive(Clone, serde::Deserialize)]
@@ -204,8 +203,8 @@ impl KeyTrait for Token {
         match self.expires_at {
             Some(expires_at) => {
                 // Consider token invalid if it expires within 2 minutes
-                let buffer = jiff::SignedDuration::from_mins(2);
-                now() < expires_at - buffer
+                let buffer = Duration::from_secs(120);
+                Timestamp::now() < expires_at - buffer
             }
             None => true, // No expiration means always valid
         }
@@ -337,15 +336,15 @@ mod tests {
         assert!(token.is_valid());
 
         // Token with future expiration
-        token.expires_at = Some(now() + jiff::SignedDuration::from_hours(1));
+        token.expires_at = Some(Timestamp::now() + Duration::from_secs(3600));
         assert!(token.is_valid());
 
         // Token that expires within 2 minutes
-        token.expires_at = Some(now() + jiff::SignedDuration::from_secs(30));
+        token.expires_at = Some(Timestamp::now() + Duration::from_secs(30));
         assert!(!token.is_valid());
 
         // Expired token
-        token.expires_at = Some(now() - jiff::SignedDuration::from_hours(1));
+        token.expires_at = Some(Timestamp::now() - Duration::from_secs(3600));
         assert!(!token.is_valid());
 
         // Empty access token
@@ -417,7 +416,7 @@ mod tests {
         // Valid token only
         let cred = Credential::with_token(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() + jiff::SignedDuration::from_hours(1)),
+            expires_at: Some(Timestamp::now() + Duration::from_secs(3600)),
         });
         assert!(cred.is_valid());
         assert!(!cred.has_service_account());
@@ -439,7 +438,7 @@ mod tests {
         });
         cred.token = Some(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() + jiff::SignedDuration::from_hours(1)),
+            expires_at: Some(Timestamp::now() + Duration::from_secs(3600)),
         });
         assert!(cred.is_valid());
         assert!(cred.has_service_account());
@@ -452,7 +451,7 @@ mod tests {
         });
         cred.token = Some(Token {
             access_token: "test".to_string(),
-            expires_at: Some(now() - jiff::SignedDuration::from_hours(1)),
+            expires_at: Some(Timestamp::now() - Duration::from_secs(3600)),
         });
         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 f805175..c37e15c 100644
--- a/services/google/src/provide_credential/authorized_user.rs
+++ b/services/google/src/provide_credential/authorized_user.rs
@@ -18,10 +18,11 @@
 use http::header::CONTENT_TYPE;
 use log::{debug, error};
 use serde::{Deserialize, Serialize};
-
-use reqsign_core::{Context, ProvideCredential, Result, time::now};
+use std::time::Duration;
 
 use crate::credential::{Credential, OAuth2Credentials, Token};
+use reqsign_core::time::Timestamp;
+use reqsign_core::{Context, ProvideCredential, Result};
 
 /// OAuth2 refresh token request.
 #[derive(Serialize)]
@@ -96,13 +97,11 @@ impl ProvideCredential for AuthorizedUserCredentialProvider 
{
 
         let expires_at = token_resp
             .expires_in
-            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
+            .map(|expires_in| Timestamp::now() + 
Duration::from_secs(expires_in));
 
-        let token = Token {
+        Ok(Some(Credential::with_token(Token {
             access_token: token_resp.access_token,
             expires_at,
-        };
-
-        Ok(Some(Credential::with_token(token)))
+        })))
     }
 }
diff --git a/services/google/src/provide_credential/external_account.rs 
b/services/google/src/provide_credential/external_account.rs
index a4dba76..c28cca7 100644
--- a/services/google/src/provide_credential/external_account.rs
+++ b/services/google/src/provide_credential/external_account.rs
@@ -22,8 +22,8 @@ use log::{debug, error};
 use serde::{Deserialize, Serialize};
 
 use crate::credential::{Credential, ExternalAccount, Token, external_account};
-use reqsign_core::time::parse_rfc3339;
-use reqsign_core::{Context, ProvideCredential, Result, time::now};
+use reqsign_core::time::Timestamp;
+use reqsign_core::{Context, ProvideCredential, Result};
 
 /// The maximum impersonated token lifetime allowed, 1 hour.
 const MAX_LIFETIME: Duration = Duration::from_secs(3600);
@@ -178,7 +178,7 @@ impl ExternalAccountCredentialProvider {
 
         let expires_at = token_resp
             .expires_in
-            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
+            .map(|expires_in| Timestamp::now() + 
Duration::from_secs(expires_in));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -247,11 +247,9 @@ impl ExternalAccountCredentialProvider {
             })?;
 
         // Parse expire time from RFC3339 format
-        let expires_at = parse_rfc3339(&token_resp.expire_time).ok();
-
         Ok(Some(Token {
             access_token: token_resp.access_token,
-            expires_at,
+            expires_at: token_resp.expire_time.parse().ok(),
         }))
     }
 }
diff --git 
a/services/google/src/provide_credential/impersonated_service_account.rs 
b/services/google/src/provide_credential/impersonated_service_account.rs
index 6035a6f..5d57c09 100644
--- a/services/google/src/provide_credential/impersonated_service_account.rs
+++ b/services/google/src/provide_credential/impersonated_service_account.rs
@@ -22,8 +22,8 @@ use log::{debug, error};
 use serde::{Deserialize, Serialize};
 
 use crate::credential::{Credential, ImpersonatedServiceAccount, Token};
-use reqsign_core::time::parse_rfc3339;
-use reqsign_core::{Context, ProvideCredential, Result, time::now};
+use reqsign_core::time::Timestamp;
+use reqsign_core::{Context, ProvideCredential, Result};
 
 /// The maximum impersonated token lifetime allowed, 1 hour.
 const MAX_LIFETIME: Duration = Duration::from_secs(3600);
@@ -137,7 +137,7 @@ impl ImpersonatedServiceAccountCredentialProvider {
 
         let expires_at = token_resp
             .expires_in
-            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
+            .map(|expires_in| Timestamp::now() + 
Duration::from_secs(expires_in));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -200,11 +200,9 @@ impl ImpersonatedServiceAccountCredentialProvider {
             })?;
 
         // Parse expire time from RFC3339 format
-        let expires_at = parse_rfc3339(&token_resp.expire_time).ok();
-
         Ok(Token {
             access_token: token_resp.access_token,
-            expires_at,
+            expires_at: token_resp.expire_time.parse().ok(),
         })
     }
 }
diff --git a/services/google/src/provide_credential/vm_metadata.rs 
b/services/google/src/provide_credential/vm_metadata.rs
index abf8839..ece9c62 100644
--- a/services/google/src/provide_credential/vm_metadata.rs
+++ b/services/google/src/provide_credential/vm_metadata.rs
@@ -17,10 +17,11 @@
 
 use log::debug;
 use serde::Deserialize;
-
-use reqsign_core::{Context, ProvideCredential, Result, time::now};
+use std::time::Duration;
 
 use crate::credential::{Credential, Token};
+use reqsign_core::time::Timestamp;
+use reqsign_core::{Context, ProvideCredential, Result};
 
 /// VM metadata token response.
 #[derive(Deserialize)]
@@ -106,13 +107,10 @@ impl ProvideCredential for VmMetadataCredentialProvider {
                     .with_source(e)
             })?;
 
-        let expires_at = now() + 
jiff::SignedDuration::from_secs(token_resp.expires_in as i64);
-
-        let token = Token {
+        let expires_at = Timestamp::now() + 
Duration::from_secs(token_resp.expires_in);
+        Ok(Some(Credential::with_token(Token {
             access_token: token_resp.access_token,
             expires_at: Some(expires_at),
-        };
-
-        Ok(Some(Credential::with_token(token)))
+        })))
     }
 }
diff --git a/services/google/src/sign_request.rs 
b/services/google/src/sign_request.rs
index e7aa3f7..4577133 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().as_second() as u64;
+        let current = Timestamp::now().as_second() as u64;
 
         Claims {
             iss: client_email.to_string(),
@@ -157,7 +157,7 @@ impl RequestSigner {
 
         let expires_at = token_resp
             .expires_in
-            .map(|expires_in| now() + 
jiff::SignedDuration::from_secs(expires_in as i64));
+            .map(|expires_in| Timestamp::now() + 
Duration::from_secs(expires_in));
 
         Ok(Token {
             access_token: token_resp.access_token,
@@ -193,7 +193,7 @@ impl RequestSigner {
         expires_in: Duration,
     ) -> Result<SigningRequest> {
         let mut req = SigningRequest::build(parts)?;
-        let now = now();
+        let now = Timestamp::now();
 
         // Canonicalize headers
         canonicalize_header(&mut req)?;
@@ -215,7 +215,7 @@ impl RequestSigner {
         // Build scope
         let scope = format!(
             "{}/{}/{}/goog4_request",
-            format_date(now),
+            now.format_date(),
             self.region,
             self.service
         );
@@ -226,7 +226,7 @@ impl RequestSigner {
             let mut f = String::new();
             f.push_str("GOOG4-RSA-SHA256");
             f.push('\n');
-            f.push_str(&format_iso8601(now));
+            f.push_str(&now.format_iso8601());
             f.push('\n');
             f.push_str(&scope);
             f.push('\n');
@@ -282,7 +282,7 @@ impl SignRequest for RequestSigner {
                     if token.is_valid() {
                         self.build_token_auth(req, token)?
                     } else if let Some(sa) = &cred.service_account {
-                        // Token expired but we have SA, exchange for new token
+                        // Token expired, but we have SA, exchange for new 
token
                         debug!("token expired, exchanging service account for 
new token");
                         let new_token = self.exchange_token(ctx, sa).await?;
                         self.build_token_auth(req, &new_token)?
@@ -385,12 +385,12 @@ fn canonicalize_query(
             format!(
                 "{}/{}/{}/{}/goog4_request",
                 &cred.client_email,
-                format_date(now),
+                now.format_date(),
                 region,
                 service
             ),
         ));
-        req.query.push(("X-Goog-Date".into(), format_iso8601(now)));
+        req.query.push(("X-Goog-Date".into(), now.format_iso8601()));
         req.query
             .push(("X-Goog-Expires".into(), expire.as_secs().to_string()));
         req.query.push((
diff --git a/services/huaweicloud-obs/Cargo.toml 
b/services/huaweicloud-obs/Cargo.toml
index eb6c8f7..7337512 100644
--- a/services/huaweicloud-obs/Cargo.toml
+++ b/services/huaweicloud-obs/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-huaweicloud-obs"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Huawei Cloud OBS signing implementation for reqsign."
 
@@ -30,7 +30,6 @@ rust-version.workspace = true
 anyhow = { workspace = true }
 async-trait = { workspace = true }
 http = { workspace = true }
-jiff = { workspace = true }
 log = { workspace = true }
 percent-encoding = { workspace = true }
 reqsign-core = { workspace = true }
diff --git a/services/huaweicloud-obs/src/sign_request.rs 
b/services/huaweicloud-obs/src/sign_request.rs
index f1beb57..3725511 100644
--- a/services/huaweicloud-obs/src/sign_request.rs
+++ b/services/huaweicloud-obs/src/sign_request.rs
@@ -33,8 +33,6 @@ use super::constants::*;
 use super::credential::Credential;
 use reqsign_core::hash::base64_hmac_sha1;
 use reqsign_core::time::Timestamp;
-use reqsign_core::time::format_http_date;
-use reqsign_core::time::now;
 use reqsign_core::{SignRequest, SigningMethod, SigningRequest};
 
 /// RequestSigner that implement Huawei Cloud Object Storage Service 
Authorization.
@@ -81,7 +79,7 @@ impl SignRequest for RequestSigner {
     ) -> Result<()> {
         let k = credential
             .ok_or_else(|| reqsign_core::Error::credential_invalid("missing 
credential"))?;
-        let now = self.time.unwrap_or_else(now);
+        let now = self.time.unwrap_or_else(Timestamp::now);
 
         let method = if let Some(expires_in) = expires_in {
             SigningMethod::Query(expires_in)
@@ -96,7 +94,7 @@ impl SignRequest for RequestSigner {
 
         match method {
             SigningMethod::Header => {
-                ctx.headers.insert(DATE, format_http_date(now).parse()?);
+                ctx.headers.insert(DATE, now.format_http_date().parse()?);
                 ctx.headers.insert(AUTHORIZATION, {
                     let mut value: HeaderValue =
                         format!("OBS {}:{}", k.access_key_id, 
signature).parse()?;
@@ -106,14 +104,9 @@ impl SignRequest for RequestSigner {
                 });
             }
             SigningMethod::Query(expire) => {
-                ctx.headers.insert(DATE, format_http_date(now).parse()?);
+                ctx.headers.insert(DATE, now.format_http_date().parse()?);
                 ctx.query_push("AccessKeyId", &k.access_key_id);
-                ctx.query_push(
-                    "Expires",
-                    (now + jiff::SignedDuration::try_from(expire).unwrap())
-                        .as_second()
-                        .to_string(),
-                );
+                ctx.query_push("Expires", (now + 
expire).as_second().to_string());
                 ctx.query_push(
                     "Signature",
                     utf8_percent_encode(&signature, 
percent_encoding::NON_ALPHANUMERIC).to_string(),
@@ -163,14 +156,10 @@ fn string_to_sign(
     s.write_str("\n")?;
     match method {
         SigningMethod::Header => {
-            writeln!(&mut s, "{}", format_http_date(now))?;
+            writeln!(&mut s, "{}", now.format_http_date())?;
         }
         SigningMethod::Query(expires) => {
-            writeln!(
-                &mut s,
-                "{}",
-                (now + 
jiff::SignedDuration::try_from(expires).unwrap()).as_second()
-            )?;
+            writeln!(&mut s, "{}", (now + expires).as_second())?;
         }
     }
 
@@ -306,7 +295,6 @@ mod tests {
     use http::Uri;
     use http::header::HeaderName;
     use reqsign_core::Result;
-    use reqsign_core::time::parse_rfc2822;
     use reqsign_core::{Context, OsEnv, Signer};
     use reqsign_file_read_tokio::TokioFileRead;
     use reqsign_http_send_reqwest::ReqwestHttpSend;
@@ -317,8 +305,8 @@ mod tests {
     #[tokio::test]
     async fn test_sign() -> Result<()> {
         let loader = StaticCredentialProvider::new("access_key", "123456");
-        let builder =
-            RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
+        let builder = RequestSigner::new("bucket")
+            .with_time(Timestamp::parse_rfc2822("Mon, 15 Aug 2022 16:50:12 
GMT")?);
 
         let ctx = Context::new()
             .with_file_read(TokioFileRead)
@@ -356,8 +344,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(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
+        let builder = RequestSigner::new("bucket")
+            .with_time(Timestamp::parse_rfc2822("Mon, 15 Aug 2022 16:50:12 
GMT")?);
 
         let ctx = Context::new()
             .with_file_read(TokioFileRead)
@@ -397,8 +385,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(parse_rfc2822("Mon, 15 Aug 
2022 16:50:12 GMT")?);
+        let builder = RequestSigner::new("bucket")
+            .with_time(Timestamp::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 1e258f6..3a86cc7 100644
--- a/services/oracle/Cargo.toml
+++ b/services/oracle/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-oracle"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Oracle Cloud signing implementation for reqsign."
 
@@ -31,7 +31,6 @@ anyhow = { workspace = true }
 async-trait = { workspace = true }
 base64 = { workspace = true }
 http = { workspace = true }
-jiff = { workspace = true }
 log = { workspace = true }
 reqsign-core = { workspace = true }
 rsa = { workspace = true }
diff --git a/services/oracle/src/credential.rs 
b/services/oracle/src/credential.rs
index a43052a..d1420f6 100644
--- a/services/oracle/src/credential.rs
+++ b/services/oracle/src/credential.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_core::SigningCredential;
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::utils::Redact;
 use std::fmt::{Debug, Formatter};
+use std::time::Duration;
 
 /// Credential that holds the API private key information.
 #[derive(Default, Clone)]
@@ -59,7 +60,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
+            .map(|v| v > Timestamp::now() + Duration::from_secs(120))
         {
             return valid;
         }
diff --git a/services/oracle/src/provide_credential/config.rs 
b/services/oracle/src/provide_credential/config.rs
index 71d8c5f..a801102 100644
--- a/services/oracle/src/provide_credential/config.rs
+++ b/services/oracle/src/provide_credential/config.rs
@@ -20,8 +20,10 @@
 use crate::{Config, Credential};
 use async_trait::async_trait;
 use log::debug;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
 use std::sync::Arc;
+use std::time::Duration;
 
 /// Static configuration based loader.
 #[derive(Debug)]
@@ -62,9 +64,7 @@ impl ProvideCredential for ConfigCredentialProvider {
                     key_file: key_file.clone(),
                     fingerprint: fingerprint.clone(),
                     // Set expires_in to 10 minutes to enforce re-read
-                    expires_in: Some(
-                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
-                    ),
+                    expires_in: Some(Timestamp::now() + 
Duration::from_secs(600)),
                 }))
             }
             _ => {
diff --git a/services/oracle/src/provide_credential/config_file.rs 
b/services/oracle/src/provide_credential/config_file.rs
index a222b5f..af9e903 100644
--- a/services/oracle/src/provide_credential/config_file.rs
+++ b/services/oracle/src/provide_credential/config_file.rs
@@ -21,7 +21,9 @@ use crate::constants::{
 };
 use async_trait::async_trait;
 use log::debug;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
+use std::time::Duration;
 
 /// ConfigFileCredentialProvider loads credentials from Oracle config file 
(~/.oci/config).
 ///
@@ -108,9 +110,7 @@ impl ProvideCredential for ConfigFileCredentialProvider {
                     user: user.to_string(),
                     key_file: expanded_key_file,
                     fingerprint: fingerprint.to_string(),
-                    expires_in: Some(
-                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
-                    ),
+                    expires_in: Some(Timestamp::now() + 
Duration::from_secs(600)),
                 }))
             }
             _ => {
diff --git a/services/oracle/src/provide_credential/env.rs 
b/services/oracle/src/provide_credential/env.rs
index 9f3c41a..8fb093c 100644
--- a/services/oracle/src/provide_credential/env.rs
+++ b/services/oracle/src/provide_credential/env.rs
@@ -17,7 +17,9 @@
 
 use crate::{Credential, constants::*};
 use async_trait::async_trait;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential, Result};
+use std::time::Duration;
 
 /// EnvCredentialProvider loads Oracle Cloud credentials from environment 
variables.
 ///
@@ -64,9 +66,7 @@ impl ProvideCredential for EnvCredentialProvider {
                     tenancy: tenancy.clone(),
                     key_file: expanded_key_file,
                     fingerprint: fingerprint.clone(),
-                    expires_in: Some(
-                        reqsign_core::time::now() + 
jiff::SignedDuration::from_mins(10),
-                    ),
+                    expires_in: Some(Timestamp::now() + 
Duration::from_secs(600)),
                 }))
             }
             _ => Ok(None),
diff --git a/services/oracle/src/sign_request.rs 
b/services/oracle/src/sign_request.rs
index 06ea20c..b7e6115 100644
--- a/services/oracle/src/sign_request.rs
+++ b/services/oracle/src/sign_request.rs
@@ -18,14 +18,11 @@
 use crate::Credential;
 use async_trait::async_trait;
 use base64::{Engine as _, engine::general_purpose};
+use http::header::{AUTHORIZATION, DATE};
 use http::request::Parts;
-use http::{
-    HeaderValue,
-    header::{AUTHORIZATION, DATE},
-};
 use log::debug;
 use reqsign_core::Result;
-use reqsign_core::time::{format_http_date, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, SignRequest, SigningRequest};
 use rsa::pkcs1v15::SigningKey;
 use rsa::sha2::Sha256;
@@ -68,13 +65,13 @@ impl SignRequest for RequestSigner {
             return Ok(());
         };
 
-        let now = now();
+        let now = Timestamp::now();
         let mut signing_req = SigningRequest::build(req)?;
 
         // Construct string to sign
         let string_to_sign = {
             let mut f = String::new();
-            writeln!(f, "date: {}", format_http_date(now))?;
+            writeln!(f, "date: {}", now.format_http_date())?;
             writeln!(
                 f,
                 "(request-target): {} {}",
@@ -103,7 +100,7 @@ impl SignRequest for RequestSigner {
         // Set headers
         signing_req
             .headers
-            .insert(DATE, HeaderValue::from_str(&format_http_date(now))?);
+            .insert(DATE, now.format_http_date().parse()?);
 
         // Build authorization header
         let mut auth_value = String::new();
@@ -119,7 +116,7 @@ impl SignRequest for RequestSigner {
 
         signing_req
             .headers
-            .insert(AUTHORIZATION, HeaderValue::from_str(&auth_value)?);
+            .insert(AUTHORIZATION, auth_value.parse()?);
 
         signing_req.apply(req)
     }
diff --git a/services/tencent-cos/Cargo.toml b/services/tencent-cos/Cargo.toml
index e1418ea..5805609 100644
--- a/services/tencent-cos/Cargo.toml
+++ b/services/tencent-cos/Cargo.toml
@@ -17,7 +17,7 @@
 
 [package]
 name = "reqsign-tencent-cos"
-version = "1.1.0"
+version = "2.0.0"
 
 description = "Tencent Cloud COS signing implementation for reqsign."
 
@@ -30,7 +30,6 @@ rust-version.workspace = true
 anyhow = { workspace = true }
 async-trait = { 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 1c04713..98ac83a 100644
--- a/services/tencent-cos/src/credential.rs
+++ b/services/tencent-cos/src/credential.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_core::SigningCredential;
-use reqsign_core::time::{Timestamp, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::utils::Redact;
 use std::fmt::{Debug, Formatter};
+use std::time::Duration;
 
 /// Credential for Tencent COS.
 #[derive(Default, Clone)]
@@ -52,7 +53,7 @@ impl SigningCredential for Credential {
         // Take 120s as buffer to avoid edge cases.
         if let Some(valid) = self
             .expires_in
-            .map(|v| v > now() + jiff::SignedDuration::from_mins(2))
+            .map(|v| v > Timestamp::now() + Duration::from_secs(120))
         {
             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 96cb55e..4805135 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
@@ -21,7 +21,7 @@ use async_trait::async_trait;
 use http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
 use log::debug;
 use reqsign_core::Result;
-use reqsign_core::time::{now, parse_rfc3339};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, ProvideCredential};
 use serde::{Deserialize, Serialize};
 
@@ -110,7 +110,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().as_second())
+            .header("X-TC-Timestamp", Timestamp::now().as_second())
             .header("X-TC-Version", "2018-08-13")
             .body(bs.into())?;
 
@@ -140,7 +140,7 @@ impl ProvideCredential for 
AssumeRoleWithWebIdentityCredentialProvider {
             secret_id: resp_cred.tmp_secret_id,
             secret_key: resp_cred.tmp_secret_key,
             security_token: Some(resp_cred.token),
-            expires_in: Some(parse_rfc3339(&resp_expiration)?),
+            expires_in: Some(resp_expiration.parse()?),
         };
 
         Ok(Some(cred))
diff --git a/services/tencent-cos/src/sign_request.rs 
b/services/tencent-cos/src/sign_request.rs
index f81249d..bae88d7 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::{Timestamp, format_http_date, now};
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, Result, SignRequest, SigningRequest};
 use std::time::Duration;
 
@@ -69,7 +69,7 @@ impl SignRequest for RequestSigner {
             return Ok(());
         };
 
-        let now = self.time.unwrap_or_else(now);
+        let now = self.time.unwrap_or_else(Timestamp::now);
         let mut signing_req = SigningRequest::build(req)?;
 
         if let Some(expires) = expires_in {
@@ -78,7 +78,7 @@ impl SignRequest for RequestSigner {
 
             signing_req
                 .headers
-                .insert(DATE, format_http_date(now).parse()?);
+                .insert(DATE, now.format_http_date().parse()?);
             signing_req.query_append(&signature);
 
             if let Some(token) = &cred.security_token {
@@ -93,7 +93,7 @@ impl SignRequest for RequestSigner {
 
             signing_req
                 .headers
-                .insert(DATE, format_http_date(now).parse()?);
+                .insert(DATE, now.format_http_date().parse()?);
             signing_req.headers.insert(AUTHORIZATION, {
                 let mut value: http::HeaderValue = signature.parse()?;
                 value.set_sensitive(true);
@@ -119,12 +119,7 @@ fn build_signature(
     now: Timestamp,
     expires: Duration,
 ) -> String {
-    let key_time = format!(
-        "{};{}",
-        now.as_second(),
-        (now + jiff::SignedDuration::try_from(expires).unwrap()).as_second()
-    );
-
+    let key_time = format!("{};{}", now.as_second(), (now + 
expires).as_second());
     let sign_key = hex_hmac_sha1(cred.secret_key.as_bytes(), 
key_time.as_bytes());
 
     let mut params = ctx
diff --git a/services/tencent-cos/tests/credential_chain.rs 
b/services/tencent-cos/tests/credential_chain.rs
index 6645714..9fdcd3c 100644
--- a/services/tencent-cos/tests/credential_chain.rs
+++ b/services/tencent-cos/tests/credential_chain.rs
@@ -19,11 +19,13 @@
 
 use async_trait::async_trait;
 use reqsign_core::ProvideCredentialChain;
+use reqsign_core::time::Timestamp;
 use reqsign_core::{Context, OsEnv, ProvideCredential, Result};
 use reqsign_file_read_tokio::TokioFileRead;
 use reqsign_http_send_reqwest::ReqwestHttpSend;
 use reqsign_tencent_cos::{Credential, EnvCredentialProvider};
 use std::sync::Arc;
+use std::time::Duration;
 
 /// Mock provider that tracks how many times it was called
 #[derive(Debug)]
@@ -199,7 +201,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() + 
jiff::SignedDuration::from_hours(1)),
+            expires_in: Some(Timestamp::now() + Duration::from_secs(3600)),
         }))
     }
 }

Reply via email to