whatwg_datetime/components/
month.rs1use crate::tokens::TOKEN_HYPHEN;
2use crate::utils::{collect_ascii_digits, is_valid_month};
3use crate::{collect_month_and_validate, parse_format};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct YearMonth {
18 pub(crate) year: i32,
19 pub(crate) month: u32,
20}
21
22impl YearMonth {
23 pub(crate) const fn new(year: i32, month: u32) -> Self {
24 Self { year, month }
25 }
26
27 pub fn new_opt(year: i32, month: u32) -> Option<Self> {
42 if year == 0 {
43 return None;
44 }
45
46 if !is_valid_month(&month) {
47 return None;
48 }
49
50 Some(Self::new(year, month))
51 }
52
53 #[inline]
63 pub const fn year(&self) -> i32 {
64 self.year
65 }
66
67 #[inline]
77 pub const fn month(&self) -> u32 {
78 self.month
79 }
80}
81
82#[inline]
99pub fn parse_month(s: &str) -> Option<YearMonth> {
100 parse_format(s, parse_month_component)
101}
102
103pub fn parse_month_component(s: &str, position: &mut usize) -> Option<YearMonth> {
125 let parsed_year = collect_ascii_digits(s, position);
126 if parsed_year.len() < 4 {
127 return None;
128 }
129
130 let year = parsed_year.parse::<i32>().ok()?;
131 if year == 0 {
132 return None;
133 }
134
135 if *position > s.len() || s.chars().nth(*position) != Some(TOKEN_HYPHEN) {
136 return None;
137 } else {
138 *position += 1;
139 }
140
141 let month = collect_month_and_validate(s, position)?;
142 Some(YearMonth::new(year, month))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::{parse_month, parse_month_component, YearMonth};
148
149 #[test]
150 fn test_parse_month_string() {
151 let parsed = parse_month("2004-12");
152 assert_eq!(parsed, Some(YearMonth::new(2004, 12)));
153 }
154
155 #[test]
156 fn test_parse_month_string_fails_invalid_month() {
157 let parsed = parse_month("2004-2a");
158 assert_eq!(parsed, None);
159 }
160
161 #[test]
162 fn test_parse_month_string_fails() {
163 let parsed = parse_month("2004-13");
164 assert_eq!(parsed, None);
165 }
166
167 #[test]
168 fn test_parse_month_component() {
169 let mut position = 0usize;
170 let parsed = parse_month_component("2004-12", &mut position);
171
172 assert_eq!(parsed, Some(YearMonth::new(2004, 12)));
173 }
174
175 #[test]
176 fn test_parse_month_component_fails_year_lt_4_digits() {
177 let mut position = 0usize;
178 let parsed = parse_month_component("200-12", &mut position);
179
180 assert_eq!(parsed, None);
181 }
182
183 #[test]
184 fn test_parse_month_component_fails_invalid_month_lower_bound() {
185 let mut position = 0usize;
186 let parsed = parse_month_component("2004-0", &mut position);
187
188 assert_eq!(parsed, None);
189 }
190
191 #[test]
192 fn test_parse_month_component_fails_invalid_month_upper_bound() {
193 let mut position = 0usize;
194 let parsed = parse_month_component("2004-13", &mut position);
195
196 assert_eq!(parsed, None);
197 }
198
199 #[test]
200 fn test_parse_month_component_fails_invalid_month_syntax() {
201 let mut position = 0usize;
202 let parsed = parse_month_component("2004-1a", &mut position);
203
204 assert_eq!(parsed, None);
205 }
206
207 #[test]
208 fn test_parse_month_component_fails_invalid_separator() {
209 let mut position = 0usize;
210 let parsed = parse_month_component("2004/12", &mut position);
211
212 assert_eq!(parsed, None);
213 }
214}