feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+2634
File diff suppressed because it is too large.
Load diff
+920
@@ -0,0 +1,920 @@
|
||||
use super::{Days, MAX_YEAR, MIN_YEAR, Months, NaiveDate};
|
||||
use crate::naive::internals::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags};
|
||||
use crate::{Datelike, TimeDelta, Weekday};
|
||||
|
||||
// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`,
|
||||
// we use a separate run-time test.
|
||||
#[test]
|
||||
fn test_date_bounds() {
|
||||
let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap();
|
||||
let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap();
|
||||
assert!(
|
||||
NaiveDate::MIN == calculated_min,
|
||||
"`NaiveDate::MIN` should have year flag {:?}",
|
||||
calculated_min.year_flags()
|
||||
);
|
||||
assert!(
|
||||
NaiveDate::MAX == calculated_max,
|
||||
"`NaiveDate::MAX` should have year flag {:?} and ordinal {}",
|
||||
calculated_max.year_flags(),
|
||||
calculated_max.ordinal()
|
||||
);
|
||||
|
||||
// let's also check that the entire range do not exceed 2^44 seconds
|
||||
// (sometimes used for bounding `TimeDelta` against overflow)
|
||||
let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds();
|
||||
let maxsecs = maxsecs + 86401; // also take care of DateTime
|
||||
assert!(
|
||||
maxsecs < (1 << MAX_BITS),
|
||||
"The entire `NaiveDate` range somehow exceeds 2^{MAX_BITS} seconds"
|
||||
);
|
||||
|
||||
const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN;
|
||||
assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year()));
|
||||
assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31));
|
||||
|
||||
const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX;
|
||||
assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year()));
|
||||
assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_months() {
|
||||
// identity
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)),
|
||||
Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap())
|
||||
);
|
||||
|
||||
// add with months exceeding `i32::MAX`
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3)
|
||||
.unwrap()
|
||||
.checked_add_months(Months::new(i32::MAX as u32 + 1)),
|
||||
None
|
||||
);
|
||||
|
||||
// sub with months exceeding `i32::MIN`
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3)
|
||||
.unwrap()
|
||||
.checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)),
|
||||
None
|
||||
);
|
||||
|
||||
// add overflowing year
|
||||
assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None);
|
||||
|
||||
// add underflowing year
|
||||
assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None);
|
||||
|
||||
// sub crossing year 0 boundary
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)),
|
||||
Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap())
|
||||
);
|
||||
|
||||
// add crossing year boundary
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)),
|
||||
Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap())
|
||||
);
|
||||
|
||||
// sub crossing year boundary
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)),
|
||||
Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap())
|
||||
);
|
||||
|
||||
// add clamping day, non-leap year
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)),
|
||||
Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap())
|
||||
);
|
||||
|
||||
// add to leap day
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)),
|
||||
Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap())
|
||||
);
|
||||
|
||||
// add into december
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)),
|
||||
Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap())
|
||||
);
|
||||
|
||||
// sub into december
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)),
|
||||
Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap())
|
||||
);
|
||||
|
||||
// add into january
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)),
|
||||
Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap())
|
||||
);
|
||||
|
||||
// sub into january
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)),
|
||||
Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readme_doomsday() {
|
||||
for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
|
||||
// even months
|
||||
let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap();
|
||||
let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap();
|
||||
let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap();
|
||||
let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap();
|
||||
let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap();
|
||||
|
||||
// nine to five, seven-eleven
|
||||
let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap();
|
||||
let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap();
|
||||
let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap();
|
||||
let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap();
|
||||
|
||||
// "March 0"
|
||||
let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap();
|
||||
|
||||
let weekday = d30.weekday();
|
||||
let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117];
|
||||
assert!(other_dates.iter().all(|d| d.weekday() == weekday));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_ymd() {
|
||||
let from_ymd = NaiveDate::from_ymd_opt;
|
||||
|
||||
assert!(from_ymd(2012, 0, 1).is_none());
|
||||
assert!(from_ymd(2012, 1, 1).is_some());
|
||||
assert!(from_ymd(2012, 2, 29).is_some());
|
||||
assert!(from_ymd(2014, 2, 29).is_none());
|
||||
assert!(from_ymd(2014, 3, 0).is_none());
|
||||
assert!(from_ymd(2014, 3, 1).is_some());
|
||||
assert!(from_ymd(2014, 3, 31).is_some());
|
||||
assert!(from_ymd(2014, 3, 32).is_none());
|
||||
assert!(from_ymd(2014, 12, 31).is_some());
|
||||
assert!(from_ymd(2014, 13, 1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_yo() {
|
||||
let from_yo = NaiveDate::from_yo_opt;
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
|
||||
assert_eq!(from_yo(2012, 0), None);
|
||||
assert_eq!(from_yo(2012, 1), Some(ymd(2012, 1, 1)));
|
||||
assert_eq!(from_yo(2012, 2), Some(ymd(2012, 1, 2)));
|
||||
assert_eq!(from_yo(2012, 32), Some(ymd(2012, 2, 1)));
|
||||
assert_eq!(from_yo(2012, 60), Some(ymd(2012, 2, 29)));
|
||||
assert_eq!(from_yo(2012, 61), Some(ymd(2012, 3, 1)));
|
||||
assert_eq!(from_yo(2012, 100), Some(ymd(2012, 4, 9)));
|
||||
assert_eq!(from_yo(2012, 200), Some(ymd(2012, 7, 18)));
|
||||
assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26)));
|
||||
assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31)));
|
||||
assert_eq!(from_yo(2012, 367), None);
|
||||
assert_eq!(from_yo(2012, (1 << 28) | 60), None);
|
||||
|
||||
assert_eq!(from_yo(2014, 0), None);
|
||||
assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1)));
|
||||
assert_eq!(from_yo(2014, 2), Some(ymd(2014, 1, 2)));
|
||||
assert_eq!(from_yo(2014, 32), Some(ymd(2014, 2, 1)));
|
||||
assert_eq!(from_yo(2014, 59), Some(ymd(2014, 2, 28)));
|
||||
assert_eq!(from_yo(2014, 60), Some(ymd(2014, 3, 1)));
|
||||
assert_eq!(from_yo(2014, 100), Some(ymd(2014, 4, 10)));
|
||||
assert_eq!(from_yo(2014, 200), Some(ymd(2014, 7, 19)));
|
||||
assert_eq!(from_yo(2014, 300), Some(ymd(2014, 10, 27)));
|
||||
assert_eq!(from_yo(2014, 365), Some(ymd(2014, 12, 31)));
|
||||
assert_eq!(from_yo(2014, 366), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_isoywd() {
|
||||
let from_isoywd = NaiveDate::from_isoywd_opt;
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
|
||||
assert_eq!(from_isoywd(2004, 0, Weekday::Sun), None);
|
||||
assert_eq!(from_isoywd(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29)));
|
||||
assert_eq!(from_isoywd(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4)));
|
||||
assert_eq!(from_isoywd(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5)));
|
||||
assert_eq!(from_isoywd(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11)));
|
||||
assert_eq!(from_isoywd(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20)));
|
||||
assert_eq!(from_isoywd(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26)));
|
||||
assert_eq!(from_isoywd(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27)));
|
||||
assert_eq!(from_isoywd(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2)));
|
||||
assert_eq!(from_isoywd(2004, 54, Weekday::Mon), None);
|
||||
|
||||
assert_eq!(from_isoywd(2011, 0, Weekday::Sun), None);
|
||||
assert_eq!(from_isoywd(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3)));
|
||||
assert_eq!(from_isoywd(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9)));
|
||||
assert_eq!(from_isoywd(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10)));
|
||||
assert_eq!(from_isoywd(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16)));
|
||||
|
||||
assert_eq!(from_isoywd(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17)));
|
||||
assert_eq!(from_isoywd(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23)));
|
||||
assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24)));
|
||||
assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30)));
|
||||
assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_isoywd_and_iso_week() {
|
||||
for year in 2000..2401 {
|
||||
for week in 1..54 {
|
||||
for &weekday in [
|
||||
Weekday::Mon,
|
||||
Weekday::Tue,
|
||||
Weekday::Wed,
|
||||
Weekday::Thu,
|
||||
Weekday::Fri,
|
||||
Weekday::Sat,
|
||||
Weekday::Sun,
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
let d = NaiveDate::from_isoywd_opt(year, week, weekday);
|
||||
if let Some(d) = d {
|
||||
assert_eq!(d.weekday(), weekday);
|
||||
let w = d.iso_week();
|
||||
assert_eq!(w.year(), year);
|
||||
assert_eq!(w.week(), week);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for year in 2000..2401 {
|
||||
for month in 1..13 {
|
||||
for day in 1..32 {
|
||||
let d = NaiveDate::from_ymd_opt(year, month, day);
|
||||
if let Some(d) = d {
|
||||
let w = d.iso_week();
|
||||
let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday());
|
||||
assert_eq!(d, d_.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_num_days_from_ce() {
|
||||
let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt;
|
||||
assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap()));
|
||||
assert_eq!(
|
||||
from_ndays_from_ce(146097 * 5 + 1),
|
||||
Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap())
|
||||
);
|
||||
assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE
|
||||
assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()));
|
||||
assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE
|
||||
|
||||
for days in (-9999..10001).map(|x| x * 100) {
|
||||
assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days));
|
||||
}
|
||||
|
||||
assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN));
|
||||
assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None);
|
||||
assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX));
|
||||
assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None);
|
||||
|
||||
assert_eq!(from_ndays_from_ce(i32::MIN), None);
|
||||
assert_eq!(from_ndays_from_ce(i32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_epoch_days() {
|
||||
let from_epoch_days = NaiveDate::from_epoch_days;
|
||||
assert_eq!(from_epoch_days(-719_162), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(0), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(1), Some(NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()));
|
||||
assert_eq!(from_epoch_days(2), Some(NaiveDate::from_ymd_opt(1970, 1, 3).unwrap()));
|
||||
assert_eq!(from_epoch_days(30), Some(NaiveDate::from_ymd_opt(1970, 1, 31).unwrap()));
|
||||
assert_eq!(from_epoch_days(31), Some(NaiveDate::from_ymd_opt(1970, 2, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(58), Some(NaiveDate::from_ymd_opt(1970, 2, 28).unwrap()));
|
||||
assert_eq!(from_epoch_days(59), Some(NaiveDate::from_ymd_opt(1970, 3, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(364), Some(NaiveDate::from_ymd_opt(1970, 12, 31).unwrap()));
|
||||
assert_eq!(from_epoch_days(365), Some(NaiveDate::from_ymd_opt(1971, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(365 * 2), Some(NaiveDate::from_ymd_opt(1972, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(1973, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(365 * 4 + 1), Some(NaiveDate::from_ymd_opt(1974, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(13036), Some(NaiveDate::from_ymd_opt(2005, 9, 10).unwrap()));
|
||||
assert_eq!(from_epoch_days(-365), Some(NaiveDate::from_ymd_opt(1969, 1, 1).unwrap()));
|
||||
assert_eq!(from_epoch_days(-366), Some(NaiveDate::from_ymd_opt(1968, 12, 31).unwrap()));
|
||||
|
||||
for days in (-9999..10001).map(|x| x * 100) {
|
||||
assert_eq!(from_epoch_days(days).map(|d| d.to_epoch_days()), Some(days));
|
||||
}
|
||||
|
||||
assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days()), Some(NaiveDate::MIN));
|
||||
assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days() - 1), None);
|
||||
assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days()), Some(NaiveDate::MAX));
|
||||
assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days() + 1), None);
|
||||
|
||||
assert_eq!(from_epoch_days(i32::MIN), None);
|
||||
assert_eq!(from_epoch_days(i32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_weekday_of_month_opt() {
|
||||
let ymwd = NaiveDate::from_weekday_of_month_opt;
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None);
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap()));
|
||||
assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_fields() {
|
||||
fn check(year: i32, month: u32, day: u32, ordinal: u32) {
|
||||
let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap();
|
||||
assert_eq!(d1.year(), year);
|
||||
assert_eq!(d1.month(), month);
|
||||
assert_eq!(d1.day(), day);
|
||||
assert_eq!(d1.ordinal(), ordinal);
|
||||
|
||||
let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap();
|
||||
assert_eq!(d2.year(), year);
|
||||
assert_eq!(d2.month(), month);
|
||||
assert_eq!(d2.day(), day);
|
||||
assert_eq!(d2.ordinal(), ordinal);
|
||||
|
||||
assert_eq!(d1, d2);
|
||||
}
|
||||
|
||||
check(2012, 1, 1, 1);
|
||||
check(2012, 1, 2, 2);
|
||||
check(2012, 2, 1, 32);
|
||||
check(2012, 2, 29, 60);
|
||||
check(2012, 3, 1, 61);
|
||||
check(2012, 4, 9, 100);
|
||||
check(2012, 7, 18, 200);
|
||||
check(2012, 10, 26, 300);
|
||||
check(2012, 12, 31, 366);
|
||||
|
||||
check(2014, 1, 1, 1);
|
||||
check(2014, 1, 2, 2);
|
||||
check(2014, 2, 1, 32);
|
||||
check(2014, 2, 28, 59);
|
||||
check(2014, 3, 1, 60);
|
||||
check(2014, 4, 10, 100);
|
||||
check(2014, 7, 19, 200);
|
||||
check(2014, 10, 27, 300);
|
||||
check(2014, 12, 31, 365);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_weekday() {
|
||||
assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri);
|
||||
// May 20, 1875 = ISO 8601 reference date
|
||||
assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu);
|
||||
assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_with_fields() {
|
||||
let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap();
|
||||
assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_year(-100), None);
|
||||
assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_year(1900), None);
|
||||
assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_year(2001), None);
|
||||
assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_year(i32::MAX), None);
|
||||
|
||||
let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap();
|
||||
assert_eq!(d.with_month(0), None);
|
||||
assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap()));
|
||||
assert_eq!(d.with_month(2), None);
|
||||
assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap()));
|
||||
assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap()));
|
||||
assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap()));
|
||||
assert_eq!(d.with_month(13), None);
|
||||
assert_eq!(d.with_month(u32::MAX), None);
|
||||
|
||||
let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap();
|
||||
assert_eq!(d.with_day(0), None);
|
||||
assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap()));
|
||||
assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_day(30), None);
|
||||
assert_eq!(d.with_day(u32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_with_ordinal() {
|
||||
let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap();
|
||||
assert_eq!(d.with_ordinal(0), None);
|
||||
assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()));
|
||||
assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap()));
|
||||
assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap()));
|
||||
assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap()));
|
||||
assert_eq!(d.with_ordinal(367), None);
|
||||
assert_eq!(d.with_ordinal((1 << 28) | 60), None);
|
||||
let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap();
|
||||
assert_eq!(d.with_ordinal(366), None);
|
||||
assert_eq!(d.with_ordinal(u32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_num_days_from_ce() {
|
||||
assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1);
|
||||
|
||||
for year in -9999..10001 {
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(),
|
||||
NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_to_epoch_days() {
|
||||
assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().to_epoch_days(), 0);
|
||||
|
||||
for year in -9999..10001 {
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(year, 1, 1).unwrap().to_epoch_days(),
|
||||
NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().to_epoch_days() + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_succ() {
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7)));
|
||||
assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1)));
|
||||
assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1)));
|
||||
assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29)));
|
||||
assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_pred() {
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29)));
|
||||
assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31)));
|
||||
assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31)));
|
||||
assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6)));
|
||||
assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_checked_add_signed() {
|
||||
fn check(lhs: Option<NaiveDate>, delta: TimeDelta, rhs: Option<NaiveDate>) {
|
||||
assert_eq!(lhs.unwrap().checked_add_signed(delta), rhs);
|
||||
assert_eq!(lhs.unwrap().checked_sub_signed(-delta), rhs);
|
||||
}
|
||||
let ymd = NaiveDate::from_ymd_opt;
|
||||
|
||||
check(ymd(2014, 1, 1), TimeDelta::zero(), ymd(2014, 1, 1));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), ymd(2014, 1, 1));
|
||||
// always round towards zero
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), ymd(2014, 1, 1));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap(), ymd(2014, 1, 2));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_days(-1).unwrap(), ymd(2013, 12, 31));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap(), ymd(2014, 12, 31));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), ymd(2018, 1, 1));
|
||||
check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), ymd(2414, 1, 1));
|
||||
|
||||
check(ymd(-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), ymd(5, 1, 1));
|
||||
|
||||
// overflow check
|
||||
check(
|
||||
ymd(0, 1, 1),
|
||||
TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(),
|
||||
ymd(MAX_YEAR, 12, 31),
|
||||
);
|
||||
check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None);
|
||||
check(ymd(0, 1, 1), TimeDelta::MAX, None);
|
||||
check(
|
||||
ymd(0, 1, 1),
|
||||
TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(),
|
||||
ymd(MIN_YEAR, 1, 1),
|
||||
);
|
||||
check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None);
|
||||
check(ymd(0, 1, 1), TimeDelta::MIN, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_signed_duration_since() {
|
||||
fn check(lhs: Option<NaiveDate>, rhs: Option<NaiveDate>, delta: TimeDelta) {
|
||||
assert_eq!(lhs.unwrap().signed_duration_since(rhs.unwrap()), delta);
|
||||
assert_eq!(rhs.unwrap().signed_duration_since(lhs.unwrap()), -delta);
|
||||
}
|
||||
let ymd = NaiveDate::from_ymd_opt;
|
||||
|
||||
check(ymd(2014, 1, 1), ymd(2014, 1, 1), TimeDelta::zero());
|
||||
check(ymd(2014, 1, 2), ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap());
|
||||
check(ymd(2014, 12, 31), ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap());
|
||||
check(ymd(2015, 1, 3), ymd(2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap());
|
||||
check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap());
|
||||
check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap());
|
||||
|
||||
check(
|
||||
ymd(MAX_YEAR, 12, 31),
|
||||
ymd(0, 1, 1),
|
||||
TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(),
|
||||
);
|
||||
check(
|
||||
ymd(MIN_YEAR, 1, 1),
|
||||
ymd(0, 1, 1),
|
||||
TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_add_days() {
|
||||
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
|
||||
assert_eq!(lhs.unwrap().checked_add_days(days), rhs);
|
||||
}
|
||||
let ymd = NaiveDate::from_ymd_opt;
|
||||
|
||||
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
|
||||
// always round towards zero
|
||||
check(ymd(2014, 1, 1), Days::new(1), ymd(2014, 1, 2));
|
||||
check(ymd(2014, 1, 1), Days::new(364), ymd(2014, 12, 31));
|
||||
check(ymd(2014, 1, 1), Days::new(365 * 4 + 1), ymd(2018, 1, 1));
|
||||
check(ymd(2014, 1, 1), Days::new(365 * 400 + 97), ymd(2414, 1, 1));
|
||||
|
||||
check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1));
|
||||
|
||||
// overflow check
|
||||
check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31));
|
||||
check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_sub_days() {
|
||||
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
|
||||
assert_eq!(lhs.unwrap().checked_sub_days(days), rhs);
|
||||
}
|
||||
let ymd = NaiveDate::from_ymd_opt;
|
||||
|
||||
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
|
||||
check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1));
|
||||
check(ymd(2014, 12, 31), Days::new(364), ymd(2014, 1, 1));
|
||||
check(ymd(2015, 1, 3), Days::new(365 + 2), ymd(2014, 1, 1));
|
||||
check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1));
|
||||
check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1));
|
||||
|
||||
check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1));
|
||||
check(
|
||||
ymd(0, 1, 1),
|
||||
Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()),
|
||||
ymd(MIN_YEAR, 1, 1),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_addassignment() {
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
let mut date = ymd(2016, 10, 1);
|
||||
date += TimeDelta::try_days(10).unwrap();
|
||||
assert_eq!(date, ymd(2016, 10, 11));
|
||||
date += TimeDelta::try_days(30).unwrap();
|
||||
assert_eq!(date, ymd(2016, 11, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_subassignment() {
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
let mut date = ymd(2016, 10, 11);
|
||||
date -= TimeDelta::try_days(10).unwrap();
|
||||
assert_eq!(date, ymd(2016, 10, 1));
|
||||
date -= TimeDelta::try_days(2).unwrap();
|
||||
assert_eq!(date, ymd(2016, 9, 29));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_fmt() {
|
||||
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04");
|
||||
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04");
|
||||
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04");
|
||||
assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04");
|
||||
|
||||
assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04");
|
||||
|
||||
// the format specifier should have no effect on `NaiveTime`
|
||||
assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06");
|
||||
assert_eq!(format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_str() {
|
||||
// valid cases
|
||||
let valid = [
|
||||
"-0000000123456-1-2",
|
||||
" -123456 - 1 - 2 ",
|
||||
"-12345-1-2",
|
||||
"-1234-12-31",
|
||||
"-7-6-5",
|
||||
"350-2-28",
|
||||
"360-02-29",
|
||||
"0360-02-29",
|
||||
"2015-2 -18",
|
||||
"2015-02-18",
|
||||
"+70-2-18",
|
||||
"+70000-2-18",
|
||||
"+00007-2-18",
|
||||
];
|
||||
for &s in &valid {
|
||||
eprintln!("test_date_from_str valid {s:?}");
|
||||
let d = match s.parse::<NaiveDate>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => panic!("parsing `{s}` has failed: {e}"),
|
||||
};
|
||||
eprintln!("d {d:?} (NaiveDate)");
|
||||
let s_ = format!("{d:?}");
|
||||
eprintln!("s_ {s_:?}");
|
||||
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
|
||||
let d_ = match s_.parse::<NaiveDate>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}")
|
||||
}
|
||||
};
|
||||
eprintln!("d_ {d_:?} (NaiveDate)");
|
||||
assert!(
|
||||
d == d_,
|
||||
"`{s}` is parsed into `{d:?}`, but reparsed result \
|
||||
`{d_:?}` does not match"
|
||||
);
|
||||
}
|
||||
|
||||
// some invalid cases
|
||||
// since `ParseErrorKind` is private, all we can do is to check if there was an error
|
||||
let invalid = [
|
||||
"", // empty
|
||||
"x", // invalid
|
||||
"Fri, 09 Aug 2013 GMT", // valid date, wrong format
|
||||
"Sat Jun 30 2012", // valid date, wrong format
|
||||
"1441497364.649", // valid datetime, wrong format
|
||||
"+1441497364.649", // valid datetime, wrong format
|
||||
"+1441497364", // valid datetime, wrong format
|
||||
"2014/02/03", // valid date, wrong format
|
||||
"2014", // datetime missing data
|
||||
"2014-01", // datetime missing data
|
||||
"2014-01-00", // invalid day
|
||||
"2014-11-32", // invalid day
|
||||
"2014-13-01", // invalid month
|
||||
"2014-13-57", // invalid month, day
|
||||
"9999999-9-9", // invalid year (out of bounds)
|
||||
];
|
||||
for &s in &invalid {
|
||||
eprintln!("test_date_from_str invalid {s:?}");
|
||||
assert!(s.parse::<NaiveDate>().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_parse_from_str() {
|
||||
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
|
||||
Ok(ymd(2014, 5, 7))
|
||||
); // ignore time and offset
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"),
|
||||
Ok(ymd(2015, 2, 2))
|
||||
);
|
||||
assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9)));
|
||||
assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err());
|
||||
assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err());
|
||||
assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient
|
||||
|
||||
assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter
|
||||
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
|
||||
NaiveDate::from_ymd_opt(2020, 1, 12),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
|
||||
NaiveDate::from_ymd_opt(2019, 1, 13),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_iterator_limit() {
|
||||
assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2);
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_week_iterator_limit() {
|
||||
assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2);
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weeks_from() {
|
||||
// tests per: https://github.com/chronotope/chrono/issues/961
|
||||
// these internally use `weeks_from` via the parsing infrastructure
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
|
||||
NaiveDate::from_ymd_opt(2020, 1, 12),
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
|
||||
NaiveDate::from_ymd_opt(2019, 1, 13),
|
||||
);
|
||||
|
||||
// direct tests
|
||||
for (y, starts_on) in &[
|
||||
(2019, Weekday::Tue),
|
||||
(2020, Weekday::Wed),
|
||||
(2021, Weekday::Fri),
|
||||
(2022, Weekday::Sat),
|
||||
(2023, Weekday::Sun),
|
||||
(2024, Weekday::Mon),
|
||||
(2025, Weekday::Wed),
|
||||
(2026, Weekday::Thu),
|
||||
] {
|
||||
for day in &[
|
||||
Weekday::Mon,
|
||||
Weekday::Tue,
|
||||
Weekday::Wed,
|
||||
Weekday::Thu,
|
||||
Weekday::Fri,
|
||||
Weekday::Sat,
|
||||
Weekday::Sun,
|
||||
] {
|
||||
assert_eq!(
|
||||
NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)),
|
||||
Some(if day == starts_on { 1 } else { 0 })
|
||||
);
|
||||
|
||||
// last day must always be in week 52 or 53
|
||||
assert!(
|
||||
[52, 53].contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap();
|
||||
|
||||
// 400 years covers all year types
|
||||
for day in &[
|
||||
Weekday::Mon,
|
||||
Weekday::Tue,
|
||||
Weekday::Wed,
|
||||
Weekday::Thu,
|
||||
Weekday::Fri,
|
||||
Weekday::Sat,
|
||||
Weekday::Sun,
|
||||
] {
|
||||
// must always be below 54
|
||||
for dplus in 1..(400 * 366) {
|
||||
assert!((base + Days::new(dplus)).weeks_from(*day) < 54)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_0_overflow() {
|
||||
let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap();
|
||||
assert!(dt.with_month0(4294967295).is_none());
|
||||
assert!(dt.with_day0(4294967295).is_none());
|
||||
assert!(dt.with_ordinal0(4294967295).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leap_year() {
|
||||
for year in 0..=MAX_YEAR {
|
||||
let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
|
||||
let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
||||
assert_eq!(date.leap_year(), is_leap);
|
||||
assert_eq!(date.leap_year(), date.with_ordinal(366).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_yearflags() {
|
||||
for (year, year_flags, _) in YEAR_FLAGS {
|
||||
assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weekday_with_yearflags() {
|
||||
for (year, year_flags, first_weekday) in YEAR_FLAGS {
|
||||
let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap();
|
||||
dbg!(year);
|
||||
assert_eq!(first_day_of_year.year_flags(), year_flags);
|
||||
assert_eq!(first_day_of_year.weekday(), first_weekday);
|
||||
|
||||
let mut prev = first_day_of_year.weekday();
|
||||
for ordinal in 2u32..=year_flags.ndays() {
|
||||
let date = NaiveDate::from_yo_opt(year, ordinal).unwrap();
|
||||
let expected = prev.succ();
|
||||
assert_eq!(date.weekday(), expected);
|
||||
prev = expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_isoweekdate_with_yearflags() {
|
||||
for (year, year_flags, _) in YEAR_FLAGS {
|
||||
// January 4 should be in the first week
|
||||
let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap();
|
||||
let iso_week = jan4.iso_week();
|
||||
assert_eq!(jan4.year_flags(), year_flags);
|
||||
assert_eq!(iso_week.week(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_to_mdf_to_date() {
|
||||
for (year, year_flags, _) in YEAR_FLAGS {
|
||||
for ordinal in 1..=year_flags.ndays() {
|
||||
let date = NaiveDate::from_yo_opt(year, ordinal).unwrap();
|
||||
assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for testing some methods with all combinations of `YearFlags`.
|
||||
// (year, flags, first weekday of year)
|
||||
const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [
|
||||
(2006, A, Weekday::Sun),
|
||||
(2005, B, Weekday::Sat),
|
||||
(2010, C, Weekday::Fri),
|
||||
(2009, D, Weekday::Thu),
|
||||
(2003, E, Weekday::Wed),
|
||||
(2002, F, Weekday::Tue),
|
||||
(2001, G, Weekday::Mon),
|
||||
(2012, AG, Weekday::Sun),
|
||||
(2000, BA, Weekday::Sat),
|
||||
(2016, CB, Weekday::Fri),
|
||||
(2004, DC, Weekday::Thu),
|
||||
(2020, ED, Weekday::Wed),
|
||||
(2008, FE, Weekday::Tue),
|
||||
(2024, GF, Weekday::Mon),
|
||||
];
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rkyv-validation")]
|
||||
fn test_rkyv_validation() {
|
||||
let date_min = NaiveDate::MIN;
|
||||
let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<NaiveDate>(&bytes).unwrap(), date_min);
|
||||
|
||||
let date_max = NaiveDate::MAX;
|
||||
let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<NaiveDate>(&bytes).unwrap(), date_max);
|
||||
}
|
||||
|
||||
// MAX_YEAR-12-31 minus 0000-01-01
|
||||
// = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01)
|
||||
// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365
|
||||
// = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR)
|
||||
const MAX_DAYS_FROM_YEAR_0: i32 =
|
||||
(MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400;
|
||||
|
||||
// MIN_YEAR-01-01 minus 0000-01-01
|
||||
// = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000)
|
||||
const MIN_DAYS_FROM_YEAR_0: i32 = MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400;
|
||||
|
||||
// only used for testing, but duplicated in naive::datetime
|
||||
const MAX_BITS: usize = 44;
|
||||
+2171
File diff suppressed because it is too large.
Load diff
+1304
File diff suppressed because it is too large.
Load diff
+405
@@ -0,0 +1,405 @@
|
||||
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 `{s}` has failed: {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!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}")
|
||||
}
|
||||
};
|
||||
assert!(
|
||||
d == d_,
|
||||
"`{s}` is parsed into `{d:?}`, but reparsed result \
|
||||
`{d_:?}` does not match"
|
||||
);
|
||||
}
|
||||
|
||||
// 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 Q2 12:3456", "%Y-%m-%d Q%q %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);
|
||||
}
|
||||
Vendored
+591
@@ -0,0 +1,591 @@
|
||||
//! Internal helper types for working with dates.
|
||||
|
||||
#![cfg_attr(feature = "__internal_bench", allow(missing_docs))]
|
||||
|
||||
use core::fmt;
|
||||
|
||||
/// Year flags (aka the dominical letter).
|
||||
///
|
||||
/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`.
|
||||
///
|
||||
/// There are 14 possible classes of year in the Gregorian calendar:
|
||||
/// common and leap years starting with Monday through Sunday.
|
||||
///
|
||||
/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1`
|
||||
/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero
|
||||
/// `Weekday` of the last day in the preceding year.
|
||||
#[allow(unreachable_pub)] // public as an alias for benchmarks only
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
|
||||
pub struct YearFlags(pub(super) u8);
|
||||
|
||||
// Weekday of the last day in the preceding year.
|
||||
// Allows for quick day of week calculation from the 1-based ordinal.
|
||||
const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`.
|
||||
const YEAR_STARTS_AFTER_THUESDAY: u8 = 1;
|
||||
const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2;
|
||||
const YEAR_STARTS_AFTER_THURSDAY: u8 = 3;
|
||||
const YEAR_STARTS_AFTER_FRIDAY: u8 = 4;
|
||||
const YEAR_STARTS_AFTER_SATURDAY: u8 = 5;
|
||||
const YEAR_STARTS_AFTER_SUNDAY: u8 = 6;
|
||||
|
||||
const COMMON_YEAR: u8 = 1 << 3;
|
||||
const LEAP_YEAR: u8 = 0 << 3;
|
||||
|
||||
pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY);
|
||||
pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY);
|
||||
pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY);
|
||||
pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY);
|
||||
pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY);
|
||||
pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY);
|
||||
pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
|
||||
pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
|
||||
pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY);
|
||||
pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY);
|
||||
pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY);
|
||||
pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY);
|
||||
pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY);
|
||||
pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY);
|
||||
|
||||
const YEAR_TO_FLAGS: &[YearFlags; 400] = &[
|
||||
BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA,
|
||||
G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G,
|
||||
F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F,
|
||||
E, DC, B, A, G, FE, D, C, B, AG, F, E, D, // 100
|
||||
C, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC,
|
||||
B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B,
|
||||
A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A,
|
||||
G, FE, D, C, B, AG, F, E, D, CB, A, G, F, // 200
|
||||
E, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE,
|
||||
D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D,
|
||||
C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C,
|
||||
B, AG, F, E, D, CB, A, G, F, ED, C, B, A, // 300
|
||||
G, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG,
|
||||
F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F,
|
||||
E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E,
|
||||
D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400
|
||||
];
|
||||
|
||||
impl YearFlags {
|
||||
#[allow(unreachable_pub)] // public as an alias for benchmarks only
|
||||
#[doc(hidden)] // for benchmarks only
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn from_year(year: i32) -> YearFlags {
|
||||
let year = year.rem_euclid(400);
|
||||
YearFlags::from_year_mod_400(year)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) const fn from_year_mod_400(year: i32) -> YearFlags {
|
||||
YEAR_TO_FLAGS[year as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) const fn ndays(&self) -> u32 {
|
||||
let YearFlags(flags) = *self;
|
||||
366 - (flags >> 3) as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) const fn isoweek_delta(&self) -> u32 {
|
||||
let YearFlags(flags) = *self;
|
||||
let mut delta = (flags & 0b0111) as u32;
|
||||
if delta < 3 {
|
||||
delta += 7;
|
||||
}
|
||||
delta
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) const fn nisoweeks(&self) -> u32 {
|
||||
let YearFlags(flags) = *self;
|
||||
52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for YearFlags {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let YearFlags(flags) = *self;
|
||||
match flags {
|
||||
0o15 => "A".fmt(f),
|
||||
0o05 => "AG".fmt(f),
|
||||
0o14 => "B".fmt(f),
|
||||
0o04 => "BA".fmt(f),
|
||||
0o13 => "C".fmt(f),
|
||||
0o03 => "CB".fmt(f),
|
||||
0o12 => "D".fmt(f),
|
||||
0o02 => "DC".fmt(f),
|
||||
0o11 => "E".fmt(f),
|
||||
0o01 => "ED".fmt(f),
|
||||
0o10 => "F?".fmt(f),
|
||||
0o00 => "FE?".fmt(f), // non-canonical
|
||||
0o17 => "F".fmt(f),
|
||||
0o07 => "FE".fmt(f),
|
||||
0o16 => "G".fmt(f),
|
||||
0o06 => "GF".fmt(f),
|
||||
_ => write!(f, "YearFlags({flags})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OL: (ordinal << 1) | leap year flag
|
||||
const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year
|
||||
const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
|
||||
|
||||
// The next table are adjustment values to convert a date encoded as month-day-leapyear to
|
||||
// ordinal-leapyear. OL = MDL - adjustment.
|
||||
// Dates that do not exist are encoded as `XX`.
|
||||
const XX: i8 = 0;
|
||||
const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[
|
||||
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
|
||||
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
|
||||
XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0
|
||||
XX, XX, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1
|
||||
XX, XX, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, XX, XX, XX, XX, XX, // 2
|
||||
XX, XX, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74,
|
||||
72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74,
|
||||
72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, // 3
|
||||
XX, XX, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76,
|
||||
74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76,
|
||||
74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, XX, XX, // 4
|
||||
XX, XX, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80,
|
||||
78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80,
|
||||
78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, // 5
|
||||
XX, XX, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82,
|
||||
80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82,
|
||||
80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, XX, XX, // 6
|
||||
XX, XX, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86,
|
||||
84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86,
|
||||
84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, // 7
|
||||
XX, XX, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88,
|
||||
86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88,
|
||||
86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, // 8
|
||||
XX, XX, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90,
|
||||
88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90,
|
||||
88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, XX, XX, // 9
|
||||
XX, XX, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94,
|
||||
92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94,
|
||||
92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, // 10
|
||||
XX, XX, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96,
|
||||
94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96,
|
||||
94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, XX, XX, // 11
|
||||
XX, XX, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
|
||||
100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
|
||||
98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
|
||||
100, // 12
|
||||
];
|
||||
|
||||
const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[
|
||||
0, 0, // 0
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, // 2
|
||||
74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72,
|
||||
74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72,
|
||||
74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, // 3
|
||||
76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74,
|
||||
76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74,
|
||||
76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, // 4
|
||||
80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78,
|
||||
80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78,
|
||||
80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, // 5
|
||||
82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80,
|
||||
82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80,
|
||||
82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, // 6
|
||||
86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84,
|
||||
86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84,
|
||||
86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, // 7
|
||||
88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86,
|
||||
88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86,
|
||||
88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, // 8
|
||||
90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88,
|
||||
90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88,
|
||||
90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, // 9
|
||||
94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92,
|
||||
94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92,
|
||||
94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, // 10
|
||||
96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94,
|
||||
96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94,
|
||||
96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, // 11
|
||||
100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
|
||||
98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
|
||||
100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
|
||||
98, // 12
|
||||
];
|
||||
|
||||
/// Month, day of month and year flags: `(month << 9) | (day << 4) | flags`
|
||||
/// `M_MMMD_DDDD_LFFF`
|
||||
///
|
||||
/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap
|
||||
/// year flag), which is an index to the `MDL_TO_OL` lookup table.
|
||||
///
|
||||
/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is
|
||||
/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for
|
||||
/// efficient lookup.
|
||||
///
|
||||
/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like
|
||||
/// February 30, can still be represented. This allows the validation to be combined with the final
|
||||
/// table lookup, which is good for performance.
|
||||
#[derive(PartialEq, PartialOrd, Copy, Clone)]
|
||||
pub(super) struct Mdf(u32);
|
||||
|
||||
impl Mdf {
|
||||
/// Makes a new `Mdf` value from month, day and `YearFlags`.
|
||||
///
|
||||
/// This method doesn't fully validate the range of the `month` and `day` parameters, only as
|
||||
/// much as what can't be deferred until later. The year `flags` are trusted to be correct.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `None` if `month > 12` or `day > 31`.
|
||||
#[inline]
|
||||
pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
|
||||
match month <= 12 && day <= 31 {
|
||||
true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year
|
||||
/// `flags`.
|
||||
///
|
||||
/// The `ol` is trusted to be valid, and the `flags` are trusted to match it.
|
||||
#[inline]
|
||||
pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf {
|
||||
debug_assert!(ol > 1 && ol <= MAX_OL as i32);
|
||||
// Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value.
|
||||
Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32)
|
||||
}
|
||||
|
||||
/// Returns the month of this `Mdf`.
|
||||
#[inline]
|
||||
pub(super) const fn month(&self) -> u32 {
|
||||
let Mdf(mdf) = *self;
|
||||
mdf >> 9
|
||||
}
|
||||
|
||||
/// Replaces the month of this `Mdf`, keeping the day and flags.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `None` if `month > 12`.
|
||||
#[inline]
|
||||
pub(super) const fn with_month(&self, month: u32) -> Option<Mdf> {
|
||||
if month > 12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Mdf(mdf) = *self;
|
||||
Some(Mdf((mdf & 0b1_1111_1111) | (month << 9)))
|
||||
}
|
||||
|
||||
/// Returns the day of this `Mdf`.
|
||||
#[inline]
|
||||
pub(super) const fn day(&self) -> u32 {
|
||||
let Mdf(mdf) = *self;
|
||||
(mdf >> 4) & 0b1_1111
|
||||
}
|
||||
|
||||
/// Replaces the day of this `Mdf`, keeping the month and flags.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `None` if `day > 31`.
|
||||
#[inline]
|
||||
pub(super) const fn with_day(&self, day: u32) -> Option<Mdf> {
|
||||
if day > 31 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Mdf(mdf) = *self;
|
||||
Some(Mdf((mdf & !0b1_1111_0000) | (day << 4)))
|
||||
}
|
||||
|
||||
/// Replaces the flags of this `Mdf`, keeping the month and day.
|
||||
#[inline]
|
||||
pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
|
||||
let Mdf(mdf) = *self;
|
||||
Mdf((mdf & !0b1111) | flags as u32)
|
||||
}
|
||||
|
||||
/// Returns the ordinal that corresponds to this `Mdf`.
|
||||
///
|
||||
/// This does a table lookup to calculate the corresponding ordinal. It will return an error if
|
||||
/// the `Mdl` turns out not to be a valid date.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
|
||||
/// given month.
|
||||
#[inline]
|
||||
pub(super) const fn ordinal(&self) -> Option<u32> {
|
||||
let mdl = self.0 >> 3;
|
||||
match MDL_TO_OL[mdl as usize] {
|
||||
XX => None,
|
||||
v => Some((mdl - v as u8 as u32) >> 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the year flags of this `Mdf`.
|
||||
#[inline]
|
||||
pub(super) const fn year_flags(&self) -> YearFlags {
|
||||
YearFlags((self.0 & 0b1111) as u8)
|
||||
}
|
||||
|
||||
/// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags.
|
||||
///
|
||||
/// This does a table lookup to calculate the corresponding ordinal. It will return an error if
|
||||
/// the `Mdl` turns out not to be a valid date.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
|
||||
/// given month.
|
||||
#[inline]
|
||||
pub(super) const fn ordinal_and_flags(&self) -> Option<i32> {
|
||||
let mdl = self.0 >> 3;
|
||||
match MDL_TO_OL[mdl as usize] {
|
||||
XX => None,
|
||||
v => Some(self.0 as i32 - ((v as i32) << 3)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn valid(&self) -> bool {
|
||||
let mdl = self.0 >> 3;
|
||||
MDL_TO_OL[mdl as usize] > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Mdf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let Mdf(mdf) = *self;
|
||||
write!(
|
||||
f,
|
||||
"Mdf(({} << 9) | ({} << 4) | {:#04o} /*{:?}*/)",
|
||||
mdf >> 9,
|
||||
(mdf >> 4) & 0b1_1111,
|
||||
mdf & 0b1111,
|
||||
YearFlags((mdf & 0b1111) as u8)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Mdf;
|
||||
use super::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags};
|
||||
|
||||
const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G];
|
||||
const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF];
|
||||
const FLAGS: [YearFlags; 14] = [A, B, C, D, E, F, G, AG, BA, CB, DC, ED, FE, GF];
|
||||
|
||||
#[test]
|
||||
fn test_year_flags_ndays_from_year() {
|
||||
assert_eq!(YearFlags::from_year(2014).ndays(), 365);
|
||||
assert_eq!(YearFlags::from_year(2012).ndays(), 366);
|
||||
assert_eq!(YearFlags::from_year(2000).ndays(), 366);
|
||||
assert_eq!(YearFlags::from_year(1900).ndays(), 365);
|
||||
assert_eq!(YearFlags::from_year(1600).ndays(), 366);
|
||||
assert_eq!(YearFlags::from_year(1).ndays(), 365);
|
||||
assert_eq!(YearFlags::from_year(0).ndays(), 366); // 1 BCE (proleptic Gregorian)
|
||||
assert_eq!(YearFlags::from_year(-1).ndays(), 365); // 2 BCE
|
||||
assert_eq!(YearFlags::from_year(-4).ndays(), 366); // 5 BCE
|
||||
assert_eq!(YearFlags::from_year(-99).ndays(), 365); // 100 BCE
|
||||
assert_eq!(YearFlags::from_year(-100).ndays(), 365); // 101 BCE
|
||||
assert_eq!(YearFlags::from_year(-399).ndays(), 365); // 400 BCE
|
||||
assert_eq!(YearFlags::from_year(-400).ndays(), 366); // 401 BCE
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_year_flags_nisoweeks() {
|
||||
assert_eq!(A.nisoweeks(), 52);
|
||||
assert_eq!(B.nisoweeks(), 52);
|
||||
assert_eq!(C.nisoweeks(), 52);
|
||||
assert_eq!(D.nisoweeks(), 53);
|
||||
assert_eq!(E.nisoweeks(), 52);
|
||||
assert_eq!(F.nisoweeks(), 52);
|
||||
assert_eq!(G.nisoweeks(), 52);
|
||||
assert_eq!(AG.nisoweeks(), 52);
|
||||
assert_eq!(BA.nisoweeks(), 52);
|
||||
assert_eq!(CB.nisoweeks(), 52);
|
||||
assert_eq!(DC.nisoweeks(), 53);
|
||||
assert_eq!(ED.nisoweeks(), 53);
|
||||
assert_eq!(FE.nisoweeks(), 52);
|
||||
assert_eq!(GF.nisoweeks(), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdf_valid() {
|
||||
fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
|
||||
for month in month1..=month2 {
|
||||
for day in day1..=day2 {
|
||||
let mdf = match Mdf::new(month, day, flags) {
|
||||
Some(mdf) => mdf,
|
||||
None if !expected => continue,
|
||||
None => panic!("Mdf::new({month}, {day}, {flags:?}) returned None"),
|
||||
};
|
||||
|
||||
assert!(
|
||||
mdf.valid() == expected,
|
||||
"month {} day {} = {:?} should be {} for dominical year {:?}",
|
||||
month,
|
||||
day,
|
||||
mdf,
|
||||
if expected { "valid" } else { "invalid" },
|
||||
flags
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for &flags in NONLEAP_FLAGS.iter() {
|
||||
check(false, flags, 0, 0, 0, 1024);
|
||||
check(false, flags, 0, 0, 16, 0);
|
||||
check(true, flags, 1, 1, 1, 31);
|
||||
check(false, flags, 1, 32, 1, 1024);
|
||||
check(true, flags, 2, 1, 2, 28);
|
||||
check(false, flags, 2, 29, 2, 1024);
|
||||
check(true, flags, 3, 1, 3, 31);
|
||||
check(false, flags, 3, 32, 3, 1024);
|
||||
check(true, flags, 4, 1, 4, 30);
|
||||
check(false, flags, 4, 31, 4, 1024);
|
||||
check(true, flags, 5, 1, 5, 31);
|
||||
check(false, flags, 5, 32, 5, 1024);
|
||||
check(true, flags, 6, 1, 6, 30);
|
||||
check(false, flags, 6, 31, 6, 1024);
|
||||
check(true, flags, 7, 1, 7, 31);
|
||||
check(false, flags, 7, 32, 7, 1024);
|
||||
check(true, flags, 8, 1, 8, 31);
|
||||
check(false, flags, 8, 32, 8, 1024);
|
||||
check(true, flags, 9, 1, 9, 30);
|
||||
check(false, flags, 9, 31, 9, 1024);
|
||||
check(true, flags, 10, 1, 10, 31);
|
||||
check(false, flags, 10, 32, 10, 1024);
|
||||
check(true, flags, 11, 1, 11, 30);
|
||||
check(false, flags, 11, 31, 11, 1024);
|
||||
check(true, flags, 12, 1, 12, 31);
|
||||
check(false, flags, 12, 32, 12, 1024);
|
||||
check(false, flags, 13, 0, 16, 1024);
|
||||
check(false, flags, u32::MAX, 0, u32::MAX, 1024);
|
||||
check(false, flags, 0, u32::MAX, 16, u32::MAX);
|
||||
check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX);
|
||||
}
|
||||
|
||||
for &flags in LEAP_FLAGS.iter() {
|
||||
check(false, flags, 0, 0, 0, 1024);
|
||||
check(false, flags, 0, 0, 16, 0);
|
||||
check(true, flags, 1, 1, 1, 31);
|
||||
check(false, flags, 1, 32, 1, 1024);
|
||||
check(true, flags, 2, 1, 2, 29);
|
||||
check(false, flags, 2, 30, 2, 1024);
|
||||
check(true, flags, 3, 1, 3, 31);
|
||||
check(false, flags, 3, 32, 3, 1024);
|
||||
check(true, flags, 4, 1, 4, 30);
|
||||
check(false, flags, 4, 31, 4, 1024);
|
||||
check(true, flags, 5, 1, 5, 31);
|
||||
check(false, flags, 5, 32, 5, 1024);
|
||||
check(true, flags, 6, 1, 6, 30);
|
||||
check(false, flags, 6, 31, 6, 1024);
|
||||
check(true, flags, 7, 1, 7, 31);
|
||||
check(false, flags, 7, 32, 7, 1024);
|
||||
check(true, flags, 8, 1, 8, 31);
|
||||
check(false, flags, 8, 32, 8, 1024);
|
||||
check(true, flags, 9, 1, 9, 30);
|
||||
check(false, flags, 9, 31, 9, 1024);
|
||||
check(true, flags, 10, 1, 10, 31);
|
||||
check(false, flags, 10, 32, 10, 1024);
|
||||
check(true, flags, 11, 1, 11, 30);
|
||||
check(false, flags, 11, 31, 11, 1024);
|
||||
check(true, flags, 12, 1, 12, 31);
|
||||
check(false, flags, 12, 32, 12, 1024);
|
||||
check(false, flags, 13, 0, 16, 1024);
|
||||
check(false, flags, u32::MAX, 0, u32::MAX, 1024);
|
||||
check(false, flags, 0, u32::MAX, 16, u32::MAX);
|
||||
check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdf_fields() {
|
||||
for &flags in FLAGS.iter() {
|
||||
for month in 1u32..=12 {
|
||||
for day in 1u32..31 {
|
||||
let mdf = match Mdf::new(month, day, flags) {
|
||||
Some(mdf) => mdf,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if mdf.valid() {
|
||||
assert_eq!(mdf.month(), month);
|
||||
assert_eq!(mdf.day(), day);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdf_with_fields() {
|
||||
fn check(flags: YearFlags, month: u32, day: u32) {
|
||||
let mdf = Mdf::new(month, day, flags).unwrap();
|
||||
|
||||
for month in 0u32..=16 {
|
||||
let mdf = match mdf.with_month(month) {
|
||||
Some(mdf) => mdf,
|
||||
None if month > 12 => continue,
|
||||
None => panic!("failed to create Mdf with month {month}"),
|
||||
};
|
||||
|
||||
if mdf.valid() {
|
||||
assert_eq!(mdf.month(), month);
|
||||
assert_eq!(mdf.day(), day);
|
||||
}
|
||||
}
|
||||
|
||||
for day in 0u32..=1024 {
|
||||
let mdf = match mdf.with_day(day) {
|
||||
Some(mdf) => mdf,
|
||||
None if day > 31 => continue,
|
||||
None => panic!("failed to create Mdf with month {month}"),
|
||||
};
|
||||
|
||||
if mdf.valid() {
|
||||
assert_eq!(mdf.month(), month);
|
||||
assert_eq!(mdf.day(), day);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for &flags in NONLEAP_FLAGS.iter() {
|
||||
check(flags, 1, 1);
|
||||
check(flags, 1, 31);
|
||||
check(flags, 2, 1);
|
||||
check(flags, 2, 28);
|
||||
check(flags, 2, 29);
|
||||
check(flags, 12, 31);
|
||||
}
|
||||
for &flags in LEAP_FLAGS.iter() {
|
||||
check(flags, 1, 1);
|
||||
check(flags, 1, 31);
|
||||
check(flags, 2, 1);
|
||||
check(flags, 2, 29);
|
||||
check(flags, 2, 30);
|
||||
check(flags, 12, 31);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdf_new_range() {
|
||||
let flags = YearFlags::from_year(2023);
|
||||
assert!(Mdf::new(13, 1, flags).is_none());
|
||||
assert!(Mdf::new(1, 32, flags).is_none());
|
||||
}
|
||||
}
|
||||
Vendored
+248
@@ -0,0 +1,248 @@
|
||||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! ISO 8601 week.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use super::internals::YearFlags;
|
||||
|
||||
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
/// ISO 8601 week.
|
||||
///
|
||||
/// This type, combined with [`Weekday`](../enum.Weekday.html),
|
||||
/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date).
|
||||
/// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types
|
||||
/// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
|
||||
#[cfg_attr(
|
||||
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
|
||||
derive(Archive, Deserialize, Serialize),
|
||||
archive(compare(PartialEq, PartialOrd)),
|
||||
archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash))
|
||||
)]
|
||||
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
|
||||
pub struct IsoWeek {
|
||||
// Note that this allows for larger year range than `NaiveDate`.
|
||||
// This is crucial because we have an edge case for the first and last week supported,
|
||||
// which year number might not match the calendar year number.
|
||||
ywf: i32, // (year << 10) | (week << 4) | flag
|
||||
}
|
||||
|
||||
impl IsoWeek {
|
||||
/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value.
|
||||
//
|
||||
// Internal use only. We don't expose the public constructor for `IsoWeek` for now
|
||||
// because the year range for the week date and the calendar date do not match, and
|
||||
// it is confusing to have a date that is out of range in one and not in another.
|
||||
// Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`.
|
||||
pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self {
|
||||
let rawweek = (ordinal + year_flags.isoweek_delta()) / 7;
|
||||
let (year, week) = if rawweek < 1 {
|
||||
// previous year
|
||||
let prevlastweek = YearFlags::from_year(year - 1).nisoweeks();
|
||||
(year - 1, prevlastweek)
|
||||
} else {
|
||||
let lastweek = year_flags.nisoweeks();
|
||||
if rawweek > lastweek {
|
||||
// next year
|
||||
(year + 1, 1)
|
||||
} else {
|
||||
(year, rawweek)
|
||||
}
|
||||
};
|
||||
let flags = YearFlags::from_year(year);
|
||||
IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) }
|
||||
}
|
||||
|
||||
/// Returns the year number for this ISO week.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Datelike, NaiveDate, Weekday};
|
||||
///
|
||||
/// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap();
|
||||
/// assert_eq!(d.iso_week().year(), 2015);
|
||||
/// ```
|
||||
///
|
||||
/// This year number might not match the calendar year number.
|
||||
/// Continuing the example...
|
||||
///
|
||||
/// ```
|
||||
/// # use chrono::{NaiveDate, Datelike, Weekday};
|
||||
/// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap();
|
||||
/// assert_eq!(d.year(), 2014);
|
||||
/// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub const fn year(&self) -> i32 {
|
||||
self.ywf >> 10
|
||||
}
|
||||
|
||||
/// Returns the ISO week number starting from 1.
|
||||
///
|
||||
/// The return value ranges from 1 to 53. (The last week of year differs by years.)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Datelike, NaiveDate, Weekday};
|
||||
///
|
||||
/// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap();
|
||||
/// assert_eq!(d.iso_week().week(), 15);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub const fn week(&self) -> u32 {
|
||||
((self.ywf >> 4) & 0x3f) as u32
|
||||
}
|
||||
|
||||
/// Returns the ISO week number starting from 0.
|
||||
///
|
||||
/// The return value ranges from 0 to 52. (The last week of year differs by years.)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Datelike, NaiveDate, Weekday};
|
||||
///
|
||||
/// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap();
|
||||
/// assert_eq!(d.iso_week().week0(), 14);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub const fn week0(&self) -> u32 {
|
||||
((self.ywf >> 4) & 0x3f) as u32 - 1
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Debug` output of the ISO week `w` is the same as
|
||||
/// [`d.format("%G-W%V")`](../format/strftime/index.html)
|
||||
/// where `d` is any `NaiveDate` value in that week.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Datelike, NaiveDate};
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()),
|
||||
/// "2015-W36"
|
||||
/// );
|
||||
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01");
|
||||
/// assert_eq!(
|
||||
/// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()),
|
||||
/// "9999-W52"
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE.
|
||||
///
|
||||
/// ```
|
||||
/// # use chrono::{NaiveDate, Datelike};
|
||||
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52");
|
||||
/// assert_eq!(
|
||||
/// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()),
|
||||
/// "+10000-W52"
|
||||
/// );
|
||||
/// ```
|
||||
impl fmt::Debug for IsoWeek {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let year = self.year();
|
||||
let week = self.week();
|
||||
if (0..=9999).contains(&year) {
|
||||
write!(f, "{year:04}-W{week:02}")
|
||||
} else {
|
||||
// ISO 8601 requires the explicit sign for out-of-range years
|
||||
write!(f, "{year:+05}-W{week:02}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for IsoWeek {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
let year = self.year();
|
||||
let week = self.week();
|
||||
if (0..=9999).contains(&year) {
|
||||
defmt::write!(fmt, "{:04}-W{:02}", year, week)
|
||||
} else {
|
||||
// ISO 8601 requires the explicit sign for out-of-range years
|
||||
let sign = ['+', '-'][(year < 0) as usize];
|
||||
defmt::write!(fmt, "{}{:05}-W{:02}", sign, year.abs(), week)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(feature = "rkyv-validation")]
|
||||
use super::IsoWeek;
|
||||
use crate::Datelike;
|
||||
use crate::naive::date::{self, NaiveDate};
|
||||
|
||||
#[test]
|
||||
fn test_iso_week_extremes() {
|
||||
let minweek = NaiveDate::MIN.iso_week();
|
||||
let maxweek = NaiveDate::MAX.iso_week();
|
||||
|
||||
assert_eq!(minweek.year(), date::MIN_YEAR);
|
||||
assert_eq!(minweek.week(), 1);
|
||||
assert_eq!(minweek.week0(), 0);
|
||||
#[cfg(feature = "alloc")]
|
||||
assert_eq!(format!("{minweek:?}"), NaiveDate::MIN.format("%G-W%V").to_string());
|
||||
|
||||
assert_eq!(maxweek.year(), date::MAX_YEAR + 1);
|
||||
assert_eq!(maxweek.week(), 1);
|
||||
assert_eq!(maxweek.week0(), 0);
|
||||
#[cfg(feature = "alloc")]
|
||||
assert_eq!(format!("{maxweek:?}"), NaiveDate::MAX.format("%G-W%V").to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_week_equivalence_for_first_week() {
|
||||
let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap();
|
||||
let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
|
||||
|
||||
assert_eq!(monday.iso_week(), friday.iso_week());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_week_equivalence_for_last_week() {
|
||||
let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap();
|
||||
let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap();
|
||||
|
||||
assert_eq!(monday.iso_week(), friday.iso_week());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_week_ordering_for_first_week() {
|
||||
let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap();
|
||||
let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
|
||||
|
||||
assert!(monday.iso_week() >= friday.iso_week());
|
||||
assert!(monday.iso_week() <= friday.iso_week());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_week_ordering_for_last_week() {
|
||||
let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap();
|
||||
let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap();
|
||||
|
||||
assert!(monday.iso_week() >= friday.iso_week());
|
||||
assert!(monday.iso_week() <= friday.iso_week());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rkyv-validation")]
|
||||
fn test_rkyv_validation() {
|
||||
let minweek = NaiveDate::MIN.iso_week();
|
||||
let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), minweek);
|
||||
|
||||
let maxweek = NaiveDate::MAX.iso_week();
|
||||
let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), maxweek);
|
||||
}
|
||||
}
|
||||
Vendored
+340
@@ -0,0 +1,340 @@
|
||||
//! Date and time types unconcerned with timezones.
|
||||
//!
|
||||
//! They are primarily building blocks for other types
|
||||
//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)),
|
||||
//! but can be also used for the simpler date and time handling.
|
||||
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
use crate::Weekday;
|
||||
use crate::expect;
|
||||
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod datetime;
|
||||
mod internals;
|
||||
pub(crate) mod isoweek;
|
||||
pub(crate) mod time;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use self::date::{MAX_DATE, MIN_DATE};
|
||||
pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator};
|
||||
#[allow(deprecated)]
|
||||
pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime};
|
||||
pub use self::isoweek::IsoWeek;
|
||||
pub use self::time::NaiveTime;
|
||||
|
||||
#[cfg(feature = "__internal_bench")]
|
||||
#[doc(hidden)]
|
||||
pub use self::internals::YearFlags as __BenchYearFlags;
|
||||
|
||||
/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
|
||||
/// day of the week.
|
||||
#[derive(Clone, Copy, Debug, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct NaiveWeek {
|
||||
date: NaiveDate,
|
||||
start: Weekday,
|
||||
}
|
||||
|
||||
impl NaiveWeek {
|
||||
/// Create a new `NaiveWeek`
|
||||
pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self {
|
||||
Self { date, start }
|
||||
}
|
||||
|
||||
/// Returns a date representing the first day of the week.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the first day of the week happens to fall just out of range of `NaiveDate`
|
||||
/// (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// assert!(week.first_day() <= date);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub const fn first_day(&self) -> NaiveDate {
|
||||
expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`")
|
||||
}
|
||||
|
||||
/// Returns a date representing the first day of the week or
|
||||
/// `None` if the date is out of `NaiveDate`'s range
|
||||
/// (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::MIN;
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// if let Some(first_day) = week.checked_first_day() {
|
||||
/// assert!(first_day == date);
|
||||
/// } else {
|
||||
/// // error handling code
|
||||
/// return;
|
||||
/// };
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn checked_first_day(&self) -> Option<NaiveDate> {
|
||||
let start = self.start.num_days_from_monday() as i32;
|
||||
let ref_day = self.date.weekday().num_days_from_monday() as i32;
|
||||
// Calculate the number of days to subtract from `self.date`.
|
||||
// Do not construct an intermediate date beyond `self.date`, because that may be out of
|
||||
// range if `date` is close to `NaiveDate::MAX`.
|
||||
let days = start - ref_day - if start > ref_day { 7 } else { 0 };
|
||||
self.date.add_days(days)
|
||||
}
|
||||
|
||||
/// Returns a date representing the last day of the week.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the last day of the week happens to fall just out of range of `NaiveDate`
|
||||
/// (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// assert!(week.last_day() >= date);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub const fn last_day(&self) -> NaiveDate {
|
||||
expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`")
|
||||
}
|
||||
|
||||
/// Returns a date representing the last day of the week or
|
||||
/// `None` if the date is out of `NaiveDate`'s range
|
||||
/// (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::MAX;
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// if let Some(last_day) = week.checked_last_day() {
|
||||
/// assert!(last_day == date);
|
||||
/// } else {
|
||||
/// // error handling code
|
||||
/// return;
|
||||
/// };
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn checked_last_day(&self) -> Option<NaiveDate> {
|
||||
let end = self.start.pred().num_days_from_monday() as i32;
|
||||
let ref_day = self.date.weekday().num_days_from_monday() as i32;
|
||||
// Calculate the number of days to add to `self.date`.
|
||||
// Do not construct an intermediate date before `self.date` (like with `first_day()`),
|
||||
// because that may be out of range if `date` is close to `NaiveDate::MIN`.
|
||||
let days = end - ref_day + if end < ref_day { 7 } else { 0 };
|
||||
self.date.add_days(days)
|
||||
}
|
||||
|
||||
/// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
|
||||
/// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the either the first or last day of the week happens to fall just out of range of
|
||||
/// `NaiveDate` (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// let days = week.days();
|
||||
/// assert!(days.contains(&date));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub const fn days(&self) -> RangeInclusive<NaiveDate> {
|
||||
// `expect` doesn't work because `RangeInclusive` is not `Copy`
|
||||
match self.checked_days() {
|
||||
Some(val) => val,
|
||||
None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an [`Option<RangeInclusive<T>>`] representing the whole week bounded by
|
||||
/// [checked_first_day](NaiveWeek::checked_first_day) and
|
||||
/// [checked_last_day](NaiveWeek::checked_last_day) functions.
|
||||
///
|
||||
/// Returns `None` if either of the boundaries are out of `NaiveDate`'s range
|
||||
/// (more than ca. 262,000 years away from common era).
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{NaiveDate, Weekday};
|
||||
///
|
||||
/// let date = NaiveDate::MAX;
|
||||
/// let week = date.week(Weekday::Mon);
|
||||
/// let _days = match week.checked_days() {
|
||||
/// Some(d) => d,
|
||||
/// None => {
|
||||
/// // error handling code
|
||||
/// return;
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn checked_days(&self) -> Option<RangeInclusive<NaiveDate>> {
|
||||
match (self.checked_first_day(), self.checked_last_day()) {
|
||||
(Some(first), Some(last)) => Some(first..=last),
|
||||
(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NaiveWeek {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.first_day() == other.first_day()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for NaiveWeek {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.first_day().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A duration in calendar days.
|
||||
///
|
||||
/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)`
|
||||
/// doesn't increment the day value as expected due to it being a fixed number of seconds. This
|
||||
/// difference applies only when dealing with `DateTime<TimeZone>` data types and in other cases
|
||||
/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Days(pub(crate) u64);
|
||||
|
||||
impl Days {
|
||||
/// Construct a new `Days` from a number of days
|
||||
pub const fn new(num: u64) -> Self {
|
||||
Self(num)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialization/Deserialization of `NaiveDateTime` in alternate formats
|
||||
///
|
||||
/// The various modules in here are intended to be used with serde's [`with` annotation] to
|
||||
/// serialize as something other than the default ISO 8601 format.
|
||||
///
|
||||
/// [`with` annotation]: https://serde.rs/field-attrs.html#with
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde {
|
||||
pub use super::datetime::serde::*;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{NaiveDate, NaiveWeek, Weekday};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
#[test]
|
||||
fn test_naiveweek() {
|
||||
let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap();
|
||||
let asserts = [
|
||||
(Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"),
|
||||
(Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"),
|
||||
(Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"),
|
||||
(Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"),
|
||||
(Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"),
|
||||
(Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"),
|
||||
(Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"),
|
||||
];
|
||||
for (start, first_day, last_day) in asserts {
|
||||
let week = date.week(start);
|
||||
let days = week.days();
|
||||
assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d"));
|
||||
assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d"));
|
||||
assert!(days.contains(&date));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naiveweek_min_max() {
|
||||
let date_max = NaiveDate::MAX;
|
||||
assert!(date_max.week(Weekday::Mon).first_day() <= date_max);
|
||||
let date_min = NaiveDate::MIN;
|
||||
assert!(date_min.week(Weekday::Mon).last_day() >= date_min);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naiveweek_checked_no_panic() {
|
||||
let date_max = NaiveDate::MAX;
|
||||
if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() {
|
||||
assert!(last == date_max);
|
||||
}
|
||||
let date_min = NaiveDate::MIN;
|
||||
if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() {
|
||||
assert!(first == date_min);
|
||||
}
|
||||
let _ = date_min.week(Weekday::Mon).checked_days();
|
||||
let _ = date_max.week(Weekday::Mon).checked_days();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naiveweek_eq() {
|
||||
let a =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon };
|
||||
let b =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon };
|
||||
assert_eq!(a, b);
|
||||
|
||||
let c =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun };
|
||||
assert_ne!(a, c);
|
||||
assert_ne!(b, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naiveweek_hash() {
|
||||
let a =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon };
|
||||
let b =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon };
|
||||
let c =
|
||||
NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun };
|
||||
|
||||
let mut hasher = DefaultHasher::default();
|
||||
a.hash(&mut hasher);
|
||||
let a_hash = hasher.finish();
|
||||
|
||||
hasher = DefaultHasher::default();
|
||||
b.hash(&mut hasher);
|
||||
let b_hash = hasher.finish();
|
||||
|
||||
hasher = DefaultHasher::default();
|
||||
c.hash(&mut hasher);
|
||||
let c_hash = hasher.finish();
|
||||
|
||||
assert_eq!(a_hash, b_hash);
|
||||
assert_ne!(b_hash, c_hash);
|
||||
assert_ne!(a_hash, c_hash);
|
||||
}
|
||||
}
|
||||
Vendored
+1668
File diff suppressed because it is too large.
Load diff
+143
@@ -0,0 +1,143 @@
|
||||
use super::NaiveTime;
|
||||
use core::fmt;
|
||||
use serde::{de, ser};
|
||||
|
||||
// TODO not very optimized for space (binary formats would want something better)
|
||||
// TODO round-trip for general leap seconds (not just those with second = 60)
|
||||
|
||||
impl ser::Serialize for NaiveTime {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
serializer.collect_str(&self)
|
||||
}
|
||||
}
|
||||
|
||||
struct NaiveTimeVisitor;
|
||||
|
||||
impl de::Visitor<'_> for NaiveTimeVisitor {
|
||||
type Value = NaiveTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a formatted time string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
value.parse().map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for NaiveTime {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(NaiveTimeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::NaiveTime;
|
||||
|
||||
#[test]
|
||||
fn test_serde_serialize() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(),
|
||||
Some(r#""00:00:00""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(),
|
||||
Some(r#""00:00:00.950""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(),
|
||||
Some(r#""00:00:60""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(),
|
||||
Some(r#""00:01:02""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(),
|
||||
Some(r#""03:05:07.098765432""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(),
|
||||
Some(r#""07:08:09""#.into())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(),
|
||||
Some(r#""12:34:56.000789""#.into())
|
||||
);
|
||||
let leap = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap();
|
||||
assert_eq!(serde_json::to_string(&leap).ok(), Some(r#""23:59:60.999999999""#.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_deserialize() {
|
||||
let from_str = serde_json::from_str::<NaiveTime>;
|
||||
|
||||
assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
|
||||
assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
|
||||
assert_eq!(
|
||||
from_str(r#""00:00:00.950""#).ok(),
|
||||
Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
from_str(r#""0:0:0.95""#).ok(),
|
||||
Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
from_str(r#""00:00:60""#).ok(),
|
||||
Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap())
|
||||
);
|
||||
assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap()));
|
||||
assert_eq!(
|
||||
from_str(r#""03:05:07.098765432""#).ok(),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap())
|
||||
);
|
||||
assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap()));
|
||||
assert_eq!(
|
||||
from_str(r#""12:34:56.000789""#).ok(),
|
||||
Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
from_str(r#""23:59:60.999999999""#).ok(),
|
||||
Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored
|
||||
Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap())
|
||||
);
|
||||
|
||||
// bad formats
|
||||
assert!(from_str(r#""""#).is_err());
|
||||
assert!(from_str(r#""000000""#).is_err());
|
||||
assert!(from_str(r#""00:00:61""#).is_err());
|
||||
assert!(from_str(r#""00:60:00""#).is_err());
|
||||
assert!(from_str(r#""24:00:00""#).is_err());
|
||||
assert!(from_str(r#""23:59:59,1""#).is_err());
|
||||
assert!(from_str(r#""012:34:56""#).is_err());
|
||||
assert!(from_str(r#""hh:mm:ss""#).is_err());
|
||||
assert!(from_str(r#"0"#).is_err());
|
||||
assert!(from_str(r#"86399"#).is_err());
|
||||
assert!(from_str(r#"{}"#).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_bincode() {
|
||||
// Bincode is relevant to test separately from JSON because
|
||||
// it is not self-describing.
|
||||
use bincode::{deserialize, serialize};
|
||||
|
||||
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
|
||||
let encoded = serialize(&t).unwrap();
|
||||
let decoded: NaiveTime = deserialize(&encoded).unwrap();
|
||||
assert_eq!(t, decoded);
|
||||
}
|
||||
}
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
use super::NaiveTime;
|
||||
use crate::{FixedOffset, TimeDelta, Timelike};
|
||||
|
||||
#[test]
|
||||
fn test_time_from_hms_milli() {
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_milli_opt(3, 5, 7, 0),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_milli_opt(3, 5, 7, 777),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap())
|
||||
);
|
||||
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None);
|
||||
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check
|
||||
assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_from_hms_micro() {
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_micro_opt(3, 5, 7, 0),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_micro_opt(3, 5, 7, 333),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 333_000).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999),
|
||||
Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap())
|
||||
);
|
||||
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None);
|
||||
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check
|
||||
assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_hms() {
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().hour(), 3);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(0),
|
||||
Some(NaiveTime::from_hms_opt(0, 5, 7).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(23),
|
||||
Some(NaiveTime::from_hms_opt(23, 5, 7).unwrap())
|
||||
);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(24), None);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(u32::MAX), None);
|
||||
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().minute(), 5);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(0),
|
||||
Some(NaiveTime::from_hms_opt(3, 0, 7).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(59),
|
||||
Some(NaiveTime::from_hms_opt(3, 59, 7).unwrap())
|
||||
);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(60), None);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(u32::MAX), None);
|
||||
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().second(), 7);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(0),
|
||||
Some(NaiveTime::from_hms_opt(3, 5, 0).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(59),
|
||||
Some(NaiveTime::from_hms_opt(3, 5, 59).unwrap())
|
||||
);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(60), None);
|
||||
assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(u32::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_add() {
|
||||
macro_rules! check {
|
||||
($lhs:expr, $rhs:expr, $sum:expr) => {{
|
||||
assert_eq!($lhs + $rhs, $sum);
|
||||
//assert_eq!($rhs + $lhs, $sum);
|
||||
}};
|
||||
}
|
||||
|
||||
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
|
||||
|
||||
check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900));
|
||||
check!(hmsm(3, 5, 59, 900), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 6, 0, 0));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-1800).unwrap(), hmsm(3, 5, 58, 500));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-800).unwrap(), hmsm(3, 5, 59, 500));
|
||||
check!(
|
||||
hmsm(3, 5, 59, 1_300),
|
||||
TimeDelta::try_milliseconds(-100).unwrap(),
|
||||
hmsm(3, 5, 59, 1_200)
|
||||
);
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 5, 59, 1_400));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(1800).unwrap(), hmsm(3, 6, 1, 100));
|
||||
check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap
|
||||
check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900));
|
||||
check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300));
|
||||
check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300));
|
||||
|
||||
// regression tests for #37
|
||||
check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-990).unwrap(), hmsm(23, 59, 59, 10));
|
||||
check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-9990).unwrap(), hmsm(23, 59, 50, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_overflowing_add() {
|
||||
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()),
|
||||
(hmsm(14, 4, 5, 678), 0)
|
||||
);
|
||||
assert_eq!(
|
||||
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()),
|
||||
(hmsm(2, 4, 5, 678), 86_400)
|
||||
);
|
||||
assert_eq!(
|
||||
hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()),
|
||||
(hmsm(20, 4, 5, 678), -86_400)
|
||||
);
|
||||
|
||||
// overflowing_add_signed with leap seconds may be counter-intuitive
|
||||
assert_eq!(
|
||||
hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(1).unwrap()),
|
||||
(hmsm(3, 4, 59, 678), 86_400)
|
||||
);
|
||||
assert_eq!(
|
||||
hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(-1).unwrap()),
|
||||
(hmsm(3, 5, 0, 678), -86_400)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_addassignment() {
|
||||
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
|
||||
let mut time = hms(12, 12, 12);
|
||||
time += TimeDelta::try_hours(10).unwrap();
|
||||
assert_eq!(time, hms(22, 12, 12));
|
||||
time += TimeDelta::try_hours(10).unwrap();
|
||||
assert_eq!(time, hms(8, 12, 12));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_subassignment() {
|
||||
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
|
||||
let mut time = hms(12, 12, 12);
|
||||
time -= TimeDelta::try_hours(10).unwrap();
|
||||
assert_eq!(time, hms(2, 12, 12));
|
||||
time -= TimeDelta::try_hours(10).unwrap();
|
||||
assert_eq!(time, hms(16, 12, 12));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_sub() {
|
||||
macro_rules! check {
|
||||
($lhs:expr, $rhs:expr, $diff:expr) => {{
|
||||
// `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration`
|
||||
assert_eq!($lhs.signed_duration_since($rhs), $diff);
|
||||
assert_eq!($rhs.signed_duration_since($lhs), -$diff);
|
||||
}};
|
||||
}
|
||||
|
||||
let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap();
|
||||
|
||||
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero());
|
||||
check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::try_milliseconds(300).unwrap());
|
||||
check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap());
|
||||
check!(
|
||||
hmsm(3, 5, 7, 200),
|
||||
hmsm(2, 4, 6, 300),
|
||||
TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::try_milliseconds(900).unwrap()
|
||||
);
|
||||
|
||||
// treats the leap second as if it coincides with the prior non-leap second,
|
||||
// as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence.
|
||||
check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::try_milliseconds(400).unwrap());
|
||||
//check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::try_milliseconds(1400).unwrap());
|
||||
//check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::try_milliseconds(1400).unwrap());
|
||||
|
||||
// additional equality: `time1 + duration = time2` is equivalent to
|
||||
// `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second.
|
||||
assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200));
|
||||
//assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_duration_ops() {
|
||||
use core::time::Duration;
|
||||
|
||||
let mut t = NaiveTime::from_hms_opt(11, 34, 23).unwrap();
|
||||
let same = t + Duration::ZERO;
|
||||
assert_eq!(t, same);
|
||||
|
||||
t += Duration::new(3600, 0);
|
||||
assert_eq!(t, NaiveTime::from_hms_opt(12, 34, 23).unwrap());
|
||||
|
||||
t -= Duration::new(7200, 0);
|
||||
assert_eq!(t, NaiveTime::from_hms_opt(10, 34, 23).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_fmt() {
|
||||
assert_eq!(
|
||||
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 999).unwrap()),
|
||||
"23:59:59.999"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap()),
|
||||
"23:59:60"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_001).unwrap()),
|
||||
"23:59:60.001"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", NaiveTime::from_hms_micro_opt(0, 0, 0, 43210).unwrap()),
|
||||
"00:00:00.043210"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", NaiveTime::from_hms_nano_opt(0, 0, 0, 6543210).unwrap()),
|
||||
"00:00:00.006543210"
|
||||
);
|
||||
|
||||
// the format specifier should have no effect on `NaiveTime`
|
||||
assert_eq!(
|
||||
format!("{:30}", NaiveTime::from_hms_milli_opt(3, 5, 7, 9).unwrap()),
|
||||
"03:05:07.009"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_from_str() {
|
||||
// valid cases
|
||||
let valid = [
|
||||
"0:0:0",
|
||||
"0:0:0.0000000",
|
||||
"0:0:0.0000003",
|
||||
" 4 : 3 : 2.1 ",
|
||||
" 09:08:07 ",
|
||||
" 09:08 ",
|
||||
" 9:8:07 ",
|
||||
"01:02:03",
|
||||
"4:3:2.1",
|
||||
"9:8:7",
|
||||
"09:8:7",
|
||||
"9:08:7",
|
||||
"9:8:07",
|
||||
"09:08:7",
|
||||
"09:8:07",
|
||||
"09:08:7",
|
||||
"9:08:07",
|
||||
"09:08:07",
|
||||
"9:8:07.123",
|
||||
"9:08:7.123",
|
||||
"09:8:7.123",
|
||||
"09:08:7.123",
|
||||
"9:08:07.123",
|
||||
"09:8:07.123",
|
||||
"09:08:07.123",
|
||||
"09:08:07.123",
|
||||
"09:08:07.1234",
|
||||
"09:08:07.12345",
|
||||
"09:08:07.123456",
|
||||
"09:08:07.1234567",
|
||||
"09:08:07.12345678",
|
||||
"09:08:07.123456789",
|
||||
"09:08:07.1234567891",
|
||||
"09:08:07.12345678912",
|
||||
"23:59:60.373929310237",
|
||||
];
|
||||
for &s in &valid {
|
||||
eprintln!("test_time_parse_from_str valid {s:?}");
|
||||
let d = match s.parse::<NaiveTime>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => panic!("parsing `{s}` has failed: {e}"),
|
||||
};
|
||||
let s_ = format!("{d:?}");
|
||||
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
|
||||
let d_ = match s_.parse::<NaiveTime>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}")
|
||||
}
|
||||
};
|
||||
assert!(
|
||||
d == d_,
|
||||
"`{s}` is parsed into `{d:?}`, but reparsed result \
|
||||
`{d_:?}` does not match"
|
||||
);
|
||||
}
|
||||
|
||||
// some invalid cases
|
||||
// since `ParseErrorKind` is private, all we can do is to check if there was an error
|
||||
let invalid = [
|
||||
"", // empty
|
||||
"x", // invalid
|
||||
"15", // missing data
|
||||
"15:8:", // trailing colon
|
||||
"15:8:x", // invalid data
|
||||
"15:8:9x", // invalid data
|
||||
"23:59:61", // invalid second (out of bounds)
|
||||
"23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime)
|
||||
"23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime)
|
||||
"1441497364.649", // valid datetime, not a NaiveTime
|
||||
"+1441497364.649", // valid datetime, not a NaiveTime
|
||||
"+1441497364", // valid datetime, not a NaiveTime
|
||||
"001:02:03", // invalid hour
|
||||
"01:002:03", // invalid minute
|
||||
"01:02:003", // invalid second
|
||||
"12:34:56.x", // invalid fraction
|
||||
"12:34:56. 0", // invalid fraction format
|
||||
"09:08:00000000007", // invalid second / invalid fraction format
|
||||
];
|
||||
for &s in &invalid {
|
||||
eprintln!("test_time_parse_from_str invalid {s:?}");
|
||||
assert!(s.parse::<NaiveTime>().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_parse_from_str() {
|
||||
let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap();
|
||||
assert_eq!(
|
||||
NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
|
||||
Ok(hms(12, 34, 56))
|
||||
); // ignore date and offset
|
||||
assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0)));
|
||||
assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0)));
|
||||
assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0)));
|
||||
assert_eq!(
|
||||
NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"),
|
||||
Ok(hms(12, 59, 0))
|
||||
);
|
||||
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok());
|
||||
assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok());
|
||||
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok());
|
||||
assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overflowing_offset() {
|
||||
let hmsm = |h, m, s, n| NaiveTime::from_hms_milli_opt(h, m, s, n).unwrap();
|
||||
|
||||
let positive_offset = FixedOffset::east_opt(4 * 60 * 60).unwrap();
|
||||
// regular time
|
||||
let t = hmsm(5, 6, 7, 890);
|
||||
assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(9, 6, 7, 890), 0));
|
||||
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(1, 6, 7, 890), 0));
|
||||
// leap second is preserved, and wrap to next day
|
||||
let t = hmsm(23, 59, 59, 1_000);
|
||||
assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(3, 59, 59, 1_000), 1));
|
||||
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(19, 59, 59, 1_000), 0));
|
||||
// wrap to previous day
|
||||
let t = hmsm(1, 2, 3, 456);
|
||||
assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(21, 2, 3, 456), -1));
|
||||
// an odd offset
|
||||
let negative_offset = FixedOffset::west_opt(((2 * 60) + 3) * 60 + 4).unwrap();
|
||||
let t = hmsm(5, 6, 7, 890);
|
||||
assert_eq!(t.overflowing_add_offset(negative_offset), (hmsm(3, 3, 3, 890), 0));
|
||||
assert_eq!(t.overflowing_sub_offset(negative_offset), (hmsm(7, 9, 11, 890), 0));
|
||||
|
||||
assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset);
|
||||
assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rkyv-validation")]
|
||||
fn test_rkyv_validation() {
|
||||
let t_min = NaiveTime::MIN;
|
||||
let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<NaiveTime>(&bytes).unwrap(), t_min);
|
||||
|
||||
let t_max = NaiveTime::MAX;
|
||||
let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap();
|
||||
assert_eq!(rkyv::from_bytes::<NaiveTime>(&bytes).unwrap(), t_max);
|
||||
}
|
||||
Reference in new issue
Block a user