Repository: sorairolake/nt-time (language: rust)

Issue:
Remove `From<FileTime> for OffsetDateTime`
Because the feature should be additive. Also removes `#[cfg(not(feature = "large-dates"))]` from `TryFrom<FileTime> for OffsetDateTime`.

<https://doc.rust-lang.org/cargo/reference/features.html#feature-unification>


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 2ef47be..75825cf 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -16,16 +16,8 @@ project adheres to https://semver.org/[Semantic Versioning].
 
 == {compare-url}/v0.6.13\...HEAD[Unreleased]
 
-=== Added
-
-* Add `From<FileTime> for OffsetDateTime` for when the `large-dates` feature is
-  enabled ({pull-request-url}/167[#167])
-
 === Changed
 
-* When the `large-dates` feature is enabled, change to use
-  `From<FileTime> for OffsetDateTime` instead of
-  `TryFrom<FileTime> for OffsetDateTime` ({pull-request-url}/167[#167])
 * Change `TryFrom<FileTime> for OffsetDateTime` to return
   `time::error::ComponentRange` instead of `OffsetDateTimeRangeError`
   ({pull-request-url}/167[#167])
diff --git a/examples/date.rs b/examples/date.rs
index dd88f29..0fe9f5d 100644
--- a/examples/date.rs
+++ b/examples/date.rs
@@ -11,6 +11,7 @@
 // Lint levels of Clippy.
 #![warn(clippy::cargo, clippy::nursery, clippy::pedantic)]
 
+use anyhow::Context;
 use clap::Parser;
 use nt_time::{time::OffsetDateTime, FileTime};
 
@@ -21,21 +22,10 @@ struct Opt {
     time: FileTime,
 }
 
-#[cfg(not(feature = "large-dates"))]
 fn main() -> anyhow::Result<()> {
-    use anyhow::Context;
-
     let opt = Opt::parse();
 
     let dt = OffsetDateTime::try_from(opt.time).context("could not convert time")?;
     println!("{dt}");
     Ok(())
 }
-
-#[cfg(feature = "large-dates")]
-fn main() {
-    let opt = Opt::parse();
-
-    let dt = OffsetDateTime::from(opt.time);
-    println!("{dt}");
-}
diff --git a/src/file_time/cmp.rs b/src/file_time/cmp.rs
index 3e144dd..696790c 100644
--- a/src/file_time/cmp.rs
+++ b/src/file_time/cmp.rs
@@ -29,32 +29,18 @@ impl PartialEq<std::time::SystemTime> for FileTime {
 }
 
 impl PartialEq<FileTime> for OffsetDateTime {
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn eq(&self, other: &FileTime) -> bool {
         self == &Self::try_from(*other).expect("`other` is out of range for `OffsetDateTime`")
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn eq(&self, other: &FileTime) -> bool {
-        self == &Self::from(*other)
-    }
 }
 
 impl PartialEq<OffsetDateTime> for FileTime {
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn eq(&self, other: &OffsetDateTime) -> bool {
         &OffsetDateTime::try_from(*self).expect("`self` is out of range for `OffsetDateTime`")
             == other
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn eq(&self, other: &OffsetDateTime) -> bool {
-        &OffsetDateTime::from(*self) == other
-    }
 }
 
 #[cfg(feature = "chrono")]
@@ -110,35 +96,21 @@ impl PartialOrd<std::time::SystemTime> for FileTime {
 }
 
 impl PartialOrd<FileTime> for OffsetDateTime {
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn partial_cmp(&self, other: &FileTime) -> Option<Ordering> {
         self.partial_cmp(
             &Self::try_from(*other).expect("`other` is out of range for `OffsetDateTime`"),
         )
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn partial_cmp(&self, other: &FileTime) -> Option<Ordering> {
-        self.partial_cmp(&Self::from(*other))
-    }
 }
 
 impl PartialOrd<OffsetDateTime> for FileTime {
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
         OffsetDateTime::try_from(*self)
             .expect("`self` is out of range for `OffsetDateTime`")
             .partial_cmp(other)
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
-        OffsetDateTime::from(*self).partial_cmp(other)
-    }
 }
 
 #[cfg(feature = "chrono")]
diff --git a/src/file_time/convert.rs b/src/file_time/convert.rs
index 9f71864..e94d628 100644
--- a/src/file_time/convert.rs
+++ b/src/file_time/convert.rs
@@ -6,7 +6,7 @@
 
 use core::num::TryFromIntError;
 
-use time::OffsetDateTime;
+use time::{error::ComponentRange, OffsetDateTime};
 
 use crate::error::{FileTimeRangeError, FileTimeRangeErrorKind};
 
@@ -115,9 +115,8 @@ impl From<FileTime> for std::time::SystemTime {
     }
 }
 
-#[cfg(not(feature = "large-dates"))]
 impl TryFrom<FileTime> for OffsetDateTime {
-    type Error = time::error::ComponentRange;
+    type Error = ComponentRange;
 
     /// Converts a `FileTime` to a [`OffsetDateTime`].
     ///
@@ -141,20 +140,19 @@ impl TryFrom<FileTime> for OffsetDateTime {
     ///     OffsetDateTime::try_from(FileTime::UNIX_EPOCH).unwrap(),
     ///     OffsetDateTime::UNIX_EPOCH
     /// );
+    /// ```
+    ///
+    /// With the `large-dates` feature disabled, returns [`Err`] if the file
+    /// time represents after "9999-12-31 23:59:59.999999900 UTC":
     ///
-    /// // After `9999-12-31 23:59:59.999999900 UTC`.
+    /// ```
+    /// # use nt_time::{time::OffsetDateTime, FileTime};
+    /// #
+    /// # #[cfg(not(feature = "large-dates"))]
     /// assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err());
     /// ```
-    fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
-        Self::from_unix_timestamp_nanos(ft.to_unix_time_nanos())
-    }
-}
-
-#[cfg(feature = "large-dates")]
-impl From<FileTime> for OffsetDateTime {
-    /// Converts a `FileTime` to a [`OffsetDateTime`].
     ///
-    /// # Examples
+    /// With the `large-dates` feature enabled, this always succeeds:
     ///
     /// ```
     /// # use nt_time::{
@@ -162,22 +160,19 @@ impl From<FileTime> for OffsetDateTime {
     /// #     FileTime,
     /// # };
     /// #
+    /// # #[cfg(feature = "large-dates")]
     /// assert_eq!(
-    ///     OffsetDateTime::from(FileTime::NT_TIME_EPOCH),
-    ///     datetime!(1601-01-01 00:00 UTC)
-    /// );
-    /// assert_eq!(
-    ///     OffsetDateTime::from(FileTime::UNIX_EPOCH),
-    ///     OffsetDateTime::UNIX_EPOCH
+    ///     OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(),
+    ///     datetime!(+10000-01-01 00:00 UTC)
     /// );
+    /// # #[cfg(feature = "large-dates")]
     /// assert_eq!(
-    ///     OffsetDateTime::from(FileTime::MAX),
+    ///     OffsetDateTime::try_from(FileTime::MAX).unwrap(),
     ///     datetime!(+60056-05-28 05:36:10.955_161_500 UTC)
     /// );
     /// ```
-    fn from(ft: FileTime) -> Self {
+    fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
         Self::from_unix_timestamp_nanos(ft.to_unix_time_nanos())
-            .expect("date and time should be in the range of `OffsetDateTime`")
     }
 }
 
@@ -600,7 +595,6 @@ mod tests {
         }
     }
 
-    #[cfg(not(feature = "large-dates"))]
     #[test]
     fn try_from_file_time_to_offset_date_time() {
         use time::macros::datetime;
@@ -627,31 +621,19 @@ mod tests {
 
     #[cfg(feature = "large-dates")]
     #[test]
-    fn from_file_time_to_offset_date_time() {
+    fn try_from_file_time_to_offset_date_time_with_large_dates() {
         use time::macros::datetime;
 
         assert_eq!(
-            OffsetDateTime::from(FileTime::NT_TIME_EPOCH),
-            datetime!(1601-01-01 00:00 UTC)
-        );
-        assert_eq!(
-            OffsetDateTime::from(FileTime::UNIX_EPOCH),
-            OffsetDateTime::UNIX_EPOCH
-        );
-        assert_eq!(
-            OffsetDateTime::from(FileTime::new(2_650_467_743_999_999_999)),
-            datetime!(9999-12-31 23:59:59.999_999_900 UTC)
-        );
-        assert_eq!(
-            OffsetDateTime::from(FileTime::new(2_650_467_744_000_000_000)),
+            OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(),
             datetime!(+10000-01-01 00:00 UTC)
         );
         assert_eq!(
-            OffsetDateTime::from(FileTime::new(i64::MAX.try_into().unwrap())),
+            OffsetDateTime::try_from(FileTime::new(i64::MAX.try_into().unwrap())).unwrap(),
             datetime!(+30828-09-14 02:48:05.477_580_700 UTC)
         );
         assert_eq!(
-            OffsetDateTime::from(FileTime::MAX),
+            OffsetDateTime::try_from(FileTime::MAX).unwrap(),
             datetime!(+60056-05-28 05:36:10.955_161_500 UTC)
         );
     }
diff --git a/src/file_time/dos_date_time.rs b/src/file_time/dos_date_time.rs
index 8a4440b..c68ee24 100644
--- a/src/file_time/dos_date_time.rs
+++ b/src/file_time/dos_date_time.rs
@@ -126,12 +126,10 @@ impl FileTime {
             // The UTC offset must be in the range of a 7-bit signed integer.
             assert!((offset!(-16:00)..=offset!(+15:45)).contains(&o));
         }
-        #[cfg(not(feature = "large-dates"))]
         let dt = OffsetDateTime::try_from(self)
-            .map_err(|_| DosDateTimeRangeError::new(DosDateTimeRangeErrorKind::Overflow))?;
-        #[cfg(feature = "large-dates")]
-        let dt = OffsetDateTime::from(self);
-        let dt = dt.to_offset(offset.unwrap_or(UtcOffset::UTC));
+            .ok()
+            .and_then(|dt| dt.checked_to_offset(offset.unwrap_or(UtcOffset::UTC)))
+            .ok_or_else(|| DosDateTimeRangeError::new(DosDateTimeRangeErrorKind::Overflow))?;
         match dt.year() {
             ..=1979 => Err(DosDateTimeRangeError::new(
                 DosDateTimeRangeErrorKind::Negative,
diff --git a/src/file_time/ops.rs b/src/file_time/ops.rs
index 96af32a..36599b4 100644
--- a/src/file_time/ops.rs
+++ b/src/file_time/ops.rs
@@ -274,33 +274,19 @@ impl Sub<std::time::SystemTime> for FileTime {
 impl Sub<FileTime> for OffsetDateTime {
     type Output = time::Duration;
 
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn sub(self, rhs: FileTime) -> Self::Output {
         self - Self::try_from(rhs).expect("RHS is out of range for `OffsetDateTime`")
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn sub(self, rhs: FileTime) -> Self::Output {
-        self - Self::from(rhs)
-    }
 }
 
 impl Sub<OffsetDateTime> for FileTime {
     type Output = time::Duration;
 
-    #[cfg(not(feature = "large-dates"))]
     #[inline]
     fn sub(self, rhs: OffsetDateTime) -> Self::Output {
         OffsetDateTime::try_from(self).expect("LHS is out of range for `OffsetDateTime`") - rhs
     }
-
-    #[cfg(feature = "large-dates")]
-    #[inline]
-    fn sub(self, rhs: OffsetDateTime) -> Self::Output {
-        OffsetDateTime::from(self) - rhs
-    }
 }
 
 #[cfg(feature = "chrono")]
diff --git a/src/serde_with/iso_8601.rs b/src/serde_with/iso_8601.rs
index 021f6e6..2d586a6 100644
--- a/src/serde_with/iso_8601.rs
+++ b/src/serde_with/iso_8601.rs
@@ -36,7 +36,7 @@
 
 pub mod option;
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::serde::iso8601;
 
 use crate::FileTime;
@@ -48,17 +48,7 @@ use crate::FileTime;
 ///
 /// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
 pub fn serialize<S: Serializer>(ft: &FileTime, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        iso8601::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        iso8601::serialize(&(*ft).into(), serializer)
-    }
+    iso8601::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
 }
 
 #[allow(clippy::missing_errors_doc)]
diff --git a/src/serde_with/iso_8601/option.rs b/src/serde_with/iso_8601/option.rs
index 226743f..f7b3b39 100644
--- a/src/serde_with/iso_8601/option.rs
+++ b/src/serde_with/iso_8601/option.rs
@@ -42,7 +42,7 @@
 //! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
 //! [`with`]: https://serde.rs/field-attrs.html#with
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::{serde::iso8601, OffsetDateTime};
 
 use crate::FileTime;
@@ -54,23 +54,13 @@ use crate::FileTime;
 ///
 /// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
 pub fn serialize<S: Serializer>(ft: &Option<FileTime>, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        iso8601::option::serialize(
-            &(*ft)
-                .map(OffsetDateTime::try_from)
-                .transpose()
-                .map_err(S::Error::custom)?,
-            serializer,
-        )
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        iso8601::option::serialize(&(*ft).map(OffsetDateTime::from), serializer)
-    }
+    iso8601::option::serialize(
+        &(*ft)
+            .map(OffsetDateTime::try_from)
+            .transpose()
+            .map_err(S::Error::custom)?,
+        serializer,
+    )
 }
 
 #[allow(clippy::missing_errors_doc)]
diff --git a/src/serde_with/rfc_2822.rs b/src/serde_with/rfc_2822.rs
index dbe599c..a46ff21 100644
--- a/src/serde_with/rfc_2822.rs
+++ b/src/serde_with/rfc_2822.rs
@@ -33,7 +33,7 @@
 
 pub mod option;
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::serde::rfc2822;
 
 use crate::FileTime;
@@ -45,17 +45,7 @@ use crate::FileTime;
 ///
 /// [RFC 2822 format]: https://datatracker.ietf.org/doc/html/rfc2822#section-3.3
 pub fn serialize<S: Serializer>(ft: &FileTime, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        rfc2822::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        rfc2822::serialize(&(*ft).into(), serializer)
-    }
+    rfc2822::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
 }
 
 #[allow(clippy::missing_errors_doc)]
diff --git a/src/serde_with/rfc_2822/option.rs b/src/serde_with/rfc_2822/option.rs
index 376bbeb..a852a59 100644
--- a/src/serde_with/rfc_2822/option.rs
+++ b/src/serde_with/rfc_2822/option.rs
@@ -39,7 +39,7 @@
 //! [RFC 2822 format]: https://datatracker.ietf.org/doc/html/rfc2822#section-3.3
 //! [`with`]: https://serde.rs/field-attrs.html#with
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::{serde::rfc2822, OffsetDateTime};
 
 use crate::FileTime;
@@ -51,23 +51,13 @@ use crate::FileTime;
 ///
 /// [RFC 2822 format]: https://datatracker.ietf.org/doc/html/rfc2822#section-3.3
 pub fn serialize<S: Serializer>(ft: &Option<FileTime>, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        rfc2822::option::serialize(
-            &(*ft)
-                .map(OffsetDateTime::try_from)
-                .transpose()
-                .map_err(S::Error::custom)?,
-            serializer,
-        )
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        rfc2822::option::serialize(&(*ft).map(OffsetDateTime::from), serializer)
-    }
+    rfc2822::option::serialize(
+        &(*ft)
+            .map(OffsetDateTime::try_from)
+            .transpose()
+            .map_err(S::Error::custom)?,
+        serializer,
+    )
 }
 
 #[allow(clippy::missing_errors_doc)]
diff --git a/src/serde_with/rfc_3339.rs b/src/serde_with/rfc_3339.rs
index 0bd7749..ebb1270 100644
--- a/src/serde_with/rfc_3339.rs
+++ b/src/serde_with/rfc_3339.rs
@@ -33,7 +33,7 @@
 
 pub mod option;
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::serde::rfc3339;
 
 use crate::FileTime;
@@ -45,17 +45,7 @@ use crate::FileTime;
 ///
 /// [RFC 3339 format]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
 pub fn serialize<S: Serializer>(ft: &FileTime, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        rfc3339::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        rfc3339::serialize(&(*ft).into(), serializer)
-    }
+    rfc3339::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
 }
 
 #[allow(clippy::missing_errors_doc)]
diff --git a/src/serde_with/rfc_3339/option.rs b/src/serde_with/rfc_3339/option.rs
index 4d9cdc9..e00f194 100644
--- a/src/serde_with/rfc_3339/option.rs
+++ b/src/serde_with/rfc_3339/option.rs
@@ -39,7 +39,7 @@
 //! [RFC 3339 format]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
 //! [`with`]: https://serde.rs/field-attrs.html#with
 
-use serde::{de::Error as _, Deserializer, Serializer};
+use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer};
 use time::{serde::rfc3339, OffsetDateTime};
 
 use crate::FileTime;
@@ -51,23 +51,13 @@ use crate::FileTime;
 ///
 /// [RFC 3339 format]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
 pub fn serialize<S: Serializer>(ft: &Option<FileTime>, serializer: S) -> Result<S::Ok, S::Error> {
-    #[cfg(not(feature = "large-dates"))]
-    {
-        use serde::ser::Error as _;
-
-        rfc3339::option::serialize(
-            &(*ft)
-                .map(OffsetDateTime::try_from)
-                .transpose()
-                .map_err(S::Error::custom)?,
-            serializer,
-        )
-    }
-
-    #[cfg(feature = "large-dates")]
-    {
-        rfc3339::option::serialize(&(*ft).map(OffsetDateTime::from), serializer)
-    }
+    rfc3339::option::serialize(
+        &(*ft)
+            .map(OffsetDateTime::try_from)
+            .transpose()
+            .map_err(S::Error::custom)?,
+        serializer,
+    )
 }
 
 #[allow(clippy::missing_errors_doc)]


Test patch: