1 use std::fmt;
2 use std::time::Duration;
3 
4 // Number of seconds in a day is a constant.
5 // We do not support leap seconds here.
6 const SECONDS_IN_DAY: u64 = 86400;
7 
8 // Gregorian calendar has 400 years cycles, this is a procedure
9 // for computing if a year is a leap year.
is_leap_year(year: i64) -> bool10 fn is_leap_year(year: i64) -> bool {
11     if year % 4 != 0 {
12         false
13     } else if year % 100 != 0 {
14         true
15     } else if year % 400 != 0 {
16         false
17     } else {
18         true
19     }
20 }
21 
days_in_year(year: i64) -> u3222 fn days_in_year(year: i64) -> u32 {
23     if is_leap_year(year) {
24         366
25     } else {
26         365
27     }
28 }
29 
30 // Number of leap years among 400 consecutive years.
31 const CYCLE_LEAP_YEARS: u32 = 400 / 4 - 400 / 100 + 400 / 400;
32 // Number of days in 400 years cycle.
33 const CYCLE_DAYS: u32 = 400 * 365 + CYCLE_LEAP_YEARS;
34 // Number of seconds in 400 years cycle.
35 const CYCLE_SECONDS: u64 = CYCLE_DAYS as u64 * SECONDS_IN_DAY;
36 
37 // Number of seconds between 1 Jan 1970 and 1 Jan 2000.
38 // Check with:
39 // `TZ=UTC gdate --rfc-3339=seconds --date @946684800`
40 const YEARS_1970_2000_SECONDS: u64 = 946684800;
41 // Number of seconds between 1 Jan 1600 and 1 Jan 1970.
42 const YEARS_1600_1970_SECONDS: u64 = CYCLE_SECONDS - YEARS_1970_2000_SECONDS;
43 
44 // For each year in the cycle, number of leap years before in the cycle.
45 #[cfg_attr(rustfmt, rustfmt_skip)]
46 static YEAR_DELTAS: [u8; 401] = [
47     0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,
48     5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,  9,  9,  9,  9, 10, 10, 10,
49     10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15,
50     15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20,
51     20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100
52     25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29,
53     29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34,
54     34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39,
55     39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44,
56     44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, // 200
57     49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53,
58     53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58,
59     58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63,
60     63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68,
61     68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, // 300
62     73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77,
63     77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82,
64     82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87,
65     87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92,
66     92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97, 97, 97,
67 ];
68 
69 /// UTC time
70 pub struct TmUtc {
71     /// Year
72     year: i64,
73     /// 1..=12
74     month: u32,
75     /// 1-based day of month
76     day: u32,
77     /// 0..=23
78     hour: u32,
79     /// 0..=59
80     minute: u32,
81     /// 0..=59; no leap seconds
82     second: u32,
83     /// 0..=999_999_999
84     nanos: u32,
85 }
86 
87 #[derive(Debug, thiserror::Error)]
88 pub enum Rfc3339ParseError {
89     #[error("Unexpected EOF")]
90     UnexpectedEof,
91     #[error("Trailing characters")]
92     TrailngCharacters,
93     #[error("Expecting digits")]
94     ExpectingDigits,
95     #[error("Expecting character: {:?}", .0)]
96     ExpectingChar(char),
97     #[error("Expecting timezone")]
98     ExpectingTimezone,
99     #[error("No digits after dot")]
100     NoDigitsAfterDot,
101     #[error("Date-time field is out of range")]
102     DateTimeFieldOutOfRange,
103     #[error("Expecting date-time separator")]
104     ExpectingDateTimeSeparator,
105 }
106 
107 pub type Rfc3339ParseResult<A> = Result<A, Rfc3339ParseError>;
108 
109 impl TmUtc {
day_of_cycle_to_year_day_of_year(day_of_cycle: u32) -> (i64, u32)110     fn day_of_cycle_to_year_day_of_year(day_of_cycle: u32) -> (i64, u32) {
111         debug_assert!(day_of_cycle < CYCLE_DAYS);
112 
113         let mut year_mod_400 = (day_of_cycle / 365) as i64;
114         let mut day_or_year = (day_of_cycle % 365) as u32;
115 
116         let delta = YEAR_DELTAS[year_mod_400 as usize] as u32;
117         if day_or_year < delta {
118             year_mod_400 -= 1;
119             day_or_year += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32;
120         } else {
121             day_or_year -= delta;
122         }
123 
124         (year_mod_400, day_or_year)
125     }
126 
year_day_of_year_to_day_of_cycle(year_mod_400: u32, day_of_year: u32) -> u32127     fn year_day_of_year_to_day_of_cycle(year_mod_400: u32, day_of_year: u32) -> u32 {
128         debug_assert!(year_mod_400 < 400);
129         debug_assert!(day_of_year < days_in_year(year_mod_400 as i64));
130 
131         year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + day_of_year
132     }
133 
134     // Convert seconds of the day of hour, minute and second
second_of_day_to_h_m_s(seconds: u32) -> (u32, u32, u32)135     fn second_of_day_to_h_m_s(seconds: u32) -> (u32, u32, u32) {
136         debug_assert!(seconds < 86400);
137 
138         let hour = seconds / 3600;
139         let minute = seconds % 3600 / 60;
140         let second = seconds % 60;
141 
142         (hour, minute, second)
143     }
144 
h_m_s_to_second_of_day(hour: u32, minute: u32, second: u32) -> u32145     fn h_m_s_to_second_of_day(hour: u32, minute: u32, second: u32) -> u32 {
146         debug_assert!(hour < 24);
147         debug_assert!(minute < 60);
148         debug_assert!(second < 60);
149 
150         hour * 3600 + minute * 60 + second
151     }
152 
days_in_months(year: i64) -> &'static [u32]153     fn days_in_months(year: i64) -> &'static [u32] {
154         if is_leap_year(year) {
155             &[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
156         } else {
157             &[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
158         }
159     }
160 
161     // Convert day of year (0-based) to month and day
day_of_year_to_month_day(year: i64, day_of_year: u32) -> (u32, u32)162     fn day_of_year_to_month_day(year: i64, day_of_year: u32) -> (u32, u32) {
163         debug_assert!(day_of_year < days_in_year(year));
164 
165         let days_in_months = TmUtc::days_in_months(year);
166 
167         let mut rem_days = day_of_year;
168         let mut month = 1;
169         while rem_days >= days_in_months[month - 1] {
170             rem_days -= days_in_months[month - 1];
171             month += 1;
172         }
173 
174         debug_assert!(rem_days + 1 <= days_in_months[month - 1]);
175 
176         (month as u32, rem_days + 1)
177     }
178 
month_day_to_day_of_year(year: i64, month: u32, day: u32) -> u32179     fn month_day_to_day_of_year(year: i64, month: u32, day: u32) -> u32 {
180         debug_assert!(month >= 1);
181         debug_assert!(month <= 12);
182 
183         debug_assert!(day >= 1);
184 
185         let days_in_months = TmUtc::days_in_months(year);
186 
187         // TODO: replace loop with precomputed table
188         let mut day_of_year = 0;
189         for next_month in 1..month {
190             day_of_year += days_in_months[next_month as usize - 1];
191         }
192 
193         debug_assert!(day <= days_in_months[month as usize - 1]);
194 
195         day_of_year + day - 1
196     }
197 
198     // Construct from duration added to cycle start year
from_cycle_start_add_duration(mut cycle_start: i64, add: Duration) -> TmUtc199     fn from_cycle_start_add_duration(mut cycle_start: i64, add: Duration) -> TmUtc {
200         debug_assert!(cycle_start % 400 == 0);
201 
202         // Split duration to days and duration within day
203 
204         let days = add.as_secs() / SECONDS_IN_DAY;
205         let duration_of_day = add - Duration::from_secs(days * SECONDS_IN_DAY);
206 
207         let cycles = days / CYCLE_DAYS as u64;
208         cycle_start += cycles as i64 * 400;
209         let day_of_cycle = days % CYCLE_DAYS as u64;
210 
211         let (year_mod_400, day_of_year) =
212             TmUtc::day_of_cycle_to_year_day_of_year(day_of_cycle as u32);
213 
214         let (year,) = (cycle_start + year_mod_400,);
215         let (month, day) = TmUtc::day_of_year_to_month_day(year, day_of_year);
216         let (hour, minute, second) =
217             TmUtc::second_of_day_to_h_m_s(duration_of_day.as_secs() as u32);
218 
219         TmUtc {
220             year,
221             month,
222             day,
223             hour,
224             minute,
225             second,
226             nanos: duration_of_day.subsec_nanos(),
227         }
228     }
229 
230     // Protobuf timestamp: seconds from epoch, and nanos 0..=999_999_999 counting forward.
from_protobuf_timestamp(seconds: i64, nanos: u32) -> TmUtc231     pub fn from_protobuf_timestamp(seconds: i64, nanos: u32) -> TmUtc {
232         assert!(nanos <= 999_999_999);
233 
234         let (mut year, mut seconds) = if seconds >= 0 {
235             (1970, seconds as u64)
236         } else {
237             let minus_seconds = if seconds == i64::MIN {
238                 i64::MIN as u64
239             } else {
240                 -seconds as u64
241             };
242 
243             let cycles = (minus_seconds + CYCLE_SECONDS) / CYCLE_SECONDS;
244 
245             (
246                 1970 - 400 * cycles as i64,
247                 cycles * CYCLE_SECONDS - minus_seconds,
248             )
249         };
250 
251         year -= 370;
252         seconds += YEARS_1600_1970_SECONDS;
253 
254         TmUtc::from_cycle_start_add_duration(year, Duration::new(seconds, nanos))
255     }
256 
to_protobuf_timestamp(&self) -> (i64, u32)257     pub fn to_protobuf_timestamp(&self) -> (i64, u32) {
258         assert!(self.year >= 0);
259         assert!(self.year <= 9999);
260 
261         let year_mod_400 = ((self.year % 400 + 400) % 400) as u32;
262         let cycle_start = self.year - year_mod_400 as i64;
263 
264         let day_of_year = TmUtc::month_day_to_day_of_year(self.year, self.month, self.day);
265         let day_of_cycle = TmUtc::year_day_of_year_to_day_of_cycle(year_mod_400, day_of_year);
266         let second_of_day = TmUtc::h_m_s_to_second_of_day(self.hour, self.minute, self.second);
267 
268         let second_of_cycle = day_of_cycle as u64 * SECONDS_IN_DAY + second_of_day as u64;
269 
270         let epoch_seconds = (cycle_start - 1600) / 400 * CYCLE_SECONDS as i64
271             - YEARS_1600_1970_SECONDS as i64
272             + second_of_cycle as i64;
273 
274         (epoch_seconds, self.nanos)
275     }
276 
parse_rfc_3339(s: &str) -> Rfc3339ParseResult<(i64, u32)>277     pub fn parse_rfc_3339(s: &str) -> Rfc3339ParseResult<(i64, u32)> {
278         struct Parser<'a> {
279             s: &'a [u8],
280             pos: usize,
281         }
282 
283         impl<'a> Parser<'a> {
284             fn next_number(&mut self, len: usize) -> Rfc3339ParseResult<u32> {
285                 let end_pos = self.pos + len;
286                 if end_pos > self.s.len() {
287                     return Err(Rfc3339ParseError::UnexpectedEof);
288                 }
289                 let mut r = 0;
290                 for i in 0..len {
291                     let c = self.s[self.pos + i];
292                     if c >= b'0' && c <= b'9' {
293                         r = r * 10 + (c - b'0') as u32;
294                     } else {
295                         return Err(Rfc3339ParseError::ExpectingDigits);
296                     }
297                 }
298                 self.pos += len;
299                 Ok(r)
300             }
301 
302             fn lookahead_char(&self) -> Rfc3339ParseResult<u8> {
303                 if self.pos == self.s.len() {
304                     return Err(Rfc3339ParseError::UnexpectedEof);
305                 }
306                 Ok(self.s[self.pos])
307             }
308 
309             fn next_char(&mut self, expect: u8) -> Rfc3339ParseResult<()> {
310                 assert!(expect < 0x80);
311                 let c = self.lookahead_char()?;
312                 if c != expect {
313                     return Err(Rfc3339ParseError::ExpectingChar(expect as char));
314                 }
315                 self.pos += 1;
316                 Ok(())
317             }
318         }
319 
320         let mut parser = Parser {
321             s: s.as_bytes(),
322             pos: 0,
323         };
324 
325         let year = parser.next_number(4)? as i64;
326         parser.next_char(b'-')?;
327         let month = parser.next_number(2)?;
328         parser.next_char(b'-')?;
329         let day = parser.next_number(2)?;
330 
331         if month < 1 || month > 12 {
332             return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
333         }
334 
335         if day < 1 || day > TmUtc::days_in_months(year as i64)[month as usize - 1] {
336             return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
337         }
338 
339         match parser.lookahead_char()? {
340             b'T' | b't' | b' ' => parser.pos += 1,
341             _ => return Err(Rfc3339ParseError::ExpectingDateTimeSeparator),
342         }
343 
344         let hour = parser.next_number(2)?;
345         parser.next_char(b':')?;
346         let minute = parser.next_number(2)?;
347         parser.next_char(b':')?;
348         let second = parser.next_number(2)?;
349 
350         if hour > 23 || minute > 59 || second > 60 {
351             return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
352         }
353 
354         // round down leap second
355         let second = if second == 60 { 59 } else { second };
356 
357         let nanos = if parser.lookahead_char()? == b'.' {
358             parser.pos += 1;
359             let mut digits = 0;
360             let mut nanos = 0;
361             while parser.lookahead_char()? >= b'0' && parser.lookahead_char()? <= b'9' {
362                 let digit = (parser.lookahead_char()? - b'0') as u32;
363                 parser.pos += 1;
364                 if digits == 9 {
365                     continue;
366                 }
367                 digits += 1;
368                 nanos = nanos * 10 + digit;
369             }
370 
371             if digits == 0 {
372                 return Err(Rfc3339ParseError::NoDigitsAfterDot);
373             }
374 
375             for _ in digits..9 {
376                 nanos *= 10;
377             }
378             nanos
379         } else {
380             0
381         };
382 
383         let offset_seconds = if parser.lookahead_char()? == b'Z' || parser.lookahead_char()? == b'z'
384         {
385             parser.pos += 1;
386             0
387         } else {
388             let sign = if parser.lookahead_char()? == b'+' {
389                 1
390             } else if parser.lookahead_char()? == b'-' {
391                 -1
392             } else {
393                 return Err(Rfc3339ParseError::ExpectingTimezone);
394             };
395 
396             parser.pos += 1;
397 
398             let hour_offset = parser.next_number(2)?;
399             parser.next_char(b':')?;
400             let minute_offset = parser.next_number(2)?;
401 
402             (hour_offset * 3600 + 60 * minute_offset) as i64 * sign
403         };
404 
405         if parser.pos != parser.s.len() {
406             return Err(Rfc3339ParseError::TrailngCharacters);
407         }
408 
409         let (seconds, nanos) = TmUtc {
410             year,
411             month,
412             day,
413             hour,
414             minute,
415             second,
416             nanos,
417         }
418         .to_protobuf_timestamp();
419 
420         Ok((seconds - offset_seconds, nanos))
421     }
422 }
423 
424 impl fmt::Display for TmUtc {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result425     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426         if self.year > 9999 {
427             write!(f, "+{}", self.year)?;
428         } else if self.year < 0 {
429             write!(f, "{:05}", self.year)?;
430         } else {
431             write!(f, "{:04}", self.year)?;
432         }
433         write!(
434             f,
435             "-{:02}-{:02}T{:02}:{:02}:{:02}",
436             self.month, self.day, self.hour, self.minute, self.second
437         )?;
438 
439         // if precision is not specified, print nanoseconds
440         let subsec_digits = f.precision().unwrap_or(9);
441         if subsec_digits != 0 {
442             let mut subsec_digits = subsec_digits;
443 
444             let width = if subsec_digits > 9 { 9 } else { subsec_digits };
445 
446             // "Truncated" nanonseconds.
447             let mut subsec = self.nanos;
448 
449             // Performs 8 iterations when precision=1,
450             // but that's probably not a issue compared to other computations.
451             for _ in width..9 {
452                 subsec /= 10;
453             }
454 
455             write!(f, ".{:0width$}", subsec, width = width as usize)?;
456 
457             // Adding more than 9 digits is meaningless,
458             // but if user requests it, we should print zeros.
459             for _ in 9..subsec_digits {
460                 write!(f, "0")?;
461                 subsec_digits -= 1;
462             }
463         }
464 
465         write!(f, "Z")
466     }
467 }
468 
469 #[cfg(test)]
470 mod test {
471     use super::*;
472 
473     #[test]
test_fmt()474     fn test_fmt() {
475         fn test_impl(expected: &str, secs: i64, nanos: u32, subsec_digits: u32) {
476             let tm_utc = TmUtc::from_protobuf_timestamp(secs, nanos);
477 
478             assert_eq!(
479                 expected,
480                 format!("{:.prec$}", tm_utc, prec = subsec_digits as usize)
481             );
482         }
483 
484         // Tests can be validated with with GNU date:
485         // `TZ=UTC gdate --date @1535585179 --iso-8601=seconds`
486 
487         test_impl("1970-01-01T00:00:00Z", 0, 0, 0);
488         test_impl("2018-08-29T23:26:19Z", 1535585179, 0, 0);
489         test_impl("2018-08-29T23:26:19.123Z", 1535585179, 123456789, 3);
490         test_impl("1646-04-01T03:45:44Z", -10216613656, 0, 0);
491         test_impl("1970-01-01T00:00:00.000000001000Z", 0, 1, 12);
492         test_impl("5138-11-16T09:46:40Z", 100000000000, 0, 0);
493         test_impl("+33658-09-27T01:46:41Z", 1000000000001, 0, 0);
494         // Leading zero
495         test_impl("0000-12-31T00:00:00Z", -62135683200, 0, 0);
496         // Minus zero
497         test_impl("-0003-10-30T14:13:20Z", -62235683200, 0, 0);
498         // More than 4 digits
499         // Largest value GNU date can handle
500         test_impl("+2147485547-12-31T23:59:59Z", 67768036191676799, 0, 0);
501         // Negative dates
502         test_impl("1969-12-31T23:59:59Z", -1, 0, 0);
503         test_impl("1969-12-31T23:59:00Z", -60, 0, 0);
504         test_impl("1969-12-31T23:59:58.900Z", -2, 900_000_000, 3);
505         test_impl("1966-10-31T14:13:20Z", -100000000, 0, 0);
506         test_impl("-29719-04-05T22:13:19Z", -1000000000001, 0, 0);
507         // Smallest value GNU date can handle
508         test_impl("-2147481748-01-01T00:00:00Z", -67768040609740800, 0, 0);
509     }
510 
511     #[test]
test_parse_fmt()512     fn test_parse_fmt() {
513         fn test_impl(s: &str, width: usize) {
514             let (seconds, nanos) = TmUtc::parse_rfc_3339(s).unwrap();
515             let formatted = format!(
516                 "{:.width$}",
517                 TmUtc::from_protobuf_timestamp(seconds, nanos),
518                 width = width
519             );
520             assert_eq!(formatted, s);
521         }
522 
523         test_impl("1970-01-01T00:00:00Z", 0);
524         test_impl("1970-01-01T00:00:00.000Z", 3);
525         test_impl("1970-01-01T00:00:00.000000000Z", 9);
526         test_impl("1970-01-02T00:00:00Z", 0);
527         test_impl("1970-03-01T00:00:00Z", 0);
528         test_impl("1974-01-01T00:00:00Z", 0);
529         test_impl("2018-01-01T00:00:00Z", 0);
530         test_impl("2018-09-02T05:49:10.123456789Z", 9);
531         test_impl("0001-01-01T00:00:00.000000000Z", 9);
532         test_impl("9999-12-31T23:59:59.999999999Z", 9);
533     }
534 
535     #[test]
test_parse_alt()536     fn test_parse_alt() {
537         fn test_impl(alt: &str, parse: &str) {
538             let reference = TmUtc::parse_rfc_3339(alt).unwrap();
539             let parsed = TmUtc::parse_rfc_3339(parse).unwrap();
540             assert_eq!(reference, parsed, "{} - {}", alt, parse);
541         }
542 
543         // alternative spelling
544         test_impl("1970-01-01 00:00:00Z", "1970-01-01T00:00:00Z");
545         test_impl("1970-01-01 00:00:00Z", "1970-01-01t00:00:00Z");
546         test_impl("1970-01-01 00:00:00Z", "1970-01-01 00:00:00z");
547         // leap second is rounded down
548         test_impl("2016-12-31 23:59:59Z", "2016-12-31 23:59:60Z");
549         // TZ offset
550         test_impl("1970-01-01 00:00:00Z", "1970-01-01T03:00:00+03:00");
551         test_impl("1970-01-01 00:00:00Z", "1969-12-31 22:15:00-01:45");
552     }
553 
554     #[test]
test_parse_incorrect_inputs()555     fn test_parse_incorrect_inputs() {
556         fn test_impl(s: &str) {
557             assert!(TmUtc::parse_rfc_3339(s).is_err(), "{}", s);
558         }
559 
560         test_impl("1970-01-01T00:00:61Z");
561         test_impl("1970-01-01T00:60:61Z");
562         test_impl("1970-01-01T24:00:61Z");
563         test_impl("1970-01-01T00:00:00.Z");
564         test_impl("1970-01-32T00:00:00Z");
565         test_impl("1970-02-29T00:00:00Z");
566         test_impl("1980-02-30T00:00:00Z");
567         test_impl("1980-13-01T00:00:00Z");
568         test_impl("1970-01-01T00:00:00");
569         test_impl("1970-01-01T00:00Z");
570     }
571 
572     #[test]
test_fmt_max_duration()573     fn test_fmt_max_duration() {
574         // Simply check that there are no integer overflows.
575         // I didn't check that resulting strings are correct.
576         assert_eq!(
577             "-292277022657-01-27T08:29:52.000000000Z",
578             format!("{}", TmUtc::from_protobuf_timestamp(i64::MIN, 0))
579         );
580         assert_eq!(
581             "+292277026596-12-04T15:30:07.999999999Z",
582             format!("{}", TmUtc::from_protobuf_timestamp(i64::MAX, 999_999_999))
583         );
584     }
585 }
586