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