cosmwasm_std/
timestamp.rs

1use core::fmt;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::Uint64;
6
7/// A point in time in nanosecond precision.
8///
9/// This type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.
10///
11/// ## Examples
12///
13/// ```
14/// # use cosmwasm_std::Timestamp;
15/// let ts = Timestamp::from_nanos(1_000_000_202);
16/// assert_eq!(ts.nanos(), 1_000_000_202);
17/// assert_eq!(ts.seconds(), 1);
18/// assert_eq!(ts.subsec_nanos(), 202);
19///
20/// let ts = ts.plus_seconds(2);
21/// assert_eq!(ts.nanos(), 3_000_000_202);
22/// assert_eq!(ts.seconds(), 3);
23/// assert_eq!(ts.subsec_nanos(), 202);
24/// ```
25#[derive(
26    Serialize, Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
27)]
28pub struct Timestamp(Uint64);
29
30impl Timestamp {
31    /// Creates a timestamp from nanoseconds since epoch
32    pub const fn from_nanos(nanos_since_epoch: u64) -> Self {
33        Timestamp(Uint64::new(nanos_since_epoch))
34    }
35
36    /// Creates a timestamp from seconds since epoch
37    pub const fn from_seconds(seconds_since_epoch: u64) -> Self {
38        Timestamp(Uint64::new(seconds_since_epoch * 1_000_000_000))
39    }
40
41    /// Adds the given amount of days to the timestamp and
42    /// returns the result. The original value remains unchanged.
43    ///
44    /// Panics if the result exceeds the value range of [`Timestamp`].
45    #[must_use = "this returns the result of the operation, without modifying the original"]
46    #[inline]
47    pub const fn plus_days(&self, addition: u64) -> Timestamp {
48        self.plus_hours(addition * 24)
49    }
50
51    /// Adds the given amount of hours to the timestamp and
52    /// returns the result. The original value remains unchanged.
53    ///
54    /// Panics if the result exceeds the value range of [`Timestamp`].
55    #[must_use = "this returns the result of the operation, without modifying the original"]
56    #[inline]
57    pub const fn plus_hours(&self, addition: u64) -> Timestamp {
58        self.plus_minutes(addition * 60)
59    }
60
61    /// Adds the given amount of minutes to the timestamp and
62    /// returns the result. The original value remains unchanged.
63    ///
64    /// Panics if the result exceeds the value range of [`Timestamp`].
65    #[must_use = "this returns the result of the operation, without modifying the original"]
66    #[inline]
67    pub const fn plus_minutes(&self, addition: u64) -> Timestamp {
68        self.plus_seconds(addition * 60)
69    }
70
71    /// Adds the given amount of seconds to the timestamp and
72    /// returns the result. The original value remains unchanged.
73    ///
74    /// Panics if the result exceeds the value range of [`Timestamp`].
75    #[must_use = "this returns the result of the operation, without modifying the original"]
76    #[inline]
77    pub const fn plus_seconds(&self, addition: u64) -> Timestamp {
78        self.plus_nanos(addition * 1_000_000_000)
79    }
80
81    /// Adds the given amount of nanoseconds to the timestamp and
82    /// returns the result. The original value remains unchanged.
83    ///
84    /// Panics if the result exceeds the value range of [`Timestamp`].
85    #[must_use = "this returns the result of the operation, without modifying the original"]
86    // no #[inline] here as this could be shared with all the callers
87    pub const fn plus_nanos(&self, addition: u64) -> Timestamp {
88        let nanos = self.0.strict_add(Uint64::new(addition));
89        Timestamp(nanos)
90    }
91
92    /// Subtracts the given amount of days from the timestamp and
93    /// returns the result. The original value remains unchanged.
94    ///
95    /// Panics if the result is not >= 0. I.e. times before epoch cannot be represented.
96    #[must_use = "this returns the result of the operation, without modifying the original"]
97    #[inline]
98    pub const fn minus_days(&self, subtrahend: u64) -> Timestamp {
99        self.minus_hours(subtrahend * 24)
100    }
101
102    /// Subtracts the given amount of hours from the timestamp and
103    /// returns the result. The original value remains unchanged.
104    ///
105    /// Panics if the result is not >= 0. I.e. times before epoch cannot be represented.
106    #[must_use = "this returns the result of the operation, without modifying the original"]
107    #[inline]
108    pub const fn minus_hours(&self, subtrahend: u64) -> Timestamp {
109        self.minus_minutes(subtrahend * 60)
110    }
111
112    /// Subtracts the given amount of minutes from the timestamp and
113    /// returns the result. The original value remains unchanged.
114    ///
115    /// Panics if the result is not >= 0. I.e. times before epoch cannot be represented.
116    #[must_use = "this returns the result of the operation, without modifying the original"]
117    #[inline]
118    pub const fn minus_minutes(&self, subtrahend: u64) -> Timestamp {
119        self.minus_seconds(subtrahend * 60)
120    }
121
122    /// Subtracts the given amount of seconds from the timestamp and
123    /// returns the result. The original value remains unchanged.
124    ///
125    /// Panics if the result is not >= 0. I.e. times before epoch cannot be represented.
126    #[must_use = "this returns the result of the operation, without modifying the original"]
127    #[inline]
128    pub const fn minus_seconds(&self, subtrahend: u64) -> Timestamp {
129        self.minus_nanos(subtrahend * 1_000_000_000)
130    }
131
132    /// Subtracts the given amount of nanoseconds from the timestamp and
133    /// returns the result. The original value remains unchanged.
134    ///
135    /// Panics if the result is not >= 0. I.e. times before epoch cannot be represented.
136    #[must_use = "this returns the result of the operation, without modifying the original"]
137    // no #[inline] here as this could be shared with all the callers
138    pub const fn minus_nanos(&self, subtrahend: u64) -> Timestamp {
139        Timestamp(self.0.strict_sub(Uint64::new(subtrahend)))
140    }
141
142    /// Returns nanoseconds since epoch
143    #[inline]
144    pub fn nanos(&self) -> u64 {
145        self.0.u64()
146    }
147
148    /// Returns seconds since epoch (truncate nanoseconds)
149    #[inline]
150    pub fn seconds(&self) -> u64 {
151        self.0.u64() / 1_000_000_000
152    }
153
154    /// Returns nanoseconds since the last whole second (the remainder truncated
155    /// by `seconds()`)
156    #[inline]
157    pub fn subsec_nanos(&self) -> u64 {
158        self.0.u64() % 1_000_000_000
159    }
160}
161
162impl fmt::Display for Timestamp {
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        let whole = self.seconds();
165        let fractional = self.subsec_nanos();
166        write!(f, "{whole}.{fractional:09}")
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn timestamp_from_nanos() {
176        let t = Timestamp::from_nanos(123);
177        assert_eq!(t.0.u64(), 123);
178        let t = Timestamp::from_nanos(0);
179        assert_eq!(t.0.u64(), 0);
180    }
181
182    #[test]
183    fn timestamp_from_seconds() {
184        let t = Timestamp::from_seconds(123);
185        assert_eq!(t.0.u64(), 123_000_000_000);
186        let t = Timestamp::from_seconds(0);
187        assert_eq!(t.0.u64(), 0);
188    }
189
190    #[test]
191    fn timestamp_plus_seconds() {
192        let sum = Timestamp::from_nanos(123).plus_seconds(42);
193        assert_eq!(sum.0.u64(), 42_000_000_123);
194        let sum = Timestamp::from_nanos(123).plus_seconds(0);
195        assert_eq!(sum.0.u64(), 123);
196    }
197
198    #[test]
199    fn timestamp_plus_nanos() {
200        let sum = Timestamp::from_nanos(123).plus_nanos(3);
201        assert_eq!(sum.0.u64(), 126);
202        let sum = Timestamp::from_nanos(123).plus_nanos(0);
203        assert_eq!(sum.0.u64(), 123);
204    }
205
206    #[test]
207    #[should_panic(expected = "attempt to add with overflow")]
208    fn timestamp_plus_nanos_panics_on_overflow() {
209        let max = Timestamp::from_nanos(u64::MAX);
210        let _earlier = max.plus_nanos(20);
211    }
212
213    #[test]
214    fn timestamp_minus_seconds() {
215        let earlier = Timestamp::from_seconds(123).minus_seconds(0);
216        assert_eq!(earlier.0.u64(), 123_000_000_000);
217        let earlier = Timestamp::from_seconds(123).minus_seconds(3);
218        assert_eq!(earlier.0.u64(), 120_000_000_000);
219        let earlier = Timestamp::from_seconds(123).minus_seconds(123);
220        assert_eq!(earlier.0.u64(), 0);
221    }
222
223    #[test]
224    #[should_panic(expected = "attempt to subtract with overflow")]
225    fn timestamp_minus_seconds_panics_on_overflow() {
226        let _earlier = Timestamp::from_seconds(100).minus_seconds(101);
227    }
228
229    #[test]
230    fn timestamp_minus_nanos() {
231        let earlier = Timestamp::from_seconds(123).minus_nanos(0);
232        assert_eq!(earlier.0.u64(), 123_000_000_000);
233        let earlier = Timestamp::from_seconds(123).minus_nanos(3);
234        assert_eq!(earlier.0.u64(), 122_999_999_997);
235        let earlier = Timestamp::from_seconds(123).minus_nanos(123_000_000_000);
236        assert_eq!(earlier.0.u64(), 0);
237    }
238
239    #[test]
240    #[should_panic(expected = "attempt to subtract with overflow")]
241    fn timestamp_minus_nanos_panics_on_overflow() {
242        let _earlier = Timestamp::from_nanos(100).minus_nanos(101);
243    }
244
245    #[test]
246    fn timestamp_plus_days() {
247        let ts = Timestamp::from_seconds(123).plus_days(0);
248        assert_eq!(ts.0.u64(), 123_000_000_000);
249        let ts = Timestamp::from_seconds(123).plus_days(10);
250        assert_eq!(ts.0.u64(), 864_123_000_000_000);
251    }
252
253    #[test]
254    fn timestamp_minus_days() {
255        let ts = Timestamp::from_seconds(123).minus_days(0);
256        assert_eq!(ts.0.u64(), 123_000_000_000);
257        let ts = Timestamp::from_seconds(2 * 86400 + 123).minus_days(1);
258        assert_eq!(ts.0.u64(), 86_523_000_000_000);
259        let ts = Timestamp::from_seconds(86400).minus_days(1);
260        assert_eq!(ts.0.u64(), 0);
261    }
262
263    #[test]
264    fn timestamp_plus_hours() {
265        let ts = Timestamp::from_seconds(123).plus_hours(0);
266        assert_eq!(ts.0.u64(), 123_000_000_000);
267        let ts = Timestamp::from_seconds(123).plus_hours(2);
268        assert_eq!(ts.0.u64(), 123_000_000_000 + 60 * 60 * 2 * 1_000_000_000);
269    }
270
271    #[test]
272    fn timestamp_minus_hours() {
273        let ts = Timestamp::from_seconds(2 * 60 * 60).minus_hours(0);
274        assert_eq!(ts.0.u64(), 2 * 60 * 60 * 1_000_000_000);
275        let ts = Timestamp::from_seconds(2 * 60 * 60 + 123).minus_hours(1);
276        assert_eq!(ts.0.u64(), 60 * 60 * 1_000_000_000 + 123_000_000_000);
277    }
278
279    #[test]
280    fn timestamp_plus_minutes() {
281        let ts = Timestamp::from_seconds(123).plus_minutes(0);
282        assert_eq!(ts.0.u64(), 123_000_000_000);
283        let ts = Timestamp::from_seconds(123).plus_minutes(2);
284        assert_eq!(ts.0.u64(), 123_000_000_000 + 60 * 2 * 1_000_000_000);
285    }
286
287    #[test]
288    fn timestamp_minus_minutes() {
289        let ts = Timestamp::from_seconds(5 * 60).minus_minutes(0);
290        assert_eq!(ts.0.u64(), 5 * 60 * 1_000_000_000);
291        let ts = Timestamp::from_seconds(5 * 60 + 123).minus_minutes(1);
292        assert_eq!(ts.0.u64(), 4 * 60 * 1_000_000_000 + 123_000_000_000);
293    }
294
295    #[test]
296    fn timestamp_nanos() {
297        let sum = Timestamp::from_nanos(123);
298        assert_eq!(sum.nanos(), 123);
299        let sum = Timestamp::from_nanos(0);
300        assert_eq!(sum.nanos(), 0);
301        let sum = Timestamp::from_nanos(987654321000);
302        assert_eq!(sum.nanos(), 987654321000);
303    }
304
305    #[test]
306    fn timestamp_seconds() {
307        let sum = Timestamp::from_nanos(987654321000);
308        assert_eq!(sum.seconds(), 987);
309        let sum = Timestamp::from_seconds(1234567).plus_nanos(8765436);
310        assert_eq!(sum.seconds(), 1234567);
311    }
312
313    #[test]
314    fn timestamp_subsec_nanos() {
315        let sum = Timestamp::from_nanos(987654321000);
316        assert_eq!(sum.subsec_nanos(), 654321000);
317        let sum = Timestamp::from_seconds(1234567).plus_nanos(8765436);
318        assert_eq!(sum.subsec_nanos(), 8765436);
319    }
320
321    #[test]
322    fn timestamp_implements_display() {
323        let embedded = format!("Time: {}", Timestamp::from_nanos(0));
324        assert_eq!(embedded, "Time: 0.000000000");
325        let embedded = format!("Time: {}", Timestamp::from_nanos(1));
326        assert_eq!(embedded, "Time: 0.000000001");
327        let embedded = format!("Time: {}", Timestamp::from_nanos(10));
328        assert_eq!(embedded, "Time: 0.000000010");
329        let embedded = format!("Time: {}", Timestamp::from_nanos(100));
330        assert_eq!(embedded, "Time: 0.000000100");
331        let embedded = format!("Time: {}", Timestamp::from_nanos(1000));
332        assert_eq!(embedded, "Time: 0.000001000");
333        let embedded = format!("Time: {}", Timestamp::from_nanos(10000));
334        assert_eq!(embedded, "Time: 0.000010000");
335        let embedded = format!("Time: {}", Timestamp::from_nanos(100000));
336        assert_eq!(embedded, "Time: 0.000100000");
337        let embedded = format!("Time: {}", Timestamp::from_nanos(1000000));
338        assert_eq!(embedded, "Time: 0.001000000");
339        let embedded = format!("Time: {}", Timestamp::from_nanos(1000000));
340        assert_eq!(embedded, "Time: 0.001000000");
341        let embedded = format!("Time: {}", Timestamp::from_nanos(10000000));
342        assert_eq!(embedded, "Time: 0.010000000");
343        let embedded = format!("Time: {}", Timestamp::from_nanos(100000000));
344        assert_eq!(embedded, "Time: 0.100000000");
345        let embedded = format!("Time: {}", Timestamp::from_nanos(1000000000));
346        assert_eq!(embedded, "Time: 1.000000000");
347        let embedded = format!("Time: {}", Timestamp::from_nanos(10000000000));
348        assert_eq!(embedded, "Time: 10.000000000");
349        let embedded = format!("Time: {}", Timestamp::from_nanos(100000000000));
350        assert_eq!(embedded, "Time: 100.000000000");
351    }
352}