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