1use super::compat::{self, time_t, tm};
4use super::tm::{
5 empty_tm, get_time_sec, init_tm_unknown, is_date_known, match_string, maybeiso8601, nodate,
6 parse_timestamp_prefix, skip_alpha, tm_to_time_t, TIMESTAMP_MAX,
7};
8
9struct TzName {
10 name: &'static str,
11 offset_hours: i32,
12 dst: i32,
13}
14
15const TIMEZONE_NAMES: &[TzName] = &[
16 TzName {
17 name: "IDLW",
18 offset_hours: -12,
19 dst: 0,
20 },
21 TzName {
22 name: "NT",
23 offset_hours: -11,
24 dst: 0,
25 },
26 TzName {
27 name: "CAT",
28 offset_hours: -10,
29 dst: 0,
30 },
31 TzName {
32 name: "HST",
33 offset_hours: -10,
34 dst: 0,
35 },
36 TzName {
37 name: "HDT",
38 offset_hours: -10,
39 dst: 1,
40 },
41 TzName {
42 name: "YST",
43 offset_hours: -9,
44 dst: 0,
45 },
46 TzName {
47 name: "YDT",
48 offset_hours: -9,
49 dst: 1,
50 },
51 TzName {
52 name: "PST",
53 offset_hours: -8,
54 dst: 0,
55 },
56 TzName {
57 name: "PDT",
58 offset_hours: -8,
59 dst: 1,
60 },
61 TzName {
62 name: "MST",
63 offset_hours: -7,
64 dst: 0,
65 },
66 TzName {
67 name: "MDT",
68 offset_hours: -7,
69 dst: 1,
70 },
71 TzName {
72 name: "CST",
73 offset_hours: -6,
74 dst: 0,
75 },
76 TzName {
77 name: "CDT",
78 offset_hours: -6,
79 dst: 1,
80 },
81 TzName {
82 name: "EST",
83 offset_hours: -5,
84 dst: 0,
85 },
86 TzName {
87 name: "EDT",
88 offset_hours: -5,
89 dst: 1,
90 },
91 TzName {
92 name: "AST",
93 offset_hours: -3,
94 dst: 0,
95 },
96 TzName {
97 name: "ADT",
98 offset_hours: -3,
99 dst: 1,
100 },
101 TzName {
102 name: "WAT",
103 offset_hours: -1,
104 dst: 0,
105 },
106 TzName {
107 name: "GMT",
108 offset_hours: 0,
109 dst: 0,
110 },
111 TzName {
112 name: "UTC",
113 offset_hours: 0,
114 dst: 0,
115 },
116 TzName {
117 name: "Z",
118 offset_hours: 0,
119 dst: 0,
120 },
121 TzName {
122 name: "WET",
123 offset_hours: 0,
124 dst: 0,
125 },
126 TzName {
127 name: "BST",
128 offset_hours: 0,
129 dst: 1,
130 },
131 TzName {
132 name: "CET",
133 offset_hours: 1,
134 dst: 0,
135 },
136 TzName {
137 name: "MET",
138 offset_hours: 1,
139 dst: 0,
140 },
141 TzName {
142 name: "MEWT",
143 offset_hours: 1,
144 dst: 0,
145 },
146 TzName {
147 name: "MEST",
148 offset_hours: 1,
149 dst: 1,
150 },
151 TzName {
152 name: "CEST",
153 offset_hours: 1,
154 dst: 1,
155 },
156 TzName {
157 name: "MESZ",
158 offset_hours: 1,
159 dst: 1,
160 },
161 TzName {
162 name: "FWT",
163 offset_hours: 1,
164 dst: 0,
165 },
166 TzName {
167 name: "FST",
168 offset_hours: 1,
169 dst: 1,
170 },
171 TzName {
172 name: "EET",
173 offset_hours: 2,
174 dst: 0,
175 },
176 TzName {
177 name: "EEST",
178 offset_hours: 2,
179 dst: 1,
180 },
181 TzName {
182 name: "WAST",
183 offset_hours: 7,
184 dst: 0,
185 },
186 TzName {
187 name: "WADT",
188 offset_hours: 7,
189 dst: 1,
190 },
191 TzName {
192 name: "CCT",
193 offset_hours: 8,
194 dst: 0,
195 },
196 TzName {
197 name: "JST",
198 offset_hours: 9,
199 dst: 0,
200 },
201 TzName {
202 name: "EAST",
203 offset_hours: 10,
204 dst: 0,
205 },
206 TzName {
207 name: "EADT",
208 offset_hours: 10,
209 dst: 1,
210 },
211 TzName {
212 name: "GST",
213 offset_hours: 10,
214 dst: 0,
215 },
216 TzName {
217 name: "NZT",
218 offset_hours: 12,
219 dst: 0,
220 },
221 TzName {
222 name: "NZST",
223 offset_hours: 12,
224 dst: 0,
225 },
226 TzName {
227 name: "NZDT",
228 offset_hours: 12,
229 dst: 1,
230 },
231 TzName {
232 name: "IDLE",
233 offset_hours: 12,
234 dst: 0,
235 },
236];
237
238pub(crate) const MONTH_NAMES: [&str; 12] = [
239 "January",
240 "February",
241 "March",
242 "April",
243 "May",
244 "June",
245 "July",
246 "August",
247 "September",
248 "October",
249 "November",
250 "December",
251];
252
253pub(crate) const WEEKDAY_NAMES: [&str; 7] = [
254 "Sundays",
255 "Mondays",
256 "Tuesdays",
257 "Wednesdays",
258 "Thursdays",
259 "Fridays",
260 "Saturdays",
261];
262
263pub fn date_string(date: u64, offset: i32) -> String {
265 let mut sign = '+';
266 let mut o = offset;
267 if o < 0 {
268 o = -o;
269 sign = '-';
270 }
271 format!("{} {}{:02}{:02}", date, sign, o / 60, o % 60)
272}
273
274pub fn parse_date(date: &str) -> Result<String, ()> {
276 let (ts, off) = parse_date_basic(date)?;
277 Ok(date_string(ts, off))
278}
279
280pub fn parse_date_basic(date: &str) -> Result<(u64, i32), ()> {
282 let bytes = date.as_bytes();
283 let mut tm = init_tm_unknown();
284 let mut offset: i32 = -1;
285 let mut tm_gmt = 0i32;
286 let mut i = 0usize;
287
288 if bytes.first() == Some(&b'@') {
289 if let Some((ts, off)) = match_object_header_date(&bytes[1..]) {
290 return Ok((ts, off));
291 }
292 }
293
294 while i < bytes.len() {
295 let c = bytes[i];
296 if c == 0 || c == b'\n' {
297 break;
298 }
299 let mut m = 0usize;
300 if c.is_ascii_alphabetic() {
301 m = match_alpha(&bytes[i..], &mut tm, &mut offset);
302 } else if c.is_ascii_digit() {
303 m = match_digit(&bytes[i..], &mut tm, &mut offset, &mut tm_gmt);
304 } else if (c == b'-' || c == b'+') && bytes.get(i + 1).is_some_and(|x| x.is_ascii_digit()) {
305 m = match_tz(&bytes[i..], &mut offset);
306 }
307 if m == 0 {
308 m = 1;
309 }
310 i += m;
311 }
312
313 let tts = tm_to_time_t(&tm);
314 if tts < 0 {
315 return Err(());
316 }
317 let mut ts = tts as u64;
318
319 if offset == -1 {
320 tm.tm_isdst = -1;
321 let temp_time = unsafe { compat::mktime(&mut tm) };
322 let tt = ts as i128;
323 let tloc = temp_time as i128;
324 offset = if tt > tloc {
325 ((tt - tloc) / 60) as i32
326 } else {
327 -(((tloc - tt) / 60) as i32)
328 };
329 }
330
331 if tm_gmt == 0 {
332 if offset > 0 && (offset as i64) * 60 > ts as i64 {
333 return Err(());
334 }
335 if offset < 0 && (-(offset as i128)) * 60 > (TIMESTAMP_MAX as i128 - ts as i128) {
336 return Err(());
337 }
338 let ts128 = ts as i128;
340 let adj = (offset as i128) * 60;
341 let new_ts = ts128 - adj;
342 if new_ts < 0 {
343 return Err(());
344 }
345 ts = new_ts as u64;
346 }
347
348 Ok((ts, offset))
349}
350
351fn match_object_header_date(date: &[u8]) -> Option<(u64, i32)> {
352 if date.is_empty() || !date[0].is_ascii_digit() {
353 return None;
354 }
355 let (stamp, mut rest) = parse_timestamp_prefix(date);
356 if rest >= date.len() || date[rest] != b' ' {
357 return None;
358 }
359 if stamp == u64::MAX {
360 return None;
361 }
362 rest += 1;
363 if rest >= date.len() || (date[rest] != b'+' && date[rest] != b'-') {
364 return None;
365 }
366 let sign = date[rest];
367 rest += 1;
368 if rest + 4 > date.len() {
369 return None;
370 }
371 let tz_digits = std::str::from_utf8(&date[rest..rest + 4]).ok()?;
372 let ofs_raw: i32 = tz_digits.parse().ok()?;
373 let mut ofs = (ofs_raw / 100) * 60 + (ofs_raw % 100);
374 if sign == b'-' {
375 ofs = -ofs;
376 }
377 let end = rest + 4;
378 if end < date.len() && date[end] != b'\n' && date[end] != 0 {
379 return None;
380 }
381 Some((stamp, ofs))
382}
383
384fn match_tz(date: &[u8], offp: &mut i32) -> usize {
386 if date.is_empty() || (date[0] != b'+' && date[0] != b'-') {
387 return 0;
388 }
389 let (hour_ul, n) = parse_timestamp_prefix(&date[1..]);
390 let mut end = 1 + n;
391 let mut min: i32 = 0;
392 let mut hour: i32 = hour_ul as i32;
393 if n == 4 {
394 min = hour % 100;
395 hour /= 100;
396 } else if n != 2 {
397 min = 99;
398 } else if end < date.len() && date[end] == b':' {
399 let (m2, n2) = parse_timestamp_prefix(&date[end + 1..]);
400 if n2 == 0 {
401 min = 99;
402 } else {
403 min = m2 as i32;
404 end += 1 + n2;
405 if end - 1 != 5 {
406 min = 99;
407 }
408 }
409 }
410 if min < 60 && hour < 24 {
411 let mut off = hour * 60 + min;
412 if date[0] == b'-' {
413 off = -off;
414 }
415 *offp = off;
416 }
417 end
418}
419
420fn parse_long_prefix(s: &[u8]) -> (i64, usize) {
422 if s.is_empty() {
423 return (0, 0);
424 }
425 let mut i = 0usize;
426 let neg = s[0] == b'-';
427 if s[0] == b'+' || s[0] == b'-' {
428 i = 1;
429 }
430 let start = i;
431 while i < s.len() && s[i].is_ascii_digit() {
432 i += 1;
433 }
434 if i == start {
435 return (0, 0);
436 }
437 let Ok(slice) = std::str::from_utf8(&s[start..i]) else {
438 return (0, 0);
439 };
440 let Ok(v) = slice.parse::<i64>() else {
441 return (0, 0);
442 };
443 let v = if neg { -v } else { v };
444 (v, i)
445}
446
447fn parse_uint_suffix(s: &[u8]) -> (u64, usize) {
448 parse_timestamp_prefix(s)
449}
450
451fn set_date(year: i32, month: i32, day: i32, now_tm: Option<&tm>, now: i64, tm: &mut tm) -> i32 {
453 if !(month > 0 && month < 13 && day > 0 && day < 32) {
454 return -1;
455 }
456 let Some(nt) = now_tm else {
457 tm.tm_mon = month - 1;
458 tm.tm_mday = day;
459 if year == -1 {
460 return 1;
461 }
462 if (1970..2100).contains(&year) {
463 tm.tm_year = year - 1900;
464 } else if (70..100).contains(&year) {
465 tm.tm_year = year;
466 } else if year < 38 {
467 tm.tm_year = year + 100;
468 } else {
469 return -1;
470 }
471 return 0;
472 };
473 let mut check = *tm;
474 check.tm_mon = month - 1;
475 check.tm_mday = day;
476 if year == -1 {
477 check.tm_year = nt.tm_year;
478 } else if (1970..2100).contains(&year) {
479 check.tm_year = year - 1900;
480 } else if (70..100).contains(&year) {
481 check.tm_year = year;
482 } else if year < 38 {
483 check.tm_year = year + 100;
484 } else {
485 return -1;
486 }
487 let specified = tm_to_time_t(&check);
488 if specified >= 0 && now + 10 * 24 * 3600 < specified {
489 return -1;
490 }
491 tm.tm_mon = check.tm_mon;
492 tm.tm_mday = check.tm_mday;
493 if year != -1 {
494 tm.tm_year = check.tm_year;
495 }
496 0
497}
498
499fn set_time(hour: i64, minute: i64, second: i64, tm: &mut tm) -> i32 {
500 if (0..=24).contains(&hour) && (0..60).contains(&minute) && (0..=60).contains(&second) {
501 tm.tm_hour = hour as i32;
502 tm.tm_min = minute as i32;
503 tm.tm_sec = second as i32;
504 0
505 } else {
506 -1
507 }
508}
509
510pub(crate) fn match_multi_number(
512 num: u64,
513 date: &[u8],
514 sep_i: usize,
515 tm: &mut tm,
516 now_in: i64,
517) -> usize {
518 let Some(&c) = date.get(sep_i) else {
519 return 0;
520 };
521 if !matches!(c, b':' | b'-' | b'/' | b'.') {
522 return 0;
523 }
524
525 let (num2, n2) = parse_long_prefix(&date[sep_i + 1..]);
526 if n2 == 0 {
527 return 0;
528 }
529 let mut pos = sep_i + 1 + n2;
530 let mut num3: i64 = -1;
531 if pos < date.len() && date[pos] == c && pos + 1 < date.len() && date[pos + 1].is_ascii_digit()
532 {
533 let (n3, rel) = parse_long_prefix(&date[pos + 1..]);
534 num3 = n3;
535 pos += 1 + rel;
536 }
537
538 match c {
539 b':' => {
540 let mut n3 = num3;
541 if n3 < 0 {
542 n3 = 0;
543 }
544 if set_time(num as i64, num2, n3, tm) == 0 {
545 if pos < date.len()
546 && date[pos] == b'.'
547 && pos + 1 < date.len()
548 && date[pos + 1].is_ascii_digit()
549 && is_date_known(tm)
550 {
551 let (_, rel) = parse_long_prefix(&date[pos + 1..]);
552 pos += 1 + rel;
553 }
554 } else {
555 return 0;
556 }
557 }
558 b'-' | b'/' | b'.' => {
559 let now = if now_in == 0 { get_time_sec() } else { now_in };
560 let mut now_tm = empty_tm();
561 let refuse_future: Option<&tm> = if compat::gmtime(now as time_t, &mut now_tm) {
562 Some(&now_tm)
563 } else {
564 None
565 };
566
567 let y = num as i32;
568 let m = num2 as i32;
569 let d = if num3 < 0 { 0 } else { num3 as i32 };
570
571 if num > 70 {
572 if set_date(y, m, d, None, now, tm) == 0 {
573 return pos;
574 }
575 if set_date(y, d, m, None, now, tm) == 0 {
576 return pos;
577 }
578 }
579 if c != b'.' && set_date(d, y, m, refuse_future, now, tm) == 0 {
580 return pos;
581 }
582 if set_date(d, m, y, refuse_future, now, tm) == 0 {
583 return pos;
584 }
585 if c == b'.' && set_date(d, y, m, refuse_future, now, tm) == 0 {
586 return pos;
587 }
588 return 0;
589 }
590 _ => return 0,
591 }
592 pos
593}
594
595fn match_alpha(date: &[u8], tm: &mut tm, offset: &mut i32) -> usize {
596 for (i, name) in MONTH_NAMES.iter().enumerate() {
597 let m = match_string(date, name);
598 if m >= 3 {
599 tm.tm_mon = i as i32;
600 return m;
601 }
602 }
603
604 for (i, name) in WEEKDAY_NAMES.iter().enumerate() {
605 let m = match_string(date, name);
606 if m >= 3 {
607 tm.tm_wday = i as i32;
608 return m;
609 }
610 }
611
612 for tz in TIMEZONE_NAMES {
613 let m = match_string(date, tz.name);
614 if m >= 3 || m == tz.name.len() {
615 let off = tz.offset_hours + tz.dst;
616 if *offset == -1 {
617 *offset = 60 * off;
618 }
619 return m;
620 }
621 }
622
623 if match_string(date, "PM") == 2 {
624 tm.tm_hour = (tm.tm_hour % 12) + 12;
625 return 2;
626 }
627
628 if match_string(date, "AM") == 2 {
629 tm.tm_hour %= 12;
630 return 2;
631 }
632
633 if date.first() == Some(&b'T')
634 && date.get(1).is_some_and(|b| b.is_ascii_digit())
635 && tm.tm_hour == -1
636 {
637 tm.tm_min = 0;
638 tm.tm_sec = 0;
639 return 1;
640 }
641
642 skip_alpha(date)
643}
644
645fn match_digit(date: &[u8], tm: &mut tm, offset: &mut i32, tm_gmt: &mut i32) -> usize {
646 let (num, n) = parse_timestamp_prefix(date);
647 if n == 0 {
648 return 0;
649 }
650 let end = n;
651
652 if num >= 100_000_000 && nodate(tm) {
653 if compat::gmtime(num as time_t, tm) {
654 *tm_gmt = 1;
655 return end;
656 }
657 }
658
659 if let Some(&sep) = date.get(end) {
660 if matches!(sep, b':' | b'.' | b'/' | b'-')
661 && date.get(end + 1).is_some_and(|b| b.is_ascii_digit())
662 {
663 let m = match_multi_number(num, date, end, tm, 0);
664 if m != 0 {
665 return m;
666 }
667 }
668 }
669
670 let mut n_digits = 0usize;
671 loop {
672 n_digits += 1;
673 if n_digits >= date.len() || !date[n_digits].is_ascii_digit() {
674 break;
675 }
676 }
677
678 if n_digits == 8 || n_digits == 6 {
679 let num1 = (num / 10000) as i32;
680 let num2 = ((num % 10000) / 100) as i32;
681 let num3 = (num % 100) as i32;
682 if n_digits == 8 {
683 let _ = set_date(num1, num2, num3, None, get_time_sec(), tm);
684 } else if set_time(num1 as i64, num2 as i64, num3 as i64, tm) == 0
685 && date.get(end) == Some(&b'.')
686 && date.get(end + 1).is_some_and(|b| b.is_ascii_digit())
687 {
688 let (_, rel) = parse_uint_suffix(&date[end + 1..]);
689 return end + 1 + rel;
690 }
691 return end;
692 }
693
694 if maybeiso8601(tm) {
695 let mut num1 = num as u32;
696 let mut num2: u32 = 0;
697 if n_digits == 4 {
698 num1 = (num / 100) as u32;
699 num2 = (num % 100) as u32;
700 }
701 if (n_digits == 4 || n_digits == 2)
702 && !nodate(tm)
703 && set_time(num1 as i64, num2 as i64, 0, tm) == 0
704 {
705 return n_digits;
706 }
707 tm.tm_min = -1;
708 tm.tm_sec = -1;
709 }
710
711 if n_digits == 4 {
712 if num <= 1400 && *offset == -1 {
713 let minutes = (num % 100) as u32;
714 let hours = (num / 100) as u32;
715 *offset = (hours * 60 + minutes) as i32;
716 } else if num > 1900 && num < 2100 {
717 tm.tm_year = (num as i32) - 1900;
718 }
719 return n_digits;
720 }
721
722 if n_digits > 2 {
723 return n_digits;
724 }
725
726 if num > 0 && num < 32 && tm.tm_mday < 0 {
727 tm.tm_mday = num as i32;
728 return n_digits;
729 }
730
731 if n_digits == 2 && tm.tm_year < 0 {
732 if num < 10 && tm.tm_mday >= 0 {
733 tm.tm_year = (num as i32) + 100;
734 return n_digits;
735 }
736 if num >= 70 {
737 tm.tm_year = num as i32;
738 return n_digits;
739 }
740 }
741
742 if num > 0 && num < 13 && tm.tm_mon < 0 {
743 tm.tm_mon = (num as i32) - 1;
744 }
745
746 n_digits
747}