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