Skip to main content

proto_types/timestamp/
mod.rs

1// Most of the content of this file is taken from (prost-types)[https://github.com/tokio-rs/prost/blob/master/prost-types/src], licensed under the Apache-2.0 license.
2// Modifications have been applied to make casting operations more explicit.
3
4#[cfg(feature = "serde")]
5mod serde;
6
7mod timestamp_conversions;
8mod timestamp_impls;
9mod timestamp_operations;
10
11use super::*;
12use crate::{
13	Timestamp,
14	constants::{NANOS_PER_SECOND, PACKAGE_PREFIX},
15	datetime_internal::DateTime,
16};
17
18impl Timestamp {
19	/// Normalizes the timestamp to a canonical format.
20	///
21	/// Based on [`google::protobuf::util::CreateNormalized`][1].
22	///
23	/// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
24	pub fn normalize(&mut self) {
25		// Make sure nanos is in the range.
26		if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
27			if let Some(seconds) = self
28				.seconds
29				.checked_add(i64::from(self.nanos / NANOS_PER_SECOND))
30			{
31				self.seconds = seconds;
32
33				self.nanos %= NANOS_PER_SECOND;
34			} else if self.nanos < 0 {
35				// Negative overflow! Set to the earliest normal value.
36
37				self.seconds = i64::MIN;
38
39				self.nanos = 0;
40			} else {
41				// Positive overflow! Set to the latest normal value.
42
43				self.seconds = i64::MAX;
44
45				self.nanos = 999_999_999;
46			}
47		}
48
49		// For Timestamp nanos should be in the range [0, 999999999].
50
51		if self.nanos < 0 {
52			if let Some(seconds) = self.seconds.checked_sub(1) {
53				self.seconds = seconds;
54
55				self.nanos += NANOS_PER_SECOND;
56			} else {
57				// Negative overflow! Set to the earliest normal value.
58
59				debug_assert_eq!(self.seconds, i64::MIN);
60
61				self.nanos = 0;
62			}
63		}
64
65		// TODO: should this be checked?
66
67		// debug_assert!(self.seconds >= -62_135_596_800 && self.seconds <= 253_402_300_799,
68
69		//               "invalid timestamp: {:?}", self);
70	}
71
72	/// Normalizes the timestamp to a canonical format, returning the original value if it cannot be
73	/// normalized.
74	///
75	/// Normalization is based on [`google::protobuf::util::CreateNormalized`][1].
76	///
77	/// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
78	pub fn try_normalize(mut self) -> Result<Self, Self> {
79		let before = self;
80
81		self.normalize();
82
83		// If the seconds value has changed, and is either i64::MIN or i64::MAX, then the timestamp
84
85		// normalization overflowed.
86
87		if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
88		{
89			Err(before)
90		} else {
91			Ok(self)
92		}
93	}
94
95	/// Return a normalized copy of the timestamp to a canonical format.
96	///
97	/// Based on [`google::protobuf::util::CreateNormalized`][1].
98	///
99	/// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L59-L77
100	#[must_use]
101	#[inline]
102	pub fn normalized(&self) -> Self {
103		let mut result = *self;
104
105		result.normalize();
106
107		result
108	}
109
110	#[inline]
111	/// Creates a new `Timestamp` at the start of the provided UTC date.
112	pub fn date(year: i64, month: u8, day: u8) -> Result<Self, TimestampError> {
113		Self::date_time_nanos(year, month, day, 0, 0, 0, 0)
114	}
115
116	#[inline]
117	/// Creates a new `Timestamp` instance with the provided UTC date and time.
118	pub fn date_time(
119		year: i64,
120
121		month: u8,
122
123		day: u8,
124
125		hour: u8,
126
127		minute: u8,
128
129		second: u8,
130	) -> Result<Self, TimestampError> {
131		Self::date_time_nanos(year, month, day, hour, minute, second, 0)
132	}
133
134	#[inline]
135	/// Creates a new `Timestamp` instance with the provided UTC date and time.
136	pub fn date_time_nanos(
137		year: i64,
138
139		month: u8,
140
141		day: u8,
142
143		hour: u8,
144
145		minute: u8,
146
147		second: u8,
148
149		nanos: u32,
150	) -> Result<Self, TimestampError> {
151		let date_time = DateTime {
152			year,
153
154			month,
155
156			day,
157
158			hour,
159
160			minute,
161
162			second,
163
164			nanos,
165		};
166
167		Self::try_from(date_time)
168	}
169}
170
171impl Name for Timestamp {
172	const PACKAGE: &'static str = PACKAGE_PREFIX;
173
174	const NAME: &'static str = "Timestamp";
175
176	fn type_url() -> String {
177		type_url_for::<Self>()
178	}
179}
180
181#[cfg(feature = "std")]
182impl From<std::time::SystemTime> for Timestamp {
183	fn from(system_time: std::time::SystemTime) -> Self {
184		let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
185			Ok(duration) => {
186				let seconds = i64::try_from(duration.as_secs()).unwrap_or_default();
187
188				// SAFETY: Safe due to the standard library's implementation
189				(seconds, duration.subsec_nanos().cast_signed())
190			}
191
192			Err(error) => {
193				let duration = error.duration();
194
195				let seconds = i64::try_from(duration.as_secs()).unwrap_or_default();
196
197				// SAFETY: Safe due to the standard library's implementation
198				let nanos = duration.subsec_nanos().cast_signed();
199
200				if nanos == 0 {
201					(-seconds, 0)
202				} else {
203					(-seconds - 1, 1_000_000_000 - nanos)
204				}
205			}
206		};
207
208		Self { seconds, nanos }
209	}
210}
211
212/// A timestamp handling error.
213#[derive(Debug, PartialEq, Eq, Clone)]
214#[non_exhaustive]
215pub enum TimestampError {
216	/// Indicates that a [`Timestamp`] could not be converted to
217	/// [`SystemTime`][std::time::SystemTime] because it is out of range.
218	///
219	/// The range of times that can be represented by `SystemTime` depends on the platform. All
220	/// `Timestamp`s are likely representable on 64-bit Unix-like platforms, but other platforms,
221	/// such as Windows and 32-bit Linux, may not be able to represent the full range of
222	/// `Timestamp`s.
223	OutOfSystemRange(Timestamp),
224	/// An error indicating failure to parse a timestamp in RFC-3339 format.
225	ParseFailure,
226	/// Indicates an error when constructing a timestamp due to invalid date or time data.
227	InvalidDateTime,
228}
229
230impl fmt::Display for TimestampError {
231	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232		match self {
233			Self::OutOfSystemRange(timestamp) => {
234				write!(
235					f,
236					"{timestamp} is not representable as a `SystemTime` because it is out of range"
237				)
238			}
239
240			Self::ParseFailure => {
241				write!(f, "failed to parse RFC-3339 formatted timestamp")
242			}
243
244			Self::InvalidDateTime => {
245				write!(f, "invalid date or time")
246			}
247		}
248	}
249}
250
251impl core::error::Error for TimestampError {}
252
253#[cfg(feature = "std")]
254impl TryFrom<Timestamp> for std::time::SystemTime {
255	type Error = TimestampError;
256
257	fn try_from(mut timestamp: Timestamp) -> Result<Self, Self::Error> {
258		let orig_timestamp = timestamp;
259
260		timestamp.normalize();
261
262		let system_time = if timestamp.seconds >= 0 {
263			std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(
264				timestamp
265					.seconds
266					.try_into()
267					.map_err(|_| TimestampError::OutOfSystemRange(timestamp))?,
268			))
269		} else {
270			std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
271				timestamp
272					.seconds
273					.checked_neg()
274					.and_then(|s| s.try_into().ok())
275					.ok_or(TimestampError::OutOfSystemRange(timestamp))?,
276			))
277		};
278
279		let system_time = system_time
280			.map(|time| {
281				let nanos = u64::try_from(timestamp.nanos)
282					.map_err(|_| TimestampError::OutOfSystemRange(timestamp))?;
283
284				time.checked_add(core::time::Duration::from_nanos(nanos))
285					.ok_or(TimestampError::OutOfSystemRange(timestamp))
286			})
287			.transpose()?;
288
289		system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
290	}
291}
292
293impl FromStr for Timestamp {
294	type Err = TimestampError;
295
296	fn from_str(s: &str) -> Result<Self, TimestampError> {
297		datetime_internal::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
298	}
299}
300
301impl fmt::Display for Timestamp {
302	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
303		core::fmt::Display::fmt(&DateTime::from(*self), f)
304	}
305}