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:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

File diff suppressed because it is too large. Load diff
+920
View File
@@ -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;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+405
View File
@@ -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);
}
+591
View File
@@ -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());
}
}
+248
View File
@@ -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);
}
}
+340
View File
@@ -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);
}
}
File diff suppressed because it is too large. Load diff
+143
View File
@@ -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
View File
@@ -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);
}