1#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(
960 FileTime::from(dos_date_time::DateTime::MIN),
961 FileTime::new(119_600_064_000_000_000)
962 );
963 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 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 assert_eq!(
985 FileTime::from(dos_date_time::DateTime::MAX),
986 FileTime::new(159_992_927_980_000_000)
987 );
988 }
989}