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

Issue:
Add from low and high DWORD
### Checklist

- [X] I have searched the existing issues

### Summary

The [`FILETIME`](https://learn.microsoft.com/windows/win32/api/minwinbase/ns-minwinbase-filetime) I'm dealing with is stored as a separate low and high [`DWORD`](https://learn.microsoft.com/openspecs/windows_protocols/ms-dtyp/262627d8-3418-4627-9218-4ffe110850b2) (u32).

Having a `FileTime::from_low_high` (or similarly named) function would be helpful as it saves me from doing `Filetime::new(u64::from(high) << u32::BITS | u64::from(low))`.

### Additional context

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:
@russellbanks I think the API looks like this:

```rust
fn from_high_low(high: u32, low: u32) -> FileTime

fn to_high_low(self) -> (u32, u32)
// or
fn to_high_low(self) -> [u32; 2]
```

I think it's more natural to put the high bit first. Because the most significant bit is often on the left. Also, do you think it's better to return a tuple (`(u32, u32)`) or an array (`[u32; 2]`) for `to_high_low`?
> Also, do you think it's better to return a tuple (`(u32, u32)`) or an array (`[u32; 2]`) for `to_high_low`?

Returning a tuple because it can be destructured:

```rust
let (high, low) = filetime.to_high_low();
```



Fix patch:
diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml
index d9d7113..42adb1f 100644
--- a/.github/workflows/CI.yaml
+++ b/.github/workflows/CI.yaml
@@ -24,7 +24,7 @@ jobs:
           - aarch64-apple-darwin
           - x86_64-pc-windows-msvc
         toolchain:
-          - 1.67.0 # MSRV
+          - 1.67.1 # MSRV
           - stable
         include:
           - target: x86_64-unknown-linux-gnu
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index f4312bd..134b3e6 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -14,6 +14,17 @@ All notable changes to this project will be documented in this file.
 The format is based on https://keepachangelog.com/[Keep a Changelog], and this
 project adheres to https://semver.org/[Semantic Versioning].
 
+== {compare-url}/v0.10.3\...HEAD[Unreleased]
+
+=== Added
+
+* Add `FileTime::to_high_low` and `FileTime::from_high_low`
+  ({pull-request-url}/252[#252])
+
+=== Changed
+
+* Change MSRV to 1.67.1 ({pull-request-url}/252[#252])
+
 == {compare-url}/v0.10.2\...v0.10.3[0.10.3] - 2024-11-09
 
 === Added
diff --git a/Cargo.toml b/Cargo.toml
index 23945f7..a589420 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ name = "nt-time"
 version = "0.10.3"
 authors = ["Shun Sakai <sorairolake@protonmail.ch>"]
 edition = "2021"
-rust-version = "1.67.0"
+rust-version = "1.67.1"
 description = "A Windows file time library"
 documentation = "https://docs.rs/nt-time"
 readme = "README.md"
diff --git a/README.md b/README.md
index 0c749d3..0264b3d 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ See the [documentation][docs-url] for more details.
 
 ## Minimum supported Rust version
 
-The minimum supported Rust version (MSRV) of this library is v1.67.0.
+The minimum supported Rust version (MSRV) of this library is v1.67.1.
 
 ## Source code
 
diff --git a/clippy.toml b/clippy.toml
index 51c36de..956257c 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -2,4 +2,4 @@
 #
 # SPDX-License-Identifier: Apache-2.0 OR MIT
 
-msrv = "1.67.0"
+msrv = "1.67.1"
diff --git a/src/file_time.rs b/src/file_time.rs
index 1ce774c..ce87da7 100644
--- a/src/file_time.rs
+++ b/src/file_time.rs
@@ -255,6 +255,77 @@ impl FileTime {
     pub const fn from_ne_bytes(bytes: [u8; mem::size_of::<Self>()]) -> Self {
         Self::new(u64::from_ne_bytes(bytes))
     }
+
+    #[allow(clippy::cast_possible_truncation)]
+    /// Returns the high-order and low-order parts of this `FileTime`.
+    ///
+    /// The first return value represents the high-order part of this
+    /// `FileTime`, and the second return value represents the low-order part of
+    /// this `FileTime`.
+    ///
+    /// The first return value corresponds to the `dwHighDateTime` member of the
+    /// [`FILETIME`] structure of the [Win32 API], and the second return value
+    /// corresponds to the `dwLowDateTime` member of the `FILETIME` structure.
+    /// The data type of these members is [`DWORD`], a 32-bit unsigned integer
+    /// type the same as [`u32`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use nt_time::FileTime;
+    /// #
+    /// assert_eq!(FileTime::NT_TIME_EPOCH.to_high_low(), (u32::MIN, u32::MIN));
+    /// assert_eq!(
+    ///     FileTime::UNIX_EPOCH.to_high_low(),
+    ///     (0x019d_b1de, 0xd53e_8000)
+    /// );
+    /// assert_eq!(FileTime::MAX.to_high_low(), (u32::MAX, u32::MAX));
+    /// ```
+    ///
+    /// [`FILETIME`]: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
+    /// [Win32 API]: https://learn.microsoft.com/en-us/windows/win32/
+    /// [`DWORD`]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/262627d8-3418-4627-9218-4ffe110850b2
+    #[must_use]
+    #[inline]
+    pub const fn to_high_low(self) -> (u32, u32) {
+        let raw = self.to_raw();
+        ((raw >> u32::BITS) as u32, raw as u32)
+    }
+
+    /// Creates a `FileTime` from [`u32`] values representing the high-order and
+    /// low-order parts of the file time.
+    ///
+    /// `high` corresponds to the `dwHighDateTime` member of the [`FILETIME`]
+    /// structure of the [Win32 API], and `low` corresponds to the
+    /// `dwLowDateTime` member of the `FILETIME` structure. The data type of
+    /// these members is [`DWORD`], a 32-bit unsigned integer type the same as
+    /// [`u32`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use nt_time::FileTime;
+    /// #
+    /// assert_eq!(
+    ///     FileTime::from_high_low(u32::MIN, u32::MIN),
+    ///     FileTime::NT_TIME_EPOCH
+    /// );
+    /// assert_eq!(
+    ///     FileTime::from_high_low(0x019d_b1de, 0xd53e_8000),
+    ///     FileTime::UNIX_EPOCH
+    /// );
+    /// assert_eq!(FileTime::from_high_low(u32::MAX, u32::MAX), FileTime::MAX);
+    /// ```
+    ///
+    /// [`FILETIME`]: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
+    /// [Win32 API]: https://learn.microsoft.com/en-us/windows/win32/
+    /// [`DWORD`]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/262627d8-3418-4627-9218-4ffe110850b2
+    #[must_use]
+    #[inline]
+    pub const fn from_high_low(high: u32, low: u32) -> Self {
+        let raw = ((high as u64) << u32::BITS) | (low as u64);
+        Self::new(raw)
+    }
 }
 
 impl Default for FileTime {
@@ -561,6 +632,58 @@ mod tests {
         const _: FileTime = FileTime::from_ne_bytes([u8::MIN; 8]);
     }
 
+    #[test]
+    fn to_high_low() {
+        assert_eq!(FileTime::NT_TIME_EPOCH.to_high_low(), (u32::MIN, u32::MIN));
+        assert_eq!(
+            FileTime::UNIX_EPOCH.to_high_low(),
+            (0x019d_b1de, 0xd53e_8000)
+        );
+        assert_eq!(FileTime::MAX.to_high_low(), (u32::MAX, u32::MAX));
+    }
+
+    #[cfg(feature = "std")]
+    #[allow(clippy::cast_possible_truncation)]
+    #[test_strategy::proptest]
+    fn to_high_low_roundtrip(ft: FileTime) {
+        use proptest::prop_assert_eq;
+
+        let raw = ft.to_raw();
+        prop_assert_eq!(ft.to_high_low(), ((raw >> u32::BITS) as u32, raw as u32));
+    }
+
+    #[test]
+    const fn to_high_low_is_const_fn() {
+        const _: (u32, u32) = FileTime::NT_TIME_EPOCH.to_high_low();
+    }
+
+    #[test]
+    fn from_high_low() {
+        assert_eq!(
+            FileTime::from_high_low(u32::MIN, u32::MIN),
+            FileTime::NT_TIME_EPOCH
+        );
+        assert_eq!(
+            FileTime::from_high_low(0x019d_b1de, 0xd53e_8000),
+            FileTime::UNIX_EPOCH
+        );
+        assert_eq!(FileTime::from_high_low(u32::MAX, u32::MAX), FileTime::MAX);
+    }
+
+    #[cfg(feature = "std")]
+    #[test_strategy::proptest]
+    fn from_high_low_roundtrip(high: u32, low: u32) {
+        use proptest::prop_assert_eq;
+
+        let raw = (u64::from(high) << u32::BITS) | u64::from(low);
+        prop_assert_eq!(FileTime::from_high_low(high, low), FileTime::new(raw));
+    }
+
+    #[test]
+    const fn from_high_low_is_const_fn() {
+        const _: FileTime = FileTime::from_high_low(u32::MIN, u32::MIN);
+    }
+
     #[test]
     fn default() {
         assert_eq!(FileTime::default(), FileTime::NT_TIME_EPOCH);


Test patch: