diff --git a/Cargo.lock b/Cargo.lock index 796ac0cb821..50ebc5e3e88 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", @@ -2261,7 +2261,6 @@ dependencies = [ "size", "sled", "suppaftp", - "time 0.3.17", "tokio", "tracing", "tracing-opentelemetry", @@ -2298,7 +2297,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 8f1f287aea4..6931cbe080d 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 aa14cfaa43b..92c3f16bc93 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 { - 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 30618e0302c..e6c172f79c8 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 b9c6fb7ccd2..f5d89f583ad 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 5320f093140..c3c167c51ec 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -133,7 +133,7 @@ suppaftp = { version = "4.5", default-features = false, features = [ "async-secure", "async-rustls", ], optional = true } -time = { version = "0.3", features = ["serde"] } +chrono = "0.4.24" tokio = { version = "1.27", features = ["fs"] } tracing = { version = "0.1", optional = true } trust-dns-resolver = { version = "0.22", optional = true } diff --git a/core/src/raw/chrono_util.rs b/core/src/raw/chrono_util.rs new file mode 100644 index 00000000000..2039ee19e2e --- /dev/null +++ b/core/src/raw/chrono_util.rs @@ -0,0 +1,55 @@ +// 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; +use std::time::UNIX_EPOCH; + +use chrono::DateTime; +use chrono::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::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::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> { + let st = UNIX_EPOCH + .checked_add(Duration::from_millis(s as u64)) + .ok_or_else(|| Error::new(ErrorKind::Unexpected, "input timestamp overflow"))?; + + Ok(st.into()) +} diff --git a/core/src/raw/http_util/header.rs b/core/src/raw/http_util/header.rs index 763d74b3dc0..19075bf4520 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 Result> { +pub fn parse_last_modified(headers: &HeaderMap) -> Result>> { match headers.get(LAST_MODIFIED) { None => Ok(None), Some(v) => { @@ -142,16 +142,8 @@ pub fn parse_last_modified(headers: &HeaderMap) -> Result .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 18a925b5958..4cfd69c7b0a 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 b0fad0e6e5f..540b016443e 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 4f414a04ce5..e3e29a027f8 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 1bed68a6d01..e5159db52a9 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 cd724d7b3d7..75262323219 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 fc669efacf2..e1e1c0af190 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 bca412fa290..0bbaddea4fe 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 6cb66aac55d..bfa99981104 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 e3869d4c744..e13948d35dc 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 ae68ba9d446..28b8ca5f9c4 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 5a129b14ff9..18bdd2775c4 100644 --- a/core/src/services/oss/pager.rs +++ b/core/src/services/oss/pager.rs @@ -22,11 +22,8 @@ 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::core::*; - use super::error::parse_error; use crate::raw::*; use crate::EntryMode; @@ -108,16 +105,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.core.root, &object.key); let path = unescape(&rel) diff --git a/core/src/services/oss/writer.rs b/core/src/services/oss/writer.rs index 8a41a47e44e..b306daad7c0 100644 --- a/core/src/services/oss/writer.rs +++ b/core/src/services/oss/writer.rs @@ -15,13 +15,13 @@ // specific language governing permissions and limitations // under the License. +use std::sync::Arc; + use async_trait::async_trait; use bytes::Bytes; use http::StatusCode; -use std::sync::Arc; use super::core::*; - use super::error::parse_error; use crate::ops::OpWrite; use crate::raw::*; diff --git a/core/src/services/s3/pager.rs b/core/src/services/s3/pager.rs index e7036134206..250516df2b2 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 12e34586c69..e8b5c700061 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 76e0cc0765a..9abc4385c83 100644 --- a/core/src/services/webhdfs/message.rs +++ b/core/src/services/webhdfs/message.rs @@ -19,6 +19,7 @@ use serde::Deserialize; +use crate::raw::*; use crate::*; #[derive(Debug, Deserialize)] @@ -62,12 +63,11 @@ impl TryFrom 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 cba43db6803..a76f58fb241 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, content_type: Option, etag: Option, - last_modified: Option, + last_modified: Option>, } 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 { + /// OpenDAL parse the raw value into [`DateTime`] for convenient. + pub fn last_modified(&self) -> Option> { 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) -> &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) -> Self { self.last_modified = Some(last_modified); self.bit |= Metakey::LastModified; self