/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 | | } |