You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2023/04/11 15:26:59 UTC

[incubator-opendal] 01/01: refactor: Use chrono instead of time to work well with ecosystem

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

xuanwo pushed a commit to branch remove-time
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git

commit 100e3f274d04b3c4d8442f11559300c421ea3ce3
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Apr 11 23:26:31 2023 +0800

    refactor: Use chrono instead of time to work well with ecosystem
    
    Signed-off-by: Xuanwo <gi...@xuanwo.io>
---
 Cargo.lock                                |  3 +-
 bindings/nodejs/Cargo.toml                |  1 -
 bindings/nodejs/src/lib.rs                |  9 +++---
 bindings/object_store/Cargo.toml          |  1 -
 bindings/object_store/src/lib.rs          | 24 ++------------
 core/Cargo.toml                           |  2 ++
 core/src/raw/chrono_util.rs               | 53 +++++++++++++++++++++++++++++++
 core/src/raw/http_util/header.rs          | 16 +++-------
 core/src/raw/mod.rs                       |  3 ++
 core/src/services/azblob/pager.rs         | 15 ++-------
 core/src/services/azdfs/pager.rs          | 12 +------
 core/src/services/fs/backend.rs           |  6 ++--
 core/src/services/ftp/backend.rs          |  3 +-
 core/src/services/ftp/pager.rs            |  3 +-
 core/src/services/gcs/backend.rs          |  7 +---
 core/src/services/gcs/pager.rs            |  7 +---
 core/src/services/hdfs/backend.rs         |  5 ++-
 core/src/services/hdfs/pager.rs           |  4 +--
 core/src/services/oss/pager.rs            | 13 +-------
 core/src/services/s3/pager.rs             | 18 +----------
 core/src/services/webdav/list_response.rs |  5 ++-
 core/src/services/webhdfs/message.rs      | 15 ++++-----
 core/src/types/metadata.rs                | 12 +++----
 23 files changed, 101 insertions(+), 136 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 796ac0cb..87bcdc19 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2174,7 +2174,6 @@ version = "0.30.5"
 dependencies = [
  "async-trait",
  "bytes",
- "chrono",
  "futures",
  "object_store",
  "opendal",
@@ -2225,6 +2224,7 @@ dependencies = [
  "bb8",
  "bytes",
  "cfg-if",
+ "chrono",
  "criterion",
  "dashmap",
  "dotenvy",
@@ -2298,7 +2298,6 @@ dependencies = [
  "napi-build",
  "napi-derive",
  "opendal",
- "time 0.3.17",
 ]
 
 [[package]]
diff --git a/bindings/nodejs/Cargo.toml b/bindings/nodejs/Cargo.toml
index 8f1f287a..6931cbe0 100644
--- a/bindings/nodejs/Cargo.toml
+++ b/bindings/nodejs/Cargo.toml
@@ -39,7 +39,6 @@ napi = { version = "2.11.3", default-features = false, features = [
 ] }
 napi-derive = "2.12.2"
 opendal = { version = "0.30", path = "../../core" }
-time = { version = "0.3.17", features = ["formatting"] }
 
 [build-dependencies]
 napi-build = "2"
diff --git a/bindings/nodejs/src/lib.rs b/bindings/nodejs/src/lib.rs
index aa14cfaa..92c3f16b 100644
--- a/bindings/nodejs/src/lib.rs
+++ b/bindings/nodejs/src/lib.rs
@@ -24,7 +24,6 @@ use std::time::Duration;
 
 use futures::TryStreamExt;
 use napi::bindgen_prelude::*;
-use time::format_description::well_known::Rfc3339;
 
 fn build_operator(
     scheme: opendal::Scheme,
@@ -599,12 +598,12 @@ impl Metadata {
         self.0.etag().map(|s| s.to_string())
     }
 
-    /// Last Modified of this object.(UTC)
+    /// Last Modified of this object.
+    ///
+    /// We will output this time in RFC3339 format like `1996-12-19T16:39:57+08:00`.
     #[napi(getter)]
     pub fn last_modified(&self) -> Option<String> {
-        self.0
-            .last_modified()
-            .map(|ta| ta.format(&Rfc3339).unwrap())
+        self.0.last_modified().map(|ta| ta.to_rfc3339())
     }
 }
 
diff --git a/bindings/object_store/Cargo.toml b/bindings/object_store/Cargo.toml
index 30618e03..e6c172f7 100644
--- a/bindings/object_store/Cargo.toml
+++ b/bindings/object_store/Cargo.toml
@@ -30,7 +30,6 @@ version.workspace = true
 [dependencies]
 async-trait = "0.1"
 bytes = "1"
-chrono = "0.4.23"
 futures = "0.3"
 object_store = "0.5"
 opendal = { version = "0.30", path = "../../core" }
diff --git a/bindings/object_store/src/lib.rs b/bindings/object_store/src/lib.rs
index b9c6fb7c..f5d89f58 100644
--- a/bindings/object_store/src/lib.rs
+++ b/bindings/object_store/src/lib.rs
@@ -22,9 +22,6 @@ use std::task::Poll;
 
 use async_trait::async_trait;
 use bytes::Bytes;
-use chrono::DateTime;
-use chrono::NaiveDateTime;
-use chrono::Utc;
 use futures::stream::BoxStream;
 use futures::Stream;
 use futures::StreamExt;
@@ -117,18 +114,9 @@ impl ObjectStore for OpendalStore {
             .await
             .map_err(|err| format_object_store_error(err, location.as_ref()))?;
 
-        let (secs, nsecs) = meta
-            .last_modified()
-            .map(|v| (v.unix_timestamp(), v.nanosecond()))
-            .unwrap_or((0, 0));
-
         Ok(ObjectMeta {
             location: location.clone(),
-            last_modified: DateTime::from_utc(
-                NaiveDateTime::from_timestamp_opt(secs, nsecs)
-                    .expect("returning timestamp must be valid"),
-                Utc,
-            ),
+            last_modified: meta.last_modified().unwrap_or_default(),
             size: meta.content_length() as usize,
         })
     }
@@ -251,17 +239,9 @@ fn format_object_store_error(err: opendal::Error, path: &str) -> object_store::E
 }
 
 fn format_object_meta(path: &str, meta: &Metadata) -> ObjectMeta {
-    let (secs, nsecs) = meta
-        .last_modified()
-        .map(|v| (v.unix_timestamp(), v.nanosecond()))
-        .unwrap_or((0, 0));
     ObjectMeta {
         location: path.into(),
-        last_modified: DateTime::from_utc(
-            NaiveDateTime::from_timestamp_opt(secs, nsecs)
-                .expect("returning timestamp must be valid"),
-            Utc,
-        ),
+        last_modified: meta.last_modified().unwrap_or_default(),
         size: meta.content_length() as usize,
     }
 }
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 5320f093..c8d91187 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -133,6 +133,8 @@ suppaftp = { version = "4.5", default-features = false, features = [
   "async-secure",
   "async-rustls",
 ], optional = true }
+# Remove this crate after oss migrated
+chrono = "0.4.24"
 time = { version = "0.3", features = ["serde"] }
 tokio = { version = "1.27", features = ["fs"] }
 tracing = { version = "0.1", optional = true }
diff --git a/core/src/raw/chrono_util.rs b/core/src/raw/chrono_util.rs
new file mode 100644
index 00000000..0f0415e8
--- /dev/null
+++ b/core/src/raw/chrono_util.rs
@@ -0,0 +1,53 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::time::{Duration, UNIX_EPOCH};
+
+use chrono::{DateTime, Utc};
+
+use crate::*;
+
+/// Parse dateimt from rfc2822.
+///
+/// For example: `Fri, 28 Nov 2014 21:00:09 +0900`
+pub fn parse_datetime_from_rfc2822(s: &str) -> Result<DateTime<Utc>> {
+    DateTime::parse_from_rfc2822(s)
+        .map(|v| v.into())
+        .map_err(|e| {
+            Error::new(ErrorKind::Unexpected, "parse datetime from rfc2822 failed").set_source(e)
+        })
+}
+
+/// Parse dateimt from rfc3339.
+///
+/// For example: `2014-11-28T21:00:09+09:00`
+pub fn parse_datetime_from_rfc3339(s: &str) -> Result<DateTime<Utc>> {
+    DateTime::parse_from_rfc3339(s)
+        .map(|v| v.into())
+        .map_err(|e| {
+            Error::new(ErrorKind::Unexpected, "parse datetime from rfc3339 failed").set_source(e)
+        })
+}
+
+/// parse datetime from given timestamp_millis
+pub fn parse_datetime_from_from_timestamp_millis(s: i64) -> Result<DateTime<Utc>> {
+    let st = UNIX_EPOCH
+        .checked_add(Duration::from_millis(s as u64))
+        .ok_or_else(|| Error::new(ErrorKind::Unexpected, "intput timestamp overflow"))?;
+
+    Ok(st.into())
+}
diff --git a/core/src/raw/http_util/header.rs b/core/src/raw/http_util/header.rs
index 763d74b3..19075bf4 100644
--- a/core/src/raw/http_util/header.rs
+++ b/core/src/raw/http_util/header.rs
@@ -17,6 +17,8 @@
 
 use base64::engine::general_purpose;
 use base64::Engine;
+use chrono::DateTime;
+use chrono::Utc;
 use http::header::HeaderName;
 use http::header::CONTENT_DISPOSITION;
 use http::header::CONTENT_LENGTH;
@@ -28,8 +30,6 @@ use http::header::LOCATION;
 use http::HeaderMap;
 use http::HeaderValue;
 use md5::Digest;
-use time::format_description::well_known::Rfc2822;
-use time::OffsetDateTime;
 
 use crate::raw::*;
 use crate::EntryMode;
@@ -130,7 +130,7 @@ pub fn parse_content_range(headers: &HeaderMap) -> Result<Option<BytesContentRan
 }
 
 /// Parse last modified from header map.
-pub fn parse_last_modified(headers: &HeaderMap) -> Result<Option<OffsetDateTime>> {
+pub fn parse_last_modified(headers: &HeaderMap) -> Result<Option<DateTime<Utc>>> {
     match headers.get(LAST_MODIFIED) {
         None => Ok(None),
         Some(v) => {
@@ -142,16 +142,8 @@ pub fn parse_last_modified(headers: &HeaderMap) -> Result<Option<OffsetDateTime>
                 .with_operation("http_util::parse_last_modified")
                 .set_source(e)
             })?;
-            let t = OffsetDateTime::parse(v, &Rfc2822).map_err(|e| {
-                Error::new(
-                    ErrorKind::Unexpected,
-                    "header value is not valid rfc2822 time",
-                )
-                .with_operation("http_util::parse_last_modified")
-                .set_source(e)
-            })?;
 
-            Ok(Some(t))
+            Ok(Some(parse_datetime_from_rfc2822(v)?))
         }
     }
 }
diff --git a/core/src/raw/mod.rs b/core/src/raw/mod.rs
index 18a925b5..4cfd69c7 100644
--- a/core/src/raw/mod.rs
+++ b/core/src/raw/mod.rs
@@ -64,5 +64,8 @@ pub use http_util::*;
 mod serde_util;
 pub use serde_util::*;
 
+mod chrono_util;
+pub use chrono_util::*;
+
 // Expose as a pub mod to avoid confusing.
 pub mod adapters;
diff --git a/core/src/services/azblob/pager.rs b/core/src/services/azblob/pager.rs
index b0fad0e6..540b0164 100644
--- a/core/src/services/azblob/pager.rs
+++ b/core/src/services/azblob/pager.rs
@@ -21,8 +21,6 @@ use async_trait::async_trait;
 use bytes::Buf;
 use quick_xml::de;
 use serde::Deserialize;
-use time::format_description::well_known::Rfc2822;
-use time::OffsetDateTime;
 
 use super::core::AzblobCore;
 use super::error::parse_error;
@@ -115,16 +113,9 @@ impl oio::Page for AzblobPager {
                 .with_content_length(object.properties.content_length)
                 .with_content_md5(object.properties.content_md5)
                 .with_content_type(object.properties.content_type)
-                .with_last_modified(
-                    OffsetDateTime::parse(object.properties.last_modified.as_str(), &Rfc2822)
-                        .map_err(|e| {
-                            Error::new(
-                                ErrorKind::Unexpected,
-                                "parse last modified RFC2822 datetime",
-                            )
-                            .set_source(e)
-                        })?,
-                );
+                .with_last_modified(parse_datetime_from_rfc2822(
+                    object.properties.last_modified.as_str(),
+                )?);
 
             let de = oio::Entry::new(&build_rel_path(&self.core.root, &object.name), meta);
 
diff --git a/core/src/services/azdfs/pager.rs b/core/src/services/azdfs/pager.rs
index 4f414a04..e3e29a02 100644
--- a/core/src/services/azdfs/pager.rs
+++ b/core/src/services/azdfs/pager.rs
@@ -20,8 +20,6 @@ use std::sync::Arc;
 use async_trait::async_trait;
 use serde::Deserialize;
 use serde_json::de;
-use time::format_description::well_known::Rfc2822;
-use time::OffsetDateTime;
 
 use super::core::AzdfsCore;
 use super::error::parse_error;
@@ -107,15 +105,7 @@ impl oio::Page for AzdfsPager {
                     Error::new(ErrorKind::Unexpected, "content length is not valid integer")
                         .set_source(err)
                 })?)
-                .with_last_modified(
-                    OffsetDateTime::parse(&object.last_modified, &Rfc2822).map_err(|e| {
-                        Error::new(
-                            ErrorKind::Unexpected,
-                            "last modified is not valid RFC2822 datetime",
-                        )
-                        .set_source(e)
-                    })?,
-                );
+                .with_last_modified(parse_datetime_from_rfc2822(&object.last_modified)?);
 
             let mut path = build_rel_path(&self.core.root, &object.name);
             if mode == EntryMode::DIR {
diff --git a/core/src/services/fs/backend.rs b/core/src/services/fs/backend.rs
index 1bed68a6..e5159db5 100644
--- a/core/src/services/fs/backend.rs
+++ b/core/src/services/fs/backend.rs
@@ -24,8 +24,8 @@ use std::path::PathBuf;
 
 use async_compat::Compat;
 use async_trait::async_trait;
+use chrono::DateTime;
 use log::debug;
-use time::OffsetDateTime;
 use tokio::fs;
 use uuid::Uuid;
 
@@ -494,7 +494,7 @@ impl Accessor for FsBackend {
             .with_content_length(meta.len())
             .with_last_modified(
                 meta.modified()
-                    .map(OffsetDateTime::from)
+                    .map(DateTime::from)
                     .map_err(parse_io_error)?,
             );
 
@@ -706,7 +706,7 @@ impl Accessor for FsBackend {
             .with_content_length(meta.len())
             .with_last_modified(
                 meta.modified()
-                    .map(OffsetDateTime::from)
+                    .map(DateTime::from)
                     .map_err(parse_io_error)?,
             );
 
diff --git a/core/src/services/ftp/backend.rs b/core/src/services/ftp/backend.rs
index cd724d7b..75262323 100644
--- a/core/src/services/ftp/backend.rs
+++ b/core/src/services/ftp/backend.rs
@@ -36,7 +36,6 @@ use suppaftp::types::Response;
 use suppaftp::FtpError;
 use suppaftp::FtpStream;
 use suppaftp::Status;
-use time::OffsetDateTime;
 use tokio::sync::OnceCell;
 
 use super::pager::FtpPager;
@@ -419,7 +418,7 @@ impl Accessor for FtpBackend {
         };
         let mut meta = Metadata::new(mode);
         meta.set_content_length(file.size() as u64);
-        meta.set_last_modified(OffsetDateTime::from(file.modified()));
+        meta.set_last_modified(file.modified().into());
 
         Ok(RpStat::new(meta))
     }
diff --git a/core/src/services/ftp/pager.rs b/core/src/services/ftp/pager.rs
index fc669efa..e1e1c0af 100644
--- a/core/src/services/ftp/pager.rs
+++ b/core/src/services/ftp/pager.rs
@@ -21,7 +21,6 @@ use std::vec::IntoIter;
 
 use async_trait::async_trait;
 use suppaftp::list::File;
-use time::OffsetDateTime;
 
 use crate::raw::*;
 use crate::*;
@@ -62,7 +61,7 @@ impl oio::Page for FtpPager {
                     &path,
                     Metadata::new(EntryMode::FILE)
                         .with_content_length(de.size() as u64)
-                        .with_last_modified(OffsetDateTime::from(de.modified())),
+                        .with_last_modified(de.modified().into()),
                 )
             } else if de.is_directory() {
                 oio::Entry::new(&format!("{}/", &path), Metadata::new(EntryMode::DIR))
diff --git a/core/src/services/gcs/backend.rs b/core/src/services/gcs/backend.rs
index bca412fa..0bbaddea 100644
--- a/core/src/services/gcs/backend.rs
+++ b/core/src/services/gcs/backend.rs
@@ -29,8 +29,6 @@ use reqsign_0_9::GoogleTokenLoad;
 use reqsign_0_9::GoogleTokenLoader;
 use serde::Deserialize;
 use serde_json;
-use time::format_description::well_known::Rfc3339;
-use time::OffsetDateTime;
 
 use super::core::GcsCore;
 use super::error::parse_error;
@@ -422,10 +420,7 @@ impl Accessor for GcsBackend {
                 m.set_content_type(&meta.content_type);
             }
 
-            let datetime = OffsetDateTime::parse(&meta.updated, &Rfc3339).map_err(|e| {
-                Error::new(ErrorKind::Unexpected, "parse date time with rfc 3339").set_source(e)
-            })?;
-            m.set_last_modified(datetime);
+            m.set_last_modified(parse_datetime_from_rfc3339(&meta.updated)?);
 
             Ok(RpStat::new(m))
         } else if resp.status() == StatusCode::NOT_FOUND && path.ends_with('/') {
diff --git a/core/src/services/gcs/pager.rs b/core/src/services/gcs/pager.rs
index 6cb66aac..bfa99981 100644
--- a/core/src/services/gcs/pager.rs
+++ b/core/src/services/gcs/pager.rs
@@ -20,8 +20,6 @@ use std::sync::Arc;
 use async_trait::async_trait;
 use serde::Deserialize;
 use serde_json;
-use time::format_description::well_known::Rfc3339;
-use time::OffsetDateTime;
 
 use super::core::GcsCore;
 use super::error::parse_error;
@@ -113,10 +111,7 @@ impl oio::Page for GcsPager {
                 meta.set_content_type(&object.content_type);
             }
 
-            let dt = OffsetDateTime::parse(object.updated.as_str(), &Rfc3339).map_err(|e| {
-                Error::new(ErrorKind::Unexpected, "parse last modified as rfc3339").set_source(e)
-            })?;
-            meta.set_last_modified(dt);
+            meta.set_last_modified(parse_datetime_from_rfc3339(object.updated.as_str())?);
 
             let de = oio::Entry::new(&build_rel_path(&self.core.root, &object.name), meta);
 
diff --git a/core/src/services/hdfs/backend.rs b/core/src/services/hdfs/backend.rs
index e3869d4c..e13948d3 100644
--- a/core/src/services/hdfs/backend.rs
+++ b/core/src/services/hdfs/backend.rs
@@ -25,7 +25,6 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use log::debug;
-use time::OffsetDateTime;
 
 use super::error::parse_io_error;
 use super::pager::HdfsPager;
@@ -362,7 +361,7 @@ impl Accessor for HdfsBackend {
         };
         let mut m = Metadata::new(mode);
         m.set_content_length(meta.len());
-        m.set_last_modified(OffsetDateTime::from(meta.modified()));
+        m.set_last_modified(meta.modified().into());
 
         Ok(RpStat::new(m))
     }
@@ -529,7 +528,7 @@ impl Accessor for HdfsBackend {
         };
         let mut m = Metadata::new(mode);
         m.set_content_length(meta.len());
-        m.set_last_modified(OffsetDateTime::from(meta.modified()));
+        m.set_last_modified(meta.modified().into());
 
         Ok(RpStat::new(m))
     }
diff --git a/core/src/services/hdfs/pager.rs b/core/src/services/hdfs/pager.rs
index ae68ba9d..28b8ca5f 100644
--- a/core/src/services/hdfs/pager.rs
+++ b/core/src/services/hdfs/pager.rs
@@ -56,7 +56,7 @@ impl oio::Page for HdfsPager {
             let d = if de.is_file() {
                 let meta = Metadata::new(EntryMode::FILE)
                     .with_content_length(de.len())
-                    .with_last_modified(time::OffsetDateTime::from(de.modified()));
+                    .with_last_modified(de.modified().into());
                 oio::Entry::new(&path, meta)
             } else if de.is_dir() {
                 // Make sure we are returning the correct path.
@@ -87,7 +87,7 @@ impl oio::BlockingPage for HdfsPager {
             let d = if de.is_file() {
                 let meta = Metadata::new(EntryMode::FILE)
                     .with_content_length(de.len())
-                    .with_last_modified(time::OffsetDateTime::from(de.modified()));
+                    .with_last_modified(de.modified().into());
                 oio::Entry::new(&path, meta)
             } else if de.is_dir() {
                 // Make sure we are returning the correct path.
diff --git a/core/src/services/oss/pager.rs b/core/src/services/oss/pager.rs
index 0ecae280..17a54ad8 100644
--- a/core/src/services/oss/pager.rs
+++ b/core/src/services/oss/pager.rs
@@ -22,8 +22,6 @@ use bytes::Buf;
 use quick_xml::de;
 use quick_xml::escape::unescape;
 use serde::Deserialize;
-use time::format_description::well_known::Rfc3339;
-use time::OffsetDateTime;
 
 use super::backend::OssBackend;
 use super::error::parse_error;
@@ -114,16 +112,7 @@ impl oio::Page for OssPager {
 
             meta.set_etag(&object.etag);
             meta.set_content_length(object.size);
-            let dt = OffsetDateTime::parse(object.last_modified.as_str(), &Rfc3339)
-                .map(|v| {
-                    v.replace_nanosecond(0)
-                        .expect("replace nanosecond of last modified must succeed")
-                })
-                .map_err(|e| {
-                    Error::new(ErrorKind::Unexpected, "parse str into rfc 3339 datetime")
-                        .set_source(e)
-                })?;
-            meta.set_last_modified(dt);
+            meta.set_last_modified(parse_datetime_from_rfc3339(object.last_modified.as_str())?);
 
             let rel = build_rel_path(&self.root, &object.key);
             let path = unescape(&rel)
diff --git a/core/src/services/s3/pager.rs b/core/src/services/s3/pager.rs
index e7036134..250516df 100644
--- a/core/src/services/s3/pager.rs
+++ b/core/src/services/s3/pager.rs
@@ -21,15 +21,11 @@ use async_trait::async_trait;
 use bytes::Buf;
 use quick_xml::de;
 use serde::Deserialize;
-use time::format_description::well_known::Rfc3339;
-use time::OffsetDateTime;
 
 use super::core::S3Core;
 use super::error::parse_error;
 use crate::raw::*;
 use crate::EntryMode;
-use crate::Error;
-use crate::ErrorKind;
 use crate::Metadata;
 use crate::Result;
 
@@ -120,19 +116,7 @@ impl oio::Page for S3Pager {
 
             // object.last_modified provides more precious time that contains
             // nanosecond, let's trim them.
-            let dt = OffsetDateTime::parse(object.last_modified.as_str(), &Rfc3339)
-                .map(|v| {
-                    v.replace_nanosecond(0)
-                        .expect("replace nanosecond of last modified must succeed")
-                })
-                .map_err(|e| {
-                    Error::new(
-                        ErrorKind::Unexpected,
-                        "parse last modified RFC3339 datetime",
-                    )
-                    .set_source(e)
-                })?;
-            meta.set_last_modified(dt);
+            meta.set_last_modified(parse_datetime_from_rfc3339(object.last_modified.as_str())?);
 
             let de = oio::Entry::new(&build_rel_path(&self.core.root, &object.key), meta);
 
diff --git a/core/src/services/webdav/list_response.rs b/core/src/services/webdav/list_response.rs
index 12e34586..e8b5c700 100644
--- a/core/src/services/webdav/list_response.rs
+++ b/core/src/services/webdav/list_response.rs
@@ -16,9 +16,8 @@
 // under the License.
 
 use serde::Deserialize;
-use time::format_description::well_known::Rfc2822;
-use time::OffsetDateTime;
 
+use crate::raw::parse_datetime_from_rfc2822;
 use crate::EntryMode;
 use crate::Error;
 use crate::ErrorKind;
@@ -83,7 +82,7 @@ impl ListOpResponse {
             m.set_etag(v);
         }
         // https://www.rfc-editor.org/rfc/rfc4918#section-14.18
-        m.set_last_modified(OffsetDateTime::parse(getlastmodified, &Rfc2822).unwrap());
+        m.set_last_modified(parse_datetime_from_rfc2822(getlastmodified)?);
         Ok(m)
     }
 }
diff --git a/core/src/services/webhdfs/message.rs b/core/src/services/webhdfs/message.rs
index 76e0cc07..44228ae6 100644
--- a/core/src/services/webhdfs/message.rs
+++ b/core/src/services/webhdfs/message.rs
@@ -17,9 +17,9 @@
 
 //! HTTP response messages
 
-use serde::Deserialize;
-
+use crate::raw::*;
 use crate::*;
+use serde::Deserialize;
 
 #[derive(Debug, Deserialize)]
 pub(super) struct BooleanResp {
@@ -62,12 +62,11 @@ impl TryFrom<FileStatus> for Metadata {
             FileStatusType::Directory => Metadata::new(EntryMode::DIR),
             FileStatusType::File => Metadata::new(EntryMode::FILE),
         };
-        let till_now = time::Duration::milliseconds(value.modification_time);
-        let last_modified = time::OffsetDateTime::UNIX_EPOCH
-            .checked_add(till_now)
-            .ok_or_else(|| Error::new(ErrorKind::Unexpected, "last modification overflowed!"))?;
-        meta.set_last_modified(last_modified)
-            .set_content_length(value.length);
+
+        meta.set_last_modified(parse_datetime_from_from_timestamp_millis(
+            value.modification_time,
+        )?)
+        .set_content_length(value.length);
         Ok(meta)
     }
 }
diff --git a/core/src/types/metadata.rs b/core/src/types/metadata.rs
index cba43db6..a76f58fb 100644
--- a/core/src/types/metadata.rs
+++ b/core/src/types/metadata.rs
@@ -15,9 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use chrono::prelude::*;
 use flagset::flags;
 use flagset::FlagSet;
-use time::OffsetDateTime;
 
 use crate::raw::*;
 use crate::*;
@@ -42,7 +42,7 @@ pub struct Metadata {
     content_range: Option<BytesContentRange>,
     content_type: Option<String>,
     etag: Option<String>,
-    last_modified: Option<OffsetDateTime>,
+    last_modified: Option<DateTime<Utc>>,
 }
 
 impl Metadata {
@@ -246,8 +246,8 @@ impl Metadata {
     /// `Last-Modified` is defined by [RFC 7232](https://httpwg.org/specs/rfc7232.html#header.last-modified)
     /// Refer to [MDN Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) for more information.
     ///
-    /// OpenDAL parse the raw value into [`OffsetDateTime`] for convenient.
-    pub fn last_modified(&self) -> Option<OffsetDateTime> {
+    /// OpenDAL parse the raw value into [`DateTime`] for convenient.
+    pub fn last_modified(&self) -> Option<DateTime<Utc>> {
         debug_assert!(
             self.bit.contains(Metakey::LastModified) || self.bit.contains(Metakey::Complete),
             "visiting not set metadata: last_modified, maybe a bug"
@@ -260,7 +260,7 @@ impl Metadata {
     ///
     /// `Last-Modified` is defined by [RFC 7232](https://httpwg.org/specs/rfc7232.html#header.last-modified)
     /// Refer to [MDN Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) for more information.
-    pub fn set_last_modified(&mut self, last_modified: OffsetDateTime) -> &mut Self {
+    pub fn set_last_modified(&mut self, last_modified: DateTime<Utc>) -> &mut Self {
         self.last_modified = Some(last_modified);
         self.bit |= Metakey::LastModified;
         self
@@ -270,7 +270,7 @@ impl Metadata {
     ///
     /// `Last-Modified` is defined by [RFC 7232](https://httpwg.org/specs/rfc7232.html#header.last-modified)
     /// Refer to [MDN Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) for more information.
-    pub fn with_last_modified(mut self, last_modified: OffsetDateTime) -> Self {
+    pub fn with_last_modified(mut self, last_modified: DateTime<Utc>) -> Self {
         self.last_modified = Some(last_modified);
         self.bit |= Metakey::LastModified;
         self