use super::tm::{
get_time_sec, init_tm_unknown, is_date_known, match_string, maybeiso8601, nodate,
parse_timestamp_prefix, skip_alpha, tm_to_time_t, TIMESTAMP_MAX,
};
use libc::{time_t, tm};
use std::mem::MaybeUninit;
struct TzName {
name: &'static str,
offset_hours: i32,
dst: i32,
}
const TIMEZONE_NAMES: &[TzName] = &[
TzName {
name: "IDLW",
offset_hours: -12,
dst: 0,
},
TzName {
name: "NT",
offset_hours: -11,
dst: 0,
},
TzName {
name: "CAT",
offset_hours: -10,
dst: 0,
},
TzName {
name: "HST",
offset_hours: -10,
dst: 0,
},
TzName {
name: "HDT",
offset_hours: -10,
dst: 1,
},
TzName {
name: "YST",
offset_hours: -9,
dst: 0,
},
TzName {
name: "YDT",
offset_hours: -9,
dst: 1,
},
TzName {
name: "PST",
offset_hours: -8,
dst: 0,
},
TzName {
name: "PDT",
offset_hours: -8,
dst: 1,
},
TzName {
name: "MST",
offset_hours: -7,
dst: 0,
},
TzName {
name: "MDT",
offset_hours: -7,
dst: 1,
},
TzName {
name: "CST",
offset_hours: -6,
dst: 0,
},
TzName {
name: "CDT",
offset_hours: -6,
dst: 1,
},
TzName {
name: "EST",
offset_hours: -5,
dst: 0,
},
TzName {
name: "EDT",
offset_hours: -5,
dst: 1,
},
TzName {
name: "AST",
offset_hours: -3,
dst: 0,
},
TzName {
name: "ADT",
offset_hours: -3,
dst: 1,
},
TzName {
name: "WAT",
offset_hours: -1,
dst: 0,
},
TzName {
name: "GMT",
offset_hours: 0,
dst: 0,
},
TzName {
name: "UTC",
offset_hours: 0,
dst: 0,
},
TzName {
name: "Z",
offset_hours: 0,
dst: 0,
},
TzName {
name: "WET",
offset_hours: 0,
dst: 0,
},
TzName {
name: "BST",
offset_hours: 0,
dst: 1,
},
TzName {
name: "CET",
offset_hours: 1,
dst: 0,
},
TzName {
name: "MET",
offset_hours: 1,
dst: 0,
},
TzName {
name: "MEWT",
offset_hours: 1,
dst: 0,
},
TzName {
name: "MEST",
offset_hours: 1,
dst: 1,
},
TzName {
name: "CEST",
offset_hours: 1,
dst: 1,
},
TzName {
name: "MESZ",
offset_hours: 1,
dst: 1,
},
TzName {
name: "FWT",
offset_hours: 1,
dst: 0,
},
TzName {
name: "FST",
offset_hours: 1,
dst: 1,
},
TzName {
name: "EET",
offset_hours: 2,
dst: 0,
},
TzName {
name: "EEST",
offset_hours: 2,
dst: 1,
},
TzName {
name: "WAST",
offset_hours: 7,
dst: 0,
},
TzName {
name: "WADT",
offset_hours: 7,
dst: 1,
},
TzName {
name: "CCT",
offset_hours: 8,
dst: 0,
},
TzName {
name: "JST",
offset_hours: 9,
dst: 0,
},
TzName {
name: "EAST",
offset_hours: 10,
dst: 0,
},
TzName {
name: "EADT",
offset_hours: 10,
dst: 1,
},
TzName {
name: "GST",
offset_hours: 10,
dst: 0,
},
TzName {
name: "NZT",
offset_hours: 12,
dst: 0,
},
TzName {
name: "NZST",
offset_hours: 12,
dst: 0,
},
TzName {
name: "NZDT",
offset_hours: 12,
dst: 1,
},
TzName {
name: "IDLE",
offset_hours: 12,
dst: 0,
},
];
pub(crate) const MONTH_NAMES: [&str; 12] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
pub(crate) const WEEKDAY_NAMES: [&str; 7] = [
"Sundays",
"Mondays",
"Tuesdays",
"Wednesdays",
"Thursdays",
"Fridays",
"Saturdays",
];
pub fn date_string(date: u64, offset: i32) -> String {
let mut sign = '+';
let mut o = offset;
if o < 0 {
o = -o;
sign = '-';
}
format!("{} {}{:02}{:02}", date, sign, o / 60, o % 60)
}
pub fn parse_date(date: &str) -> Result<String, ()> {
let (ts, off) = parse_date_basic(date)?;
Ok(date_string(ts, off))
}
pub fn parse_date_basic(date: &str) -> Result<(u64, i32), ()> {
let bytes = date.as_bytes();
let mut tm = init_tm_unknown();
let mut offset: i32 = -1;
let mut tm_gmt = 0i32;
let mut i = 0usize;
if bytes.first() == Some(&b'@') {
if let Some((ts, off)) = match_object_header_date(&bytes[1..]) {
return Ok((ts, off));
}
}
while i < bytes.len() {
let c = bytes[i];
if c == 0 || c == b'\n' {
break;
}
let mut m = 0usize;
if c.is_ascii_alphabetic() {
m = match_alpha(&bytes[i..], &mut tm, &mut offset);
} else if c.is_ascii_digit() {
m = match_digit(&bytes[i..], &mut tm, &mut offset, &mut tm_gmt);
} else if (c == b'-' || c == b'+') && bytes.get(i + 1).is_some_and(|x| x.is_ascii_digit()) {
m = match_tz(&bytes[i..], &mut offset);
}
if m == 0 {
m = 1;
}
i += m;
}
let tts = tm_to_time_t(&tm);
if tts < 0 {
return Err(());
}
let mut ts = tts as u64;
if offset == -1 {
tm.tm_isdst = -1;
let temp_time = unsafe { libc::mktime(&mut tm) };
let tt = ts as i128;
let tloc = temp_time as i128;
offset = if tt > tloc {
((tt - tloc) / 60) as i32
} else {
-(((tloc - tt) / 60) as i32)
};
}
if tm_gmt == 0 {
if offset > 0 && (offset as i64) * 60 > ts as i64 {
return Err(());
}
if offset < 0 && (-(offset as i128)) * 60 > (TIMESTAMP_MAX as i128 - ts as i128) {
return Err(());
}
let ts128 = ts as i128;
let adj = (offset as i128) * 60;
let new_ts = ts128 - adj;
if new_ts < 0 {
return Err(());
}
ts = new_ts as u64;
}
Ok((ts, offset))
}
fn match_object_header_date(date: &[u8]) -> Option<(u64, i32)> {
if date.is_empty() || !date[0].is_ascii_digit() {
return None;
}
let (stamp, mut rest) = parse_timestamp_prefix(date);
if rest >= date.len() || date[rest] != b' ' {
return None;
}
if stamp == u64::MAX {
return None;
}
rest += 1;
if rest >= date.len() || (date[rest] != b'+' && date[rest] != b'-') {
return None;
}
let sign = date[rest];
rest += 1;
if rest + 4 > date.len() {
return None;
}
let tz_digits = std::str::from_utf8(&date[rest..rest + 4]).ok()?;
let ofs_raw: i32 = tz_digits.parse().ok()?;
let mut ofs = (ofs_raw / 100) * 60 + (ofs_raw % 100);
if sign == b'-' {
ofs = -ofs;
}
let end = rest + 4;
if end < date.len() && date[end] != b'\n' && date[end] != 0 {
return None;
}
Some((stamp, ofs))
}
fn match_tz(date: &[u8], offp: &mut i32) -> usize {
if date.is_empty() || (date[0] != b'+' && date[0] != b'-') {
return 0;
}
let (hour_ul, n) = parse_timestamp_prefix(&date[1..]);
let mut end = 1 + n;
let mut min: i32 = 0;
let mut hour: i32 = hour_ul as i32;
if n == 4 {
min = hour % 100;
hour /= 100;
} else if n != 2 {
min = 99;
} else if end < date.len() && date[end] == b':' {
let (m2, n2) = parse_timestamp_prefix(&date[end + 1..]);
if n2 == 0 {
min = 99;
} else {
min = m2 as i32;
end += 1 + n2;
if end - 1 != 5 {
min = 99;
}
}
}
if min < 60 && hour < 24 {
let mut off = hour * 60 + min;
if date[0] == b'-' {
off = -off;
}
*offp = off;
}
end
}
fn parse_long_prefix(s: &[u8]) -> (i64, usize) {
if s.is_empty() {
return (0, 0);
}
let mut i = 0usize;
let neg = s[0] == b'-';
if s[0] == b'+' || s[0] == b'-' {
i = 1;
}
let start = i;
while i < s.len() && s[i].is_ascii_digit() {
i += 1;
}
if i == start {
return (0, 0);
}
let Ok(slice) = std::str::from_utf8(&s[start..i]) else {
return (0, 0);
};
let Ok(v) = slice.parse::<i64>() else {
return (0, 0);
};
let v = if neg { -v } else { v };
(v, i)
}
fn parse_uint_suffix(s: &[u8]) -> (u64, usize) {
parse_timestamp_prefix(s)
}
fn set_date(year: i32, month: i32, day: i32, now_tm: Option<&tm>, now: i64, tm: &mut tm) -> i32 {
if !(month > 0 && month < 13 && day > 0 && day < 32) {
return -1;
}
if now_tm.is_none() {
tm.tm_mon = month - 1;
tm.tm_mday = day;
if year == -1 {
return 1;
}
if (1970..2100).contains(&year) {
tm.tm_year = year - 1900;
} else if (70..100).contains(&year) {
tm.tm_year = year;
} else if year < 38 {
tm.tm_year = year + 100;
} else {
return -1;
}
return 0;
}
let nt = now_tm.unwrap();
let mut check = *tm;
check.tm_mon = month - 1;
check.tm_mday = day;
if year == -1 {
check.tm_year = nt.tm_year;
} else if (1970..2100).contains(&year) {
check.tm_year = year - 1900;
} else if (70..100).contains(&year) {
check.tm_year = year;
} else if year < 38 {
check.tm_year = year + 100;
} else {
return -1;
}
let specified = tm_to_time_t(&check);
if specified >= 0 && now + 10 * 24 * 3600 < specified {
return -1;
}
tm.tm_mon = check.tm_mon;
tm.tm_mday = check.tm_mday;
if year != -1 {
tm.tm_year = check.tm_year;
}
0
}
fn set_time(hour: i64, minute: i64, second: i64, tm: &mut tm) -> i32 {
if (0..=24).contains(&hour) && (0..60).contains(&minute) && (0..=60).contains(&second) {
tm.tm_hour = hour as i32;
tm.tm_min = minute as i32;
tm.tm_sec = second as i32;
0
} else {
-1
}
}
pub(crate) fn match_multi_number(
num: u64,
date: &[u8],
sep_i: usize,
tm: &mut tm,
now_in: i64,
) -> usize {
let Some(&c) = date.get(sep_i) else {
return 0;
};
if !matches!(c, b':' | b'-' | b'/' | b'.') {
return 0;
}
let (num2, n2) = parse_long_prefix(&date[sep_i + 1..]);
if n2 == 0 {
return 0;
}
let mut pos = sep_i + 1 + n2;
let mut num3: i64 = -1;
if pos < date.len() && date[pos] == c && pos + 1 < date.len() && date[pos + 1].is_ascii_digit()
{
let (n3, rel) = parse_long_prefix(&date[pos + 1..]);
num3 = n3;
pos += 1 + rel;
}
match c {
b':' => {
let mut n3 = num3;
if n3 < 0 {
n3 = 0;
}
if set_time(num as i64, num2, n3, tm) == 0 {
if pos < date.len()
&& date[pos] == b'.'
&& pos + 1 < date.len()
&& date[pos + 1].is_ascii_digit()
&& is_date_known(tm)
{
let (_, rel) = parse_long_prefix(&date[pos + 1..]);
pos += 1 + rel;
}
} else {
return 0;
}
}
b'-' | b'/' | b'.' => {
let now = if now_in == 0 { get_time_sec() } else { now_in };
let mut now_tm_uninit = MaybeUninit::<tm>::uninit();
let refuse_future: Option<&tm> = unsafe {
let tt = now as time_t;
let p = libc::gmtime_r(&tt, now_tm_uninit.as_mut_ptr());
if p.is_null() {
None
} else {
Some(&*now_tm_uninit.as_ptr())
}
};
let y = num as i32;
let m = num2 as i32;
let d = if num3 < 0 { 0 } else { num3 as i32 };
if num > 70 {
if set_date(y, m, d, None, now, tm) == 0 {
return pos;
}
if set_date(y, d, m, None, now, tm) == 0 {
return pos;
}
}
if c != b'.' && set_date(d, y, m, refuse_future, now, tm) == 0 {
return pos;
}
if set_date(d, m, y, refuse_future, now, tm) == 0 {
return pos;
}
if c == b'.' && set_date(d, y, m, refuse_future, now, tm) == 0 {
return pos;
}
return 0;
}
_ => return 0,
}
pos
}
fn match_alpha(date: &[u8], tm: &mut tm, offset: &mut i32) -> usize {
for (i, name) in MONTH_NAMES.iter().enumerate() {
let m = match_string(date, name);
if m >= 3 {
tm.tm_mon = i as i32;
return m;
}
}
for (i, name) in WEEKDAY_NAMES.iter().enumerate() {
let m = match_string(date, name);
if m >= 3 {
tm.tm_wday = i as i32;
return m;
}
}
for tz in TIMEZONE_NAMES {
let m = match_string(date, tz.name);
if m >= 3 || m == tz.name.len() {
let off = tz.offset_hours + tz.dst;
if *offset == -1 {
*offset = 60 * off;
}
return m;
}
}
if match_string(date, "PM") == 2 {
tm.tm_hour = (tm.tm_hour % 12) + 12;
return 2;
}
if match_string(date, "AM") == 2 {
tm.tm_hour %= 12;
return 2;
}
if date.first() == Some(&b'T')
&& date.get(1).is_some_and(|b| b.is_ascii_digit())
&& tm.tm_hour == -1
{
tm.tm_min = 0;
tm.tm_sec = 0;
return 1;
}
skip_alpha(date)
}
fn match_digit(date: &[u8], tm: &mut tm, offset: &mut i32, tm_gmt: &mut i32) -> usize {
let (num, n) = parse_timestamp_prefix(date);
if n == 0 {
return 0;
}
let end = n;
if num >= 100_000_000 && nodate(tm) {
let tt = num as time_t;
let p = unsafe { libc::gmtime_r(&tt, tm) };
if !p.is_null() {
*tm_gmt = 1;
return end;
}
}
if let Some(&sep) = date.get(end) {
if matches!(sep, b':' | b'.' | b'/' | b'-')
&& date.get(end + 1).is_some_and(|b| b.is_ascii_digit())
{
let m = match_multi_number(num, date, end, tm, 0);
if m != 0 {
return m;
}
}
}
let mut n_digits = 0usize;
loop {
n_digits += 1;
if n_digits >= date.len() || !date[n_digits].is_ascii_digit() {
break;
}
}
if n_digits == 8 || n_digits == 6 {
let num1 = (num / 10000) as i32;
let num2 = ((num % 10000) / 100) as i32;
let num3 = (num % 100) as i32;
if n_digits == 8 {
let _ = set_date(num1, num2, num3, None, get_time_sec(), tm);
} else if set_time(num1 as i64, num2 as i64, num3 as i64, tm) == 0
&& date.get(end) == Some(&b'.')
&& date.get(end + 1).is_some_and(|b| b.is_ascii_digit())
{
let (_, rel) = parse_uint_suffix(&date[end + 1..]);
return end + 1 + rel;
}
return end;
}
if maybeiso8601(tm) {
let mut num1 = num as u32;
let mut num2: u32 = 0;
if n_digits == 4 {
num1 = (num / 100) as u32;
num2 = (num % 100) as u32;
}
if (n_digits == 4 || n_digits == 2)
&& !nodate(tm)
&& set_time(num1 as i64, num2 as i64, 0, tm) == 0
{
return n_digits;
}
tm.tm_min = -1;
tm.tm_sec = -1;
}
if n_digits == 4 {
if num <= 1400 && *offset == -1 {
let minutes = (num % 100) as u32;
let hours = (num / 100) as u32;
*offset = (hours * 60 + minutes) as i32;
} else if num > 1900 && num < 2100 {
tm.tm_year = (num as i32) - 1900;
}
return n_digits;
}
if n_digits > 2 {
return n_digits;
}
if num > 0 && num < 32 && tm.tm_mday < 0 {
tm.tm_mday = num as i32;
return n_digits;
}
if n_digits == 2 && tm.tm_year < 0 {
if num < 10 && tm.tm_mday >= 0 {
tm.tm_year = (num as i32) + 100;
return n_digits;
}
if num >= 70 {
tm.tm_year = num as i32;
return n_digits;
}
}
if num > 0 && num < 13 && tm.tm_mon < 0 {
tm.tm_mon = (num as i32) - 1;
}
n_digits
}