Skip to main content

bilrost_types/
lib.rs

1#![no_std]
2#![doc(html_root_url = "https://docs.rs/bilrost-types/0.1015.0")]
3#![forbid(unsafe_code)]
4
5//! Analogs for protobuf well-known types, implemented alongside the
6//! [`bilrost`][bilrost] crate. See that crate's documentation for details about the
7//! library, and the [Protobuf reference][proto] for more information about the use cases and
8//! semantics of these types.
9//!
10//! [bilrost]: https://docs.rs/bilrost
11//!
12//! [proto]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
13
14extern crate alloc;
15#[cfg(feature = "std")]
16extern crate std;
17
18mod datetime;
19mod types;
20
21use core::fmt;
22use core::str::FromStr;
23use core::time;
24
25pub use types::*;
26
27// The Protobuf `Duration` and `Timestamp` types can't delegate to the standard library equivalents
28// because the Protobuf versions are signed. To make them easier to work with, `From` conversions
29// are defined in both directions.
30
31const NANOS_PER_SECOND: i32 = 1_000_000_000;
32const NANOS_MAX: i32 = NANOS_PER_SECOND - 1;
33
34// TODO: Message and into/from impls on time::Duration, time::Instant as optional features
35
36impl core::ops::Neg for Duration {
37    type Output = Self;
38
39    fn neg(self) -> Self {
40        Self {
41            seconds: -self.seconds,
42            nanos: -self.nanos,
43        }
44    }
45}
46
47// TODO: addition and subtraction with Timestamp & Duration
48
49impl Duration {
50    /// Returns true iff the duration is already normalized.
51    pub fn is_canonical(&self) -> bool {
52        (-NANOS_MAX..=NANOS_MAX).contains(&self.nanos)
53            && match self.seconds.signum() {
54                -1 => self.nanos <= 0,
55                1 => self.nanos >= 0,
56                _ => true,
57            }
58    }
59
60    /// Normalizes the duration to a canonical format.
61    ///
62    /// Based on [`google::protobuf::util::CreateNormalized`][1].
63    ///
64    /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L79-L100
65    pub fn normalize(&mut self) {
66        // Make sure nanos is in the range.
67        if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
68            if let Some(seconds) = self
69                .seconds
70                .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
71            {
72                self.seconds = seconds;
73                self.nanos %= NANOS_PER_SECOND;
74            } else if self.nanos < 0 {
75                // Negative overflow! Set to the least normal value.
76                self.seconds = i64::MIN;
77                self.nanos = -NANOS_MAX;
78            } else {
79                // Positive overflow! Set to the greatest normal value.
80                self.seconds = i64::MAX;
81                self.nanos = NANOS_MAX;
82            }
83        }
84
85        // nanos should have the same sign as seconds.
86        if self.seconds < 0 && self.nanos > 0 {
87            if let Some(seconds) = self.seconds.checked_add(1) {
88                self.seconds = seconds;
89                self.nanos -= NANOS_PER_SECOND;
90            } else {
91                // Positive overflow! Set to the greatest normal value.
92                debug_assert_eq!(self.seconds, i64::MAX);
93                self.nanos = NANOS_MAX;
94            }
95        } else if self.seconds > 0 && self.nanos < 0 {
96            if let Some(seconds) = self.seconds.checked_sub(1) {
97                self.seconds = seconds;
98                self.nanos += NANOS_PER_SECOND;
99            } else {
100                // Negative overflow! Set to the least normal value.
101                debug_assert_eq!(self.seconds, i64::MIN);
102                self.nanos = -NANOS_MAX;
103            }
104        }
105    }
106}
107
108impl TryFrom<time::Duration> for Duration {
109    type Error = DurationError;
110
111    /// Converts a `std::time::Duration` to a `Duration`, failing if the duration is too large.
112    fn try_from(duration: time::Duration) -> Result<Duration, DurationError> {
113        let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
114        let nanos = duration.subsec_nanos() as i32;
115
116        let mut duration = Duration { seconds, nanos };
117        duration.normalize();
118        Ok(duration)
119    }
120}
121
122impl TryFrom<Duration> for time::Duration {
123    type Error = DurationError;
124
125    /// Converts a `Duration` to a `std::time::Duration`, failing if the duration is negative.
126    fn try_from(mut duration: Duration) -> Result<time::Duration, DurationError> {
127        duration.normalize();
128        if duration.seconds >= 0 && duration.nanos >= 0 {
129            Ok(time::Duration::new(
130                duration.seconds as u64,
131                duration.nanos as u32,
132            ))
133        } else {
134            Err(DurationError::NegativeDuration(time::Duration::new(
135                (-duration.seconds) as u64,
136                (-duration.nanos) as u32,
137            )))
138        }
139    }
140}
141
142impl fmt::Display for Duration {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        let mut d = self.clone();
145        d.normalize();
146        if self.seconds < 0 || self.nanos < 0 {
147            write!(f, "-")?;
148        }
149        write!(f, "{}", d.seconds.abs())?;
150
151        // Format subseconds to either nothing, millis, micros, or nanos.
152        let nanos = d.nanos.abs();
153        if nanos == 0 {
154            write!(f, "s")
155        } else if nanos % 1_000_000 == 0 {
156            write!(f, ".{:03}s", nanos / 1_000_000)
157        } else if nanos % 1_000 == 0 {
158            write!(f, ".{:06}s", nanos / 1_000)
159        } else {
160            write!(f, ".{:09}s", nanos)
161        }
162    }
163}
164
165/// A duration handling error.
166#[derive(Debug, PartialEq)]
167#[non_exhaustive]
168pub enum DurationError {
169    /// Indicates failure to parse a [`Duration`] from a string.
170    ///
171    /// The [`Duration`] string format is specified in the [Protobuf JSON mapping specification][1].
172    ///
173    /// [1]: https://developers.google.com/protocol-buffers/docs/proto3#json
174    ParseFailure,
175
176    /// Indicates failure to convert a `bilrost_types::Duration` to a `std::time::Duration` because
177    /// the duration is negative. The included `std::time::Duration` matches the magnitude of the
178    /// original negative `bilrost_types::Duration`.
179    NegativeDuration(time::Duration),
180
181    /// Indicates failure to convert a `std::time::Duration` to a `bilrost_types::Duration`.
182    ///
183    /// Converting a `std::time::Duration` to a `bilrost_types::Duration` fails if the magnitude
184    /// exceeds that representable by `bilrost_types::Duration`.
185    OutOfRange,
186}
187
188impl fmt::Display for DurationError {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        match self {
191            DurationError::ParseFailure => write!(f, "failed to parse duration"),
192            DurationError::NegativeDuration(duration) => {
193                write!(f, "failed to convert negative duration: {:?}", duration)
194            }
195            DurationError::OutOfRange => {
196                write!(f, "failed to convert duration out of range")
197            }
198        }
199    }
200}
201
202#[cfg(feature = "std")]
203impl std::error::Error for DurationError {}
204
205impl FromStr for Duration {
206    type Err = DurationError;
207
208    fn from_str(s: &str) -> Result<Duration, DurationError> {
209        datetime::parse_duration(s).ok_or(DurationError::ParseFailure)
210    }
211}
212
213impl Timestamp {
214    /// Returns true iff the timestamp is already normalized.
215    pub fn is_canonical(&self) -> bool {
216        (0..NANOS_PER_SECOND).contains(&self.nanos)
217    }
218
219    /// Normalizes the timestamp to a canonical format.
220    ///
221    /// Based on [`google::protobuf::util::CreateNormalized`][1].
222    ///
223    /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
224    pub fn normalize(&mut self) {
225        // Make sure nanos is in the range.
226        if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
227            if let Some(seconds) = self
228                .seconds
229                .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
230            {
231                self.seconds = seconds;
232                self.nanos %= NANOS_PER_SECOND;
233            } else if self.nanos < 0 {
234                // Negative overflow! Set to the earliest normal value.
235                self.seconds = i64::MIN;
236                self.nanos = 0;
237            } else {
238                // Positive overflow! Set to the latest normal value.
239                self.seconds = i64::MAX;
240                self.nanos = 999_999_999;
241            }
242        }
243
244        // For Timestamp nanos should be in the range [0, 999999999].
245        if self.nanos < 0 {
246            if let Some(seconds) = self.seconds.checked_sub(1) {
247                self.seconds = seconds;
248                self.nanos += NANOS_PER_SECOND;
249            } else {
250                // Negative overflow! Set to the earliest normal value.
251                debug_assert_eq!(self.seconds, i64::MIN);
252                self.nanos = 0;
253            }
254        }
255    }
256
257    /// Normalizes the timestamp to a canonical format, returning the original value if it cannot be
258    /// normalized.
259    ///
260    /// Normalization is based on [`google::protobuf::util::CreateNormalized`][1].
261    ///
262    /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
263    pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
264        let before = self.clone();
265        self.normalize();
266        // If the seconds value has changed, and is either i64::MIN or i64::MAX, then the timestamp
267        // normalization overflowed.
268        if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
269        {
270            Err(before)
271        } else {
272            Ok(self)
273        }
274    }
275
276    /// Creates a new `Timestamp` at the start of the provided UTC date.
277    pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
278        Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
279    }
280
281    /// Creates a new `Timestamp` instance with the provided UTC date and time.
282    pub fn date_time(
283        year: i64,
284        month: u8,
285        day: u8,
286        hour: u8,
287        minute: u8,
288        second: u8,
289    ) -> Result<Timestamp, TimestampError> {
290        Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
291    }
292
293    /// Creates a new `Timestamp` instance with the provided UTC date and time.
294    pub fn date_time_nanos(
295        year: i64,
296        month: u8,
297        day: u8,
298        hour: u8,
299        minute: u8,
300        second: u8,
301        nanos: u32,
302    ) -> Result<Timestamp, TimestampError> {
303        let date_time = datetime::DateTime {
304            year,
305            month,
306            day,
307            hour,
308            minute,
309            second,
310            nanos,
311        };
312
313        Timestamp::try_from(date_time).map_err(|_| TimestampError::InvalidDateTime)
314    }
315}
316
317#[cfg(feature = "std")]
318impl From<std::time::SystemTime> for Timestamp {
319    fn from(system_time: std::time::SystemTime) -> Timestamp {
320        let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
321            Ok(duration) => {
322                let seconds = i64::try_from(duration.as_secs()).unwrap();
323                (seconds, duration.subsec_nanos() as i32)
324            }
325            Err(error) => {
326                let duration = error.duration();
327                let seconds = i64::try_from(duration.as_secs()).unwrap();
328                let nanos = duration.subsec_nanos() as i32;
329                if nanos == 0 {
330                    (-seconds, 0)
331                } else {
332                    (-seconds - 1, 1_000_000_000 - nanos)
333                }
334            }
335        };
336        Timestamp { seconds, nanos }
337    }
338}
339
340/// A timestamp handling error.
341#[derive(Debug, PartialEq)]
342#[non_exhaustive]
343pub enum TimestampError {
344    /// Indicates that a [`Timestamp`] could not be converted to
345    /// [`SystemTime`][std::time::SystemTime] because it is out of range.
346    ///
347    /// The range of times that can be represented by `SystemTime` depends on the platform. All
348    /// `Timestamp`s are likely representable on 64-bit Unix-like platforms, but other platforms,
349    /// such as Windows and 32-bit Linux, may not be able to represent the full range of
350    /// `Timestamp`s.
351    OutOfSystemRange(Timestamp),
352
353    /// An error indicating failure to parse a timestamp in RFC-3339 format.
354    ParseFailure,
355
356    /// Indicates an error when constructing a timestamp due to invalid date or time data.
357    InvalidDateTime,
358}
359
360impl fmt::Display for TimestampError {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        match self {
363            TimestampError::OutOfSystemRange(timestamp) => {
364                write!(
365                    f,
366                    "{} is not representable as a `SystemTime` because it is out of range",
367                    timestamp
368                )
369            }
370            TimestampError::ParseFailure => {
371                write!(f, "failed to parse RFC-3339 formatted timestamp")
372            }
373            TimestampError::InvalidDateTime => {
374                write!(f, "invalid date or time")
375            }
376        }
377    }
378}
379
380#[cfg(feature = "std")]
381impl std::error::Error for TimestampError {}
382
383#[cfg(feature = "std")]
384impl TryFrom<Timestamp> for std::time::SystemTime {
385    type Error = TimestampError;
386
387    fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
388        let orig_timestamp = timestamp.clone();
389        timestamp.normalize();
390
391        let system_time = if timestamp.seconds >= 0 {
392            std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
393        } else {
394            std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
395                timestamp
396                    .seconds
397                    .checked_neg()
398                    .ok_or_else(|| TimestampError::OutOfSystemRange(timestamp.clone()))?
399                    as u64,
400            ))
401        };
402
403        let system_time = system_time.and_then(|system_time| {
404            system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
405        });
406
407        system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
408    }
409}
410
411impl FromStr for Timestamp {
412    type Err = TimestampError;
413
414    fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
415        datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
416    }
417}
418
419impl fmt::Display for Timestamp {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        datetime::DateTime::from(self.clone()).fmt(f)
422    }
423}
424
425#[cfg(feature = "serde_json")]
426impl From<serde_json::Value> for Value {
427    fn from(from: serde_json::Value) -> Self {
428        use Value::*;
429        match from {
430            serde_json::Value::Null => Null,
431            serde_json::Value::Bool(value) => Bool(value),
432            serde_json::Value::Number(number) => {
433                if number.is_i64() {
434                    Signed(number.as_i64().unwrap())
435                } else if number.is_u64() {
436                    Unsigned(number.as_u64().unwrap())
437                } else {
438                    Float(number.as_f64().unwrap())
439                }
440            }
441            serde_json::Value::String(value) => String(value),
442            serde_json::Value::Array(values) => List(ListValue {
443                values: values.into_iter().map(Into::into).collect(),
444            }),
445            serde_json::Value::Object(items) => Struct(StructValue {
446                fields: items
447                    .into_iter()
448                    .map(|(key, value)| (key, value.into()))
449                    .collect(),
450            }),
451        }
452    }
453}
454
455#[cfg(feature = "serde_json")]
456impl TryFrom<Value> for serde_json::Value {
457    type Error = ();
458
459    fn try_from(value: Value) -> Result<Self, ()> {
460        Ok(match value {
461            Value::Null => serde_json::Value::Null,
462            Value::Float(value) => {
463                serde_json::Value::Number(serde_json::Number::from_f64(value).ok_or(())?)
464            }
465            Value::Signed(value) => serde_json::Value::Number(serde_json::Number::from(value)),
466            Value::Unsigned(value) => serde_json::Value::Number(serde_json::Number::from(value)),
467            Value::String(value) => serde_json::Value::String(value),
468            Value::Bool(value) => serde_json::Value::Bool(value),
469            Value::Struct(items) => serde_json::Value::Object(
470                items
471                    .fields
472                    .into_iter()
473                    .map(|(key, value)| Ok((key, value.try_into()?)))
474                    .collect::<Result<_, _>>()?,
475            ),
476            Value::List(list) => serde_json::Value::Array(
477                list.values
478                    .into_iter()
479                    .map(TryInto::try_into)
480                    .collect::<Result<_, _>>()?,
481            ),
482        })
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[cfg(feature = "std")]
491    use ::{
492        alloc::format,
493        alloc::string::ToString,
494        proptest::prelude::*,
495        std::time::{self, SystemTime, UNIX_EPOCH},
496    };
497
498    use crate::datetime::DateTime;
499
500    #[test]
501    fn check_overflowing_datetimes() {
502        // These DateTimes cause(d) overflows and crashes in the prost crate.
503        assert_eq!(
504            Timestamp::try_from(DateTime {
505                year: i64::from_le_bytes([178, 2, 0, 0, 0, 0, 0, 128]),
506                month: 2,
507                day: 2,
508                hour: 8,
509                minute: 58,
510                second: 8,
511                nanos: u32::from_le_bytes([0, 0, 0, 50]),
512            }),
513            Err(())
514        );
515        assert_eq!(
516            Timestamp::try_from(DateTime {
517                year: i64::from_le_bytes([132, 7, 0, 0, 0, 0, 0, 128]),
518                month: 2,
519                day: 2,
520                hour: 8,
521                minute: 58,
522                second: 8,
523                nanos: u32::from_le_bytes([0, 0, 0, 50]),
524            }),
525            Err(())
526        );
527        assert_eq!(
528            Timestamp::try_from(DateTime {
529                year: i64::from_le_bytes([80, 96, 32, 240, 99, 0, 32, 180]),
530                month: 1,
531                day: 18,
532                hour: 19,
533                minute: 26,
534                second: 8,
535                nanos: u32::from_le_bytes([0, 0, 0, 50]),
536            }),
537            Err(())
538        );
539        assert_eq!(
540            Timestamp::try_from(DateTime {
541                year: DateTime::MIN.year - 1,
542                month: 0,
543                day: 0,
544                hour: 0,
545                minute: 0,
546                second: 0,
547                nanos: 0,
548            }),
549            Err(())
550        );
551        assert_eq!(
552            Timestamp::try_from(DateTime {
553                year: i64::MIN,
554                month: 0,
555                day: 0,
556                hour: 0,
557                minute: 0,
558                second: 0,
559                nanos: 0,
560            }),
561            Err(())
562        );
563        assert_eq!(
564            Timestamp::try_from(DateTime {
565                year: DateTime::MAX.year + 1,
566                month: u8::MAX,
567                day: u8::MAX,
568                hour: u8::MAX,
569                minute: u8::MAX,
570                second: u8::MAX,
571                nanos: u32::MAX,
572            }),
573            Err(())
574        );
575        assert_eq!(
576            Timestamp::try_from(DateTime {
577                year: i64::MAX,
578                month: u8::MAX,
579                day: u8::MAX,
580                hour: u8::MAX,
581                minute: u8::MAX,
582                second: u8::MAX,
583                nanos: u32::MAX,
584            }),
585            Err(())
586        );
587        assert_eq!(
588            Timestamp::try_from(DateTime {
589                year: 2001,
590                month: u8::MAX,
591                day: u8::MAX,
592                hour: u8::MAX,
593                minute: u8::MAX,
594                second: u8::MAX,
595                nanos: u32::MAX,
596            }),
597            Err(())
598        );
599        assert_eq!(Timestamp::try_from(DateTime::MIN), Ok(Timestamp::MIN));
600        assert_eq!(Timestamp::try_from(DateTime::MAX), Ok(Timestamp::MAX));
601        assert_eq!(DateTime::from(Timestamp::MIN), DateTime::MIN);
602        assert_eq!(DateTime::from(Timestamp::MAX), DateTime::MAX);
603    }
604
605    #[cfg(feature = "std")]
606    proptest! {
607        #[test]
608        fn check_system_time_roundtrip(
609            system_time: SystemTime,
610        ) {
611            prop_assert_eq!(SystemTime::try_from(Timestamp::from(system_time))?, system_time);
612        }
613
614        #[test]
615        fn check_timestamp_roundtrip_via_system_time(
616            seconds: i64,
617            nanos: i32,
618        ) {
619            let mut timestamp = Timestamp { seconds, nanos };
620            let is_canonical = timestamp.is_canonical();
621            timestamp.normalize();
622            prop_assert_eq!(is_canonical, timestamp == Timestamp { seconds, nanos });
623            if let Ok(system_time) = SystemTime::try_from(timestamp.clone()) {
624                prop_assert_eq!(Timestamp::from(system_time), timestamp);
625            }
626        }
627
628        #[test]
629        fn check_timestamp_datetime_roundtrip(seconds: i64, nanos: i32) {
630            let mut timestamp = Timestamp { seconds, nanos };
631            let is_canonical = timestamp.is_canonical();
632            timestamp.normalize();
633            prop_assert_eq!(is_canonical, timestamp == Timestamp { seconds, nanos });
634            let timestamp = timestamp;
635            let datetime: DateTime = timestamp.clone().into();
636            prop_assert_eq!(Timestamp::try_from(datetime), Ok(timestamp));
637        }
638
639        #[test]
640        fn check_duration_roundtrip(
641            seconds: u64,
642            nanos in 0u32..1_000_000_000u32,
643        ) {
644            let std_duration = time::Duration::new(seconds, nanos);
645            let bilrost_duration = match Duration::try_from(std_duration) {
646                Ok(duration) => duration,
647                Err(_) => return Err(TestCaseError::reject("duration out of range")),
648            };
649            prop_assert_eq!(time::Duration::try_from(bilrost_duration.clone())?, std_duration);
650
651            if std_duration != time::Duration::default() {
652                let neg_bilrost_duration = Duration {
653                    seconds: -bilrost_duration.seconds,
654                    nanos: -bilrost_duration.nanos,
655                };
656
657                prop_assert!(
658                    matches!(
659                        time::Duration::try_from(neg_bilrost_duration),
660                        Err(DurationError::NegativeDuration(d)) if d == std_duration,
661                    )
662                )
663            }
664        }
665
666        #[test]
667        fn check_duration_roundtrip_nanos(
668            nanos: u32,
669        ) {
670            let seconds = 0;
671            let std_duration = std::time::Duration::new(seconds, nanos);
672            let bilrost_duration = match Duration::try_from(std_duration) {
673                Ok(duration) => duration,
674                Err(_) => return Err(TestCaseError::reject("duration out of range")),
675            };
676            prop_assert_eq!(time::Duration::try_from(bilrost_duration.clone())?, std_duration);
677
678            if std_duration != time::Duration::default() {
679                let neg_bilrost_duration = Duration {
680                    seconds: -bilrost_duration.seconds,
681                    nanos: -bilrost_duration.nanos,
682                };
683
684                prop_assert!(
685                    matches!(
686                        time::Duration::try_from(neg_bilrost_duration),
687                        Err(DurationError::NegativeDuration(d)) if d == std_duration,
688                    )
689                )
690            }
691        }
692    }
693
694    #[cfg(feature = "std")]
695    #[test]
696    fn test_duration_from_str() {
697        assert_eq!(
698            Duration::from_str("0s"),
699            Ok(Duration {
700                seconds: 0,
701                nanos: 0
702            })
703        );
704        assert_eq!(
705            Duration::from_str("123s"),
706            Ok(Duration {
707                seconds: 123,
708                nanos: 0
709            })
710        );
711        assert_eq!(
712            Duration::from_str("0.123s"),
713            Ok(Duration {
714                seconds: 0,
715                nanos: 123_000_000
716            })
717        );
718        assert_eq!(
719            Duration::from_str("-123s"),
720            Ok(Duration {
721                seconds: -123,
722                nanos: 0
723            })
724        );
725        assert_eq!(
726            Duration::from_str("-0.123s"),
727            Ok(Duration {
728                seconds: 0,
729                nanos: -123_000_000
730            })
731        );
732        assert_eq!(
733            Duration::from_str("22041211.6666666666666s"),
734            Ok(Duration {
735                seconds: 22041211,
736                nanos: 666_666_666
737            })
738        );
739    }
740
741    #[cfg(feature = "std")]
742    #[test]
743    fn test_format_duration() {
744        assert_eq!(
745            "0s",
746            Duration {
747                seconds: 0,
748                nanos: 0
749            }
750            .to_string()
751        );
752        assert_eq!(
753            "123s",
754            Duration {
755                seconds: 123,
756                nanos: 0
757            }
758            .to_string()
759        );
760        assert_eq!(
761            "0.123s",
762            Duration {
763                seconds: 0,
764                nanos: 123_000_000
765            }
766            .to_string()
767        );
768        assert_eq!(
769            "-123s",
770            Duration {
771                seconds: -123,
772                nanos: 0
773            }
774            .to_string()
775        );
776        assert_eq!(
777            "-0.123s",
778            Duration {
779                seconds: 0,
780                nanos: -123_000_000
781            }
782            .to_string()
783        );
784    }
785
786    #[cfg(feature = "std")]
787    #[test]
788    fn check_duration_try_from_negative_nanos() {
789        let seconds: u64 = 0;
790        let nanos: u32 = 1;
791        let std_duration = time::Duration::new(seconds, nanos);
792
793        let neg_bilrost_duration = Duration {
794            seconds: 0,
795            nanos: -1,
796        };
797
798        assert!(matches!(
799           time::Duration::try_from(neg_bilrost_duration),
800           Err(DurationError::NegativeDuration(d)) if d == std_duration,
801        ))
802    }
803
804    #[cfg(feature = "std")]
805    #[test]
806    fn check_timestamp_negative_seconds() {
807        // Representative tests for the case of timestamps before the UTC Epoch time:
808        // validate the expected behaviour that "negative second values with fractions
809        // must still have non-negative nanos values that count forward in time"
810        // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp
811        //
812        // To ensure cross-platform compatibility, all nanosecond values in these
813        // tests are in minimum 100 ns increments.  This does not affect the general
814        // character of the behaviour being tested, but ensures that the tests are
815        // valid for both POSIX (1 ns precision) and Windows (100 ns precision).
816        assert_eq!(
817            Timestamp::from(UNIX_EPOCH - time::Duration::new(1_001, 0)),
818            Timestamp {
819                seconds: -1_001,
820                nanos: 0,
821            }
822        );
823        assert_eq!(
824            Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_900)),
825            Timestamp {
826                seconds: -1,
827                nanos: 100,
828            }
829        );
830        assert_eq!(
831            Timestamp::from(UNIX_EPOCH - time::Duration::new(2_001_234, 12_300)),
832            Timestamp {
833                seconds: -2_001_235,
834                nanos: 999_987_700,
835            }
836        );
837        assert_eq!(
838            Timestamp::from(UNIX_EPOCH - time::Duration::new(768, 65_432_100)),
839            Timestamp {
840                seconds: -769,
841                nanos: 934_567_900,
842            }
843        );
844    }
845
846    #[cfg(all(unix, feature = "std"))]
847    #[test]
848    fn check_timestamp_negative_seconds_1ns() {
849        // UNIX-only test cases with 1 ns precision
850        assert_eq!(
851            Timestamp::from(UNIX_EPOCH - time::Duration::new(0, 999_999_999)),
852            Timestamp {
853                seconds: -1,
854                nanos: 1,
855            }
856        );
857        assert_eq!(
858            Timestamp::from(UNIX_EPOCH - time::Duration::new(1_234_567, 123)),
859            Timestamp {
860                seconds: -1_234_568,
861                nanos: 999_999_877,
862            }
863        );
864        assert_eq!(
865            Timestamp::from(UNIX_EPOCH - time::Duration::new(890, 987_654_321)),
866            Timestamp {
867                seconds: -891,
868                nanos: 12_345_679,
869            }
870        );
871    }
872
873    #[test]
874    fn check_duration_normalize() {
875        #[rustfmt::skip] // Don't mangle the table formatting.
876        let cases = [
877            // --- Table of test cases ---
878            //        test seconds      test nanos  expected seconds  expected nanos
879            (line!(),            0,              0,                0,              0),
880            (line!(),            1,              1,                1,              1),
881            (line!(),           -1,             -1,               -1,             -1),
882            (line!(),            0,    999_999_999,                0,    999_999_999),
883            (line!(),            0,   -999_999_999,                0,   -999_999_999),
884            (line!(),            0,  1_000_000_000,                1,              0),
885            (line!(),            0, -1_000_000_000,               -1,              0),
886            (line!(),            0,  1_000_000_001,                1,              1),
887            (line!(),            0, -1_000_000_001,               -1,             -1),
888            (line!(),           -1,              1,                0,   -999_999_999),
889            (line!(),            1,             -1,                0,    999_999_999),
890            (line!(),           -1,  1_000_000_000,                0,              0),
891            (line!(),            1, -1_000_000_000,                0,              0),
892            (line!(), i64::MIN    ,              0,     i64::MIN    ,              0),
893            (line!(), i64::MIN + 1,              0,     i64::MIN + 1,              0),
894            (line!(), i64::MIN    ,              1,     i64::MIN + 1,   -999_999_999),
895            (line!(), i64::MIN    ,  1_000_000_000,     i64::MIN + 1,              0),
896            (line!(), i64::MIN    , -1_000_000_000,     i64::MIN    ,   -999_999_999),
897            (line!(), i64::MIN + 1, -1_000_000_000,     i64::MIN    ,              0),
898            (line!(), i64::MIN + 2, -1_000_000_000,     i64::MIN + 1,              0),
899            (line!(), i64::MIN    , -1_999_999_998,     i64::MIN    ,   -999_999_999),
900            (line!(), i64::MIN + 1, -1_999_999_998,     i64::MIN    ,   -999_999_998),
901            (line!(), i64::MIN + 2, -1_999_999_998,     i64::MIN + 1,   -999_999_998),
902            (line!(), i64::MIN    , -1_999_999_999,     i64::MIN    ,   -999_999_999),
903            (line!(), i64::MIN + 1, -1_999_999_999,     i64::MIN    ,   -999_999_999),
904            (line!(), i64::MIN + 2, -1_999_999_999,     i64::MIN + 1,   -999_999_999),
905            (line!(), i64::MIN    , -2_000_000_000,     i64::MIN    ,   -999_999_999),
906            (line!(), i64::MIN + 1, -2_000_000_000,     i64::MIN    ,   -999_999_999),
907            (line!(), i64::MIN + 2, -2_000_000_000,     i64::MIN    ,              0),
908            (line!(), i64::MIN    ,   -999_999_998,     i64::MIN    ,   -999_999_998),
909            (line!(), i64::MIN + 1,   -999_999_998,     i64::MIN + 1,   -999_999_998),
910            (line!(), i64::MAX    ,              0,     i64::MAX    ,              0),
911            (line!(), i64::MAX - 1,              0,     i64::MAX - 1,              0),
912            (line!(), i64::MAX    ,             -1,     i64::MAX - 1,    999_999_999),
913            (line!(), i64::MAX    ,  1_000_000_000,     i64::MAX    ,    999_999_999),
914            (line!(), i64::MAX - 1,  1_000_000_000,     i64::MAX    ,              0),
915            (line!(), i64::MAX - 2,  1_000_000_000,     i64::MAX - 1,              0),
916            (line!(), i64::MAX    ,  1_999_999_998,     i64::MAX    ,    999_999_999),
917            (line!(), i64::MAX - 1,  1_999_999_998,     i64::MAX    ,    999_999_998),
918            (line!(), i64::MAX - 2,  1_999_999_998,     i64::MAX - 1,    999_999_998),
919            (line!(), i64::MAX    ,  1_999_999_999,     i64::MAX    ,    999_999_999),
920            (line!(), i64::MAX - 1,  1_999_999_999,     i64::MAX    ,    999_999_999),
921            (line!(), i64::MAX - 2,  1_999_999_999,     i64::MAX - 1,    999_999_999),
922            (line!(), i64::MAX    ,  2_000_000_000,     i64::MAX    ,    999_999_999),
923            (line!(), i64::MAX - 1,  2_000_000_000,     i64::MAX    ,    999_999_999),
924            (line!(), i64::MAX - 2,  2_000_000_000,     i64::MAX    ,              0),
925            (line!(), i64::MAX    ,    999_999_998,     i64::MAX    ,    999_999_998),
926            (line!(), i64::MAX - 1,    999_999_998,     i64::MAX - 1,    999_999_998),
927        ];
928
929        for case in cases.into_iter() {
930            let (line, seconds, nanos, canonical_seconds, canonical_nanos) = case;
931            let mut test_duration = Duration { seconds, nanos };
932            let is_canonical = test_duration.is_canonical();
933            test_duration.normalize();
934            assert_eq!(is_canonical, test_duration == Duration { seconds, nanos });
935
936            assert_eq!(
937                test_duration,
938                Duration {
939                    seconds: canonical_seconds,
940                    nanos: canonical_nanos,
941                },
942                "test case on line {line} doesn't match",
943            );
944        }
945    }
946
947    #[cfg(feature = "std")]
948    #[test]
949    fn check_timestamp_normalize() {
950        // Make sure that `Timestamp::normalize` behaves correctly on and near overflow.
951        #[rustfmt::skip] // Don't mangle the table formatting.
952        let cases = [
953            // --- Table of test cases ---
954            //        test seconds      test nanos  expected seconds  expected nanos
955            (line!(),            0,              0,                0,              0),
956            (line!(),            1,              1,                1,              1),
957            (line!(),           -1,             -1,               -2,    999_999_999),
958            (line!(),            0,    999_999_999,                0,    999_999_999),
959            (line!(),            0,   -999_999_999,               -1,              1),
960            (line!(),            0,  1_000_000_000,                1,              0),
961            (line!(),            0, -1_000_000_000,               -1,              0),
962            (line!(),            0,  1_000_000_001,                1,              1),
963            (line!(),            0, -1_000_000_001,               -2,    999_999_999),
964            (line!(),           -1,              1,               -1,              1),
965            (line!(),            1,             -1,                0,    999_999_999),
966            (line!(),           -1,  1_000_000_000,                0,              0),
967            (line!(),            1, -1_000_000_000,                0,              0),
968            (line!(), i64::MIN    ,              0,     i64::MIN    ,              0),
969            (line!(), i64::MIN + 1,              0,     i64::MIN + 1,              0),
970            (line!(), i64::MIN    ,              1,     i64::MIN    ,              1),
971            (line!(), i64::MIN    ,  1_000_000_000,     i64::MIN + 1,              0),
972            (line!(), i64::MIN    , -1_000_000_000,     i64::MIN    ,              0),
973            (line!(), i64::MIN + 1, -1_000_000_000,     i64::MIN    ,              0),
974            (line!(), i64::MIN + 2, -1_000_000_000,     i64::MIN + 1,              0),
975            (line!(), i64::MIN    , -1_999_999_998,     i64::MIN    ,              0),
976            (line!(), i64::MIN + 1, -1_999_999_998,     i64::MIN    ,              0),
977            (line!(), i64::MIN + 2, -1_999_999_998,     i64::MIN    ,              2),
978            (line!(), i64::MIN    , -1_999_999_999,     i64::MIN    ,              0),
979            (line!(), i64::MIN + 1, -1_999_999_999,     i64::MIN    ,              0),
980            (line!(), i64::MIN + 2, -1_999_999_999,     i64::MIN    ,              1),
981            (line!(), i64::MIN    , -2_000_000_000,     i64::MIN    ,              0),
982            (line!(), i64::MIN + 1, -2_000_000_000,     i64::MIN    ,              0),
983            (line!(), i64::MIN + 2, -2_000_000_000,     i64::MIN    ,              0),
984            (line!(), i64::MIN    ,   -999_999_998,     i64::MIN    ,              0),
985            (line!(), i64::MIN + 1,   -999_999_998,     i64::MIN    ,              2),
986            (line!(), i64::MAX    ,              0,     i64::MAX    ,              0),
987            (line!(), i64::MAX - 1,              0,     i64::MAX - 1,              0),
988            (line!(), i64::MAX    ,             -1,     i64::MAX - 1,    999_999_999),
989            (line!(), i64::MAX    ,  1_000_000_000,     i64::MAX    ,    999_999_999),
990            (line!(), i64::MAX - 1,  1_000_000_000,     i64::MAX    ,              0),
991            (line!(), i64::MAX - 2,  1_000_000_000,     i64::MAX - 1,              0),
992            (line!(), i64::MAX    ,  1_999_999_998,     i64::MAX    ,    999_999_999),
993            (line!(), i64::MAX - 1,  1_999_999_998,     i64::MAX    ,    999_999_998),
994            (line!(), i64::MAX - 2,  1_999_999_998,     i64::MAX - 1,    999_999_998),
995            (line!(), i64::MAX    ,  1_999_999_999,     i64::MAX    ,    999_999_999),
996            (line!(), i64::MAX - 1,  1_999_999_999,     i64::MAX    ,    999_999_999),
997            (line!(), i64::MAX - 2,  1_999_999_999,     i64::MAX - 1,    999_999_999),
998            (line!(), i64::MAX    ,  2_000_000_000,     i64::MAX    ,    999_999_999),
999            (line!(), i64::MAX - 1,  2_000_000_000,     i64::MAX    ,    999_999_999),
1000            (line!(), i64::MAX - 2,  2_000_000_000,     i64::MAX    ,              0),
1001            (line!(), i64::MAX    ,    999_999_998,     i64::MAX    ,    999_999_998),
1002            (line!(), i64::MAX - 1,    999_999_998,     i64::MAX - 1,    999_999_998),
1003        ];
1004
1005        for case in cases.into_iter() {
1006            let (line, seconds, nanos, canonical_seconds, canonical_nanos) = case;
1007            let mut test_timestamp = Timestamp { seconds, nanos };
1008            let is_canonical = test_timestamp.is_canonical();
1009            test_timestamp.normalize();
1010            assert_eq!(is_canonical, test_timestamp == Timestamp { seconds, nanos });
1011
1012            assert_eq!(
1013                test_timestamp,
1014                Timestamp {
1015                    seconds: canonical_seconds,
1016                    nanos: canonical_nanos,
1017                },
1018                "test case on line {line} doesn't match",
1019            );
1020        }
1021    }
1022}