nt_time/file_time/
convert.rs

1// SPDX-FileCopyrightText: 2023 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Implementations of conversions between [`FileTime`] and other types.
6
7use core::num::TryFromIntError;
8
9use time::{OffsetDateTime, error::ComponentRange};
10
11use super::FileTime;
12use crate::error::{FileTimeRangeError, FileTimeRangeErrorKind};
13
14impl From<FileTime> for u64 {
15    /// Converts a `FileTime` to the file time.
16    ///
17    /// Equivalent to [`FileTime::to_raw`] except that it is not callable in
18    /// const contexts.
19    ///
20    /// # Examples
21    ///
22    /// ```
23    /// # use nt_time::FileTime;
24    /// #
25    /// assert_eq!(u64::from(FileTime::NT_TIME_EPOCH), u64::MIN);
26    /// assert_eq!(u64::from(FileTime::UNIX_EPOCH), 116_444_736_000_000_000);
27    /// assert_eq!(u64::from(FileTime::SIGNED_MAX), i64::MAX as u64);
28    /// assert_eq!(u64::from(FileTime::MAX), u64::MAX);
29    /// ```
30    #[inline]
31    fn from(ft: FileTime) -> Self {
32        ft.to_raw()
33    }
34}
35
36impl TryFrom<FileTime> for i64 {
37    type Error = TryFromIntError;
38
39    /// Converts a `FileTime` to the file time.
40    ///
41    /// The file time is sometimes represented as an [`i64`] value, such as in
42    /// the [`DateTime.FromFileTimeUtc`] method and the
43    /// [`DateTime.ToFileTimeUtc`] method in [.NET].
44    ///
45    /// # Errors
46    ///
47    /// Returns [`Err`] if `ft` is after "+30828-09-14 02:48:05.477580700 UTC".
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// # use nt_time::FileTime;
53    /// #
54    /// assert_eq!(i64::try_from(FileTime::NT_TIME_EPOCH), Ok(0));
55    /// assert_eq!(
56    ///     i64::try_from(FileTime::UNIX_EPOCH),
57    ///     Ok(116_444_736_000_000_000)
58    /// );
59    /// assert_eq!(i64::try_from(FileTime::SIGNED_MAX), Ok(i64::MAX));
60    ///
61    /// assert!(i64::try_from(FileTime::MAX).is_err());
62    /// ```
63    ///
64    /// [`DateTime.FromFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.fromfiletimeutc
65    /// [`DateTime.ToFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tofiletimeutc
66    /// [.NET]: https://dotnet.microsoft.com/
67    #[inline]
68    fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
69        ft.to_raw().try_into()
70    }
71}
72
73#[cfg(feature = "std")]
74impl From<FileTime> for std::time::SystemTime {
75    /// Converts a `FileTime` to a [`SystemTime`](std::time::SystemTime).
76    ///
77    /// # Panics
78    ///
79    /// Panics if the resulting time cannot be represented by a
80    /// [`SystemTime`](std::time::SystemTime).
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// # use std::time::{Duration, SystemTime};
86    /// #
87    /// # use nt_time::FileTime;
88    /// #
89    /// assert_eq!(
90    ///     SystemTime::from(FileTime::NT_TIME_EPOCH),
91    ///     SystemTime::UNIX_EPOCH - Duration::from_secs(11_644_473_600)
92    /// );
93    /// assert_eq!(
94    ///     SystemTime::from(FileTime::UNIX_EPOCH),
95    ///     SystemTime::UNIX_EPOCH
96    /// );
97    /// ```
98    #[inline]
99    fn from(ft: FileTime) -> Self {
100        use std::time::Duration;
101
102        use super::FILE_TIMES_PER_SEC;
103
104        let duration = Duration::new(
105            ft.to_raw() / FILE_TIMES_PER_SEC,
106            u32::try_from((ft.to_raw() % FILE_TIMES_PER_SEC) * 100)
107                .expect("the number of nanoseconds should be in the range of `u32`"),
108        );
109        (Self::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)) + duration
110    }
111}
112
113impl TryFrom<FileTime> for OffsetDateTime {
114    type Error = ComponentRange;
115
116    /// Converts a `FileTime` to a [`OffsetDateTime`].
117    ///
118    /// # Errors
119    ///
120    /// Returns [`Err`] if `ft` is out of range for [`OffsetDateTime`].
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// # use nt_time::{
126    /// #     FileTime,
127    /// #     time::{OffsetDateTime, macros::datetime},
128    /// # };
129    /// #
130    /// assert_eq!(
131    ///     OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH),
132    ///     Ok(datetime!(1601-01-01 00:00 UTC))
133    /// );
134    /// assert_eq!(
135    ///     OffsetDateTime::try_from(FileTime::UNIX_EPOCH),
136    ///     Ok(OffsetDateTime::UNIX_EPOCH)
137    /// );
138    /// ```
139    ///
140    /// With the `large-dates` feature disabled, returns [`Err`] if the file
141    /// time represents after "9999-12-31 23:59:59.999999900 UTC":
142    ///
143    /// ```
144    /// # #[cfg(not(feature = "large-dates"))]
145    /// # {
146    /// # use nt_time::{FileTime, time::OffsetDateTime};
147    /// #
148    /// assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err());
149    /// # }
150    /// ```
151    ///
152    /// With the `large-dates` feature enabled, this always succeeds:
153    ///
154    /// ```
155    /// # #[cfg(feature = "large-dates")]
156    /// # {
157    /// # use nt_time::{
158    /// #     FileTime,
159    /// #     time::{OffsetDateTime, macros::datetime},
160    /// # };
161    /// #
162    /// assert_eq!(
163    ///     OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)),
164    ///     Ok(datetime!(+10000-01-01 00:00 UTC))
165    /// );
166    /// assert_eq!(
167    ///     OffsetDateTime::try_from(FileTime::SIGNED_MAX),
168    ///     Ok(datetime!(+30828-09-14 02:48:05.477_580_700 UTC))
169    /// );
170    /// assert_eq!(
171    ///     OffsetDateTime::try_from(FileTime::MAX),
172    ///     Ok(datetime!(+60056-05-28 05:36:10.955_161_500 UTC))
173    /// );
174    /// # }
175    /// ```
176    #[inline]
177    fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
178        Self::from_unix_timestamp_nanos(ft.to_unix_time_nanos())
179    }
180}
181
182#[cfg(feature = "chrono")]
183impl From<FileTime> for chrono::DateTime<chrono::Utc> {
184    /// Converts a `FileTime` to a [`DateTime<Utc>`](chrono::DateTime).
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// # use nt_time::{
190    /// #     FileTime,
191    /// #     chrono::{DateTime, Utc},
192    /// # };
193    /// #
194    /// assert_eq!(
195    ///     DateTime::<Utc>::from(FileTime::NT_TIME_EPOCH),
196    ///     "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
197    /// );
198    /// assert_eq!(
199    ///     DateTime::<Utc>::from(FileTime::UNIX_EPOCH),
200    ///     DateTime::<Utc>::UNIX_EPOCH
201    /// );
202    /// ```
203    #[inline]
204    fn from(ft: FileTime) -> Self {
205        let ut = ft.to_unix_time();
206        Self::from_timestamp(ut.0, ut.1)
207            .expect("Unix time in nanoseconds should be in the range of `DateTime<Utc>`")
208    }
209}
210
211#[cfg(feature = "jiff")]
212impl TryFrom<FileTime> for jiff::Timestamp {
213    type Error = jiff::Error;
214
215    /// Converts a `FileTime` to a [`Timestamp`](jiff::Timestamp).
216    ///
217    /// # Errors
218    ///
219    /// Returns [`Err`] if `ft` is out of range for
220    /// [`Timestamp`](jiff::Timestamp).
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use nt_time::{FileTime, jiff::Timestamp};
226    /// #
227    /// assert_eq!(
228    ///     Timestamp::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
229    ///     Timestamp::from_second(-11_644_473_600).unwrap()
230    /// );
231    /// assert_eq!(
232    ///     Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap(),
233    ///     Timestamp::UNIX_EPOCH
234    /// );
235    ///
236    /// assert!(Timestamp::try_from(FileTime::MAX).is_err());
237    /// ```
238    #[inline]
239    fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
240        Self::from_nanosecond(ft.to_unix_time_nanos())
241    }
242}
243
244impl From<u64> for FileTime {
245    /// Converts the file time to a `FileTime`.
246    ///
247    /// Equivalent to [`FileTime::new`] except that it is not callable in const
248    /// contexts.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// # use nt_time::FileTime;
254    /// #
255    /// assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH);
256    /// assert_eq!(
257    ///     FileTime::from(116_444_736_000_000_000),
258    ///     FileTime::UNIX_EPOCH
259    /// );
260    /// assert_eq!(FileTime::from(i64::MAX as u64), FileTime::SIGNED_MAX);
261    /// assert_eq!(FileTime::from(u64::MAX), FileTime::MAX);
262    /// ```
263    #[inline]
264    fn from(ft: u64) -> Self {
265        Self::new(ft)
266    }
267}
268
269impl TryFrom<i64> for FileTime {
270    type Error = FileTimeRangeError;
271
272    /// Converts the file time to a `FileTime`.
273    ///
274    /// The file time is sometimes represented as an [`i64`] value, such as in
275    /// the [`DateTime.FromFileTimeUtc`] method and the
276    /// [`DateTime.ToFileTimeUtc`] method in [.NET].
277    ///
278    /// # Errors
279    ///
280    /// Returns [`Err`] if `ft` is negative.
281    ///
282    /// # Examples
283    ///
284    /// ```
285    /// # use nt_time::FileTime;
286    /// #
287    /// assert_eq!(FileTime::try_from(0_i64), Ok(FileTime::NT_TIME_EPOCH));
288    /// assert_eq!(
289    ///     FileTime::try_from(116_444_736_000_000_000_i64),
290    ///     Ok(FileTime::UNIX_EPOCH)
291    /// );
292    /// assert_eq!(FileTime::try_from(i64::MAX), Ok(FileTime::SIGNED_MAX));
293    ///
294    /// assert!(FileTime::try_from(i64::MIN).is_err());
295    /// ```
296    ///
297    /// [`DateTime.FromFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.fromfiletimeutc
298    /// [`DateTime.ToFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tofiletimeutc
299    /// [.NET]: https://dotnet.microsoft.com/
300    #[inline]
301    fn try_from(ft: i64) -> Result<Self, Self::Error> {
302        ft.try_into()
303            .map_err(|_| FileTimeRangeErrorKind::Negative.into())
304            .map(Self::new)
305    }
306}
307
308#[cfg(feature = "std")]
309impl TryFrom<std::time::SystemTime> for FileTime {
310    type Error = FileTimeRangeError;
311
312    /// Converts a [`SystemTime`](std::time::SystemTime) to a `FileTime`.
313    ///
314    /// # Errors
315    ///
316    /// Returns [`Err`] if `st` is out of range for the file time.
317    ///
318    /// # Examples
319    ///
320    /// ```
321    /// # use std::time::{Duration, SystemTime};
322    /// #
323    /// # use nt_time::FileTime;
324    /// #
325    /// assert_eq!(
326    ///     FileTime::try_from(SystemTime::UNIX_EPOCH - Duration::from_secs(11_644_473_600)),
327    ///     Ok(FileTime::NT_TIME_EPOCH)
328    /// );
329    /// assert_eq!(
330    ///     FileTime::try_from(SystemTime::UNIX_EPOCH),
331    ///     Ok(FileTime::UNIX_EPOCH)
332    /// );
333    ///
334    /// // Before `1601-01-01 00:00:00 UTC`.
335    /// assert!(
336    ///     FileTime::try_from(
337    ///         SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100)
338    ///     )
339    ///     .is_err()
340    /// );
341    /// // After `+60056-05-28 05:36:10.955161500 UTC`.
342    /// #[cfg(not(windows))]
343    /// assert!(
344    ///     FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600))
345    ///         .is_err()
346    /// );
347    /// ```
348    #[inline]
349    fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
350        use std::time::SystemTime;
351
352        let elapsed = st
353            .duration_since(SystemTime::UNIX_EPOCH - (Self::UNIX_EPOCH - Self::NT_TIME_EPOCH))
354            .map(|d| d.as_nanos())
355            .map_err(|_| FileTimeRangeErrorKind::Negative)?;
356        let ft = u64::try_from(elapsed / 100).map_err(|_| FileTimeRangeErrorKind::Overflow)?;
357        Ok(Self::new(ft))
358    }
359}
360
361impl TryFrom<OffsetDateTime> for FileTime {
362    type Error = FileTimeRangeError;
363
364    /// Converts a [`OffsetDateTime`] to a `FileTime`.
365    ///
366    /// # Errors
367    ///
368    /// Returns [`Err`] if `dt` is out of range for the file time.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// # use nt_time::{
374    /// #     FileTime,
375    /// #     time::{Duration, OffsetDateTime, macros::datetime},
376    /// # };
377    /// #
378    /// assert_eq!(
379    ///     FileTime::try_from(datetime!(1601-01-01 00:00 UTC)),
380    ///     Ok(FileTime::NT_TIME_EPOCH)
381    /// );
382    /// assert_eq!(
383    ///     FileTime::try_from(OffsetDateTime::UNIX_EPOCH),
384    ///     Ok(FileTime::UNIX_EPOCH)
385    /// );
386    ///
387    /// // Before `1601-01-01 00:00:00 UTC`.
388    /// assert!(
389    ///     FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100)).is_err()
390    /// );
391    /// ```
392    ///
393    /// With the `large-dates` feature enabled, returns [`Err`] if
394    /// [`OffsetDateTime`] represents after "+60056-05-28 05:36:10.955161500
395    /// UTC":
396    ///
397    /// ```
398    /// # #[cfg(feature = "large-dates")]
399    /// # {
400    /// # use nt_time::{
401    /// #     FileTime,
402    /// #     time::{Duration, macros::datetime},
403    /// # };
404    /// #
405    /// assert!(
406    ///     FileTime::try_from(
407    ///         datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100)
408    ///     )
409    ///     .is_err()
410    /// );
411    /// # }
412    /// ```
413    #[inline]
414    fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
415        Self::from_unix_time_nanos(dt.unix_timestamp_nanos())
416    }
417}
418
419#[cfg(feature = "chrono")]
420impl TryFrom<chrono::DateTime<chrono::Utc>> for FileTime {
421    type Error = FileTimeRangeError;
422
423    /// Converts a [`DateTime<Utc>`](chrono::DateTime) to a `FileTime`.
424    ///
425    /// # Errors
426    ///
427    /// Returns [`Err`] if `dt` is out of range for the file time.
428    ///
429    /// # Examples
430    ///
431    /// ```
432    /// # use nt_time::{
433    /// #     FileTime,
434    /// #     chrono::{DateTime, TimeDelta, Utc},
435    /// # };
436    /// #
437    /// assert_eq!(
438    ///     FileTime::try_from("1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()),
439    ///     Ok(FileTime::NT_TIME_EPOCH)
440    /// );
441    /// assert_eq!(
442    ///     FileTime::try_from(DateTime::<Utc>::UNIX_EPOCH),
443    ///     Ok(FileTime::UNIX_EPOCH)
444    /// );
445    ///
446    /// // Before `1601-01-01 00:00:00 UTC`.
447    /// assert!(
448    ///     FileTime::try_from(
449    ///         "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
450    ///             - TimeDelta::nanoseconds(100)
451    ///     )
452    ///     .is_err()
453    /// );
454    /// // After `+60056-05-28 05:36:10.955161500 UTC`.
455    /// assert!(
456    ///     FileTime::try_from(
457    ///         "+60056-05-28 05:36:10.955161500 UTC"
458    ///             .parse::<DateTime<Utc>>()
459    ///             .unwrap()
460    ///             + TimeDelta::nanoseconds(100)
461    ///     )
462    ///     .is_err()
463    /// );
464    /// ```
465    #[inline]
466    fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
467        Self::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos())
468    }
469}
470
471#[cfg(feature = "jiff")]
472impl TryFrom<jiff::Timestamp> for FileTime {
473    type Error = FileTimeRangeError;
474
475    /// Converts a [`Timestamp`](jiff::Timestamp) to a `FileTime`.
476    ///
477    /// # Errors
478    ///
479    /// Returns [`Err`] if `ts` is out of range for the file time.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// # use nt_time::{FileTime, jiff::Timestamp};
485    /// #
486    /// assert_eq!(
487    ///     FileTime::try_from(Timestamp::from_second(-11_644_473_600).unwrap()),
488    ///     Ok(FileTime::NT_TIME_EPOCH)
489    /// );
490    /// assert_eq!(
491    ///     FileTime::try_from(Timestamp::UNIX_EPOCH),
492    ///     Ok(FileTime::UNIX_EPOCH)
493    /// );
494    ///
495    /// // Before `1601-01-01 00:00:00 UTC`.
496    /// assert!(
497    ///     FileTime::try_from(Timestamp::from_nanosecond(-11_644_473_600_000_000_001).unwrap())
498    ///         .is_err()
499    /// );
500    /// ```
501    #[inline]
502    fn try_from(ts: jiff::Timestamp) -> Result<Self, Self::Error> {
503        Self::from_unix_time_nanos(ts.as_nanosecond())
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510
511    #[test]
512    fn from_file_time_to_u64() {
513        assert_eq!(u64::from(FileTime::NT_TIME_EPOCH), u64::MIN);
514        assert_eq!(u64::from(FileTime::UNIX_EPOCH), 116_444_736_000_000_000);
515        assert_eq!(u64::from(FileTime::SIGNED_MAX), i64::MAX as u64);
516        assert_eq!(u64::from(FileTime::MAX), u64::MAX);
517    }
518
519    #[cfg(feature = "std")]
520    #[test_strategy::proptest]
521    fn from_file_time_to_u64_roundtrip(ft: FileTime) {
522        use proptest::prop_assert_eq;
523
524        prop_assert_eq!(u64::from(ft), ft.to_raw());
525    }
526
527    #[test]
528    fn try_from_file_time_to_i64() {
529        assert_eq!(
530            i64::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
531            i64::default()
532        );
533        assert_eq!(
534            i64::try_from(FileTime::UNIX_EPOCH).unwrap(),
535            116_444_736_000_000_000
536        );
537        assert_eq!(i64::try_from(FileTime::SIGNED_MAX).unwrap(), i64::MAX);
538    }
539
540    #[cfg(feature = "std")]
541    #[test_strategy::proptest]
542    fn try_from_file_time_to_i64_roundtrip(ft: FileTime) {
543        use proptest::prop_assert;
544
545        if ft <= FileTime::SIGNED_MAX {
546            prop_assert!(i64::try_from(ft).is_ok());
547        } else {
548            prop_assert!(i64::try_from(ft).is_err());
549        }
550    }
551
552    #[test]
553    fn try_from_file_time_to_i64_with_too_big_file_time() {
554        assert!(i64::try_from(FileTime::new(u64::try_from(i64::MAX).unwrap() + 1)).is_err());
555        assert!(i64::try_from(FileTime::MAX).is_err());
556    }
557
558    #[cfg(feature = "std")]
559    #[test]
560    fn from_file_time_to_system_time() {
561        use std::time::{Duration, SystemTime};
562
563        assert_eq!(
564            SystemTime::from(FileTime::NT_TIME_EPOCH),
565            SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
566        );
567        assert_eq!(
568            SystemTime::from(FileTime::UNIX_EPOCH),
569            SystemTime::UNIX_EPOCH
570        );
571        assert_eq!(
572            SystemTime::from(FileTime::new(2_650_467_743_999_999_999)),
573            SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
574        );
575        assert_eq!(
576            SystemTime::from(FileTime::new(2_650_467_744_000_000_000)),
577            SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800)
578        );
579        // Largest `SystemTime` on Windows.
580        assert_eq!(
581            SystemTime::from(FileTime::new(9_223_372_036_854_775_807)),
582            SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
583        );
584        if !cfg!(windows) {
585            assert_eq!(
586                SystemTime::from(FileTime::MAX),
587                SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
588            );
589        }
590    }
591
592    #[test]
593    fn try_from_file_time_to_offset_date_time() {
594        use time::macros::datetime;
595
596        assert_eq!(
597            OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
598            datetime!(1601-01-01 00:00 UTC)
599        );
600        assert_eq!(
601            OffsetDateTime::try_from(FileTime::UNIX_EPOCH).unwrap(),
602            OffsetDateTime::UNIX_EPOCH
603        );
604        assert_eq!(
605            OffsetDateTime::try_from(FileTime::new(2_650_467_743_999_999_999)).unwrap(),
606            datetime!(9999-12-31 23:59:59.999_999_900 UTC)
607        );
608    }
609
610    #[cfg(not(feature = "large-dates"))]
611    #[test]
612    fn try_from_file_time_to_offset_date_time_with_invalid_file_time() {
613        assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err());
614    }
615
616    #[cfg(feature = "large-dates")]
617    #[test]
618    fn try_from_file_time_to_offset_date_time_with_large_dates() {
619        use time::macros::datetime;
620
621        assert_eq!(
622            OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(),
623            datetime!(+10000-01-01 00:00 UTC)
624        );
625        assert_eq!(
626            OffsetDateTime::try_from(FileTime::SIGNED_MAX).unwrap(),
627            datetime!(+30828-09-14 02:48:05.477_580_700 UTC)
628        );
629        assert_eq!(
630            OffsetDateTime::try_from(FileTime::MAX).unwrap(),
631            datetime!(+60056-05-28 05:36:10.955_161_500 UTC)
632        );
633    }
634
635    #[cfg(feature = "chrono")]
636    #[test]
637    fn from_file_time_to_chrono_date_time() {
638        use chrono::{DateTime, Utc};
639
640        assert_eq!(
641            DateTime::<Utc>::from(FileTime::NT_TIME_EPOCH),
642            "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
643        );
644        assert_eq!(
645            DateTime::<Utc>::from(FileTime::UNIX_EPOCH),
646            DateTime::<Utc>::UNIX_EPOCH
647        );
648        assert_eq!(
649            DateTime::<Utc>::from(FileTime::new(2_650_467_743_999_999_999)),
650            "9999-12-31 23:59:59.999999900 UTC"
651                .parse::<DateTime<Utc>>()
652                .unwrap()
653        );
654        assert_eq!(
655            DateTime::<Utc>::from(FileTime::new(2_650_467_744_000_000_000)),
656            "+10000-01-01 00:00:00 UTC"
657                .parse::<DateTime<Utc>>()
658                .unwrap()
659        );
660        assert_eq!(
661            DateTime::<Utc>::from(FileTime::SIGNED_MAX),
662            "+30828-09-14 02:48:05.477580700 UTC"
663                .parse::<DateTime<Utc>>()
664                .unwrap()
665        );
666        assert_eq!(
667            DateTime::<Utc>::from(FileTime::MAX),
668            "+60056-05-28 05:36:10.955161500 UTC"
669                .parse::<DateTime<Utc>>()
670                .unwrap()
671        );
672    }
673
674    #[cfg(feature = "jiff")]
675    #[test]
676    fn try_from_file_time_to_jiff_timestamp() {
677        use jiff::{Timestamp, ToSpan};
678
679        assert_eq!(
680            Timestamp::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
681            Timestamp::from_second(-11_644_473_600).unwrap()
682        );
683        assert_eq!(
684            Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap(),
685            Timestamp::UNIX_EPOCH
686        );
687        assert_eq!(
688            Timestamp::try_from(FileTime::new(2_650_466_808_009_999_999)).unwrap(),
689            Timestamp::MAX - 99.nanoseconds()
690        );
691    }
692
693    #[cfg(feature = "jiff")]
694    #[test]
695    fn try_from_file_time_to_jiff_timestamp_with_invalid_file_time() {
696        use jiff::Timestamp;
697
698        assert!(Timestamp::try_from(FileTime::new(2_650_466_808_010_000_000)).is_err());
699    }
700
701    #[test]
702    fn from_u64_to_file_time() {
703        assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH);
704        assert_eq!(
705            FileTime::from(116_444_736_000_000_000),
706            FileTime::UNIX_EPOCH
707        );
708        assert_eq!(FileTime::from(i64::MAX as u64), FileTime::SIGNED_MAX);
709        assert_eq!(FileTime::from(u64::MAX), FileTime::MAX);
710    }
711
712    #[cfg(feature = "std")]
713    #[test_strategy::proptest]
714    fn from_u64_to_file_time_roundtrip(ft: u64) {
715        use proptest::prop_assert_eq;
716
717        prop_assert_eq!(FileTime::from(ft), FileTime::new(ft));
718    }
719
720    #[test]
721    fn try_from_i64_to_file_time_before_nt_time_epoch() {
722        assert_eq!(
723            FileTime::try_from(i64::MIN).unwrap_err(),
724            FileTimeRangeErrorKind::Negative.into()
725        );
726        assert_eq!(
727            FileTime::try_from(i64::default() - 1).unwrap_err(),
728            FileTimeRangeErrorKind::Negative.into()
729        );
730    }
731
732    #[cfg(feature = "std")]
733    #[test_strategy::proptest]
734    fn try_from_i64_to_file_time_before_nt_time_epoch_roundtrip(
735        #[strategy(..i64::default())] ft: i64,
736    ) {
737        use proptest::prop_assert_eq;
738
739        prop_assert_eq!(
740            FileTime::try_from(ft).unwrap_err(),
741            FileTimeRangeErrorKind::Negative.into()
742        );
743    }
744
745    #[test]
746    fn try_from_i64_to_file_time() {
747        assert_eq!(
748            FileTime::try_from(i64::default()).unwrap(),
749            FileTime::NT_TIME_EPOCH
750        );
751        assert_eq!(
752            FileTime::try_from(116_444_736_000_000_000_i64).unwrap(),
753            FileTime::UNIX_EPOCH
754        );
755        assert_eq!(FileTime::try_from(i64::MAX).unwrap(), FileTime::SIGNED_MAX);
756    }
757
758    #[cfg(feature = "std")]
759    #[test_strategy::proptest]
760    fn try_from_i64_to_file_time_roundtrip(#[strategy(i64::default()..)] ft: i64) {
761        use proptest::prop_assert;
762
763        prop_assert!(FileTime::try_from(ft).is_ok());
764    }
765
766    #[cfg(feature = "std")]
767    #[test]
768    fn try_from_system_time_to_file_time_before_nt_time_epoch() {
769        use std::time::{Duration, SystemTime};
770
771        assert_eq!(
772            FileTime::try_from(if cfg!(windows) {
773                SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100)
774            } else {
775                SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_001)
776            })
777            .unwrap_err(),
778            FileTimeRangeErrorKind::Negative.into()
779        );
780    }
781
782    #[cfg(feature = "std")]
783    #[test]
784    fn try_from_system_time_to_file_time() {
785        use std::time::{Duration, SystemTime};
786
787        assert_eq!(
788            FileTime::try_from(
789                SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
790            )
791            .unwrap(),
792            FileTime::NT_TIME_EPOCH
793        );
794        assert_eq!(
795            FileTime::try_from(SystemTime::UNIX_EPOCH).unwrap(),
796            FileTime::UNIX_EPOCH
797        );
798        assert_eq!(
799            FileTime::try_from(
800                SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
801            )
802            .unwrap(),
803            FileTime::new(2_650_467_743_999_999_999)
804        );
805        assert_eq!(
806            FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800))
807                .unwrap(),
808            FileTime::new(2_650_467_744_000_000_000)
809        );
810        // Largest `SystemTime` on Windows.
811        assert_eq!(
812            FileTime::try_from(
813                SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
814            )
815            .unwrap(),
816            FileTime::new(9_223_372_036_854_775_807)
817        );
818        if !cfg!(windows) {
819            assert_eq!(
820                FileTime::try_from(
821                    SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
822                )
823                .unwrap(),
824                FileTime::MAX
825            );
826        }
827    }
828
829    #[cfg(feature = "std")]
830    #[test]
831    fn try_from_system_time_to_file_time_with_too_big_system_time() {
832        use std::time::{Duration, SystemTime};
833
834        if cfg!(windows) {
835            assert!(
836                SystemTime::UNIX_EPOCH
837                    .checked_add(Duration::new(910_692_730_085, 477_580_800))
838                    .is_none()
839            );
840        } else {
841            assert_eq!(
842                FileTime::try_from(
843                    SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600)
844                )
845                .unwrap_err(),
846                FileTimeRangeErrorKind::Overflow.into()
847            );
848        }
849    }
850
851    #[test]
852    fn try_from_offset_date_time_to_file_time_before_nt_time_epoch() {
853        use time::{Duration, macros::datetime};
854
855        assert_eq!(
856            FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100))
857                .unwrap_err(),
858            FileTimeRangeErrorKind::Negative.into()
859        );
860    }
861
862    #[test]
863    fn try_from_offset_date_time_to_file_time() {
864        use time::macros::datetime;
865
866        assert_eq!(
867            FileTime::try_from(datetime!(1601-01-01 00:00 UTC)).unwrap(),
868            FileTime::NT_TIME_EPOCH
869        );
870        assert_eq!(
871            FileTime::try_from(OffsetDateTime::UNIX_EPOCH).unwrap(),
872            FileTime::UNIX_EPOCH
873        );
874        assert_eq!(
875            FileTime::try_from(datetime!(9999-12-31 23:59:59.999_999_999 UTC)).unwrap(),
876            FileTime::new(2_650_467_743_999_999_999)
877        );
878    }
879
880    #[cfg(feature = "large-dates")]
881    #[test]
882    fn try_from_offset_date_time_to_file_time_with_large_dates() {
883        use time::macros::datetime;
884
885        assert_eq!(
886            FileTime::try_from(datetime!(+10000-01-01 00:00 UTC)).unwrap(),
887            FileTime::new(2_650_467_744_000_000_000)
888        );
889        assert_eq!(
890            FileTime::try_from(datetime!(+30828-09-14 02:48:05.477_580_700 UTC)).unwrap(),
891            FileTime::SIGNED_MAX
892        );
893        assert_eq!(
894            FileTime::try_from(datetime!(+60056-05-28 05:36:10.955_161_500 UTC)).unwrap(),
895            FileTime::MAX
896        );
897    }
898
899    #[cfg(feature = "large-dates")]
900    #[test]
901    fn try_from_offset_date_time_to_file_time_with_too_big_date_time() {
902        use time::{Duration, macros::datetime};
903
904        assert_eq!(
905            FileTime::try_from(
906                datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100)
907            )
908            .unwrap_err(),
909            FileTimeRangeErrorKind::Overflow.into()
910        );
911    }
912
913    #[cfg(feature = "chrono")]
914    #[test]
915    fn try_from_chrono_date_time_to_file_time_before_nt_time_epoch() {
916        use chrono::{DateTime, TimeDelta, Utc};
917
918        assert_eq!(
919            FileTime::try_from(
920                "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
921                    - TimeDelta::nanoseconds(100)
922            )
923            .unwrap_err(),
924            FileTimeRangeErrorKind::Negative.into()
925        );
926    }
927
928    #[cfg(feature = "chrono")]
929    #[test]
930    fn try_from_chrono_date_time_to_file_time() {
931        use chrono::{DateTime, Utc};
932
933        assert_eq!(
934            FileTime::try_from("1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap())
935                .unwrap(),
936            FileTime::NT_TIME_EPOCH
937        );
938        assert_eq!(
939            FileTime::try_from(DateTime::<Utc>::UNIX_EPOCH).unwrap(),
940            FileTime::UNIX_EPOCH
941        );
942        assert_eq!(
943            FileTime::try_from(
944                "9999-12-31 23:59:59.999999999 UTC"
945                    .parse::<DateTime<Utc>>()
946                    .unwrap()
947            )
948            .unwrap(),
949            FileTime::new(2_650_467_743_999_999_999)
950        );
951        assert_eq!(
952            FileTime::try_from(
953                "+10000-01-01 00:00:00 UTC"
954                    .parse::<DateTime<Utc>>()
955                    .unwrap()
956            )
957            .unwrap(),
958            FileTime::new(2_650_467_744_000_000_000)
959        );
960        assert_eq!(
961            FileTime::try_from(
962                "+30828-09-14 02:48:05.477580700 UTC"
963                    .parse::<DateTime<Utc>>()
964                    .unwrap()
965            )
966            .unwrap(),
967            FileTime::SIGNED_MAX
968        );
969        assert_eq!(
970            FileTime::try_from(
971                "+60056-05-28 05:36:10.955161500 UTC"
972                    .parse::<DateTime<Utc>>()
973                    .unwrap()
974            )
975            .unwrap(),
976            FileTime::MAX
977        );
978    }
979
980    #[cfg(feature = "chrono")]
981    #[test]
982    fn try_from_chrono_date_time_to_file_time_with_too_big_date_time() {
983        use chrono::{DateTime, TimeDelta, Utc};
984
985        assert_eq!(
986            FileTime::try_from(
987                "+60056-05-28 05:36:10.955161500 UTC"
988                    .parse::<DateTime<Utc>>()
989                    .unwrap()
990                    + TimeDelta::nanoseconds(100)
991            )
992            .unwrap_err(),
993            FileTimeRangeErrorKind::Overflow.into()
994        );
995    }
996
997    #[cfg(feature = "jiff")]
998    #[test]
999    fn try_from_jiff_timestamp_to_file_time_before_nt_time_epoch() {
1000        use jiff::Timestamp;
1001
1002        assert_eq!(
1003            FileTime::try_from(Timestamp::from_nanosecond(-11_644_473_600_000_000_001).unwrap())
1004                .unwrap_err(),
1005            FileTimeRangeErrorKind::Negative.into()
1006        );
1007    }
1008
1009    #[cfg(feature = "jiff")]
1010    #[test]
1011    fn try_from_jiff_timestamp_to_file_time() {
1012        use jiff::Timestamp;
1013
1014        assert_eq!(
1015            FileTime::try_from(Timestamp::from_second(-11_644_473_600).unwrap()).unwrap(),
1016            FileTime::NT_TIME_EPOCH
1017        );
1018        assert_eq!(
1019            FileTime::try_from(Timestamp::UNIX_EPOCH).unwrap(),
1020            FileTime::UNIX_EPOCH
1021        );
1022        assert_eq!(
1023            FileTime::try_from(Timestamp::MAX).unwrap(),
1024            FileTime::new(2_650_466_808_009_999_999)
1025        );
1026    }
1027}