1use core::{
4 fmt,
5 ops::{Add, Sub},
6 str::FromStr,
7 time::Duration,
8};
9
10use serde::{Deserialize, Serialize};
11use tendermint_proto::{google::protobuf::Timestamp, serializers::timestamp, Protobuf};
12use time::{
13 format_description::well_known::Rfc3339,
14 macros::{datetime, offset},
15 OffsetDateTime, PrimitiveDateTime,
16};
17
18use crate::{error::Error, prelude::*};
19
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
37#[serde(try_from = "Timestamp", into = "Timestamp")]
38pub struct Time(PrimitiveDateTime);
39
40impl Protobuf<Timestamp> for Time {}
41
42impl TryFrom<Timestamp> for Time {
43 type Error = Error;
44
45 fn try_from(value: Timestamp) -> Result<Self, Error> {
46 let nanos = value
47 .nanos
48 .try_into()
49 .map_err(|_| Error::timestamp_nanos_out_of_range())?;
50 Self::from_unix_timestamp(value.seconds, nanos)
51 }
52}
53
54impl From<Time> for Timestamp {
55 fn from(value: Time) -> Self {
56 let t = value.0.assume_utc();
57 let seconds = t.unix_timestamp();
58 let nanos = t.nanosecond() as i32;
61 Timestamp { seconds, nanos }
62 }
63}
64
65impl Time {
66 #[cfg(feature = "clock")]
67 pub fn now() -> Time {
68 OffsetDateTime::now_utc().try_into().unwrap()
69 }
70
71 fn from_utc(t: OffsetDateTime) -> Result<Self, Error> {
75 debug_assert_eq!(t.offset(), offset!(UTC));
76 match t.year() {
77 1..=9999 => Ok(Self(PrimitiveDateTime::new(t.date(), t.time()))),
78 _ => Err(Error::date_out_of_range()),
79 }
80 }
81
82 pub fn unix_epoch() -> Self {
84 Self(datetime!(1970-01-01 00:00:00))
85 }
86
87 pub fn from_unix_timestamp(secs: i64, nanos: u32) -> Result<Self, Error> {
88 if nanos > 999_999_999 {
89 return Err(Error::timestamp_nanos_out_of_range());
90 }
91 let total_nanos = secs as i128 * 1_000_000_000 + nanos as i128;
92 match OffsetDateTime::from_unix_timestamp_nanos(total_nanos) {
93 Ok(odt) => Self::from_utc(odt),
94 _ => Err(Error::timestamp_conversion()),
95 }
96 }
97
98 pub fn duration_since(&self, other: Time) -> Result<Duration, Error> {
101 let duration = self.0.assume_utc() - other.0.assume_utc();
102 duration
103 .try_into()
104 .map_err(|_| Error::duration_out_of_range())
105 }
106
107 pub fn parse_from_rfc3339(s: &str) -> Result<Self, Error> {
109 let date = OffsetDateTime::parse(s, &Rfc3339)
110 .map_err(Error::time_parse)?
111 .to_offset(offset!(UTC));
112 Self::from_utc(date)
113 }
114
115 pub fn to_rfc3339(&self) -> String {
117 timestamp::to_rfc3339_nanos(self.0.assume_utc())
118 }
119
120 pub fn unix_timestamp(&self) -> i64 {
122 self.0.assume_utc().unix_timestamp()
123 }
124
125 pub fn unix_timestamp_nanos(&self) -> i128 {
127 self.0.assume_utc().unix_timestamp_nanos()
128 }
129
130 pub fn checked_add(self, duration: Duration) -> Option<Self> {
132 let duration = duration.try_into().ok()?;
133 let t = self.0.checked_add(duration)?;
134 Self::from_utc(t.assume_utc()).ok()
135 }
136
137 pub fn checked_sub(self, duration: Duration) -> Option<Self> {
139 let duration = duration.try_into().ok()?;
140 let t = self.0.checked_sub(duration)?;
141 Self::from_utc(t.assume_utc()).ok()
142 }
143
144 pub fn before(&self, other: Time) -> bool {
146 self.0.assume_utc() < other.0.assume_utc()
147 }
148
149 pub fn after(&self, other: Time) -> bool {
151 self.0.assume_utc() > other.0.assume_utc()
152 }
153}
154
155impl fmt::Display for Time {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
157 timestamp::fmt_as_rfc3339_nanos(self.0.assume_utc(), f)
158 }
159}
160
161impl FromStr for Time {
162 type Err = Error;
163
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
165 Self::parse_from_rfc3339(s)
166 }
167}
168
169impl TryFrom<OffsetDateTime> for Time {
170 type Error = Error;
171
172 fn try_from(t: OffsetDateTime) -> Result<Time, Error> {
173 Self::from_utc(t.to_offset(offset!(UTC)))
174 }
175}
176
177impl From<Time> for OffsetDateTime {
178 fn from(t: Time) -> OffsetDateTime {
179 t.0.assume_utc()
180 }
181}
182
183impl Add<Duration> for Time {
184 type Output = Result<Self, Error>;
185
186 fn add(self, rhs: Duration) -> Self::Output {
187 let duration = rhs.try_into().map_err(|_| Error::duration_out_of_range())?;
188 let t = self
189 .0
190 .checked_add(duration)
191 .ok_or_else(Error::duration_out_of_range)?;
192 Self::from_utc(t.assume_utc())
193 }
194}
195
196impl Sub<Duration> for Time {
197 type Output = Result<Self, Error>;
198
199 fn sub(self, rhs: Duration) -> Self::Output {
200 let duration = rhs.try_into().map_err(|_| Error::duration_out_of_range())?;
201 let t = self
202 .0
203 .checked_sub(duration)
204 .ok_or_else(Error::duration_out_of_range)?;
205 Self::from_utc(t.assume_utc())
206 }
207}
208
209pub trait ParseTimestamp {
211 fn parse_timestamp(&self) -> Result<Time, Error>;
213}
214
215#[cfg(test)]
216mod tests {
217 use proptest::{prelude::*, sample::select};
218 use tendermint_pbt_gen as pbt;
219 use time::{Date, Month::*};
220
221 use super::*;
222 use crate::error::ErrorDetail;
223
224 fn particular_rfc3339_timestamps() -> impl Strategy<Value = String> {
226 let strs: Vec<String> = vec![
227 "0001-01-01T00:00:00Z",
228 "9999-12-31T23:59:59.999999999Z",
229 "2020-09-14T16:33:54.21191421Z",
230 "2020-09-14T16:33:00Z",
231 "2020-09-14T16:33:00.1Z",
232 "2020-09-14T16:33:00.211914212Z",
233 "1970-01-01T00:00:00Z",
234 "2021-01-07T20:25:56.0455760Z",
235 "2021-01-07T20:25:57.039219Z",
236 "2021-01-07T20:25:58.03562100Z",
237 "2021-01-07T20:25:59.000955200Z",
238 "2021-01-07T20:26:04.0121030Z",
239 "2021-01-07T20:26:05.005096Z",
240 "2021-01-07T20:26:09.08488400Z",
241 "2021-01-07T20:26:11.0875340Z",
242 "2021-01-07T20:26:12.078268Z",
243 "2021-01-07T20:26:13.08074100Z",
244 "2021-01-07T20:26:15.079663000Z",
245 ]
246 .into_iter()
247 .map(String::from)
248 .collect();
249
250 select(strs)
251 }
252
253 fn particular_datetimes_out_of_range() -> impl Strategy<Value = OffsetDateTime> {
254 let dts = vec![
255 datetime!(0000-12-31 23:59:59.999999999 UTC),
256 datetime!(0001-01-01 00:00:00.999999999 +00:00:01),
257 Date::from_calendar_date(-1, October, 9)
258 .unwrap()
259 .midnight()
260 .assume_utc(),
261 ];
262 select(dts)
263 }
264
265 proptest! {
266 #[test]
267 fn can_parse_rfc3339_timestamps(stamp in pbt::time::arb_protobuf_safe_rfc3339_timestamp()) {
268 prop_assert!(stamp.parse::<Time>().is_ok())
269 }
270
271 #[test]
272 fn serde_from_value_is_the_inverse_of_to_value_within_reasonable_time_range(
273 datetime in pbt::time::arb_protobuf_safe_datetime()
274 ) {
275 let time: Time = datetime.try_into().unwrap();
278 let json_encoded_time = serde_json::to_value(time).unwrap();
279 let decoded_time: Time = serde_json::from_value(json_encoded_time).unwrap();
280 prop_assert_eq!(time, decoded_time);
281 }
282
283 #[test]
284 fn serde_of_rfc3339_timestamps_is_safe(
285 stamp in prop_oneof![
286 pbt::time::arb_protobuf_safe_rfc3339_timestamp(),
287 particular_rfc3339_timestamps(),
288 ]
289 ) {
290 let time: Time = stamp.parse().unwrap();
295 let json_encoded_time = serde_json::to_value(time).unwrap();
296 let decoded_time: Time = serde_json::from_value(json_encoded_time).unwrap();
297 prop_assert_eq!(time, decoded_time);
298 }
299
300 #[test]
301 fn conversion_unix_timestamp_is_safe(
302 stamp in prop_oneof![
303 pbt::time::arb_protobuf_safe_rfc3339_timestamp(),
304 particular_rfc3339_timestamps(),
305 ]
306 ) {
307 let time: Time = stamp.parse().unwrap();
308 let timestamp = time.unix_timestamp();
309 let parsed = Time::from_unix_timestamp(timestamp, 0).unwrap();
310 prop_assert_eq!(timestamp, parsed.unix_timestamp());
311 }
312
313 #[test]
314 fn conversion_from_datetime_succeeds_for_4_digit_ce_years(
315 datetime in prop_oneof![
316 pbt::time::arb_datetime_with_offset(),
317 particular_datetimes_out_of_range(),
318 ]
319 ) {
320 let res: Result<Time, _> = datetime.try_into();
321 match datetime.to_offset(offset!(UTC)).year() {
322 1 ..= 9999 => {
323 let t = res.unwrap();
324 let dt_converted_back: OffsetDateTime = t.into();
325 assert_eq!(dt_converted_back, datetime);
326 }
327 _ => {
328 let e = res.unwrap_err();
329 assert!(matches!(e.detail(), ErrorDetail::DateOutOfRange(_)))
330 }
331 }
332 }
333
334 #[test]
335 fn from_unix_timestamp_rejects_out_of_range_nanos(
336 datetime in pbt::time::arb_protobuf_safe_datetime(),
337 nanos in 1_000_000_000 ..= u32::MAX,
338 ) {
339 let secs = datetime.unix_timestamp();
340 let res = Time::from_unix_timestamp(secs, nanos);
341 let e = res.unwrap_err();
342 assert!(matches!(e.detail(), ErrorDetail::TimestampNanosOutOfRange(_)))
343 }
344 }
345
346 fn duration_from_nanos(whole_nanos: u128) -> Duration {
347 let secs: u64 = (whole_nanos / 1_000_000_000).try_into().unwrap();
348 let nanos = (whole_nanos % 1_000_000_000) as u32;
349 Duration::new(secs, nanos)
350 }
351
352 prop_compose! {
353 fn args_for_regular_add()
354 (t in pbt::time::arb_protobuf_safe_datetime())
355 (
356 t in Just(t),
357 d_nanos in 0 ..= (pbt::time::max_protobuf_time() - t).whole_nanoseconds() as u128,
358 ) -> (OffsetDateTime, Duration)
359 {
360 (t, duration_from_nanos(d_nanos))
361 }
362 }
363
364 prop_compose! {
365 fn args_for_regular_sub()
366 (t in pbt::time::arb_protobuf_safe_datetime())
367 (
368 t in Just(t),
369 d_nanos in 0 ..= (t - pbt::time::min_protobuf_time()).whole_nanoseconds() as u128,
370 ) -> (OffsetDateTime, Duration)
371 {
372 (t, duration_from_nanos(d_nanos))
373 }
374 }
375
376 prop_compose! {
377 fn args_for_overflowed_add()
378 (t in pbt::time::arb_protobuf_safe_datetime())
379 (
380 t in Just(t),
381 d_nanos in (
382 (pbt::time::max_protobuf_time() - t).whole_nanoseconds() as u128 + 1
383 ..=
384 Duration::MAX.as_nanos()
385 ),
386 ) -> (OffsetDateTime, Duration)
387 {
388 (t, duration_from_nanos(d_nanos))
389 }
390 }
391
392 prop_compose! {
393 fn args_for_overflowed_sub()
394 (t in pbt::time::arb_protobuf_safe_datetime())
395 (
396 t in Just(t),
397 d_nanos in (
398 (t - pbt::time::min_protobuf_time()).whole_nanoseconds() as u128 + 1
399 ..=
400 Duration::MAX.as_nanos()
401 ),
402 ) -> (OffsetDateTime, Duration)
403 {
404 (t, duration_from_nanos(d_nanos))
405 }
406 }
407
408 proptest! {
409 #[test]
410 fn checked_add_regular((dt, d) in args_for_regular_add()) {
411 let t: Time = dt.try_into().unwrap();
412 let t = t.checked_add(d).unwrap();
413 let res: OffsetDateTime = t.into();
414 assert_eq!(res, dt + d);
415 }
416
417 #[test]
418 fn checked_sub_regular((dt, d) in args_for_regular_sub()) {
419 let t: Time = dt.try_into().unwrap();
420 let t = t.checked_sub(d).unwrap();
421 let res: OffsetDateTime = t.into();
422 assert_eq!(res, dt - d);
423 }
424
425 #[test]
426 fn checked_add_overflow((dt, d) in args_for_overflowed_add()) {
427 let t: Time = dt.try_into().unwrap();
428 assert_eq!(t.checked_add(d), None);
429 }
430
431 #[test]
432 fn checked_sub_overflow((dt, d) in args_for_overflowed_sub()) {
433 let t: Time = dt.try_into().unwrap();
434 assert_eq!(t.checked_sub(d), None);
435 }
436 }
437}