1use core::num::TryFromIntError;
8
9use time::{OffsetDateTime, error::ComponentRange};
10
11use super::FileTime;
12use crate::error::{FileTimeRangeError, FileTimeRangeErrorKind};
13
14impl From<FileTime> for u64 {
15 #[inline]
31 fn from(ft: FileTime) -> Self {
32 ft.to_raw()
33 }
34}
35
36impl TryFrom<FileTime> for i64 {
37 type Error = TryFromIntError;
38
39 #[inline]
68 fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
69 ft.to_raw().try_into()
70 }
71}
72
73#[cfg(feature = "std")]
74impl From<FileTime> for std::time::SystemTime {
75 #[inline]
99 fn from(ft: FileTime) -> Self {
100 use std::time::Duration;
101
102 use super::FILE_TIMES_PER_SEC;
103
104 let duration = Duration::new(
105 ft.to_raw() / FILE_TIMES_PER_SEC,
106 u32::try_from((ft.to_raw() % FILE_TIMES_PER_SEC) * 100)
107 .expect("the number of nanoseconds should be in the range of `u32`"),
108 );
109 (Self::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)) + duration
110 }
111}
112
113impl TryFrom<FileTime> for OffsetDateTime {
114 type Error = ComponentRange;
115
116 #[inline]
177 fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
178 Self::from_unix_timestamp_nanos(ft.to_unix_time_nanos())
179 }
180}
181
182#[cfg(feature = "chrono")]
183impl From<FileTime> for chrono::DateTime<chrono::Utc> {
184 #[inline]
204 fn from(ft: FileTime) -> Self {
205 let ut = ft.to_unix_time();
206 Self::from_timestamp(ut.0, ut.1)
207 .expect("Unix time in nanoseconds should be in the range of `DateTime<Utc>`")
208 }
209}
210
211#[cfg(feature = "jiff")]
212impl TryFrom<FileTime> for jiff::Timestamp {
213 type Error = jiff::Error;
214
215 #[inline]
239 fn try_from(ft: FileTime) -> Result<Self, Self::Error> {
240 Self::from_nanosecond(ft.to_unix_time_nanos())
241 }
242}
243
244impl From<u64> for FileTime {
245 #[inline]
264 fn from(ft: u64) -> Self {
265 Self::new(ft)
266 }
267}
268
269impl TryFrom<i64> for FileTime {
270 type Error = FileTimeRangeError;
271
272 #[inline]
301 fn try_from(ft: i64) -> Result<Self, Self::Error> {
302 ft.try_into()
303 .map_err(|_| FileTimeRangeErrorKind::Negative.into())
304 .map(Self::new)
305 }
306}
307
308#[cfg(feature = "std")]
309impl TryFrom<std::time::SystemTime> for FileTime {
310 type Error = FileTimeRangeError;
311
312 #[inline]
349 fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
350 use std::time::SystemTime;
351
352 let elapsed = st
353 .duration_since(SystemTime::UNIX_EPOCH - (Self::UNIX_EPOCH - Self::NT_TIME_EPOCH))
354 .map(|d| d.as_nanos())
355 .map_err(|_| FileTimeRangeErrorKind::Negative)?;
356 let ft = u64::try_from(elapsed / 100).map_err(|_| FileTimeRangeErrorKind::Overflow)?;
357 Ok(Self::new(ft))
358 }
359}
360
361impl TryFrom<OffsetDateTime> for FileTime {
362 type Error = FileTimeRangeError;
363
364 #[inline]
414 fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
415 Self::from_unix_time_nanos(dt.unix_timestamp_nanos())
416 }
417}
418
419#[cfg(feature = "chrono")]
420impl TryFrom<chrono::DateTime<chrono::Utc>> for FileTime {
421 type Error = FileTimeRangeError;
422
423 #[inline]
466 fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
467 Self::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos())
468 }
469}
470
471#[cfg(feature = "jiff")]
472impl TryFrom<jiff::Timestamp> for FileTime {
473 type Error = FileTimeRangeError;
474
475 #[inline]
502 fn try_from(ts: jiff::Timestamp) -> Result<Self, Self::Error> {
503 Self::from_unix_time_nanos(ts.as_nanosecond())
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
512 fn from_file_time_to_u64() {
513 assert_eq!(u64::from(FileTime::NT_TIME_EPOCH), u64::MIN);
514 assert_eq!(u64::from(FileTime::UNIX_EPOCH), 116_444_736_000_000_000);
515 assert_eq!(u64::from(FileTime::SIGNED_MAX), i64::MAX as u64);
516 assert_eq!(u64::from(FileTime::MAX), u64::MAX);
517 }
518
519 #[cfg(feature = "std")]
520 #[test_strategy::proptest]
521 fn from_file_time_to_u64_roundtrip(ft: FileTime) {
522 use proptest::prop_assert_eq;
523
524 prop_assert_eq!(u64::from(ft), ft.to_raw());
525 }
526
527 #[test]
528 fn try_from_file_time_to_i64() {
529 assert_eq!(
530 i64::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
531 i64::default()
532 );
533 assert_eq!(
534 i64::try_from(FileTime::UNIX_EPOCH).unwrap(),
535 116_444_736_000_000_000
536 );
537 assert_eq!(i64::try_from(FileTime::SIGNED_MAX).unwrap(), i64::MAX);
538 }
539
540 #[cfg(feature = "std")]
541 #[test_strategy::proptest]
542 fn try_from_file_time_to_i64_roundtrip(ft: FileTime) {
543 use proptest::prop_assert;
544
545 if ft <= FileTime::SIGNED_MAX {
546 prop_assert!(i64::try_from(ft).is_ok());
547 } else {
548 prop_assert!(i64::try_from(ft).is_err());
549 }
550 }
551
552 #[test]
553 fn try_from_file_time_to_i64_with_too_big_file_time() {
554 assert!(i64::try_from(FileTime::new(u64::try_from(i64::MAX).unwrap() + 1)).is_err());
555 assert!(i64::try_from(FileTime::MAX).is_err());
556 }
557
558 #[cfg(feature = "std")]
559 #[test]
560 fn from_file_time_to_system_time() {
561 use std::time::{Duration, SystemTime};
562
563 assert_eq!(
564 SystemTime::from(FileTime::NT_TIME_EPOCH),
565 SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
566 );
567 assert_eq!(
568 SystemTime::from(FileTime::UNIX_EPOCH),
569 SystemTime::UNIX_EPOCH
570 );
571 assert_eq!(
572 SystemTime::from(FileTime::new(2_650_467_743_999_999_999)),
573 SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
574 );
575 assert_eq!(
576 SystemTime::from(FileTime::new(2_650_467_744_000_000_000)),
577 SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800)
578 );
579 assert_eq!(
581 SystemTime::from(FileTime::new(9_223_372_036_854_775_807)),
582 SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
583 );
584 if !cfg!(windows) {
585 assert_eq!(
586 SystemTime::from(FileTime::MAX),
587 SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
588 );
589 }
590 }
591
592 #[test]
593 fn try_from_file_time_to_offset_date_time() {
594 use time::macros::datetime;
595
596 assert_eq!(
597 OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
598 datetime!(1601-01-01 00:00 UTC)
599 );
600 assert_eq!(
601 OffsetDateTime::try_from(FileTime::UNIX_EPOCH).unwrap(),
602 OffsetDateTime::UNIX_EPOCH
603 );
604 assert_eq!(
605 OffsetDateTime::try_from(FileTime::new(2_650_467_743_999_999_999)).unwrap(),
606 datetime!(9999-12-31 23:59:59.999_999_900 UTC)
607 );
608 }
609
610 #[cfg(not(feature = "large-dates"))]
611 #[test]
612 fn try_from_file_time_to_offset_date_time_with_invalid_file_time() {
613 assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err());
614 }
615
616 #[cfg(feature = "large-dates")]
617 #[test]
618 fn try_from_file_time_to_offset_date_time_with_large_dates() {
619 use time::macros::datetime;
620
621 assert_eq!(
622 OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(),
623 datetime!(+10000-01-01 00:00 UTC)
624 );
625 assert_eq!(
626 OffsetDateTime::try_from(FileTime::SIGNED_MAX).unwrap(),
627 datetime!(+30828-09-14 02:48:05.477_580_700 UTC)
628 );
629 assert_eq!(
630 OffsetDateTime::try_from(FileTime::MAX).unwrap(),
631 datetime!(+60056-05-28 05:36:10.955_161_500 UTC)
632 );
633 }
634
635 #[cfg(feature = "chrono")]
636 #[test]
637 fn from_file_time_to_chrono_date_time() {
638 use chrono::{DateTime, Utc};
639
640 assert_eq!(
641 DateTime::<Utc>::from(FileTime::NT_TIME_EPOCH),
642 "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
643 );
644 assert_eq!(
645 DateTime::<Utc>::from(FileTime::UNIX_EPOCH),
646 DateTime::<Utc>::UNIX_EPOCH
647 );
648 assert_eq!(
649 DateTime::<Utc>::from(FileTime::new(2_650_467_743_999_999_999)),
650 "9999-12-31 23:59:59.999999900 UTC"
651 .parse::<DateTime<Utc>>()
652 .unwrap()
653 );
654 assert_eq!(
655 DateTime::<Utc>::from(FileTime::new(2_650_467_744_000_000_000)),
656 "+10000-01-01 00:00:00 UTC"
657 .parse::<DateTime<Utc>>()
658 .unwrap()
659 );
660 assert_eq!(
661 DateTime::<Utc>::from(FileTime::SIGNED_MAX),
662 "+30828-09-14 02:48:05.477580700 UTC"
663 .parse::<DateTime<Utc>>()
664 .unwrap()
665 );
666 assert_eq!(
667 DateTime::<Utc>::from(FileTime::MAX),
668 "+60056-05-28 05:36:10.955161500 UTC"
669 .parse::<DateTime<Utc>>()
670 .unwrap()
671 );
672 }
673
674 #[cfg(feature = "jiff")]
675 #[test]
676 fn try_from_file_time_to_jiff_timestamp() {
677 use jiff::{Timestamp, ToSpan};
678
679 assert_eq!(
680 Timestamp::try_from(FileTime::NT_TIME_EPOCH).unwrap(),
681 Timestamp::from_second(-11_644_473_600).unwrap()
682 );
683 assert_eq!(
684 Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap(),
685 Timestamp::UNIX_EPOCH
686 );
687 assert_eq!(
688 Timestamp::try_from(FileTime::new(2_650_466_808_009_999_999)).unwrap(),
689 Timestamp::MAX - 99.nanoseconds()
690 );
691 }
692
693 #[cfg(feature = "jiff")]
694 #[test]
695 fn try_from_file_time_to_jiff_timestamp_with_invalid_file_time() {
696 use jiff::Timestamp;
697
698 assert!(Timestamp::try_from(FileTime::new(2_650_466_808_010_000_000)).is_err());
699 }
700
701 #[test]
702 fn from_u64_to_file_time() {
703 assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH);
704 assert_eq!(
705 FileTime::from(116_444_736_000_000_000),
706 FileTime::UNIX_EPOCH
707 );
708 assert_eq!(FileTime::from(i64::MAX as u64), FileTime::SIGNED_MAX);
709 assert_eq!(FileTime::from(u64::MAX), FileTime::MAX);
710 }
711
712 #[cfg(feature = "std")]
713 #[test_strategy::proptest]
714 fn from_u64_to_file_time_roundtrip(ft: u64) {
715 use proptest::prop_assert_eq;
716
717 prop_assert_eq!(FileTime::from(ft), FileTime::new(ft));
718 }
719
720 #[test]
721 fn try_from_i64_to_file_time_before_nt_time_epoch() {
722 assert_eq!(
723 FileTime::try_from(i64::MIN).unwrap_err(),
724 FileTimeRangeErrorKind::Negative.into()
725 );
726 assert_eq!(
727 FileTime::try_from(i64::default() - 1).unwrap_err(),
728 FileTimeRangeErrorKind::Negative.into()
729 );
730 }
731
732 #[cfg(feature = "std")]
733 #[test_strategy::proptest]
734 fn try_from_i64_to_file_time_before_nt_time_epoch_roundtrip(
735 #[strategy(..i64::default())] ft: i64,
736 ) {
737 use proptest::prop_assert_eq;
738
739 prop_assert_eq!(
740 FileTime::try_from(ft).unwrap_err(),
741 FileTimeRangeErrorKind::Negative.into()
742 );
743 }
744
745 #[test]
746 fn try_from_i64_to_file_time() {
747 assert_eq!(
748 FileTime::try_from(i64::default()).unwrap(),
749 FileTime::NT_TIME_EPOCH
750 );
751 assert_eq!(
752 FileTime::try_from(116_444_736_000_000_000_i64).unwrap(),
753 FileTime::UNIX_EPOCH
754 );
755 assert_eq!(FileTime::try_from(i64::MAX).unwrap(), FileTime::SIGNED_MAX);
756 }
757
758 #[cfg(feature = "std")]
759 #[test_strategy::proptest]
760 fn try_from_i64_to_file_time_roundtrip(#[strategy(i64::default()..)] ft: i64) {
761 use proptest::prop_assert;
762
763 prop_assert!(FileTime::try_from(ft).is_ok());
764 }
765
766 #[cfg(feature = "std")]
767 #[test]
768 fn try_from_system_time_to_file_time_before_nt_time_epoch() {
769 use std::time::{Duration, SystemTime};
770
771 assert_eq!(
772 FileTime::try_from(if cfg!(windows) {
773 SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100)
774 } else {
775 SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_001)
776 })
777 .unwrap_err(),
778 FileTimeRangeErrorKind::Negative.into()
779 );
780 }
781
782 #[cfg(feature = "std")]
783 #[test]
784 fn try_from_system_time_to_file_time() {
785 use std::time::{Duration, SystemTime};
786
787 assert_eq!(
788 FileTime::try_from(
789 SystemTime::UNIX_EPOCH - (FileTime::UNIX_EPOCH - FileTime::NT_TIME_EPOCH)
790 )
791 .unwrap(),
792 FileTime::NT_TIME_EPOCH
793 );
794 assert_eq!(
795 FileTime::try_from(SystemTime::UNIX_EPOCH).unwrap(),
796 FileTime::UNIX_EPOCH
797 );
798 assert_eq!(
799 FileTime::try_from(
800 SystemTime::UNIX_EPOCH + Duration::new(253_402_300_799, 999_999_900)
801 )
802 .unwrap(),
803 FileTime::new(2_650_467_743_999_999_999)
804 );
805 assert_eq!(
806 FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::from_secs(253_402_300_800))
807 .unwrap(),
808 FileTime::new(2_650_467_744_000_000_000)
809 );
810 assert_eq!(
812 FileTime::try_from(
813 SystemTime::UNIX_EPOCH + Duration::new(910_692_730_085, 477_580_700)
814 )
815 .unwrap(),
816 FileTime::new(9_223_372_036_854_775_807)
817 );
818 if !cfg!(windows) {
819 assert_eq!(
820 FileTime::try_from(
821 SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_500)
822 )
823 .unwrap(),
824 FileTime::MAX
825 );
826 }
827 }
828
829 #[cfg(feature = "std")]
830 #[test]
831 fn try_from_system_time_to_file_time_with_too_big_system_time() {
832 use std::time::{Duration, SystemTime};
833
834 if cfg!(windows) {
835 assert!(
836 SystemTime::UNIX_EPOCH
837 .checked_add(Duration::new(910_692_730_085, 477_580_800))
838 .is_none()
839 );
840 } else {
841 assert_eq!(
842 FileTime::try_from(
843 SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600)
844 )
845 .unwrap_err(),
846 FileTimeRangeErrorKind::Overflow.into()
847 );
848 }
849 }
850
851 #[test]
852 fn try_from_offset_date_time_to_file_time_before_nt_time_epoch() {
853 use time::{Duration, macros::datetime};
854
855 assert_eq!(
856 FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100))
857 .unwrap_err(),
858 FileTimeRangeErrorKind::Negative.into()
859 );
860 }
861
862 #[test]
863 fn try_from_offset_date_time_to_file_time() {
864 use time::macros::datetime;
865
866 assert_eq!(
867 FileTime::try_from(datetime!(1601-01-01 00:00 UTC)).unwrap(),
868 FileTime::NT_TIME_EPOCH
869 );
870 assert_eq!(
871 FileTime::try_from(OffsetDateTime::UNIX_EPOCH).unwrap(),
872 FileTime::UNIX_EPOCH
873 );
874 assert_eq!(
875 FileTime::try_from(datetime!(9999-12-31 23:59:59.999_999_999 UTC)).unwrap(),
876 FileTime::new(2_650_467_743_999_999_999)
877 );
878 }
879
880 #[cfg(feature = "large-dates")]
881 #[test]
882 fn try_from_offset_date_time_to_file_time_with_large_dates() {
883 use time::macros::datetime;
884
885 assert_eq!(
886 FileTime::try_from(datetime!(+10000-01-01 00:00 UTC)).unwrap(),
887 FileTime::new(2_650_467_744_000_000_000)
888 );
889 assert_eq!(
890 FileTime::try_from(datetime!(+30828-09-14 02:48:05.477_580_700 UTC)).unwrap(),
891 FileTime::SIGNED_MAX
892 );
893 assert_eq!(
894 FileTime::try_from(datetime!(+60056-05-28 05:36:10.955_161_500 UTC)).unwrap(),
895 FileTime::MAX
896 );
897 }
898
899 #[cfg(feature = "large-dates")]
900 #[test]
901 fn try_from_offset_date_time_to_file_time_with_too_big_date_time() {
902 use time::{Duration, macros::datetime};
903
904 assert_eq!(
905 FileTime::try_from(
906 datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100)
907 )
908 .unwrap_err(),
909 FileTimeRangeErrorKind::Overflow.into()
910 );
911 }
912
913 #[cfg(feature = "chrono")]
914 #[test]
915 fn try_from_chrono_date_time_to_file_time_before_nt_time_epoch() {
916 use chrono::{DateTime, TimeDelta, Utc};
917
918 assert_eq!(
919 FileTime::try_from(
920 "1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap()
921 - TimeDelta::nanoseconds(100)
922 )
923 .unwrap_err(),
924 FileTimeRangeErrorKind::Negative.into()
925 );
926 }
927
928 #[cfg(feature = "chrono")]
929 #[test]
930 fn try_from_chrono_date_time_to_file_time() {
931 use chrono::{DateTime, Utc};
932
933 assert_eq!(
934 FileTime::try_from("1601-01-01 00:00:00 UTC".parse::<DateTime<Utc>>().unwrap())
935 .unwrap(),
936 FileTime::NT_TIME_EPOCH
937 );
938 assert_eq!(
939 FileTime::try_from(DateTime::<Utc>::UNIX_EPOCH).unwrap(),
940 FileTime::UNIX_EPOCH
941 );
942 assert_eq!(
943 FileTime::try_from(
944 "9999-12-31 23:59:59.999999999 UTC"
945 .parse::<DateTime<Utc>>()
946 .unwrap()
947 )
948 .unwrap(),
949 FileTime::new(2_650_467_743_999_999_999)
950 );
951 assert_eq!(
952 FileTime::try_from(
953 "+10000-01-01 00:00:00 UTC"
954 .parse::<DateTime<Utc>>()
955 .unwrap()
956 )
957 .unwrap(),
958 FileTime::new(2_650_467_744_000_000_000)
959 );
960 assert_eq!(
961 FileTime::try_from(
962 "+30828-09-14 02:48:05.477580700 UTC"
963 .parse::<DateTime<Utc>>()
964 .unwrap()
965 )
966 .unwrap(),
967 FileTime::SIGNED_MAX
968 );
969 assert_eq!(
970 FileTime::try_from(
971 "+60056-05-28 05:36:10.955161500 UTC"
972 .parse::<DateTime<Utc>>()
973 .unwrap()
974 )
975 .unwrap(),
976 FileTime::MAX
977 );
978 }
979
980 #[cfg(feature = "chrono")]
981 #[test]
982 fn try_from_chrono_date_time_to_file_time_with_too_big_date_time() {
983 use chrono::{DateTime, TimeDelta, Utc};
984
985 assert_eq!(
986 FileTime::try_from(
987 "+60056-05-28 05:36:10.955161500 UTC"
988 .parse::<DateTime<Utc>>()
989 .unwrap()
990 + TimeDelta::nanoseconds(100)
991 )
992 .unwrap_err(),
993 FileTimeRangeErrorKind::Overflow.into()
994 );
995 }
996
997 #[cfg(feature = "jiff")]
998 #[test]
999 fn try_from_jiff_timestamp_to_file_time_before_nt_time_epoch() {
1000 use jiff::Timestamp;
1001
1002 assert_eq!(
1003 FileTime::try_from(Timestamp::from_nanosecond(-11_644_473_600_000_000_001).unwrap())
1004 .unwrap_err(),
1005 FileTimeRangeErrorKind::Negative.into()
1006 );
1007 }
1008
1009 #[cfg(feature = "jiff")]
1010 #[test]
1011 fn try_from_jiff_timestamp_to_file_time() {
1012 use jiff::Timestamp;
1013
1014 assert_eq!(
1015 FileTime::try_from(Timestamp::from_second(-11_644_473_600).unwrap()).unwrap(),
1016 FileTime::NT_TIME_EPOCH
1017 );
1018 assert_eq!(
1019 FileTime::try_from(Timestamp::UNIX_EPOCH).unwrap(),
1020 FileTime::UNIX_EPOCH
1021 );
1022 assert_eq!(
1023 FileTime::try_from(Timestamp::MAX).unwrap(),
1024 FileTime::new(2_650_466_808_009_999_999)
1025 );
1026 }
1027}