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