]> git.proxmox.com Git - rustc.git/blame - vendor/humantime/src/date.rs
New upstream version 1.40.0+dfsg1
[rustc.git] / vendor / humantime / src / date.rs
CommitLineData
0531ce1d
XL
1use std::fmt;
2use std::str;
3use std::time::{SystemTime, Duration, UNIX_EPOCH};
4
5#[cfg(target_os="cloudabi")]
6mod 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))]
17mod 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))]
28mod 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
34quick_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
54enum 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
64pub struct Rfc3339Timestamp(SystemTime, Precision);
65
66#[inline]
67fn 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.
80pub 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.
104pub 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
189fn 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.
200pub 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.
209pub 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.
218pub 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.
227pub 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.
236pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
237 return Rfc3339Timestamp(system_time, Precision::Nanos);
238}
239
240impl 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)]
361mod 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}