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 `time` 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
211impl From<u64> for FileTime {
212    /// Converts the file time to a `FileTime`.
213    ///
214    /// Equivalent to [`FileTime::new`] except that it is not callable in const
215    /// contexts.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// # use nt_time::FileTime;
221    /// #
222    /// assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH);
223    /// assert_eq!(
224    ///     FileTime::from(116_444_736_000_000_000),
225    ///     FileTime::UNIX_EPOCH
226    /// );
227    /// assert_eq!(FileTime::from(i64::MAX as u64), FileTime::SIGNED_MAX);
228    /// assert_eq!(FileTime::from(u64::MAX), FileTime::MAX);
229    /// ```
230    #[inline]
231    fn from(ft: u64) -> Self {
232        Self::new(ft)
233    }
234}
235
236impl TryFrom<i64> for FileTime {
237    type Error = FileTimeRangeError;
238
239    /// Converts the file time to a `FileTime`.
240    ///
241    /// The file time is sometimes represented as an [`i64`] value, such as in
242    /// the [`DateTime.FromFileTimeUtc`] method and the
243    /// [`DateTime.ToFileTimeUtc`] method in [.NET].
244    ///
245    /// # Errors
246    ///
247    /// Returns [`Err`] if `ft` is negative.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// # use nt_time::FileTime;
253    /// #
254    /// assert_eq!(FileTime::try_from(0_i64), Ok(FileTime::NT_TIME_EPOCH));
255    /// assert_eq!(
256    ///     FileTime::try_from(116_444_736_000_000_000_i64),
257    ///     Ok(FileTime::UNIX_EPOCH)
258    /// );
259    /// assert_eq!(FileTime::try_from(i64::MAX), Ok(FileTime::SIGNED_MAX));
260    ///
261    /// assert!(FileTime::try_from(i64::MIN).is_err());
262    /// ```
263    ///
264    /// [`DateTime.FromFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.fromfiletimeutc
265    /// [`DateTime.ToFileTimeUtc`]: https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tofiletimeutc
266    /// [.NET]: https://dotnet.microsoft.com/
267    #[inline]
268    fn try_from(ft: i64) -> Result<Self, Self::Error> {
269        ft.try_into()
270            .map_err(|_| FileTimeRangeErrorKind::Negative.into())
271            .map(Self::new)
272    }
273}
274
275#[cfg(feature = "std")]
276impl TryFrom<std::time::SystemTime> for FileTime {
277    type Error = FileTimeRangeError;
278
279    /// Converts a [`SystemTime`](std::time::SystemTime) to a `FileTime`.
280    ///
281    /// # Errors
282    ///
283    /// Returns [`Err`] if `time` is out of range for the file time.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// # use std::time::{Duration, SystemTime};
289    /// #
290    /// # use nt_time::FileTime;
291    /// #
292    /// assert_eq!(
293    ///     FileTime::try_from(SystemTime::UNIX_EPOCH - Duration::from_secs(11_644_473_600)),
294    ///     Ok(FileTime::NT_TIME_EPOCH)
295    /// );
296    /// assert_eq!(
297    ///     FileTime::try_from(SystemTime::UNIX_EPOCH),
298    ///     Ok(FileTime::UNIX_EPOCH)
299    /// );
300    ///
301    /// // Before `1601-01-01 00:00:00 UTC`.
302    /// assert!(
303    ///     FileTime::try_from(
304    ///         SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100)
305    ///     )
306    ///     .is_err()
307    /// );
308    /// // After `+60056-05-28 05:36:10.955161500 UTC`.
309    /// #[cfg(not(windows))]
310    /// assert!(
311    ///     FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600))
312    ///         .is_err()
313    /// );
314    /// ```
315    #[inline]
316    fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
317        use std::time::SystemTime;
318
319        let elapsed = st
320            .duration_since(SystemTime::UNIX_EPOCH - (Self::UNIX_EPOCH - Self::NT_TIME_EPOCH))
321            .map(|d| d.as_nanos())
322            .map_err(|_| FileTimeRangeErrorKind::Negative)?;
323        let ft = u64::try_from(elapsed / 100).map_err(|_| FileTimeRangeErrorKind::Overflow)?;
324        Ok(Self::new(ft))
325    }
326}
327
328impl TryFrom<OffsetDateTime> for FileTime {
329    type Error = FileTimeRangeError;
330
331    /// Converts a [`OffsetDateTime`] to a `FileTime`.
332    ///
333    /// # Errors
334    ///
335    /// Returns [`Err`] if `dt` is out of range for the file time.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// # use nt_time::{
341    /// #     FileTime,
342    /// #     time::{Duration, OffsetDateTime, macros::datetime},
343    /// # };
344    /// #
345    /// assert_eq!(
346    ///     FileTime::try_from(datetime!(1601-01-01 00:00 UTC)),
347    ///     Ok(FileTime::NT_TIME_EPOCH)
348    /// );
349    /// assert_eq!(
350    ///     FileTime::try_from(OffsetDateTime::UNIX_EPOCH),
351    ///     Ok(FileTime::UNIX_EPOCH)
352    /// );
353    ///
354    /// // Before `1601-01-01 00:00:00 UTC`.
355    /// assert!(
356    ///     FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100)).is_err()
357    /// );
358    /// ```
359    ///
360    /// With the `large-dates` feature enabled, returns [`Err`] if
361    /// [`OffsetDateTime`] represents after "+60056-05-28 05:36:10.955161500
362    /// UTC":
363    ///
364    /// ```
365    /// # #[cfg(feature = "large-dates")]
366    /// # {
367    /// # use nt_time::{
368    /// #     FileTime,
369    /// #     time::{Duration, macros::datetime},
370    /// # };
371    /// #
372    /// assert!(
373    ///     FileTime::try_from(
374    ///         datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100)
375    ///     )
376    ///     .is_err()
377    /// );
378    /// # }
379    /// ```
380    #[inline]
381    fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
382        Self::from_unix_time_nanos(dt.unix_timestamp_nanos())
383    }
384}
385
386#[cfg(feature = "chrono")]
387impl TryFrom<chrono::DateTime<chrono::Utc>> for FileTime {
388    type Error = FileTimeRangeError;
389
390    /// Converts a [`DateTime<Utc>`](chrono::DateTime) to a `FileTime`.
391    ///
392    /// # Errors
393    ///
394    /// Returns [`Err`] if `dt` is out of range for the file time.
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// # use nt_time::{
400    /// #     FileTime,
401    /// #     chrono::{DateTime, TimeDelta, Utc},
402    /// # };
403    /// #
404    /// assert_eq!(
405    ///     FileTime::try_from("1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()),
406    ///     Ok(FileTime::NT_TIME_EPOCH)
407    /// );
408    /// assert_eq!(
409    ///     FileTime::try_from(DateTime::<Utc>::UNIX_EPOCH),
410    ///     Ok(FileTime::UNIX_EPOCH)
411    /// );
412    ///
413    /// // Before `1601-01-01 00:00:00 UTC`.
414    /// assert!(
415    ///     FileTime::try_from(
416    ///         "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
417    ///             - TimeDelta::nanoseconds(100)
418    ///     )
419    ///     .is_err()
420    /// );
421    /// // After `+60056-05-28 05:36:10.955161500 UTC`.
422    /// assert!(
423    ///     FileTime::try_from(
424    ///         "+60056-05-28 05:36:10.955161500 UTC"
425    ///             .parse::<DateTime<Utc>>()
426    ///             .unwrap()
427    ///             + TimeDelta::nanoseconds(100)
428    ///     )
429    ///     .is_err()
430    /// );
431    /// ```
432    #[inline]
433    fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
434        Self::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos())
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    #[test]
443    fn from_file_time_to_u64() {
444        assert_eq!(u64::from(FileTime::NT_TIME_EPOCH), u64::MIN);
445        assert_eq!(u64::from(FileTime::UNIX_EPOCH), 116_444_736_000_000_000);
446        assert_eq!(u64::from(FileTime::SIGNED_MAX), i64::MAX as u64);
447        assert_eq!(u64::from(FileTime::MAX), u64::MAX);
448    }
449
450    #[cfg(feature = "std")]
451    #[test_strategy::proptest]
452    fn from_file_time_to_u64_roundtrip(ft: FileTime) {
453        use proptest::prop_assert_eq;
454
455        prop_assert_eq!(u64::from(ft), ft.to_raw());
456    }
457
458    #[test]
459    fn try_from_file_time_to_i64() {
460        assert_eq!(
461            i64::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
462            i64::default()
463        );
464        assert_eq!(
465            i64::try_from(FileTime::UNIX_EPOCH).unwrap(),
466            116_444_736_000_000_000
467        );
468        assert_eq!(i64::try_from(FileTime::SIGNED_MAX).unwrap(), i64::MAX);
469    }
470
471    #[cfg(feature = "std")]
472    #[test_strategy::proptest]
473    fn try_from_file_time_to_i64_roundtrip(ft: FileTime) {
474        use proptest::prop_assert;
475
476        if ft <= FileTime::SIGNED_MAX {
477            prop_assert!(i64::try_from(ft).is_ok());
478        } else {
479            prop_assert!(i64::try_from(ft).is_err());
480        }
481    }
482
483    #[test]
484    fn try_from_file_time_to_i64_with_too_big_file_time() {
485        assert!(i64::try_from(FileTime::new(u64::try_from(i64::MAX).unwrap() + 1)).is_err());
486        assert!(i64::try_from(FileTime::MAX).is_err());
487    }
488
489    #[cfg(feature = "std")]
490    #[test]
491    fn from_file_time_to_system_time() {
492        use std::time::{Duration, SystemTime};
493
494        assert_eq!(
495            SystemTime::from(FileTime::NT_TIME_EPOCH),
496            SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
497        );
498        assert_eq!(
499            SystemTime::from(FileTime::UNIX_EPOCH),
500            SystemTime::UNIX_EPOCH
501        );
502        assert_eq!(
503            SystemTime::from(FileTime::new(2_650_467_743_999_999_999)),
504            SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
505        );
506        assert_eq!(
507            SystemTime::from(FileTime::new(2_650_467_744_000_000_000)),
508            SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800)
509        );
510        // Largest `SystemTime` on Windows.
511        assert_eq!(
512            SystemTime::from(FileTime::new(9_223_372_036_854_775_807)),
513            SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
514        );
515        if !cfg!(windows) {
516            assert_eq!(
517                SystemTime::from(FileTime::MAX),
518                SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
519            );
520        }
521    }
522
523    #[test]
524    fn try_from_file_time_to_offset_date_time() {
525        use time::macros::datetime;
526
527        assert_eq!(
528            OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
529            datetime!(1601-01-01 00:00 UTC)
530        );
531        assert_eq!(
532            OffsetDateTime::try_from(FileTime::UNIX_EPOCH).unwrap(),
533            OffsetDateTime::UNIX_EPOCH
534        );
535        assert_eq!(
536            OffsetDateTime::try_from(FileTime::new(2_650_467_743_999_999_999)).unwrap(),
537            datetime!(9999-12-31 23:59:59.999_999_900 UTC)
538        );
539    }
540
541    #[cfg(not(feature = "large-dates"))]
542    #[test]
543    fn try_from_file_time_to_offset_date_time_with_invalid_file_time() {
544        assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err());
545    }
546
547    #[cfg(feature = "large-dates")]
548    #[test]
549    fn try_from_file_time_to_offset_date_time_with_large_dates() {
550        use time::macros::datetime;
551
552        assert_eq!(
553            OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(),
554            datetime!(+10000-01-01 00:00 UTC)
555        );
556        assert_eq!(
557            OffsetDateTime::try_from(FileTime::SIGNED_MAX).unwrap(),
558            datetime!(+30828-09-14 02:48:05.477_580_700 UTC)
559        );
560        assert_eq!(
561            OffsetDateTime::try_from(FileTime::MAX).unwrap(),
562            datetime!(+60056-05-28 05:36:10.955_161_500 UTC)
563        );
564    }
565
566    #[cfg(feature = "chrono")]
567    #[test]
568    fn from_file_time_to_chrono_date_time() {
569        use chrono::{DateTime, Utc};
570
571        assert_eq!(
572            DateTime::<Utc>::from(FileTime::NT_TIME_EPOCH),
573            "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
574        );
575        assert_eq!(
576            DateTime::<Utc>::from(FileTime::UNIX_EPOCH),
577            DateTime::<Utc>::UNIX_EPOCH
578        );
579        assert_eq!(
580            DateTime::<Utc>::from(FileTime::new(2_650_467_743_999_999_999)),
581            "9999-12-31 23:59:59.999999900 UTC"
582                .parse::<DateTime<Utc>>()
583                .unwrap()
584        );
585        assert_eq!(
586            DateTime::<Utc>::from(FileTime::new(2_650_467_744_000_000_000)),
587            "+10000-01-01 00:00:00 UTC"
588                .parse::<DateTime<Utc>>()
589                .unwrap()
590        );
591        assert_eq!(
592            DateTime::<Utc>::from(FileTime::SIGNED_MAX),
593            "+30828-09-14 02:48:05.477580700 UTC"
594                .parse::<DateTime<Utc>>()
595                .unwrap()
596        );
597        assert_eq!(
598            DateTime::<Utc>::from(FileTime::MAX),
599            "+60056-05-28 05:36:10.955161500 UTC"
600                .parse::<DateTime<Utc>>()
601                .unwrap()
602        );
603    }
604
605    #[test]
606    fn from_u64_to_file_time() {
607        assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH);
608        assert_eq!(
609            FileTime::from(116_444_736_000_000_000),
610            FileTime::UNIX_EPOCH
611        );
612        assert_eq!(FileTime::from(i64::MAX as u64), FileTime::SIGNED_MAX);
613        assert_eq!(FileTime::from(u64::MAX), FileTime::MAX);
614    }
615
616    #[cfg(feature = "std")]
617    #[test_strategy::proptest]
618    fn from_u64_to_file_time_roundtrip(ft: u64) {
619        use proptest::prop_assert_eq;
620
621        prop_assert_eq!(FileTime::from(ft), FileTime::new(ft));
622    }
623
624    #[test]
625    fn try_from_i64_to_file_time_before_nt_time_epoch() {
626        assert_eq!(
627            FileTime::try_from(i64::MIN).unwrap_err(),
628            FileTimeRangeErrorKind::Negative.into()
629        );
630        assert_eq!(
631            FileTime::try_from(i64::default() - 1).unwrap_err(),
632            FileTimeRangeErrorKind::Negative.into()
633        );
634    }
635
636    #[cfg(feature = "std")]
637    #[test_strategy::proptest]
638    fn try_from_i64_to_file_time_before_nt_time_epoch_roundtrip(
639        #[strategy(..i64::default())] ft: i64,
640    ) {
641        use proptest::prop_assert_eq;
642
643        prop_assert_eq!(
644            FileTime::try_from(ft).unwrap_err(),
645            FileTimeRangeErrorKind::Negative.into()
646        );
647    }
648
649    #[test]
650    fn try_from_i64_to_file_time() {
651        assert_eq!(
652            FileTime::try_from(i64::default()).unwrap(),
653            FileTime::NT_TIME_EPOCH
654        );
655        assert_eq!(
656            FileTime::try_from(116_444_736_000_000_000_i64).unwrap(),
657            FileTime::UNIX_EPOCH
658        );
659        assert_eq!(FileTime::try_from(i64::MAX).unwrap(), FileTime::SIGNED_MAX);
660    }
661
662    #[cfg(feature = "std")]
663    #[test_strategy::proptest]
664    fn try_from_i64_to_file_time_roundtrip(#[strategy(i64::default()..)] ft: i64) {
665        use proptest::prop_assert;
666
667        prop_assert!(FileTime::try_from(ft).is_ok());
668    }
669
670    #[cfg(feature = "std")]
671    #[test]
672    fn try_from_system_time_to_file_time_before_nt_time_epoch() {
673        use std::time::{Duration, SystemTime};
674
675        assert_eq!(
676            FileTime::try_from(if cfg!(windows) {
677                SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100)
678            } else {
679                SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_001)
680            })
681            .unwrap_err(),
682            FileTimeRangeErrorKind::Negative.into()
683        );
684    }
685
686    #[cfg(feature = "std")]
687    #[test]
688    fn try_from_system_time_to_file_time() {
689        use std::time::{Duration, SystemTime};
690
691        assert_eq!(
692            FileTime::try_from(
693                SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
694            )
695            .unwrap(),
696            FileTime::NT_TIME_EPOCH
697        );
698        assert_eq!(
699            FileTime::try_from(SystemTime::UNIX_EPOCH).unwrap(),
700            FileTime::UNIX_EPOCH
701        );
702        assert_eq!(
703            FileTime::try_from(
704                SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
705            )
706            .unwrap(),
707            FileTime::new(2_650_467_743_999_999_999)
708        );
709        assert_eq!(
710            FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800))
711                .unwrap(),
712            FileTime::new(2_650_467_744_000_000_000)
713        );
714        // Largest `SystemTime` on Windows.
715        assert_eq!(
716            FileTime::try_from(
717                SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
718            )
719            .unwrap(),
720            FileTime::new(9_223_372_036_854_775_807)
721        );
722        if !cfg!(windows) {
723            assert_eq!(
724                FileTime::try_from(
725                    SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
726                )
727                .unwrap(),
728                FileTime::MAX
729            );
730        }
731    }
732
733    #[cfg(feature = "std")]
734    #[test]
735    fn try_from_system_time_to_file_time_with_too_big_system_time() {
736        use std::time::{Duration, SystemTime};
737
738        if cfg!(windows) {
739            assert!(
740                SystemTime::UNIX_EPOCH
741                    .checked_add(Duration::new(910_692_730_085, 477_580_800))
742                    .is_none()
743            );
744        } else {
745            assert_eq!(
746                FileTime::try_from(
747                    SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600)
748                )
749                .unwrap_err(),
750                FileTimeRangeErrorKind::Overflow.into()
751            );
752        }
753    }
754
755    #[test]
756    fn try_from_offset_date_time_to_file_time_before_nt_time_epoch() {
757        use time::{Duration, macros::datetime};
758
759        assert_eq!(
760            FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100))
761                .unwrap_err(),
762            FileTimeRangeErrorKind::Negative.into()
763        );
764    }
765
766    #[test]
767    fn try_from_offset_date_time_to_file_time() {
768        use time::macros::datetime;
769
770        assert_eq!(
771            FileTime::try_from(datetime!(1601-01-01 00:00 UTC)).unwrap(),
772            FileTime::NT_TIME_EPOCH
773        );
774        assert_eq!(
775            FileTime::try_from(OffsetDateTime::UNIX_EPOCH).unwrap(),
776            FileTime::UNIX_EPOCH
777        );
778        assert_eq!(
779            FileTime::try_from(datetime!(9999-12-31 23:59:59.999_999_999 UTC)).unwrap(),
780            FileTime::new(2_650_467_743_999_999_999)
781        );
782    }
783
784    #[cfg(feature = "large-dates")]
785    #[test]
786    fn try_from_offset_date_time_to_file_time_with_large_dates() {
787        use time::macros::datetime;
788
789        assert_eq!(
790            FileTime::try_from(datetime!(+10000-01-01 00:00 UTC)).unwrap(),
791            FileTime::new(2_650_467_744_000_000_000)
792        );
793        assert_eq!(
794            FileTime::try_from(datetime!(+30828-09-14 02:48:05.477_580_700 UTC)).unwrap(),
795            FileTime::SIGNED_MAX
796        );
797        assert_eq!(
798            FileTime::try_from(datetime!(+60056-05-28 05:36:10.955_161_500 UTC)).unwrap(),
799            FileTime::MAX
800        );
801    }
802
803    #[cfg(feature = "large-dates")]
804    #[test]
805    fn try_from_offset_date_time_to_file_time_with_too_big_date_time() {
806        use time::{Duration, macros::datetime};
807
808        assert_eq!(
809            FileTime::try_from(
810                datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100)
811            )
812            .unwrap_err(),
813            FileTimeRangeErrorKind::Overflow.into()
814        );
815    }
816
817    #[cfg(feature = "chrono")]
818    #[test]
819    fn try_from_chrono_date_time_to_file_time_before_nt_time_epoch() {
820        use chrono::{DateTime, TimeDelta, Utc};
821
822        assert_eq!(
823            FileTime::try_from(
824                "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
825                    - TimeDelta::nanoseconds(100)
826            )
827            .unwrap_err(),
828            FileTimeRangeErrorKind::Negative.into()
829        );
830    }
831
832    #[cfg(feature = "chrono")]
833    #[test]
834    fn try_from_chrono_date_time_to_file_time() {
835        use chrono::{DateTime, Utc};
836
837        assert_eq!(
838            FileTime::try_from("1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap())
839                .unwrap(),
840            FileTime::NT_TIME_EPOCH
841        );
842        assert_eq!(
843            FileTime::try_from(DateTime::<Utc>::UNIX_EPOCH).unwrap(),
844            FileTime::UNIX_EPOCH
845        );
846        assert_eq!(
847            FileTime::try_from(
848                "9999-12-31 23:59:59.999999999 UTC"
849                    .parse::<DateTime<Utc>>()
850                    .unwrap()
851            )
852            .unwrap(),
853            FileTime::new(2_650_467_743_999_999_999)
854        );
855        assert_eq!(
856            FileTime::try_from(
857                "+10000-01-01 00:00:00 UTC"
858                    .parse::<DateTime<Utc>>()
859                    .unwrap()
860            )
861            .unwrap(),
862            FileTime::new(2_650_467_744_000_000_000)
863        );
864        assert_eq!(
865            FileTime::try_from(
866                "+30828-09-14 02:48:05.477580700 UTC"
867                    .parse::<DateTime<Utc>>()
868                    .unwrap()
869            )
870            .unwrap(),
871            FileTime::SIGNED_MAX
872        );
873        assert_eq!(
874            FileTime::try_from(
875                "+60056-05-28 05:36:10.955161500 UTC"
876                    .parse::<DateTime<Utc>>()
877                    .unwrap()
878            )
879            .unwrap(),
880            FileTime::MAX
881        );
882    }
883
884    #[cfg(feature = "chrono")]
885    #[test]
886    fn try_from_chrono_date_time_to_file_time_with_too_big_date_time() {
887        use chrono::{DateTime, TimeDelta, Utc};
888
889        assert_eq!(
890            FileTime::try_from(
891                "+60056-05-28 05:36:10.955161500 UTC"
892                    .parse::<DateTime<Utc>>()
893                    .unwrap()
894                    + TimeDelta::nanoseconds(100)
895            )
896            .unwrap_err(),
897            FileTimeRangeErrorKind::Overflow.into()
898        );
899    }
900}