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}