/rust/registry/src/index.crates.io-6f17d22bba15001f/humantime-2.1.0/src/date.rs
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #[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 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
50 | 0 | match self { |
51 | 0 | Error::OutOfRange => write!(f, "numeric component is out of range"), |
52 | 0 | Error::InvalidDigit => write!(f, "bad character where digit is expected"), |
53 | 0 | Error::InvalidFormat => write!(f, "timestamp format is invalid"), |
54 | | } |
55 | 0 | } |
56 | | } |
57 | | |
58 | 0 | #[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 | 0 | #[derive(Debug, Clone)] |
69 | | pub struct Rfc3339Timestamp(SystemTime, Precision); |
70 | | |
71 | | #[inline] |
72 | 0 | fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> { |
73 | 0 | if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' { |
74 | 0 | return Err(Error::InvalidDigit); |
75 | 0 | } |
76 | 0 | Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64) |
77 | 0 | } |
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 | 0 | pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> { |
86 | 0 | if s.len() < "2018-02-14T00:28:07Z".len() { |
87 | 0 | return Err(Error::InvalidFormat); |
88 | 0 | } |
89 | 0 | let b = s.as_bytes(); |
90 | 0 | if b[10] != b'T' || b[b.len()-1] != b'Z' { |
91 | 0 | return Err(Error::InvalidFormat); |
92 | 0 | } |
93 | 0 | parse_rfc3339_weak(s) |
94 | 0 | } |
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 | 0 | pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> { |
110 | 0 | if s.len() < "2018-02-14T00:28:07".len() { |
111 | 0 | return Err(Error::InvalidFormat); |
112 | 0 | } |
113 | 0 | let b = s.as_bytes(); // for careless slicing |
114 | 0 | if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') || |
115 | 0 | b[13] != b':' || b[16] != b':' |
116 | | { |
117 | 0 | return Err(Error::InvalidFormat); |
118 | 0 | } |
119 | 0 | let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?; |
120 | 0 | let month = two_digits(b[5], b[6])?; |
121 | 0 | let day = two_digits(b[8], b[9])?; |
122 | 0 | let hour = two_digits(b[11], b[12])?; |
123 | 0 | let minute = two_digits(b[14], b[15])?; |
124 | 0 | let mut second = two_digits(b[17], b[18])?; |
125 | | |
126 | 0 | if year < 1970 || hour > 23 || minute > 59 || second > 60 { |
127 | 0 | return Err(Error::OutOfRange); |
128 | 0 | } |
129 | 0 | // TODO(tailhook) should we check that leaps second is only on midnight ? |
130 | 0 | if second == 60 { |
131 | 0 | second = 59 |
132 | 0 | }; |
133 | 0 | let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + |
134 | 0 | ((year - 1) - 1600) / 400; |
135 | 0 | let leap = is_leap_year(year); |
136 | 0 | let (mut ydays, mdays) = match month { |
137 | 0 | 1 => (0, 31), |
138 | 0 | 2 if leap => (31, 29), |
139 | 0 | 2 => (31, 28), |
140 | 0 | 3 => (59, 31), |
141 | 0 | 4 => (90, 30), |
142 | 0 | 5 => (120, 31), |
143 | 0 | 6 => (151, 30), |
144 | 0 | 7 => (181, 31), |
145 | 0 | 8 => (212, 31), |
146 | 0 | 9 => (243, 30), |
147 | 0 | 10 => (273, 31), |
148 | 0 | 11 => (304, 30), |
149 | 0 | 12 => (334, 31), |
150 | 0 | _ => return Err(Error::OutOfRange), |
151 | | }; |
152 | 0 | if day > mdays || day == 0 { |
153 | 0 | return Err(Error::OutOfRange); |
154 | 0 | } |
155 | 0 | ydays += day - 1; |
156 | 0 | if leap && month > 2 { |
157 | 0 | ydays += 1; |
158 | 0 | } |
159 | 0 | let days = (year - 1970) * 365 + leap_years + ydays; |
160 | 0 |
|
161 | 0 | let time = second + minute * 60 + hour * 3600; |
162 | 0 |
|
163 | 0 | let mut nanos = 0; |
164 | 0 | let mut mult = 100_000_000; |
165 | 0 | if b.get(19) == Some(&b'.') { |
166 | 0 | for idx in 20..b.len() { |
167 | 0 | if b[idx] == b'Z' { |
168 | 0 | if idx == b.len()-1 { |
169 | 0 | break; |
170 | | } else { |
171 | 0 | return Err(Error::InvalidDigit); |
172 | | } |
173 | 0 | } |
174 | 0 | if b[idx] < b'0' || b[idx] > b'9' { |
175 | 0 | return Err(Error::InvalidDigit); |
176 | 0 | } |
177 | 0 | nanos += mult * (b[idx] - b'0') as u32; |
178 | 0 | mult /= 10; |
179 | | } |
180 | 0 | } else if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') { |
181 | 0 | return Err(Error::InvalidFormat); |
182 | 0 | } |
183 | | |
184 | 0 | let total_seconds = time + days * 86400; |
185 | 0 | if total_seconds > max::SECONDS { |
186 | 0 | return Err(Error::OutOfRange); |
187 | 0 | } |
188 | 0 |
|
189 | 0 | Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos)) |
190 | 0 | } |
191 | | |
192 | 0 | fn is_leap_year(y: u64) -> bool { |
193 | 0 | y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) |
194 | 0 | } |
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 | 0 | pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp { |
204 | 0 | Rfc3339Timestamp(system_time, Precision::Smart) |
205 | 0 | } |
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 | 0 | pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp { |
213 | 0 | Rfc3339Timestamp(system_time, Precision::Seconds) |
214 | 0 | } |
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 | 0 | pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp { |
222 | 0 | Rfc3339Timestamp(system_time, Precision::Millis) |
223 | 0 | } |
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 | 0 | pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp { |
231 | 0 | Rfc3339Timestamp(system_time, Precision::Micros) |
232 | 0 | } |
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 | 0 | pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp { |
240 | 0 | Rfc3339Timestamp(system_time, Precision::Nanos) |
241 | 0 | } |
242 | | |
243 | | impl Rfc3339Timestamp { |
244 | | /// Returns a reference to the [`SystemTime`][] that is being formatted. |
245 | 0 | pub fn get_ref(&self) -> &SystemTime { |
246 | 0 | &self.0 |
247 | 0 | } |
248 | | } |
249 | | |
250 | | impl fmt::Display for Rfc3339Timestamp { |
251 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
252 | 0 | use self::Precision::*; |
253 | 0 |
|
254 | 0 | let dur = self.0.duration_since(UNIX_EPOCH) |
255 | 0 | .expect("all times should be after the epoch"); |
256 | 0 | let secs_since_epoch = dur.as_secs(); |
257 | 0 | let nanos = dur.subsec_nanos(); |
258 | 0 |
|
259 | 0 | if secs_since_epoch >= 253_402_300_800 { // year 9999 |
260 | 0 | return Err(fmt::Error); |
261 | 0 | } |
262 | 0 |
|
263 | 0 | /* 2000-03-01 (mod 400 year, immediately after feb29 */ |
264 | 0 | const LEAPOCH: i64 = 11017; |
265 | 0 | const DAYS_PER_400Y: i64 = 365*400 + 97; |
266 | 0 | const DAYS_PER_100Y: i64 = 365*100 + 24; |
267 | 0 | const DAYS_PER_4Y: i64 = 365*4 + 1; |
268 | 0 |
|
269 | 0 | let days = (secs_since_epoch / 86400) as i64 - LEAPOCH; |
270 | 0 | let secs_of_day = secs_since_epoch % 86400; |
271 | 0 |
|
272 | 0 | let mut qc_cycles = days / DAYS_PER_400Y; |
273 | 0 | let mut remdays = days % DAYS_PER_400Y; |
274 | 0 |
|
275 | 0 | if remdays < 0 { |
276 | 0 | remdays += DAYS_PER_400Y; |
277 | 0 | qc_cycles -= 1; |
278 | 0 | } |
279 | | |
280 | 0 | let mut c_cycles = remdays / DAYS_PER_100Y; |
281 | 0 | if c_cycles == 4 { c_cycles -= 1; } |
282 | 0 | remdays -= c_cycles * DAYS_PER_100Y; |
283 | 0 |
|
284 | 0 | let mut q_cycles = remdays / DAYS_PER_4Y; |
285 | 0 | if q_cycles == 25 { q_cycles -= 1; } |
286 | 0 | remdays -= q_cycles * DAYS_PER_4Y; |
287 | 0 |
|
288 | 0 | let mut remyears = remdays / 365; |
289 | 0 | if remyears == 4 { remyears -= 1; } |
290 | 0 | remdays -= remyears * 365; |
291 | 0 |
|
292 | 0 | let mut year = 2000 + |
293 | 0 | remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles; |
294 | 0 |
|
295 | 0 | let months = [31,30,31,30,31,31,30,31,30,31,31,29]; |
296 | 0 | let mut mon = 0; |
297 | 0 | for mon_len in months.iter() { |
298 | 0 | mon += 1; |
299 | 0 | if remdays < *mon_len { |
300 | 0 | break; |
301 | 0 | } |
302 | 0 | remdays -= *mon_len; |
303 | | } |
304 | 0 | let mday = remdays+1; |
305 | 0 | let mon = if mon + 2 > 12 { |
306 | 0 | year += 1; |
307 | 0 | mon - 10 |
308 | | } else { |
309 | 0 | mon + 2 |
310 | | }; |
311 | | |
312 | 0 | let mut buf: [u8; 30] = [ |
313 | 0 | // Too long to write as: b"0000-00-00T00:00:00.000000000Z" |
314 | 0 | b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T', |
315 | 0 | b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', |
316 | 0 | b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z', |
317 | 0 | ]; |
318 | 0 | buf[0] = b'0' + (year / 1000) as u8; |
319 | 0 | buf[1] = b'0' + (year / 100 % 10) as u8; |
320 | 0 | buf[2] = b'0' + (year / 10 % 10) as u8; |
321 | 0 | buf[3] = b'0' + (year % 10) as u8; |
322 | 0 | buf[5] = b'0' + (mon / 10) as u8; |
323 | 0 | buf[6] = b'0' + (mon % 10) as u8; |
324 | 0 | buf[8] = b'0' + (mday / 10) as u8; |
325 | 0 | buf[9] = b'0' + (mday % 10) as u8; |
326 | 0 | buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8; |
327 | 0 | buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8; |
328 | 0 | buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8; |
329 | 0 | buf[15] = b'0' + (secs_of_day / 60 % 10) as u8; |
330 | 0 | buf[17] = b'0' + (secs_of_day / 10 % 6) as u8; |
331 | 0 | buf[18] = b'0' + (secs_of_day % 10) as u8; |
332 | | |
333 | 0 | let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart { |
334 | 0 | buf[19] = b'Z'; |
335 | 0 | 19 |
336 | 0 | } else if self.1 == Millis { |
337 | 0 | buf[20] = b'0' + (nanos / 100_000_000) as u8; |
338 | 0 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; |
339 | 0 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; |
340 | 0 | buf[23] = b'Z'; |
341 | 0 | 23 |
342 | 0 | } else if self.1 == Micros { |
343 | 0 | buf[20] = b'0' + (nanos / 100_000_000) as u8; |
344 | 0 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; |
345 | 0 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; |
346 | 0 | buf[23] = b'0' + (nanos / 100_000 % 10) as u8; |
347 | 0 | buf[24] = b'0' + (nanos / 10_000 % 10) as u8; |
348 | 0 | buf[25] = b'0' + (nanos / 1_000 % 10) as u8; |
349 | 0 | buf[26] = b'Z'; |
350 | 0 | 26 |
351 | | } else { |
352 | 0 | buf[20] = b'0' + (nanos / 100_000_000) as u8; |
353 | 0 | buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; |
354 | 0 | buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; |
355 | 0 | buf[23] = b'0' + (nanos / 100_000 % 10) as u8; |
356 | 0 | buf[24] = b'0' + (nanos / 10_000 % 10) as u8; |
357 | 0 | buf[25] = b'0' + (nanos / 1_000 % 10) as u8; |
358 | 0 | buf[26] = b'0' + (nanos / 100 % 10) as u8; |
359 | 0 | buf[27] = b'0' + (nanos / 10 % 10) as u8; |
360 | 0 | buf[28] = b'0' + (nanos / 1 % 10) as u8; |
361 | 0 | // 29th is 'Z' |
362 | 0 | 29 |
363 | | }; |
364 | | |
365 | | // we know our chars are all ascii |
366 | 0 | f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed")) |
367 | 0 | } |
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 | | } |