Skip to main content

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