| 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::<NaiveDateTime>() { |
| 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::<NaiveDateTime>() { |
| 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::<NaiveDateTime>().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::<NaiveDateTime>(&bytes).unwrap(), dt_min); |
| |
| let dt_max = NaiveDateTime::MAX; |
| let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); |
| assert_eq!(rkyv::from_bytes::<NaiveDateTime>(&bytes).unwrap(), dt_max); |
| } |