use super::NaiveDateTime; use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; #[test] fn test_datetime_add() { fn check( (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), rhs: TimeDelta, result: Option<(i32, u32, u32, u32, u32, u32)>, ) { let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let sum = result.map(|(y, m, d, h, n, s)| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() }); assert_eq!(lhs.checked_add_signed(rhs), sum); assert_eq!(lhs.checked_sub_signed(-rhs), sum); } let seconds = |s| TimeDelta::try_seconds(s).unwrap(); check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8))); check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. // (they are private constants, but the equivalence is tested in that module.) let max_days_from_year_0 = NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None); check((0, 1, 1, 0, 0, 0), TimeDelta::MAX, None); let min_days_from_year_0 = NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None); check((0, 1, 1, 0, 0, 0), TimeDelta::MIN, None); } #[test] fn test_datetime_sub() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let since = NaiveDateTime::signed_duration_since; assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero()); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::try_seconds(1).unwrap() ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), TimeDelta::try_seconds(-1).unwrap() ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), TimeDelta::try_seconds(86399).unwrap() ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), TimeDelta::try_seconds(999_999_999).unwrap() ); } #[test] fn test_datetime_addassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date += TimeDelta::try_minutes(10_000_000).unwrap(); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); date += TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); } #[test] fn test_datetime_subassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); date -= TimeDelta::try_minutes(10_000_000).unwrap(); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); date -= TimeDelta::try_days(10).unwrap(); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } #[test] fn test_core_duration_ops() { use core::time::Duration; let mut dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); let same = dt + Duration::ZERO; assert_eq!(dt, same); dt += Duration::new(3600, 0); assert_eq!(dt, NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(12, 34, 12).unwrap()); } #[test] #[should_panic] fn test_core_duration_max() { use core::time::Duration; let mut utc_dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); utc_dt += Duration::MAX; } #[test] fn test_datetime_from_str() { // valid cases let valid = [ "2001-02-03T04:05:06", "2012-12-12T12:12:12", "2015-02-18T23:16:09.153", "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", "+82701-05-6T15:9:60.898989898989", " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; let s_ = format!("{:?}", d); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, Err(e) => { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ `{:?}` does not match", s, d, d_ ); } // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ "", // empty "x", // invalid / missing data "15", // missing data "15:8:9", // looks like a time (invalid date) "15-8-9", // looks like a date (invalid) "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format "Sat Jun 30 23:59:60 2012", // valid date, wrong format "1441497364.649", // valid date, wrong format "+1441497364.649", // valid date, wrong format "+1441497364", // valid date, wrong format "2014/02/03 04:05:06", // valid date, wrong format "2015-15-15T15:15:15", // invalid date "2012-12-12T12:12:12x", // bad timezone / trailing literal "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal "2012-123-12T12:12:12", // invalid month "2012-12-12t12:12:12", // bad divider 't' "2012-12-12 12:12:12", // missing divider 'T' "2012-12-12T12:12:12Z", // trailing char 'Z' "+ 82701-123-12T12:12:12", // strange year, invalid month "+802701-123-12T12:12:12", // out-of-bound year, invalid month ]; for &s in &invalid { eprintln!("test_datetime_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } } #[test] fn test_datetime_parse_from_str() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let ymdhmsn = |y, m, d, h, n, s, nano| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap() }; assert_eq!( NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) ); // ignore offset assert_eq!( NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), Ok(ymdhms(2015, 2, 2, 0, 0, 0)) ); assert_eq!( NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(ymdhms(2013, 8, 9, 23, 54, 35)) ); assert!(NaiveDateTime::parse_from_str( "Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT" ) .is_err()); assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient assert_eq!( NaiveDateTime::parse_from_str("1441497364", "%s"), Ok(ymdhms(2015, 9, 5, 23, 56, 4)) ); assert_eq!( NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) ); assert_eq!( NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) ); assert_eq!( NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) ); assert_eq!( NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) ); } #[test] fn test_datetime_parse_from_str_with_spaces() { let parse_from_str = NaiveDateTime::parse_from_str; let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); // with varying spaces - should succeed assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); // with varying spaces - should fail // leading space in data assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); // trailing space in data assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); // trailing tab in data assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); // mismatched newlines assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); // trailing literal in data assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); } #[test] fn test_datetime_add_sub_invariant() { // issue #37 let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let t = -946684799990000; let time = base + TimeDelta::microseconds(t); assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); } #[test] fn test_and_local_timezone() { let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); let dt_utc = ndt.and_utc(); assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); let offset_tz = FixedOffset::west_opt(4 * 3600).unwrap(); let dt_offset = ndt.and_local_timezone(offset_tz).unwrap(); assert_eq!(dt_offset.naive_local(), ndt); assert_eq!(dt_offset.timezone(), offset_tz); } #[test] fn test_and_utc() { let ndt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap(); let dt_utc = ndt.and_utc(); assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); } #[test] fn test_checked_add_offset() { let ymdhmsm = |y, m, d, h, mn, s, mi| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) }; let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none()); let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none()); } #[test] fn test_checked_sub_offset() { let ymdhmsm = |y, m, d, h, mn, s, mi| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) }; let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none()); let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none()); assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); } #[test] fn test_overflowing_add_offset() { let ymdhmsm = |y, m, d, h, mn, s, mi| { NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() }; let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX); let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); // regular date let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); // leap second is preserved let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); // out of range assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN); } #[test] fn test_and_timezone_min_max_dates() { for offset_hour in -23..=23 { dbg!(offset_hour); let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); let local_max = NaiveDateTime::MAX.and_local_timezone(offset); if offset_hour >= 0 { assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); } else { assert_eq!(local_max, MappedLocalTime::None); } let local_min = NaiveDateTime::MIN.and_local_timezone(offset); if offset_hour <= 0 { assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); } else { assert_eq!(local_min, MappedLocalTime::None); } } } #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { let dt_min = NaiveDateTime::MIN; let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap(); assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_min); let dt_max = NaiveDateTime::MAX; let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_max); }