Coverage Report

Created: 2026-02-14 06:23

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