whatwg_datetime/components/
time.rs1use crate::parse_format;
2use crate::tokens::{TOKEN_COLON, TOKEN_DOT};
3use crate::utils::{collect_ascii_digits, is_valid_hour, is_valid_min_or_sec};
4use chrono::NaiveTime;
5use whatwg_infra::collect_codepoints;
6
7#[inline]
31pub fn parse_time(s: &str) -> Option<NaiveTime> {
32 parse_format(s, parse_time_component)
33}
34
35pub fn parse_time_component(s: &str, position: &mut usize) -> Option<NaiveTime> {
58 let parsed_hour = collect_ascii_digits(s, position);
59 if parsed_hour.len() != 2 {
60 return None;
61 }
62
63 let hour = parsed_hour.parse::<u32>().ok()?;
64 if !is_valid_hour(&hour) {
65 return None;
66 }
67
68 if *position > s.len() || s.chars().nth(*position) != Some(TOKEN_COLON) {
69 return None;
70 } else {
71 *position += 1;
72 }
73
74 let parsed_minute = collect_ascii_digits(s, position);
75 if parsed_minute.len() != 2 {
76 return None;
77 }
78 let minute = parsed_minute.parse::<u32>().ok()?;
79 if !is_valid_min_or_sec(&minute) {
80 return None;
81 }
82
83 let mut seconds = 0u32;
84 let mut milliseconds = 0u32;
85 if *position < s.len() && s.chars().nth(*position) == Some(TOKEN_COLON) {
86 *position += 1;
87
88 if *position >= s.len() {
89 return None;
90 }
91
92 let parsed_second =
93 collect_codepoints(s, position, |c| c.is_ascii_digit() || c == TOKEN_DOT);
94 let parsed_second_len = parsed_second.len();
95 if parsed_second_len == 3
96 || (parsed_second_len > 3
97 && parsed_second.chars().nth(2) != Some(TOKEN_DOT))
98 || has_at_least_n_instances(s, TOKEN_DOT, 2)
99 {
100 return None;
101 }
102
103 let (parsed_seconds, parsed_milliseconds) =
104 parse_seconds_milliseconds(&parsed_second);
105 seconds = parsed_seconds;
106 milliseconds = parsed_milliseconds;
107 if !is_valid_min_or_sec(&seconds) {
108 return None;
109 }
110 }
111
112 NaiveTime::from_hms_milli_opt(hour, minute, seconds, milliseconds)
113}
114
115fn has_at_least_n_instances(s: &str, c: char, n: usize) -> bool {
116 let mut count = 0usize;
117 for ch in s.chars() {
118 if ch == c {
119 count += 1usize;
120 if count >= n {
121 return true;
122 }
123 }
124 }
125 false
126}
127
128fn parse_seconds_milliseconds(s: &str) -> (u32, u32) {
129 let parts: Vec<&str> = s.split(TOKEN_DOT).collect();
130 let seconds = parts.first().unwrap_or(&"0").parse().unwrap_or(0);
131 let milliseconds = parts.get(1).unwrap_or(&"0").parse().unwrap_or(0);
132
133 (seconds, milliseconds)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{parse_time, parse_time_component, NaiveTime};
139
140 #[test]
141 fn test_parse_time_succeeds_hm() {
142 assert_eq!(
143 parse_time("12:31"),
144 NaiveTime::from_hms_milli_opt(12, 31, 0, 0)
145 );
146 }
147
148 #[test]
149 fn test_parse_time_succeeds_hms() {
150 assert_eq!(
151 parse_time("12:31:59"),
152 NaiveTime::from_hms_milli_opt(12, 31, 59, 0)
153 );
154 }
155
156 #[test]
157 fn test_parse_time_succeeds_hms_fractional_seconds() {
158 assert_eq!(
159 parse_time("14:54:39.929"),
160 NaiveTime::from_hms_milli_opt(14, 54, 39, 929)
161 );
162 }
163
164 #[test]
165 fn test_parse_time_fails_multiple_decimals() {
166 assert_eq!(parse_time("12:31:59...29"), None);
167 }
168
169 #[test]
170 fn test_parse_time_fails_hour_length() {
171 assert_eq!(parse_time("123:31:59"), None);
172 }
173
174 #[test]
175 fn test_parse_time_fails_hour_value_upper_bound() {
176 assert_eq!(parse_time("24:31:59"), None);
177 }
178
179 #[test]
180 fn test_parse_time_fails_delimiter() {
181 assert_eq!(parse_time("12-31-59"), None);
182 }
183
184 #[test]
185 fn test_parse_time_fails_minute_length() {
186 assert_eq!(parse_time("12:311:59"), None);
187 }
188
189 #[test]
190 fn test_parse_time_fails_minute_value_upper_bound() {
191 assert_eq!(parse_time("12:79:59"), None);
192 }
193
194 #[test]
195 fn test_parse_time_fails_seconds_length() {
196 assert_eq!(parse_time("12:31:591"), None);
197 }
198
199 #[test]
200 fn test_parse_time_fails_seconds_value_upper_bound() {
201 assert_eq!(parse_time("12:31:79"), None);
202 }
203
204 #[test]
205 fn test_parse_time_component() {
206 let mut position = 0usize;
207 let parsed = parse_time_component("12:31:59", &mut position);
208
209 assert_eq!(parsed, NaiveTime::from_hms_milli_opt(12, 31, 59, 0));
210 }
211}