1use std::collections::BTreeMap;
4use std::fmt::Display;
5use std::io::Read;
6use std::ops::{Add, Sub};
7use std::str::FromStr;
8
9use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
10use chrono::ParseError;
11pub use chrono::{DateTime, Duration, TimeZone, Utc};
12use namada_macros::BorshDeserializer;
13#[cfg(feature = "migrations")]
14use namada_migrations::*;
15use serde::{Deserialize, Serialize};
16
17#[allow(clippy::arithmetic_side_effects)]
19pub fn duration_passed(
20 current: DateTimeUtc,
21 start: DateTimeUtc,
22 duration: DurationSecs,
23) -> bool {
24 start + duration <= current
25}
26
27#[derive(
29 Clone,
30 Copy,
31 Debug,
32 PartialEq,
33 Eq,
34 PartialOrd,
35 Ord,
36 Hash,
37 Serialize,
38 Deserialize,
39 BorshSerialize,
40 BorshDeserialize,
41 BorshDeserializer,
42 BorshSchema,
43)]
44pub struct DurationSecs(pub u64);
45
46impl From<Duration> for DurationSecs {
47 fn from(duration_chrono: Duration) -> Self {
48 let duration_std = duration_chrono
49 .to_std()
50 .expect("Duration must not be negative");
51 duration_std.into()
52 }
53}
54
55impl From<std::time::Duration> for DurationSecs {
56 fn from(duration_std: std::time::Duration) -> Self {
57 DurationSecs(duration_std.as_secs())
58 }
59}
60
61impl From<DurationSecs> for std::time::Duration {
62 fn from(duration_secs: DurationSecs) -> Self {
63 std::time::Duration::new(duration_secs.0, 0)
64 }
65}
66
67impl Display for DurationSecs {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(f, "{}", self.0)
70 }
71}
72
73#[derive(
75 Clone,
76 Copy,
77 Debug,
78 PartialEq,
79 Eq,
80 PartialOrd,
81 Ord,
82 Hash,
83 Serialize,
84 Deserialize,
85 BorshSerialize,
86 BorshDeserialize,
87 BorshDeserializer,
88 BorshSchema,
89)]
90pub struct DurationNanos {
91 pub secs: u64,
93 pub nanos: u32,
95}
96
97impl From<std::time::Duration> for DurationNanos {
98 fn from(duration_std: std::time::Duration) -> Self {
99 DurationNanos {
100 secs: duration_std.as_secs(),
101 nanos: duration_std.subsec_nanos(),
102 }
103 }
104}
105
106impl From<DurationNanos> for std::time::Duration {
107 fn from(DurationNanos { secs, nanos }: DurationNanos) -> Self {
108 Self::new(secs, nanos)
109 }
110}
111
112#[derive(
114 Clone,
115 Debug,
116 Deserialize,
117 Serialize,
118 BorshDeserialize,
119 BorshDeserializer,
120 BorshSerialize,
121 PartialEq,
122 Eq,
123 PartialOrd,
124 Ord,
125 Hash,
126)]
127pub struct Rfc3339String(pub String);
128
129#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
131#[derive(
132 Clone,
133 Copy,
134 Debug,
135 Default,
136 PartialEq,
137 Eq,
138 PartialOrd,
139 Ord,
140 Hash,
141 serde::Serialize,
142 serde::Deserialize,
143 BorshDeserializer,
144)]
145#[serde(try_from = "Rfc3339String", into = "Rfc3339String")]
146pub struct DateTimeUtc(pub DateTime<Utc>);
147
148pub const MIN_UTC: DateTimeUtc = DateTimeUtc(chrono::DateTime::<Utc>::MIN_UTC);
150
151impl Display for DateTimeUtc {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "{}", self.to_rfc3339())
154 }
155}
156
157impl DateTimeUtc {
158 const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S%.9f+00:00";
159
160 pub fn now() -> Self {
162 Self(
163 #[allow(clippy::disallowed_methods)]
164 Utc::now(),
165 )
166 }
167
168 #[inline]
170 pub fn to_unix_timestamp(&self) -> i64 {
171 self.0.timestamp()
172 }
173
174 #[inline]
176 pub fn from_unix_timestamp(timestamp: i64) -> Option<Self> {
177 Some(Self(chrono::DateTime::<Utc>::from_timestamp(timestamp, 0)?))
178 }
179
180 #[inline]
182 pub fn unix_epoch() -> Self {
183 Self::from_unix_timestamp(0)
184 .expect("This operation should be infallible")
185 }
186
187 pub fn to_rfc3339(&self) -> String {
189 self.0.format(DateTimeUtc::FORMAT).to_string()
190 }
191
192 pub fn from_rfc3339(s: &str) -> Result<Self, ParseError> {
194 use chrono::format;
195 use chrono::format::strftime::StrftimeItems;
196
197 let format = StrftimeItems::new(Self::FORMAT);
198 let mut parsed = format::Parsed::new();
199 format::parse(&mut parsed, s, format)?;
200
201 parsed.to_datetime_with_timezone(&chrono::Utc).map(Self)
202 }
203
204 #[allow(clippy::arithmetic_side_effects)]
206 pub fn next_second(&self) -> Self {
207 *self + DurationSecs(1)
208 }
209
210 #[allow(clippy::arithmetic_side_effects)]
213 pub fn time_diff(&self, earlier: DateTimeUtc) -> DurationSecs {
214 (self.0 - earlier.0)
215 .to_std()
216 .map(DurationSecs::from)
217 .unwrap_or(DurationSecs(0))
218 }
219}
220
221impl FromStr for DateTimeUtc {
222 type Err = ParseError;
223
224 #[inline]
225 fn from_str(s: &str) -> Result<Self, Self::Err> {
226 Self::from_rfc3339(s)
227 }
228}
229
230impl Add<DurationSecs> for DateTimeUtc {
231 type Output = DateTimeUtc;
232
233 #[allow(clippy::arithmetic_side_effects)]
234 fn add(self, duration: DurationSecs) -> Self::Output {
235 let duration_std = std::time::Duration::from_secs(duration.0);
236 let duration_chrono = Duration::from_std(duration_std).expect(
237 "Duration shouldn't be larger than the maximum value supported \
238 for chrono::Duration",
239 );
240 (self.0 + duration_chrono).into()
241 }
242}
243
244impl Add<Duration> for DateTimeUtc {
245 type Output = DateTimeUtc;
246
247 #[allow(clippy::arithmetic_side_effects)]
248 fn add(self, rhs: Duration) -> Self::Output {
249 (self.0 + rhs).into()
250 }
251}
252
253impl Sub<Duration> for DateTimeUtc {
254 type Output = DateTimeUtc;
255
256 #[allow(clippy::arithmetic_side_effects)]
257 fn sub(self, rhs: Duration) -> Self::Output {
258 (self.0 - rhs).into()
259 }
260}
261
262impl Sub<DateTimeUtc> for DateTimeUtc {
263 type Output = DurationSecs;
264
265 #[allow(clippy::arithmetic_side_effects)]
266 fn sub(self, rhs: DateTimeUtc) -> Self::Output {
267 (self.0 - rhs.0)
268 .to_std()
269 .map(DurationSecs::from)
270 .unwrap_or(DurationSecs(0))
271 }
272}
273
274impl BorshSerialize for DateTimeUtc {
275 fn serialize<W: std::io::Write>(
276 &self,
277 writer: &mut W,
278 ) -> std::io::Result<()> {
279 let raw = self.to_rfc3339();
280 BorshSerialize::serialize(&raw, writer)
281 }
282}
283
284impl BorshDeserialize for DateTimeUtc {
285 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
286 use std::io::{Error, ErrorKind};
287 let raw: String = BorshDeserialize::deserialize_reader(reader)?;
288 Self::from_rfc3339(&raw)
289 .map_err(|err| Error::new(ErrorKind::InvalidData, err))
290 }
291}
292
293impl BorshSchema for DateTimeUtc {
294 fn add_definitions_recursively(
295 definitions: &mut BTreeMap<
296 borsh::schema::Declaration,
297 borsh::schema::Definition,
298 >,
299 ) {
300 let fields =
302 borsh::schema::Fields::UnnamedFields(vec!["string".into()]);
303 let definition = borsh::schema::Definition::Struct { fields };
304 definitions.insert(Self::declaration(), definition);
305 }
306
307 fn declaration() -> borsh::schema::Declaration {
308 "DateTimeUtc".into()
309 }
310}
311
312impl From<DateTime<Utc>> for DateTimeUtc {
313 fn from(dt: DateTime<Utc>) -> Self {
314 Self(dt)
315 }
316}
317
318impl TryFrom<prost_types::Timestamp> for DateTimeUtc {
319 type Error = prost_types::TimestampError;
320
321 fn try_from(
322 timestamp: prost_types::Timestamp,
323 ) -> Result<Self, Self::Error> {
324 let system_time: std::time::SystemTime = timestamp.try_into()?;
325 Ok(Self(system_time.into()))
326 }
327}
328
329impl From<DateTimeUtc> for prost_types::Timestamp {
330 fn from(dt: DateTimeUtc) -> Self {
331 let seconds = dt.0.timestamp();
332 #[allow(clippy::cast_possible_wrap)]
334 let nanos = dt.0.timestamp_subsec_nanos() as i32;
335 prost_types::Timestamp { seconds, nanos }
336 }
337}
338
339impl TryFrom<crate::tendermint_proto::google::protobuf::Timestamp>
340 for DateTimeUtc
341{
342 type Error = prost_types::TimestampError;
343
344 fn try_from(
345 timestamp: crate::tendermint_proto::google::protobuf::Timestamp,
346 ) -> Result<Self, Self::Error> {
347 Self::try_from(prost_types::Timestamp {
348 seconds: timestamp.seconds,
349 nanos: timestamp.nanos,
350 })
351 }
352}
353
354impl From<DateTimeUtc> for std::time::SystemTime {
355 fn from(dt: DateTimeUtc) -> Self {
356 dt.0.into()
357 }
358}
359
360impl TryFrom<Rfc3339String> for DateTimeUtc {
361 type Error = chrono::ParseError;
362
363 fn try_from(str: Rfc3339String) -> Result<Self, Self::Error> {
364 Self::from_rfc3339(&str.0)
365 }
366}
367
368impl From<DateTimeUtc> for Rfc3339String {
369 fn from(dt: DateTimeUtc) -> Self {
370 Self(dt.to_rfc3339())
371 }
372}
373
374impl TryFrom<DateTimeUtc> for crate::tendermint::time::Time {
375 type Error = crate::tendermint::Error;
376
377 fn try_from(dt: DateTimeUtc) -> Result<Self, Self::Error> {
378 Self::parse_from_rfc3339(&dt.to_rfc3339())
379 }
380}
381
382impl TryFrom<crate::tendermint::time::Time> for DateTimeUtc {
383 type Error = prost_types::TimestampError;
384
385 fn try_from(t: crate::tendermint::time::Time) -> Result<Self, Self::Error> {
386 crate::tendermint_proto::google::protobuf::Timestamp::from(t).try_into()
387 }
388}
389
390impl From<crate::tendermint::Timeout> for DurationNanos {
391 fn from(val: crate::tendermint::Timeout) -> Self {
392 Self::from(std::time::Duration::from(val))
393 }
394}
395
396impl From<DurationNanos> for crate::tendermint::Timeout {
397 fn from(val: DurationNanos) -> Self {
398 Self::from(std::time::Duration::from(val))
399 }
400}
401
402#[cfg(any(test, feature = "testing"))]
403pub mod test_utils {
404 pub const GENESIS_TIME: &str = "2023-08-30T00:00:00.000000000+00:00";
408}
409
410#[cfg(test)]
411mod core_time_tests {
412 use proptest::prelude::*;
413
414 use super::*;
415
416 proptest! {
417 #[test]
418 fn test_valid_reverse_datetime_utc_encoding_roundtrip(
419 year in 1974..=3_000,
420 month in 1..=12,
421 day in 1..=28,
422 hour in 0..=23,
423 min in 0..=59,
424 sec in 0..=59,
425 nanos in 0..=999_999_999,
426 )
427 {
428 let timestamp = format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}.{nanos:09}+00:00");
429 println!("Testing timestamp: {timestamp}");
430 test_valid_reverse_datetime_utc_encoding_roundtrip_inner(×tamp);
431 }
432 }
433
434 fn test_valid_reverse_datetime_utc_encoding_roundtrip_inner(
435 timestamp: &str,
436 ) {
437 let datetime = DateTimeUtc::from_rfc3339(timestamp).unwrap();
439
440 let datetime_inner = DateTime::parse_from_rfc3339(timestamp)
443 .unwrap()
444 .with_timezone(&Utc);
445 assert_eq!(datetime, DateTimeUtc(datetime_inner));
446
447 let encoded = datetime.to_rfc3339();
448
449 assert_eq!(encoded, timestamp);
450 }
451
452 #[test]
453 fn test_invalid_datetime_utc_encoding() {
454 const TIMESTAMP: &str = "1966-03-03T00:06:56.520Z";
458 assert!(DateTime::parse_from_rfc3339(TIMESTAMP).is_ok());
462
463 assert!(DateTimeUtc::from_rfc3339(TIMESTAMP).is_err());
465 }
466
467 #[test]
468 fn test_valid_test_utils_genesis_time() {
469 assert!(DateTimeUtc::from_rfc3339(test_utils::GENESIS_TIME).is_ok());
470 }
471}