1use 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#[derive(Debug, Protobuf, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
35pub struct Timestamp {
36 #[mesh(1, encoding = "mesh_protobuf::encoding::VarintField")]
38 pub seconds: i64,
39 #[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
99pub 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}