]>
Commit | Line | Data |
---|---|---|
0531ce1d XL |
1 | use std::fmt; |
2 | use std::str; | |
3 | use std::time::{SystemTime, Duration, UNIX_EPOCH}; | |
4 | ||
5 | #[cfg(target_os="cloudabi")] | |
6 | mod max { | |
7 | pub const SECONDS: u64 = ::std::u64::MAX / 1_000_000_000; | |
8 | #[allow(unused)] | |
9 | pub const TIMESTAMP: &'static str = "2554-07-21T23:34:33Z"; | |
10 | } | |
11 | #[cfg(all( | |
12 | target_pointer_width="32", | |
13 | not(target_os="cloudabi"), | |
14 | not(target_os="windows"), | |
15 | not(all(target_arch="wasm32", not(target_os="emscripten"))) | |
16 | ))] | |
17 | mod max { | |
18 | pub const SECONDS: u64 = ::std::i32::MAX as u64; | |
19 | #[allow(unused)] | |
20 | pub const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z"; | |
21 | } | |
22 | ||
23 | #[cfg(any( | |
24 | target_pointer_width="64", | |
25 | target_os="windows", | |
26 | all(target_arch="wasm32", not(target_os="emscripten")), | |
27 | ))] | |
28 | mod max { | |
29 | pub const SECONDS: u64 = 253402300800-1; // last second of year 9999 | |
30 | #[allow(unused)] | |
31 | pub const TIMESTAMP: &'static str = "9999-12-31T23:59:59Z"; | |
32 | } | |
33 | ||
34 | quick_error! { | |
35 | /// Error parsing datetime (timestamp) | |
36 | #[derive(Debug, PartialEq, Clone, Copy)] | |
37 | pub enum Error { | |
38 | /// Numeric component is out of range | |
39 | OutOfRange { | |
40 | display("numeric component is out of range") | |
41 | } | |
42 | /// Bad character where digit is expected | |
43 | InvalidDigit { | |
44 | display("bad character where digit is expected") | |
45 | } | |
46 | /// Other formatting errors | |
47 | InvalidFormat { | |
48 | display("timestamp format is invalid") | |
49 | } | |
50 | } | |
51 | } | |
52 | ||
0731742a | 53 | #[derive(Debug, Clone, PartialEq, Eq)] |
0531ce1d XL |
54 | enum Precision { |
55 | Smart, | |
56 | Seconds, | |
e74abb32 XL |
57 | Millis, |
58 | Micros, | |
0531ce1d XL |
59 | Nanos, |
60 | } | |
61 | ||
62 | /// A wrapper type that allows you to Display a SystemTime | |
0731742a | 63 | #[derive(Debug, Clone)] |
0531ce1d XL |
64 | pub struct Rfc3339Timestamp(SystemTime, Precision); |
65 | ||
66 | #[inline] | |
67 | fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> { | |
68 | if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' { | |
69 | return Err(Error::InvalidDigit); | |
70 | } | |
71 | Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64) | |
72 | } | |
73 | ||
74 | /// Parse RFC3339 timestamp `2018-02-14T00:28:07Z` | |
75 | /// | |
76 | /// Supported feature: any precision of fractional | |
77 | /// digits `2018-02-14T00:28:07.133Z`. | |
78 | /// | |
79 | /// Unsupported feature: localized timestamps. Only UTC is supported. | |
80 | pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> { | |
81 | if s.len() < "2018-02-14T00:28:07Z".len() { | |
82 | return Err(Error::InvalidFormat); | |
83 | } | |
84 | let b = s.as_bytes(); | |
85 | if b[10] != b'T' || b[b.len()-1] != b'Z' { | |
86 | return Err(Error::InvalidFormat); | |
87 | } | |
88 | return parse_rfc3339_weak(s); | |
89 | } | |
90 | ||
91 | /// Parse RFC3339-like timestamp `2018-02-14 00:28:07` | |
92 | /// | |
93 | /// Supported features: | |
94 | /// | |
95 | /// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`. | |
96 | /// 2. Supports timestamp with or without either of `T` or `Z` | |
97 | /// 3. Anything valid for `parse_3339` is valid for this function | |
98 | /// | |
99 | /// Unsupported feature: localized timestamps. Only UTC is supported, even if | |
100 | /// `Z` is not specified. | |
101 | /// | |
102 | /// This function is intended to use for parsing human input. Whereas | |
103 | /// `parse_rfc3339` is for strings generated programmatically. | |
104 | pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> { | |
105 | if s.len() < "2018-02-14T00:28:07".len() { | |
106 | return Err(Error::InvalidFormat); | |
107 | } | |
108 | let b = s.as_bytes(); // for careless slicing | |
109 | if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') || | |
110 | b[13] != b':' || b[16] != b':' | |
111 | { | |
112 | return Err(Error::InvalidFormat); | |
113 | } | |
114 | let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?; | |
115 | let month = two_digits(b[5], b[6])?; | |
116 | let day = two_digits(b[8], b[9])?; | |
117 | let hour = two_digits(b[11], b[12])?; | |
118 | let minute = two_digits(b[14], b[15])?; | |
119 | let mut second = two_digits(b[17], b[18])?; | |
120 | ||
121 | if year < 1970 || hour > 23 || minute > 59 || second > 60 { | |
122 | return Err(Error::OutOfRange); | |
123 | } | |
124 | // TODO(tailhook) should we check that leaps second is only on midnight ? | |
125 | if second == 60 { | |
126 | second = 59 | |
127 | }; | |
128 | let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + | |
129 | ((year - 1) - 1600) / 400; | |
130 | let leap = is_leap_year(year); | |
131 | let (mut ydays, mdays) = match month { | |
132 | 1 => (0, 31), | |
133 | 2 if leap => (31, 29), | |
134 | 2 => (31, 28), | |
135 | 3 => (59, 31), | |
136 | 4 => (90, 30), | |
137 | 5 => (120, 31), | |
138 | 6 => (151, 30), | |
139 | 7 => (181, 31), | |
140 | 8 => (212, 31), | |
141 | 9 => (243, 30), | |
142 | 10 => (273, 31), | |
143 | 11 => (304, 30), | |
144 | 12 => (334, 31), | |
145 | _ => return Err(Error::OutOfRange), | |
146 | }; | |
147 | if day > mdays || day == 0 { | |
148 | return Err(Error::OutOfRange); | |
149 | } | |
150 | ydays += day - 1; | |
151 | if leap && month > 2 { | |
152 | ydays += 1; | |
153 | } | |
154 | let days = (year - 1970) * 365 + leap_years + ydays; | |
155 | ||
156 | let time = second + minute * 60 + hour * 3600; | |
157 | ||
158 | let mut nanos = 0; | |
159 | let mut mult = 100_000_000; | |
160 | if b.get(19) == Some(&b'.') { | |
161 | for idx in 20..b.len() { | |
162 | if b[idx] == b'Z' { | |
163 | if idx == b.len()-1 { | |
164 | break; | |
165 | } else { | |
166 | return Err(Error::InvalidDigit); | |
167 | } | |
168 | } | |
169 | if b[idx] < b'0' || b[idx] > b'9' { | |
170 | return Err(Error::InvalidDigit); | |
171 | } | |
172 | nanos += mult * (b[idx] - b'0') as u32; | |
173 | mult /= 10; | |
174 | } | |
175 | } else { | |
176 | if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') { | |
177 | return Err(Error::InvalidFormat); | |
178 | } | |
179 | } | |
180 | ||
181 | let total_seconds = time + days * 86400; | |
182 | if total_seconds > max::SECONDS { | |
183 | return Err(Error::OutOfRange); | |
184 | } | |
185 | ||
186 | return Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos)); | |
187 | } | |
188 | ||
189 | fn is_leap_year(y: u64) -> bool { | |
190 | y % 4 == 0 && (!(y % 100 == 0) || y % 400 == 0) | |
191 | } | |
192 | ||
193 | /// Format an RFC3339 timestamp `2018-02-14T00:28:07Z` | |
194 | /// | |
195 | /// This function formats timestamp with smart precision: i.e. if it has no | |
196 | /// fractional seconds, they aren't written at all. And up to nine digits if | |
197 | /// they are. | |
198 | /// | |
199 | /// The value is always UTC and ignores system timezone. | |
200 | pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp { | |
201 | return Rfc3339Timestamp(system_time, Precision::Smart); | |
202 | } | |
203 | ||
204 | /// Format an RFC3339 timestamp `2018-02-14T00:28:07Z` | |
205 | /// | |
206 | /// This format always shows timestamp without fractional seconds. | |
207 | /// | |
208 | /// The value is always UTC and ignores system timezone. | |
209 | pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp { | |
210 | return Rfc3339Timestamp(system_time, Precision::Seconds); | |
211 | } | |
212 | ||
e74abb32 XL |
213 | /// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z` |
214 | /// | |
215 | /// This format always shows milliseconds even if millisecond value is zero. | |
216 | /// | |
217 | /// The value is always UTC and ignores system timezone. | |
218 | pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp { | |
219 | return Rfc3339Timestamp(system_time, Precision::Millis); | |
220 | } | |
221 | ||
222 | /// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z` | |
223 | /// | |
224 | /// This format always shows microseconds even if microsecond value is zero. | |
225 | /// | |
226 | /// The value is always UTC and ignores system timezone. | |
227 | pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp { | |
228 | return Rfc3339Timestamp(system_time, Precision::Micros); | |
229 | } | |
230 | ||
0531ce1d XL |
231 | /// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z` |
232 | /// | |
233 | /// This format always shows nanoseconds even if nanosecond value is zero. | |
234 | /// | |
235 | /// The value is always UTC and ignores system timezone. | |
236 | pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp { | |
237 | return Rfc3339Timestamp(system_time, Precision::Nanos); | |
238 | } | |
239 | ||
240 | impl fmt::Display for Rfc3339Timestamp { | |
241 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
242 | use self::Precision::*; | |
243 | ||
244 | let dur = self.0.duration_since(UNIX_EPOCH) | |
245 | .expect("all times should be after the epoch"); | |
246 | let secs_since_epoch = dur.as_secs(); | |
247 | let nanos = dur.subsec_nanos(); | |
248 | ||
249 | if secs_since_epoch >= 253402300800 { // year 9999 | |
250 | return Err(fmt::Error); | |
251 | } | |
252 | ||
253 | /* 2000-03-01 (mod 400 year, immediately after feb29 */ | |
254 | const LEAPOCH: i64 = 11017; | |
255 | const DAYS_PER_400Y: i64 = 365*400 + 97; | |
256 | const DAYS_PER_100Y: i64 = 365*100 + 24; | |
257 | const DAYS_PER_4Y: i64 = 365*4 + 1; | |
258 | ||
259 | let days = (secs_since_epoch / 86400) as i64 - LEAPOCH; | |
260 | let secs_of_day = secs_since_epoch % 86400; | |
261 | ||
262 | let mut qc_cycles = days / DAYS_PER_400Y; | |
263 | let mut remdays = days % DAYS_PER_400Y; | |
264 | ||
265 | if remdays < 0 { | |
266 | remdays += DAYS_PER_400Y; | |
267 | qc_cycles -= 1; | |
268 | } | |
269 | ||
270 | let mut c_cycles = remdays / DAYS_PER_100Y; | |
271 | if c_cycles == 4 { c_cycles -= 1; } | |
272 | remdays -= c_cycles * DAYS_PER_100Y; | |
273 | ||
274 | let mut q_cycles = remdays / DAYS_PER_4Y; | |
275 | if q_cycles == 25 { q_cycles -= 1; } | |
276 | remdays -= q_cycles * DAYS_PER_4Y; | |
277 | ||
278 | let mut remyears = remdays / 365; | |
279 | if remyears == 4 { remyears -= 1; } | |
280 | remdays -= remyears * 365; | |
281 | ||
282 | let mut year = 2000 + | |
283 | remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles; | |
284 | ||
285 | let months = [31,30,31,30,31,31,30,31,30,31,31,29]; | |
286 | let mut mon = 0; | |
287 | for mon_len in months.iter() { | |
288 | mon += 1; | |
289 | if remdays < *mon_len { | |
290 | break; | |
291 | } | |
292 | remdays -= *mon_len; | |
293 | } | |
294 | let mday = remdays+1; | |
295 | let mon = if mon + 2 > 12 { | |
296 | year += 1; | |
297 | mon - 10 | |
298 | } else { | |
299 | mon + 2 | |
300 | }; | |
301 | ||
302 | let mut buf: [u8; 30] = [ | |
303 | // Too long to write as: b"0000-00-00T00:00:00.000000000Z" | |
304 | b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T', | |
305 | b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', | |
306 | b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z', | |
307 | ]; | |
308 | buf[0] = b'0' + (year / 1000) as u8; | |
309 | buf[1] = b'0' + (year / 100 % 10) as u8; | |
310 | buf[2] = b'0' + (year / 10 % 10) as u8; | |
311 | buf[3] = b'0' + (year % 10) as u8; | |
312 | buf[5] = b'0' + (mon / 10) as u8; | |
313 | buf[6] = b'0' + (mon % 10) as u8; | |
314 | buf[8] = b'0' + (mday / 10) as u8; | |
315 | buf[9] = b'0' + (mday % 10) as u8; | |
316 | buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8; | |
317 | buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8; | |
318 | buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8; | |
319 | buf[15] = b'0' + (secs_of_day / 60 % 10) as u8; | |
320 | buf[17] = b'0' + (secs_of_day / 10 % 6) as u8; | |
321 | buf[18] = b'0' + (secs_of_day % 10) as u8; | |
322 | ||
e74abb32 | 323 | let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart { |
0531ce1d | 324 | buf[19] = b'Z'; |
e74abb32 XL |
325 | 19 |
326 | } else if self.1 == Millis { | |
327 | buf[20] = b'0' + (nanos / 100_000_000) as u8; | |
328 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; | |
329 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; | |
330 | buf[23] = b'Z'; | |
331 | 23 | |
332 | } else if self.1 == Micros { | |
333 | buf[20] = b'0' + (nanos / 100_000_000) as u8; | |
334 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; | |
335 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; | |
336 | buf[23] = b'0' + (nanos / 100_000 % 10) as u8; | |
337 | buf[24] = b'0' + (nanos / 10_000 % 10) as u8; | |
338 | buf[25] = b'0' + (nanos / 1_000 % 10) as u8; | |
339 | buf[26] = b'Z'; | |
340 | 26 | |
0531ce1d XL |
341 | } else { |
342 | buf[20] = b'0' + (nanos / 100_000_000) as u8; | |
343 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; | |
344 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; | |
345 | buf[23] = b'0' + (nanos / 100_000 % 10) as u8; | |
346 | buf[24] = b'0' + (nanos / 10_000 % 10) as u8; | |
347 | buf[25] = b'0' + (nanos / 1_000 % 10) as u8; | |
348 | buf[26] = b'0' + (nanos / 100 % 10) as u8; | |
349 | buf[27] = b'0' + (nanos / 10 % 10) as u8; | |
350 | buf[28] = b'0' + (nanos / 1 % 10) as u8; | |
e74abb32 XL |
351 | // 29th is 'Z' |
352 | 29 | |
353 | }; | |
354 | ||
355 | // we know our chars are all ascii | |
356 | f.write_str(unsafe { str::from_utf8_unchecked(&buf[..offset+1]) }) | |
0531ce1d XL |
357 | } |
358 | } | |
359 | ||
360 | #[cfg(test)] | |
361 | mod test { | |
362 | extern crate time; | |
363 | extern crate rand; | |
364 | ||
365 | use std::str::from_utf8; | |
366 | use self::rand::Rng; | |
367 | use std::time::{UNIX_EPOCH, SystemTime, Duration}; | |
368 | use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339}; | |
e74abb32 XL |
369 | use super::{format_rfc3339_millis, format_rfc3339_micros}; |
370 | use super::{format_rfc3339_nanos}; | |
0531ce1d XL |
371 | use super::max; |
372 | ||
373 | fn from_sec(sec: u64) -> (String, SystemTime) { | |
374 | let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 }) | |
375 | .rfc3339().to_string(); | |
376 | let time = UNIX_EPOCH + Duration::new(sec, 0); | |
377 | return (s, time) | |
378 | } | |
379 | ||
380 | #[test] | |
381 | #[cfg(all(target_pointer_width="32", target_os="linux"))] | |
382 | fn year_after_2038_fails_gracefully() { | |
383 | // next second | |
384 | assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(), | |
385 | super::Error::OutOfRange); | |
386 | assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(), | |
387 | super::Error::OutOfRange); | |
388 | } | |
389 | ||
390 | #[test] | |
391 | fn smoke_tests_parse() { | |
392 | assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(), | |
393 | UNIX_EPOCH + Duration::new(0, 0)); | |
394 | assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(), | |
395 | UNIX_EPOCH + Duration::new(1, 0)); | |
396 | assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(), | |
397 | UNIX_EPOCH + Duration::new(1518563312, 0)); | |
398 | assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(), | |
399 | UNIX_EPOCH + Duration::new(1325376000, 0)); | |
400 | } | |
401 | ||
402 | #[test] | |
403 | fn smoke_tests_format() { | |
404 | assert_eq!( | |
405 | format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(), | |
406 | "1970-01-01T00:00:00Z"); | |
407 | assert_eq!( | |
408 | format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(), | |
409 | "1970-01-01T00:00:01Z"); | |
410 | assert_eq!( | |
411 | format_rfc3339(UNIX_EPOCH + Duration::new(1518563312, 0)).to_string(), | |
412 | "2018-02-13T23:08:32Z"); | |
413 | assert_eq!( | |
414 | format_rfc3339(UNIX_EPOCH + Duration::new(1325376000, 0)).to_string(), | |
415 | "2012-01-01T00:00:00Z"); | |
416 | } | |
417 | ||
e74abb32 XL |
418 | #[test] |
419 | fn smoke_tests_format_millis() { | |
420 | assert_eq!( | |
421 | format_rfc3339_millis(UNIX_EPOCH + | |
422 | Duration::new(0, 0)).to_string(), | |
423 | "1970-01-01T00:00:00.000Z"); | |
424 | assert_eq!( | |
425 | format_rfc3339_millis(UNIX_EPOCH + | |
426 | Duration::new(1518563312, 123_000_000)).to_string(), | |
427 | "2018-02-13T23:08:32.123Z"); | |
428 | } | |
429 | ||
430 | #[test] | |
431 | fn smoke_tests_format_micros() { | |
432 | assert_eq!( | |
433 | format_rfc3339_micros(UNIX_EPOCH + | |
434 | Duration::new(0, 0)).to_string(), | |
435 | "1970-01-01T00:00:00.000000Z"); | |
436 | assert_eq!( | |
437 | format_rfc3339_micros(UNIX_EPOCH + | |
438 | Duration::new(1518563312, 123_000_000)).to_string(), | |
439 | "2018-02-13T23:08:32.123000Z"); | |
440 | assert_eq!( | |
441 | format_rfc3339_micros(UNIX_EPOCH + | |
442 | Duration::new(1518563312, 456_123_000)).to_string(), | |
443 | "2018-02-13T23:08:32.456123Z"); | |
444 | } | |
445 | ||
446 | #[test] | |
447 | fn smoke_tests_format_nanos() { | |
448 | assert_eq!( | |
449 | format_rfc3339_nanos(UNIX_EPOCH + | |
450 | Duration::new(0, 0)).to_string(), | |
451 | "1970-01-01T00:00:00.000000000Z"); | |
452 | assert_eq!( | |
453 | format_rfc3339_nanos(UNIX_EPOCH + | |
454 | Duration::new(1518563312, 123_000_000)).to_string(), | |
455 | "2018-02-13T23:08:32.123000000Z"); | |
456 | assert_eq!( | |
457 | format_rfc3339_nanos(UNIX_EPOCH + | |
458 | Duration::new(1518563312, 789_456_123)).to_string(), | |
459 | "2018-02-13T23:08:32.789456123Z"); | |
460 | } | |
461 | ||
0531ce1d XL |
462 | #[test] |
463 | fn upper_bound() { | |
464 | let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0); | |
465 | assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max); | |
466 | assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP); | |
467 | } | |
468 | ||
469 | #[test] | |
470 | fn leap_second() { | |
471 | assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(), | |
472 | UNIX_EPOCH + Duration::new(1483228799, 0)); | |
473 | } | |
474 | ||
475 | #[test] | |
476 | fn first_731_days() { | |
477 | let year_start = 0; // 1970 | |
478 | for day in 0.. (365 * 2 + 1) { // scan leap year and non-leap year | |
479 | let (s, time) = from_sec(year_start + day * 86400); | |
480 | assert_eq!(parse_rfc3339(&s).unwrap(), time); | |
481 | assert_eq!(format_rfc3339(time).to_string(), s); | |
482 | } | |
483 | } | |
484 | ||
485 | #[test] | |
486 | fn the_731_consecutive_days() { | |
487 | let year_start = 1325376000; // 2012 | |
488 | for day in 0.. (365 * 2 + 1) { // scan leap year and non-leap year | |
489 | let (s, time) = from_sec(year_start + day * 86400); | |
490 | assert_eq!(parse_rfc3339(&s).unwrap(), time); | |
491 | assert_eq!(format_rfc3339(time).to_string(), s); | |
492 | } | |
493 | } | |
494 | ||
495 | #[test] | |
496 | fn all_86400_seconds() { | |
497 | let day_start = 1325376000; | |
498 | for second in 0..86400 { // scan leap year and non-leap year | |
499 | let (s, time) = from_sec(day_start + second); | |
500 | assert_eq!(parse_rfc3339(&s).unwrap(), time); | |
501 | assert_eq!(format_rfc3339(time).to_string(), s); | |
502 | } | |
503 | } | |
504 | ||
505 | #[test] | |
506 | fn random_past() { | |
507 | let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() | |
508 | .as_secs(); | |
509 | for _ in 0..10000 { | |
510 | let sec = rand::thread_rng().gen_range(0, upper); | |
511 | let (s, time) = from_sec(sec); | |
512 | assert_eq!(parse_rfc3339(&s).unwrap(), time); | |
513 | assert_eq!(format_rfc3339(time).to_string(), s); | |
514 | } | |
515 | } | |
516 | ||
517 | #[test] | |
518 | fn random_wide_range() { | |
519 | for _ in 0..100000 { | |
520 | let sec = rand::thread_rng().gen_range(0, max::SECONDS); | |
521 | let (s, time) = from_sec(sec); | |
522 | assert_eq!(parse_rfc3339(&s).unwrap(), time); | |
523 | assert_eq!(format_rfc3339(time).to_string(), s); | |
524 | } | |
525 | } | |
526 | ||
527 | #[test] | |
528 | fn milliseconds() { | |
529 | assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(), | |
530 | UNIX_EPOCH + Duration::new(0, 123000000)); | |
531 | assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123000000)) | |
532 | .to_string(), "1970-01-01T00:00:00.123000000Z"); | |
533 | } | |
534 | ||
535 | #[test] | |
536 | #[should_panic(expected="OutOfRange")] | |
537 | fn zero_month() { | |
538 | parse_rfc3339("1970-00-01T00:00:00Z").unwrap(); | |
539 | } | |
540 | ||
541 | #[test] | |
542 | #[should_panic(expected="OutOfRange")] | |
543 | fn big_month() { | |
544 | parse_rfc3339("1970-32-01T00:00:00Z").unwrap(); | |
545 | } | |
546 | ||
547 | #[test] | |
548 | #[should_panic(expected="OutOfRange")] | |
549 | fn zero_day() { | |
550 | parse_rfc3339("1970-01-00T00:00:00Z").unwrap(); | |
551 | } | |
552 | ||
553 | #[test] | |
554 | #[should_panic(expected="OutOfRange")] | |
555 | fn big_day() { | |
556 | parse_rfc3339("1970-12-35T00:00:00Z").unwrap(); | |
557 | } | |
558 | ||
559 | #[test] | |
560 | #[should_panic(expected="OutOfRange")] | |
561 | fn big_day2() { | |
562 | parse_rfc3339("1970-02-30T00:00:00Z").unwrap(); | |
563 | } | |
564 | ||
565 | #[test] | |
566 | #[should_panic(expected="OutOfRange")] | |
567 | fn big_second() { | |
568 | parse_rfc3339("1970-12-30T00:00:78Z").unwrap(); | |
569 | } | |
570 | ||
571 | #[test] | |
572 | #[should_panic(expected="OutOfRange")] | |
573 | fn big_minute() { | |
574 | parse_rfc3339("1970-12-30T00:78:00Z").unwrap(); | |
575 | } | |
576 | ||
577 | #[test] | |
578 | #[should_panic(expected="OutOfRange")] | |
579 | fn big_hour() { | |
580 | parse_rfc3339("1970-12-30T24:00:00Z").unwrap(); | |
581 | } | |
582 | ||
583 | #[test] | |
584 | fn break_data() { | |
585 | for pos in 0.."2016-12-31T23:59:60Z".len() { | |
586 | let mut s = b"2016-12-31T23:59:60Z".to_vec(); | |
587 | s[pos] = b'x'; | |
588 | parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err(); | |
589 | } | |
590 | } | |
591 | ||
592 | #[test] | |
593 | fn weak_smoke_tests() { | |
594 | assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(), | |
595 | UNIX_EPOCH + Duration::new(0, 0)); | |
596 | parse_rfc3339("1970-01-01 00:00:00").unwrap_err(); | |
597 | ||
598 | assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(), | |
599 | UNIX_EPOCH + Duration::new(0, 123000)); | |
600 | parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err(); | |
601 | ||
602 | assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(), | |
603 | UNIX_EPOCH + Duration::new(0, 123000)); | |
604 | parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err(); | |
605 | ||
606 | assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(), | |
607 | UNIX_EPOCH + Duration::new(0, 123000)); | |
608 | parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err(); | |
609 | ||
610 | assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(), | |
611 | UNIX_EPOCH + Duration::new(0, 0)); | |
612 | parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err(); | |
613 | } | |
614 | } |