mesh_protobuf/
time.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Time types for mesh protobuf encoding.
5
6use crate::DecodeError;
7use crate::MessageDecode;
8use crate::MessageEncode;
9use crate::inplace::InplaceOption;
10use crate::inplace_some;
11use crate::protobuf::MessageReader;
12use crate::protobuf::MessageSizer;
13use crate::protobuf::MessageWriter;
14use crate::protofile::DescribeField;
15use crate::protofile::FieldType;
16use crate::protofile::MessageDescription;
17use crate::table::DescribeTable;
18use crate::table::TableEncoder;
19use core::time::Duration;
20use mesh_protobuf::Protobuf;
21#[cfg(feature = "std")]
22use thiserror::Error;
23
24const NANOS_PER_SEC: u32 = 1_000_000_000;
25
26impl DescribeTable for Timestamp {
27    const DESCRIPTION: MessageDescription<'static> = MessageDescription::External {
28        name: "google.protobuf.Timestamp",
29        import_path: "google/protobuf/timestamp.proto",
30    };
31}
32
33/// A timestamp representing a point in UTC time with nanosecond resolution.
34#[derive(Debug, Protobuf, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
35pub struct Timestamp {
36    /// The number of seconds of UTC time since the Unix epoch.
37    #[mesh(1, encoding = "mesh_protobuf::encoding::VarintField")]
38    pub seconds: i64,
39    /// Non-negative fractions of a second at nanosecond resolution.
40    #[mesh(2, encoding = "mesh_protobuf::encoding::VarintField")]
41    pub nanos: i32,
42}
43
44#[cfg(feature = "std")]
45impl From<std::time::SystemTime> for Timestamp {
46    fn from(value: std::time::SystemTime) -> Self {
47        match value.duration_since(std::time::UNIX_EPOCH) {
48            Ok(since_epoch) => Self {
49                seconds: since_epoch.as_secs() as i64,
50                nanos: since_epoch.subsec_nanos() as i32,
51            },
52            Err(err) => {
53                let since_epoch = err.duration();
54                if since_epoch.subsec_nanos() == 0 {
55                    Self {
56                        seconds: -(since_epoch.as_secs() as i64),
57                        nanos: 0,
58                    }
59                } else {
60                    Self {
61                        seconds: -(since_epoch.as_secs() as i64) - 1,
62                        nanos: (1_000_000_000 - since_epoch.subsec_nanos()) as i32,
63                    }
64                }
65            }
66        }
67    }
68}
69
70#[cfg(feature = "std")]
71#[derive(Debug, Error)]
72#[error("timestamp out of range for system time")]
73pub struct TimestampOutOfRange;
74
75#[cfg(feature = "std")]
76impl TryFrom<Timestamp> for std::time::SystemTime {
77    type Error = TimestampOutOfRange;
78
79    fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
80        if value.nanos < 0 || value.nanos >= NANOS_PER_SEC as i32 {
81            return Err(TimestampOutOfRange);
82        }
83        if value.seconds >= 0 {
84            std::time::SystemTime::UNIX_EPOCH
85                .checked_add(Duration::new(value.seconds as u64, value.nanos as u32))
86        } else {
87            let secs = value.seconds.checked_neg().ok_or(TimestampOutOfRange)? as u64;
88            if value.nanos == 0 {
89                std::time::SystemTime::UNIX_EPOCH.checked_sub(Duration::new(secs, 0))
90            } else {
91                std::time::SystemTime::UNIX_EPOCH
92                    .checked_sub(Duration::new(secs - 1, NANOS_PER_SEC - value.nanos as u32))
93            }
94        }
95        .ok_or(TimestampOutOfRange)
96    }
97}
98
99/// Protobuf-compatible encoding for [`Duration`].
100pub struct DurationEncoding;
101
102impl DescribeField<Duration> for DurationEncoding {
103    const FIELD_TYPE: FieldType<'static> = FieldType::builtin("google.protobuf.Duration");
104}
105
106impl<R> MessageEncode<Duration, R> for DurationEncoding {
107    fn write_message(item: Duration, writer: MessageWriter<'_, '_, R>) {
108        TableEncoder::write_message((item.as_secs(), item.subsec_nanos()), writer);
109    }
110
111    fn compute_message_size(item: &mut Duration, sizer: MessageSizer<'_>) {
112        <TableEncoder as MessageEncode<_, R>>::compute_message_size(
113            &mut (item.as_secs(), item.subsec_nanos()),
114            sizer,
115        );
116    }
117}
118
119impl<R> MessageDecode<'_, Duration, R> for DurationEncoding {
120    fn read_message(
121        item: &mut InplaceOption<'_, Duration>,
122        reader: MessageReader<'_, '_, R>,
123    ) -> crate::Result<()> {
124        let duration = item.take().unwrap_or_default();
125        let message = (duration.as_secs(), duration.subsec_nanos());
126        inplace_some!(message);
127        TableEncoder::read_message(&mut message, reader)?;
128        let (secs, nanos) = message.take().unwrap();
129        if (secs as i64) < 0 || nanos >= NANOS_PER_SEC {
130            return Err(DecodeError::DurationRange.into());
131        }
132        item.set(Duration::new(secs, nanos));
133        Ok(())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139
140    #[cfg(feature = "std")]
141    #[test]
142    fn test_timestamp_system_time() {
143        use super::Timestamp;
144        use std::time::SystemTime;
145
146        let check = |st: SystemTime| {
147            let st2 = SystemTime::try_from(Timestamp::from(st)).unwrap();
148            assert_eq!(st, st2);
149        };
150
151        check(SystemTime::now());
152        check(SystemTime::now() + core::time::Duration::from_secs(1));
153        check(SystemTime::now() - core::time::Duration::from_secs(1));
154        check(SystemTime::UNIX_EPOCH - core::time::Duration::from_nanos(1_500_000_000));
155        check(SystemTime::UNIX_EPOCH + core::time::Duration::from_nanos(1_500_000_000));
156
157        assert_eq!(
158            Timestamp::from(
159                SystemTime::UNIX_EPOCH - core::time::Duration::from_nanos(1_500_000_000)
160            ),
161            Timestamp {
162                seconds: -2,
163                nanos: 500_000_000,
164            }
165        );
166    }
167}