1use crate::header::time::tm;
6use alloc::string::String;
7use core::{
8 ffi::{CStr, c_char, c_int, c_void},
9 ptr,
10 ptr::NonNull,
11 str,
12};
13
14const SHORT_DAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
17const LONG_DAYS: [&str; 7] = [
19 "Sunday",
20 "Monday",
21 "Tuesday",
22 "Wednesday",
23 "Thursday",
24 "Friday",
25 "Saturday",
26];
27const SHORT_MONTHS: [&str; 12] = [
29 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
30];
31const LONG_MONTHS: [&str; 12] = [
33 "January",
34 "February",
35 "March",
36 "April",
37 "May",
38 "June",
39 "July",
40 "August",
41 "September",
42 "October",
43 "November",
44 "December",
45];
46
47#[unsafe(no_mangle)]
49pub unsafe extern "C" fn strptime(
50 buf: *const c_char,
51 format: *const c_char,
52 tm: *mut tm,
53) -> *mut c_char {
54 let buf_ptr = if let Some(ptr) = NonNull::new(buf.cast::<c_void>().cast_mut()) {
56 ptr
57 } else {
58 return ptr::null_mut();
59 };
60 let fmt_ptr = if let Some(ptr) = NonNull::new(format.cast::<c_void>().cast_mut()) {
62 ptr
63 } else {
64 return ptr::null_mut();
65 };
66
67 let tm_ptr = if let Some(ptr) = NonNull::new(tm) {
68 ptr
69 } else {
70 return ptr::null_mut();
71 };
72
73 let input_str = unsafe {
75 if buf.is_null() {
76 return ptr::null_mut();
77 }
78 match CStr::from_ptr(buf).to_str() {
79 Ok(s) => s,
80 Err(_) => return ptr::null_mut(), }
82 };
83
84 let fmt_str = unsafe {
85 if format.is_null() {
86 return ptr::null_mut();
87 }
88 match CStr::from_ptr(format).to_str() {
89 Ok(s) => s,
90 Err(_) => return ptr::null_mut(), }
92 };
93
94 let mut fmt_chars = fmt_str.chars().peekable();
96 let mut index_in_input = 0;
97
98 while let Some(fc) = fmt_chars.next() {
99 if fc != '%' {
100 if input_str.len() <= index_in_input {
102 return ptr::null_mut(); }
104 let in_char = input_str.as_bytes()[index_in_input] as char;
105 if in_char != fc {
106 return ptr::null_mut();
108 }
109 index_in_input += 1;
110 continue;
111 }
112
113 let Some(spec) = fmt_chars.next() else {
115 return ptr::null_mut();
117 };
118
119 let final_spec = if spec == 'E' || spec == 'O' {
122 match fmt_chars.next() {
123 Some(ch) => ch,
124 None => return ptr::null_mut(),
125 }
126 } else {
127 spec
128 };
129
130 match final_spec {
132 'n' | 't' => {
134 while index_in_input < input_str.len()
136 && input_str.as_bytes()[index_in_input].is_ascii_whitespace()
137 {
138 index_in_input += 1;
139 }
140 }
141
142 '%' => {
144 if index_in_input >= input_str.len()
145 || input_str.as_bytes()[index_in_input] as char != '%'
146 {
147 return ptr::null_mut();
148 }
149 index_in_input += 1;
150 }
151
152 'd' | 'e' => {
154 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
156 Some(v) => v,
157 None => return ptr::null_mut(),
158 };
159 unsafe {
160 (*tm).tm_mday = val as c_int;
161 if (*tm).tm_mday < 1 || (*tm).tm_mday > 31 {
163 return ptr::null_mut();
164 }
165 }
166 index_in_input += len;
167 }
168
169 'm' => {
171 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
173 Some(v) => v,
174 None => return ptr::null_mut(),
175 };
176 unsafe {
178 (*tm).tm_mon = (val as c_int) - 1;
179 if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 {
180 return ptr::null_mut();
181 }
182 }
183 index_in_input += len;
184 }
185
186 'y' => {
188 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
190 Some(v) => v,
191 None => return ptr::null_mut(),
192 };
193 let fullyear = if val >= 69 { val + 1900 } else { val + 2000 };
196 unsafe {
197 (*tm).tm_year = (fullyear - 1900) as c_int;
198 }
199 index_in_input += len;
200 }
201
202 'Y' => {
204 let (val, len) = match parse_int(&input_str[index_in_input..], 4, true) {
207 Some(v) => v,
208 None => return ptr::null_mut(),
209 };
210 unsafe {
211 (*tm).tm_year = (val as c_int) - 1900;
212 }
213 index_in_input += len;
214 }
215
216 'H' => {
218 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
219 Some(v) => v,
220 None => return ptr::null_mut(),
221 };
222 if val > 23 {
223 return ptr::null_mut();
224 }
225 unsafe {
226 (*tm).tm_hour = val as c_int;
227 }
228 index_in_input += len;
229 }
230
231 'I' => {
233 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
234 Some(v) => v,
235 None => return ptr::null_mut(),
236 };
237 if !(1..=12).contains(&val) {
238 return ptr::null_mut();
239 }
240 unsafe {
241 (*tm).tm_hour = val as c_int;
242 }
243 index_in_input += len;
245 }
246
247 'M' => {
249 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
250 Some(v) => v,
251 None => return ptr::null_mut(),
252 };
253 if val > 59 {
254 return ptr::null_mut();
255 }
256 unsafe {
257 (*tm).tm_min = val as c_int;
258 }
259 index_in_input += len;
260 }
261
262 'S' => {
264 let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) {
265 Some(v) => v,
266 None => return ptr::null_mut(),
267 };
268 if val > 60 {
269 return ptr::null_mut();
270 }
271 unsafe {
272 (*tm).tm_sec = val as c_int;
273 }
274 index_in_input += len;
275 }
276
277 'p' => {
279 let leftover = &input_str[index_in_input..];
282 let parsed_len = match parse_am_pm(leftover) {
283 Some((is_pm, used)) => {
284 if unsafe { (*tm).tm_hour } == 12 {
285 unsafe {
287 (*tm).tm_hour = if is_pm { 12 } else { 0 };
288 }
289 } else {
290 if is_pm {
292 unsafe {
293 (*tm).tm_hour += 12;
294 }
295 }
296 }
297 used
298 }
299 None => return ptr::null_mut(),
300 };
301 index_in_input += parsed_len;
302 }
303
304 'a' => {
306 let leftover = &input_str[index_in_input..];
308 let parsed_len = match parse_weekday(leftover, true) {
309 Some((wday, used)) => {
310 unsafe {
311 (*tm).tm_wday = wday as c_int;
312 }
313 used
314 }
315 None => return ptr::null_mut(),
316 };
317 index_in_input += parsed_len;
318 }
319 'A' => {
320 let leftover = &input_str[index_in_input..];
322 let parsed_len = match parse_weekday(leftover, false) {
323 Some((wday, used)) => {
324 unsafe {
325 (*tm).tm_wday = wday as c_int;
326 }
327 used
328 }
329 None => return ptr::null_mut(),
330 };
331 index_in_input += parsed_len;
332 }
333
334 'b' | 'h' => {
336 let leftover = &input_str[index_in_input..];
338 let parsed_len = match parse_month(leftover, true) {
339 Some((mon, used)) => {
340 unsafe {
341 (*tm).tm_mon = mon as c_int;
342 }
343 used
344 }
345 None => return ptr::null_mut(),
346 };
347 index_in_input += parsed_len;
348 }
349 'B' => {
350 let leftover = &input_str[index_in_input..];
352 let parsed_len = match parse_month(leftover, false) {
353 Some((mon, used)) => {
354 unsafe {
355 (*tm).tm_mon = mon as c_int;
356 }
357 used
358 }
359 None => return ptr::null_mut(),
360 };
361 index_in_input += parsed_len;
362 }
363
364 'j' => {
366 let (val, len) = match parse_int(&input_str[index_in_input..], 3, false) {
368 Some(v) => v,
369 None => return ptr::null_mut(),
370 };
371 if !(1..=366).contains(&val) {
372 return ptr::null_mut();
373 }
374 unsafe {
376 (*tm).tm_yday = (val - 1) as c_int;
377 }
378 index_in_input += len;
379 }
380
381 'D' => {
383 let subfmt = "%m/%d/%y";
387 let used =
388 match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } {
389 Some(v) => v,
390 None => return ptr::null_mut(),
391 };
392 index_in_input += used;
393 }
394 'F' => {
395 let subfmt = "%Y-%m-%d";
397 let used =
398 match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } {
399 Some(v) => v,
400 None => return ptr::null_mut(),
401 };
402 index_in_input += used;
403 }
404 'T' => {
405 let subfmt = "%H:%M:%S";
407 let used =
408 match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } {
409 Some(v) => v,
410 None => return ptr::null_mut(),
411 };
412 index_in_input += used;
413 }
414
415 'x' | 'X' | 'c' | 'r' | 'R' => {
418 return ptr::null_mut();
420 }
421
422 'Z' | 'z' => {
424 return ptr::null_mut();
427 }
428
429 _ => {
430 return ptr::null_mut();
432 }
433 }
434 }
435
436 let ret_ptr = unsafe { buf.add(index_in_input) };
439 ret_ptr.cast_mut()
440}
441
442fn parse_int(input: &str, width: usize, allow_variable: bool) -> Option<(i32, usize)> {
450 let mut val = 0i32;
451 let chars = input.chars();
452 let mut count = 0;
453
454 for c in chars {
455 if !c.is_ascii_digit() {
456 break;
457 }
458
459 val = val
461 .checked_mul(10)?
462 .checked_add(i32::from(c as u8 - b'0'))?;
463
464 count += 1;
465 if count == width && !allow_variable {
466 break;
467 }
468 }
469
470 if count == 0 { None } else { Some((val, count)) }
471}
472
473fn parse_am_pm(s: &str) -> Option<(bool, usize)> {
476 let trimmed = s.trim_start();
477 let diff = s.len() - trimmed.len();
479 let s = trimmed.get(0..2)?;
480
481 if s.eq_ignore_ascii_case("AM") {
482 return Some((false, diff + 2));
483 }
484 if s.eq_ignore_ascii_case("PM") {
485 return Some((true, diff + 2));
486 }
487 None
488}
489
490fn parse_weekday(s: &str, abbrev: bool) -> Option<(usize, usize)> {
496 let list = if abbrev { &SHORT_DAYS } else { &LONG_DAYS };
497 for (i, name) in list.iter().enumerate() {
498 if s.len() >= name.len()
499 && s.get(0..name.len())
500 .is_some_and(|sub| sub.eq_ignore_ascii_case(name))
501 {
502 return Some((i, name.len()));
503 }
504 }
505 None
506}
507
508fn parse_month(s: &str, abbrev: bool) -> Option<(usize, usize)> {
514 let list = if abbrev { &SHORT_MONTHS } else { &LONG_MONTHS };
515 for (i, name) in list.iter().enumerate() {
516 if s.len() >= name.len()
517 && s.get(0..name.len())
518 .is_some_and(|sub| sub.eq_ignore_ascii_case(name))
519 {
520 return Some((i, name.len()));
521 }
522 }
523 None
524}
525
526unsafe fn apply_subformat(input: &str, subfmt: &str, tm: *mut tm) -> Option<usize> {
529 let mut tmpbuf = String::with_capacity(input.len() + 1);
535 tmpbuf.push_str(input);
536 tmpbuf.push('\0');
537
538 let mut tmpfmt = String::with_capacity(subfmt.len() + 1);
539 tmpfmt.push_str(subfmt);
540 tmpfmt.push('\0');
541
542 let old_tm = unsafe { ptr::read(tm) }; let consumed_ptr = unsafe {
546 strptime(
547 tmpbuf.as_ptr().cast::<c_char>(),
548 tmpfmt.as_ptr().cast::<c_char>(),
549 tm,
550 )
551 };
552
553 if consumed_ptr.is_null() {
554 unsafe {
556 *tm = old_tm;
557 }
558 return None;
559 }
560
561 let diff = (consumed_ptr as usize) - (tmpbuf.as_ptr() as usize);
563 Some(diff)
564}
565
566#[cfg(test)]
567mod tests {
568 use super::parse_am_pm;
569
570 #[test]
571 fn am_pm_parser_works() {
572 let am = "am";
573 let am_expected = Some((false, 2));
574 assert_eq!(am_expected, parse_am_pm(am));
575
576 let pm = "pm";
577 let pm_expected = Some((true, 2));
578 assert_eq!(pm_expected, parse_am_pm(pm));
579
580 let am_caps = "AM";
581 assert_eq!(am_expected, parse_am_pm(am_caps));
582
583 let pm_caps = "PM";
584 assert_eq!(pm_expected, parse_am_pm(pm_caps));
585
586 let am_weird = "aM";
587 assert_eq!(am_expected, parse_am_pm(am_weird));
588
589 let am_prefix = " \tam";
590 let am_prefix_expected = Some((false, 11));
591 assert_eq!(am_prefix_expected, parse_am_pm(am_prefix));
592
593 let pm_spaces = " pm ";
594 let pm_spaces_expected = Some((true, 10));
595 assert_eq!(pm_spaces_expected, parse_am_pm(pm_spaces));
596 }
597}