1use crate::MailParseError;
2
3enum DateParseState {
4 Date,
5 Month,
6 Year,
7 Hour,
8 Minute,
9 Second,
10 Timezone,
11}
12
13fn days_in_month(month: i64, year: i64) -> i64 {
14 match month {
15 0 | 2 | 4 | 6 | 7 | 9 | 11 => 31,
16 3 | 5 | 8 | 10 => 30,
17 1 => {
18 if (year % 400) == 0 {
19 29
20 } else if (year % 100) == 0 {
21 28
22 } else if (year % 4) == 0 {
23 29
24 } else {
25 28
26 }
27 }
28 _ => 0,
29 }
30}
31
32fn seconds_to_date(year: i64, month: i64, day: i64) -> i64 {
33 let mut result: i64 = 0;
34 for y in 1970..2001 {
35 if y == year {
36 break;
37 }
38 result += 86400 * 365;
39 if (y % 4) == 0 {
40 result += 86400;
41 }
42 }
43 let mut y = 2001;
44 while y < year {
45 if year - y >= 400 {
46 result += (86400 * 365 * 400) + (86400 * 97);
47 y += 400;
48 continue;
49 }
50 if year - y >= 100 {
51 result += (86400 * 365 * 100) + (86400 * 24);
52 y += 100;
53 continue;
54 }
55 if year - y >= 4 {
56 result += (86400 * 365 * 4) + (86400);
57 y += 4;
58 continue;
59 }
60 result += 86400 * 365;
61 y += 1;
62 }
63 for m in 0..month {
64 result += 86400 * days_in_month(m, year)
65 }
66 result + 86400 * (day - 1)
67}
68
69pub fn dateparse(date: &str) -> Result<i64, MailParseError> {
79 let mut result = 0;
80 let mut month = 0;
81 let mut day_of_month = 0;
82 let mut state = DateParseState::Date;
83 for tok in date.split(|c| c == ' ' || c == ':') {
84 if tok.is_empty() {
85 continue;
86 }
87 match state {
88 DateParseState::Date => {
89 if let Ok(v) = tok.parse::<u8>() {
90 if !(1..=31).contains(&v) {
91 return Err(MailParseError::Generic("Invalid day"));
92 }
93 day_of_month = v;
94 state = DateParseState::Month;
95 };
96 continue;
97 }
98 DateParseState::Month => {
99 month = match tok.to_uppercase().as_str() {
100 "JAN" | "JANUARY" => 0,
101 "FEB" | "FEBRUARY" => 1,
102 "MAR" | "MARCH" => 2,
103 "APR" | "APRIL" => 3,
104 "MAY" => 4,
105 "JUN" | "JUNE" => 5,
106 "JUL" | "JULY" => 6,
107 "AUG" | "AUGUST" => 7,
108 "SEP" | "SEPTEMBER" => 8,
109 "OCT" | "OCTOBER" => 9,
110 "NOV" | "NOVEMBER" => 10,
111 "DEC" | "DECEMBER" => 11,
112 _ => return Err(MailParseError::Generic("Unrecognized month")),
113 };
114 state = DateParseState::Year;
115 continue;
116 }
117 DateParseState::Year => {
118 let year = match tok.parse::<u32>() {
119 Ok(v) if v < 70 => 2000 + v,
120 Ok(v) if v < 100 => 1900 + v,
121 Ok(v) if v < 1970 => return Err(MailParseError::Generic("Disallowed year")),
122 Ok(v) => v,
123 Err(_) => return Err(MailParseError::Generic("Invalid year")),
124 };
125 result =
126 seconds_to_date(i64::from(year), i64::from(month), i64::from(day_of_month));
127 state = DateParseState::Hour;
128 continue;
129 }
130 DateParseState::Hour => {
131 let hour = match tok.parse::<u8>() {
132 Ok(v) => v,
133 Err(_) => return Err(MailParseError::Generic("Invalid hour")),
134 };
135 result += 3600 * i64::from(hour);
136 state = DateParseState::Minute;
137 continue;
138 }
139 DateParseState::Minute => {
140 let minute = match tok.parse::<u8>() {
141 Ok(v) => v,
142 Err(_) => return Err(MailParseError::Generic("Invalid minute")),
143 };
144 result += 60 * i64::from(minute);
145 state = DateParseState::Second;
146 continue;
147 }
148 DateParseState::Second => {
149 let second = match tok.parse::<u8>() {
150 Ok(v) => v,
151 Err(_) => return Err(MailParseError::Generic("Invalid second")),
152 };
153 result += i64::from(second);
154 state = DateParseState::Timezone;
155 continue;
156 }
157 DateParseState::Timezone => {
158 let (tz, tz_sign) = match tok.parse::<i32>() {
159 Ok(v) if !(-2400..=2400).contains(&v) => {
160 return Err(MailParseError::Generic("Invalid timezone"))
161 }
162 Ok(v) if v < 0 => (-v, -1),
163 Ok(v) => (v, 1),
164 Err(_) => {
165 match tok.to_uppercase().as_str() {
166 "UTC" | "UT" | "GMT" | "Z" => (0, 1),
168 "EDT" => (400, -1),
169 "EST" | "CDT" => (500, -1),
170 "CST" | "MDT" => (600, -1),
171 "MST" | "PDT" => (700, -1),
172 "PST" => (800, -1),
173 "A" => (100, -1),
174 "M" => (1200, -1),
175 "N" => (100, 1),
176 "Y" => (1200, 1),
177 _ => return Err(MailParseError::Generic("Invalid timezone")),
178 }
179 }
180 };
181 let tz_hours = tz / 100;
182 let tz_mins = tz % 100;
183 let tz_delta = (tz_hours * 3600) + (tz_mins * 60);
184 if tz_sign < 0 {
185 result += i64::from(tz_delta);
186 } else {
187 result -= i64::from(tz_delta);
188 }
189 break;
190 }
191 }
192 }
193 Ok(result)
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn parse_dates() {
202 assert_eq!(
203 dateparse("Sun, 25 Sep 2016 18:36:33 -0400").unwrap(),
204 1474842993
205 );
206 assert_eq!(
207 dateparse("Fri, 01 Jan 2100 11:12:13 +0000").unwrap(),
208 4102485133
209 );
210 assert_eq!(
211 dateparse("Fri, 31 Dec 2100 00:00:00 +0000").unwrap(),
212 4133894400
213 );
214 assert_eq!(
215 dateparse("Fri, 31 Dec 2399 00:00:00 +0000").unwrap(),
216 13569379200
217 );
218 assert_eq!(
219 dateparse("Fri, 31 Dec 2400 00:00:00 +0000").unwrap(),
220 13601001600
221 );
222 assert_eq!(dateparse("17 Sep 2016 16:05:38 -1000").unwrap(), 1474164338);
223 assert_eq!(
224 dateparse("Fri, 30 Nov 2012 20:57:23 GMT").unwrap(),
225 1354309043
226 );
227
228 assert!(dateparse("Wed, 0 Jan 1970 00:00:00 +0000").is_err());
230
231 assert!(dateparse("Thu, 1 Jan 1970 00:00:00 +2147483647").is_err());
233 }
234}