whatwg_datetime/
utils.rs

1use chrono::{Datelike, NaiveDate, Weekday};
2use whatwg_infra::collect_codepoints;
3
4#[inline]
5pub(crate) fn is_valid_month(month: &u32) -> bool {
6	(1..=12).contains(month)
7}
8
9#[inline]
10pub(crate) fn is_valid_hour(hour: &u32) -> bool {
11	(0..=23).contains(hour)
12}
13
14#[inline]
15pub(crate) fn is_valid_min_or_sec(val: &u32) -> bool {
16	(0..60).contains(val)
17}
18
19#[inline]
20pub(crate) fn collect_ascii_digits(s: &str, position: &mut usize) -> String {
21	collect_codepoints(s, position, |c| c.is_ascii_digit())
22}
23
24pub const fn max_days_in_month_year(month: u32, year: u32) -> Option<u32> {
25	match month {
26		1 | 3 | 5 | 7 | 8 | 10 | 12 => Some(31),
27		4 | 6 | 9 | 11 => Some(30),
28		2 => {
29			if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
30				Some(29)
31			} else {
32				Some(28)
33			}
34		}
35		_ => None,
36	}
37}
38
39// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
40pub fn week_number_of_year(year: i32) -> Option<u32> {
41	// We call unwrap() here since `NaiveDate::from_ymd_opt` returns `None` only
42	// if the month/day are out-of-range, which is not possible here since they're hardcoded.
43	let naive_date = NaiveDate::from_ymd_opt(year, 1u32, 1u32).unwrap();
44	let weekday = naive_date.weekday();
45
46	match weekday {
47		Weekday::Thu => Some(53u32),
48		Weekday::Wed => {
49			if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
50				Some(53u32)
51			} else {
52				Some(52u32)
53			}
54		}
55		_ => Some(52u32),
56	}
57}
58
59#[cfg(test)]
60mod tests {
61	use super::{max_days_in_month_year, week_number_of_year};
62
63	#[test]
64	fn test_max_days_in_month_28_days() {
65		assert_eq!(max_days_in_month_year(2, 2021), Some(28));
66		assert_eq!(max_days_in_month_year(2, 2022), Some(28));
67		assert_eq!(max_days_in_month_year(2, 2023), Some(28));
68	}
69
70	#[test]
71	fn test_max_days_in_month_29_days() {
72		assert_eq!(max_days_in_month_year(2, 2020), Some(29));
73		assert_eq!(max_days_in_month_year(2, 2024), Some(29));
74		assert_eq!(max_days_in_month_year(2, 2028), Some(29));
75		assert_eq!(max_days_in_month_year(2, 2400), Some(29));
76	}
77
78	#[test]
79	fn test_max_days_in_month_30_days() {
80		assert_eq!(max_days_in_month_year(4, 2021), Some(30));
81		assert_eq!(max_days_in_month_year(6, 2021), Some(30));
82		assert_eq!(max_days_in_month_year(9, 2021), Some(30));
83		assert_eq!(max_days_in_month_year(11, 2021), Some(30));
84	}
85
86	#[test]
87	fn test_max_days_in_month_31_days() {
88		assert_eq!(max_days_in_month_year(1, 2021), Some(31));
89		assert_eq!(max_days_in_month_year(3, 2019), Some(31));
90		assert_eq!(max_days_in_month_year(5, 2000), Some(31));
91		assert_eq!(max_days_in_month_year(7, 3097), Some(31));
92		assert_eq!(max_days_in_month_year(8, 1985), Some(31));
93		assert_eq!(max_days_in_month_year(10, 1426), Some(31));
94		assert_eq!(max_days_in_month_year(12, 1953), Some(31));
95	}
96
97	#[test]
98	fn test_max_days_in_month_nothing() {
99		assert_eq!(max_days_in_month_year(13, 2022), None);
100	}
101
102	// https://www.epochconverter.com/years
103	#[test]
104	fn test_week_number_of_year_is_52() {
105		assert_eq!(week_number_of_year(2012), Some(52));
106		assert_eq!(week_number_of_year(2017), Some(52));
107		assert_eq!(week_number_of_year(2018), Some(52));
108		assert_eq!(week_number_of_year(2019), Some(52));
109		assert_eq!(week_number_of_year(2021), Some(52));
110		assert_eq!(week_number_of_year(2022), Some(52));
111		assert_eq!(week_number_of_year(2023), Some(52));
112	}
113
114	// https://www.epochconverter.com/years
115	#[test]
116	fn test_week_number_of_year_is_53() {
117		assert_eq!(week_number_of_year(1801), Some(53));
118		assert_eq!(week_number_of_year(2004), Some(53));
119		assert_eq!(week_number_of_year(2009), Some(53));
120		assert_eq!(week_number_of_year(2015), Some(53));
121		assert_eq!(week_number_of_year(2020), Some(53));
122	}
123
124	/// Test for the corner case where the first day of the year is a Wednesday,
125	/// but the year isn't a leap year
126	#[test]
127	fn test_week_number_of_year_starts_on_wednesday_and_not_leap_year_is_52() {
128		assert_eq!(week_number_of_year(2014), Some(52));
129		assert_eq!(week_number_of_year(2025), Some(52));
130	}
131}