bilrost_types/
lib.rs

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