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