Skip to main content

jiff_core/
timestamp.rs

1use crate::{
2    bounds::{self as b, RangeError},
3    civil::DateTime,
4    constants as c,
5    macros::{rbail, rtry, unwrapr},
6    tz::Offset,
7};
8
9/// An instant in time represented as the number of nanoseconds since the Unix
10/// epoch.
11///
12/// A timestamp is always in the Unix timescale with a UTC offset of zero.
13#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
14#[cfg_attr(feature = "defmt", derive(defmt::Format))]
15pub struct Timestamp {
16    second: i64,
17    nanosecond: i32,
18}
19
20impl Timestamp {
21    /// The minimum allow Unix timestamp.
22    pub const MIN: Timestamp = Timestamp {
23        second: b::UnixEpochSeconds::MIN,
24        nanosecond: b::SubsecNanosecond::MIN,
25    };
26
27    /// The maximum allow Unix timestamp.
28    pub const MAX: Timestamp = Timestamp {
29        second: b::UnixEpochSeconds::MAX,
30        nanosecond: b::SubsecNanosecond::MAX,
31    };
32
33    /// The Unix epoch represented as a timestamp.
34    pub const UNIX_EPOCH: Timestamp = Timestamp { second: 0, nanosecond: 0 };
35
36    /// Create a new timestamp from the given number of seconds and its
37    /// sub-second component.
38    ///
39    /// This returns an error if `nanos` is not in the range specified by
40    /// [`SignedSubsecNanosecond`](b::SignedSubsecNanosecond). An error
41    /// is also returned when `secs` is not in the range specified by
42    /// [`UnixEpochSeconds`](b::UnixEpochSeconds).
43    #[inline]
44    pub const fn new(secs: i64, nanos: i32) -> Result<Timestamp, RangeError> {
45        let mut secs = rtry!(b::UnixEpochSeconds::checkc(secs));
46        let mut nanos = rtry!(b::SignedSubsecNanosecond::checkc(nanos as i64));
47        if secs == b::UnixEpochSeconds::MIN && nanos < 0 {
48            rbail!(b::UnixEpochSeconds::error());
49        }
50        // At this point, we're done if either unit is zero or if they have the
51        // same sign.
52        if nanos == 0 || secs == 0 || secs.signum() == (nanos.signum() as i64)
53        {
54            return Ok(Timestamp::new_unchecked(secs, nanos));
55        }
56        // Otherwise, the only work we have to do is to balance negative nanos
57        // into positive seconds, or positive nanos into negative seconds.
58        if secs < 0 {
59            debug_assert!(nanos > 0);
60            // Never wraps because adding +1 to a negative i64 never overflows.
61            //
62            // MSRV(1.79): Consider using `unchecked_add` here.
63            secs += 1;
64            // Never wraps because subtracting +1_000_000_000 from a positive
65            // i32 never overflows.
66            //
67            // MSRV(1.79): Consider using `unchecked_sub` here.
68            nanos -= c::NANOS_PER_SEC_32;
69        } else {
70            debug_assert!(secs > 0);
71            debug_assert!(nanos < 0);
72            // Never wraps because subtracting +1 from a positive i64 never
73            // overflows.
74            //
75            // MSRV(1.79): Consider using `unchecked_add` here.
76            secs -= 1;
77            // Never wraps because adding +1_000_000_000 to a negative i32
78            // never overflows.
79            //
80            // MSRV(1.79): Consider using `unchecked_add` here.
81            nanos += c::NANOS_PER_SEC_32;
82        }
83        Ok(Timestamp::new_unchecked(secs, nanos))
84    }
85
86    /// Creates a new `Timestamp` value in a `const` context.
87    ///
88    /// This is identical to [`Timestamp::new`], except that it panics when
89    /// `Timestamp::new` would return an error. This can be more convenient in
90    /// a `const` context where unwrapping a `Result` is not ergonomic.
91    #[inline]
92    pub const fn constant(second: i64, nanosecond: i32) -> Timestamp {
93        unwrapr!(Timestamp::new(second, nanosecond), "invalid timestamp")
94    }
95
96    /// Creates a new `Timestamp` without bounds checks.
97    ///
98    /// Note that this should not be made public *and* safe.
99    #[inline]
100    pub(crate) const fn new_unchecked(secs: i64, nanos: i32) -> Timestamp {
101        debug_assert!(b::UnixEpochSeconds::checkc(secs).is_ok());
102        debug_assert!(b::SignedSubsecNanosecond::checkc(nanos as i64).is_ok());
103        debug_assert!(secs != b::UnixEpochSeconds::MIN || nanos >= 0);
104        debug_assert!(
105            nanos == 0
106                || secs == 0
107                || secs.signum() == (nanos.signum() as i64)
108        );
109        Timestamp { second: secs, nanosecond: nanos }
110    }
111
112    /// Constructs a timestamp from seconds since the Unix epoch.
113    ///
114    /// This is preferred to [`Timestamp::new`] when it is known that the
115    /// sub-second component is always `0`. In particular, this generates
116    /// less code and is likely to be faster.
117    ///
118    /// An error is returned when `second` is not in the range specified by
119    /// [`UnixEpochSeconds`](b::UnixEpochSeconds).
120    #[inline]
121    pub const fn from_second(second: i64) -> Result<Timestamp, RangeError> {
122        let second = rtry!(b::UnixEpochSeconds::checkc(second));
123        Ok(Timestamp::new_unchecked(second, 0))
124    }
125
126    /// Constructs a timestamp from milliseconds since the Unix epoch.
127    ///
128    /// An error is returned when `millisecond` is not in the range specified by
129    /// [`UnixEpochMilliseconds`](b::UnixEpochMilliseconds).
130    #[inline]
131    pub const fn from_millisecond(
132        millisecond: i64,
133    ) -> Result<Timestamp, RangeError> {
134        let millisecond = rtry!(b::UnixEpochMilliseconds::checkc(millisecond));
135        // OK because MILLIS_PER_SEC!={-1,0}.
136        let secs = millisecond / c::MILLIS_PER_SEC;
137        // OK because MILLIS_PER_SEC!={-1,0} and because
138        // millis % MILLIS_PER_SEC can be at most 999, and 999 * 1_000_000
139        // never overflows i32.
140        let nanos =
141            (millisecond % c::MILLIS_PER_SEC) as i32 * c::NANOS_PER_MILLI_32;
142        // OK because we've already verified that `millisecond` is in range.
143        Ok(Timestamp::new_unchecked(secs, nanos))
144    }
145
146    /// Constructs a timestamp from microseconds since the Unix epoch.
147    ///
148    /// An error is returned when `microsecond` is not in the range specified
149    /// by [`UnixEpochMicroseconds`](b::UnixEpochMicroseconds).
150    #[inline]
151    pub const fn from_microsecond(
152        microsecond: i64,
153    ) -> Result<Timestamp, RangeError> {
154        let microsecond = rtry!(b::UnixEpochMicroseconds::checkc(microsecond));
155        // OK because MILLIS_PER_SEC!={-1,0}.
156        let secs = microsecond / c::MICROS_PER_SEC;
157        // OK because MILLIS_PER_SEC!={-1,0} and because
158        // millis % MILLIS_PER_SEC can be at most 999, and 999 * 1_000_000
159        // never overflows i32.
160        let nanos =
161            (microsecond % c::MICROS_PER_SEC) as i32 * c::NANOS_PER_MICRO_32;
162        // OK because we've already verified that `millisecond` is in range.
163        Ok(Timestamp::new_unchecked(secs, nanos))
164    }
165
166    /// Constructs a timestamp from nanoseconds since the Unix epoch.
167    ///
168    /// An error is returned when `nanosecond` refers to a timestamp outside
169    /// of the range [`Timestamp::MIN`] to [`Timestamp::MAX`].
170    #[inline]
171    pub const fn from_nanosecond(
172        nanosecond: i128,
173    ) -> Result<Timestamp, RangeError> {
174        const NANOS_PER_SEC: i128 = c::NANOS_PER_SEC as i128;
175        // OK because NANOS_PER_SEC!={-1,0}.
176        let secs = nanosecond / NANOS_PER_SEC;
177        // RUST: Use `i64::try_from` when available in `const`.
178        if !(i64::MIN as i128 <= secs && secs <= i64::MAX as i128) {
179            rbail!(b::SpecialBoundsError::UnixEpochNanoseconds);
180        }
181        let secs64 = secs as i64;
182        // OK because NANOS_PER_SEC!={-1,0}.
183        let nanosecond = (nanosecond % NANOS_PER_SEC) as i32;
184        Ok(Timestamp::new_unchecked(secs64, nanosecond))
185    }
186
187    /// Returns this timestamp as a number of seconds since the Unix epoch.
188    ///
189    /// This only returns the number of whole seconds. That is, if there are
190    /// any fractional seconds in this timestamp, then they are truncated.
191    #[inline]
192    pub const fn as_second(self) -> i64 {
193        self.second
194    }
195
196    /// Returns this timestamp as a number of milliseconds since the Unix
197    /// epoch.
198    ///
199    /// This only returns the number of whole milliseconds. That is, if there
200    /// are any fractional milliseconds in this timestamp, then they are
201    /// truncated.
202    #[inline]
203    pub const fn as_millisecond(self) -> i64 {
204        // OK because the range of `Timestamp` guarantees that its
205        // representation as milliseconds fits into an i64.
206        let millis = self.as_second() * c::MILLIS_PER_SEC;
207        // OK because subsec_millis maxes out at 999, and adding that to
208        // b::UnixSeconds::MAX*1_000 will never overflow an i64.
209        millis + (self.subsec_millisecond() as i64)
210    }
211
212    /// Returns this timestamp as a number of microseconds since the Unix
213    /// epoch.
214    ///
215    /// This only returns the number of whole microseconds. That is, if there
216    /// are any fractional microseconds in this timestamp, then they are
217    /// truncated.
218    #[inline]
219    pub const fn as_microsecond(self) -> i64 {
220        // OK because the range of `Timestamp` guarantees that its
221        // representation as microseconds fits into an i64.
222        let micros = self.as_second() * c::MICROS_PER_SEC;
223        // OK because subsec_micros maxes out at 999_999, and adding that to
224        // b::UnixSeconds::MAX*1_000_000 will never overflow an i64.
225        micros + (self.subsec_microsecond() as i64)
226    }
227
228    /// Returns this timestamp as a number of nanoseconds since the Unix
229    /// epoch.
230    #[inline]
231    pub const fn as_nanosecond(self) -> i128 {
232        // OK because 1_000_000_000 times any i64 will never overflow i128.
233        let nanos = (self.second as i128) * (c::NANOS_PER_SEC as i128);
234        // OK because nanosecond maxes out at 999_999_999, and adding that to
235        // i64::MAX*1_000_000_000 will never overflow a i128.
236        nanos + (self.nanosecond as i128)
237    }
238
239    /// Returns the fractional second component of this timestamp in units of
240    /// microseconds.
241    ///
242    /// The value returned is negative when the timestamp is negative. It is
243    /// guaranteed that the range of the value returned is in the inclusive
244    /// range `-999_999..=999_999`.
245    #[inline]
246    pub const fn subsec_millisecond(&self) -> i32 {
247        // OK because NANOS_PER_MILLI!={-1,0}.
248        self.nanosecond / c::NANOS_PER_MILLI_32
249    }
250
251    /// Returns the fractional second component of this timestamp in units of
252    /// milliseconds.
253    ///
254    /// The value returned is negative when the timestamp is negative. It is
255    /// guaranteed that the range of the value returned is in the inclusive
256    /// range `-999..=999`.
257    #[inline]
258    pub const fn subsec_microsecond(&self) -> i32 {
259        // OK because NANOS_PER_MICRO!={-1,0}.
260        self.nanosecond / c::NANOS_PER_MICRO_32
261    }
262
263    /// Returns the fractional second component of this timestamp in units of
264    /// nanoseconds.
265    ///
266    /// The value returned is negative when the timestamp is negative. It is
267    /// guaranteed that the range of the value returned is in the inclusive
268    /// range `-999,999,999..=999,999,999`.
269    #[inline]
270    pub const fn subsec_nanosecond(&self) -> i32 {
271        self.nanosecond
272    }
273
274    /// Returns a number that represents the sign of this timestamp.
275    ///
276    /// * When [`Timestamp::is_zero`] is true, this returns `0`.
277    /// * When [`Timestamp::is_positive`] is true, this returns `1`.
278    /// * When [`Timestamp::is_negative`] is true, this returns `-1`.
279    ///
280    /// The above cases are mutually exclusive.
281    ///
282    /// # Example
283    ///
284    /// ```
285    /// use jiff_core::Timestamp;
286    ///
287    /// assert_eq!(0, Timestamp::UNIX_EPOCH.signum());
288    ///
289    /// let ts = Timestamp::new(5, -999_999_999).unwrap();
290    /// assert_eq!(ts.signum(), 1);
291    /// // The mixed signs were normalized away!
292    /// assert_eq!(ts.as_second(), 4);
293    /// assert_eq!(ts.subsec_nanosecond(), 1);
294    ///
295    /// // The same applies for negative timestamps.
296    /// let ts = Timestamp::new(-5, 999_999_999).unwrap();
297    /// assert_eq!(ts.signum(), -1);
298    /// assert_eq!(ts.as_second(), -4);
299    /// assert_eq!(ts.subsec_nanosecond(), -1);
300    /// ```
301    #[inline]
302    pub const fn signum(self) -> i8 {
303        if self.is_zero() {
304            0
305        } else if self.is_positive() {
306            1
307        } else {
308            debug_assert!(self.is_negative());
309            -1
310        }
311    }
312
313    /// Returns true if and only if this timestamp corresponds to the instant
314    /// in time known as the Unix epoch.
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// use jiff_core::Timestamp;
320    ///
321    /// assert!(Timestamp::UNIX_EPOCH.is_zero());
322    /// ```
323    #[inline]
324    pub const fn is_zero(self) -> bool {
325        self.second == 0 && self.nanosecond == 0
326    }
327
328    /// Returns true when this timestamp is positive. That is, after the Unix
329    /// epoch.
330    ///
331    /// # Example
332    ///
333    /// ```
334    /// use jiff_core::Timestamp;
335    ///
336    /// let ts = Timestamp::new(0, 1).unwrap();
337    /// assert!(ts.is_positive());
338    /// ```
339    #[inline]
340    pub const fn is_positive(&self) -> bool {
341        self.second.is_positive() || self.nanosecond.is_positive()
342    }
343
344    /// Returns true when this timestamp is negative. That is, before the Unix
345    /// epoch.
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// use jiff_core::Timestamp;
351    ///
352    /// let ts = Timestamp::new(0, -1).unwrap();
353    /// assert!(ts.is_negative());
354    /// ```
355    #[inline]
356    pub const fn is_negative(&self) -> bool {
357        self.second.is_negative() || self.nanosecond.is_negative()
358    }
359
360    /// Converts a Unix timestamp with an offset to a Gregorian datetime.
361    ///
362    /// The offset should correspond to the number of seconds required to
363    /// add to this timestamp to get the local time.
364    #[inline]
365    pub const fn to_datetime(&self, offset: Offset) -> DateTime {
366        offset.to_datetime(*self)
367    }
368
369    /// Add the given number of seconds and nanoseconds to this timestamp.
370    ///
371    /// If this would result in a timestamp outside of its boundaries, then
372    /// this returns an error.
373    ///
374    /// # Examples
375    ///
376    /// ```
377    /// use jiff_core::Timestamp;
378    ///
379    /// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
380    /// let ts = mkts(123, 0);
381    ///
382    /// assert_eq!(ts.checked_add(1, 0), Ok(mkts(124, 0)));
383    /// assert_eq!(ts.checked_add(1, 1), Ok(mkts(124, 1)));
384    /// assert_eq!(ts.checked_add(1, -1), Ok(mkts(123, 999_999_999)));
385    /// assert_eq!(ts.checked_add(0, 1), Ok(mkts(123, 1)));
386    /// assert_eq!(ts.checked_add(0, -1), Ok(mkts(122, 999_999_999)));
387    /// assert_eq!(ts.checked_add(-1, 0), Ok(mkts(122, 0)));
388    /// assert_eq!(ts.checked_add(-1, 1), Ok(mkts(122, 1)));
389    /// assert_eq!(ts.checked_add(-1, -1), Ok(mkts(121, 999_999_999)));
390    ///
391    /// assert_eq!(ts.checked_add(0, i32::MIN), Ok(mkts(121, -147_483_648)));
392    /// assert_eq!(ts.checked_add(1, i32::MIN), Ok(mkts(122, -147_483_648)));
393    /// assert_eq!(ts.checked_add(-1, i32::MIN), Ok(mkts(120, -147_483_648)));
394    /// assert_eq!(ts.checked_add(0, i32::MAX), Ok(mkts(125, 147_483_647)));
395    /// assert_eq!(ts.checked_add(1, i32::MAX), Ok(mkts(126, 147_483_647)));
396    /// assert_eq!(ts.checked_add(-1, i32::MAX), Ok(mkts(124, 147_483_647)));
397    ///
398    /// assert!(ts.checked_add(i64::MAX, 0).is_err());
399    /// assert!(ts.checked_add(i64::MIN, 0).is_err());
400    ///
401    /// let ts = Timestamp::UNIX_EPOCH;
402    /// let max = Timestamp::MAX.as_second();
403    /// assert!(ts.checked_add(max, 0).is_ok());
404    /// assert!(ts.checked_add(max + 1, 0).is_err());
405    /// assert!(ts.checked_add(max, 999_999_999).is_ok());
406    /// assert!(ts.checked_add(max, 1_000_000_000).is_err());
407    /// ```
408    #[inline]
409    pub const fn checked_add(
410        self,
411        mut seconds: i64,
412        mut nanos: i32,
413    ) -> Result<Timestamp, RangeError> {
414        // When |nanos| exceeds 1 second, we balance the excess up to seconds.
415        if !(-c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32) {
416            // Never wraps or panics because NANOS_PER_SEC!={0,-1}.
417            let addsecs = nanos / c::NANOS_PER_SEC_32;
418            seconds = match seconds.checked_add(addsecs as i64) {
419                Some(secs) => secs,
420                None => panic!(
421                    "nanoseconds overflowed seconds in SignedDuration::new"
422                ),
423            };
424            // Never wraps or panics because NANOS_PER_SEC!={0,-1}.
425            nanos = nanos % c::NANOS_PER_SEC_32;
426        }
427
428        self.checked_add_sensible(seconds, nanos)
429    }
430
431    /// Subtracts the given number of seconds and nanoseconds from this
432    /// timestamp.
433    ///
434    /// # Examples
435    ///
436    /// ```
437    /// use jiff_core::Timestamp;
438    ///
439    /// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
440    /// let ts = mkts(123, 0);
441    ///
442    /// assert_eq!(ts.checked_sub(1, 0), Ok(mkts(122, 0)));
443    /// assert_eq!(ts.checked_sub(1, 1), Ok(mkts(121, 999_999_999)));
444    /// assert_eq!(ts.checked_sub(1, -1), Ok(mkts(122, 1)));
445    /// assert_eq!(ts.checked_sub(0, 1), Ok(mkts(122, 999_999_999)));
446    /// assert_eq!(ts.checked_sub(0, -1), Ok(mkts(123, 1)));
447    /// assert_eq!(ts.checked_sub(-1, 0), Ok(mkts(124, 0)));
448    /// assert_eq!(ts.checked_sub(-1, 1), Ok(mkts(123, 999_999_999)));
449    /// assert_eq!(ts.checked_sub(-1, -1), Ok(mkts(124, 1)));
450    ///
451    /// assert_eq!(ts.checked_sub(0, i32::MIN), Ok(mkts(125, 147_483_648)));
452    /// assert_eq!(ts.checked_sub(1, i32::MIN), Ok(mkts(124, 147_483_648)));
453    /// assert_eq!(ts.checked_sub(-1, i32::MIN), Ok(mkts(126, 147_483_648)));
454    /// assert_eq!(ts.checked_sub(0, i32::MAX), Ok(mkts(121, -147_483_647)));
455    /// assert_eq!(ts.checked_sub(1, i32::MAX), Ok(mkts(120, -147_483_647)));
456    /// assert_eq!(ts.checked_sub(-1, i32::MAX), Ok(mkts(122, -147_483_647)));
457    ///
458    /// assert!(ts.checked_sub(i64::MAX, 0).is_err());
459    /// assert!(ts.checked_sub(i64::MIN, 0).is_err());
460    ///
461    /// let ts = Timestamp::UNIX_EPOCH;
462    /// let min = Timestamp::MIN.as_second();
463    /// assert!(ts.checked_sub(-min, 0).is_ok());
464    /// assert!(ts.checked_sub(-(min - 1), 0).is_err());
465    /// assert!(ts.checked_sub(-min, 999_999_999).is_ok());
466    /// assert!(ts.checked_sub(-min, 1_000_000_000).is_err());
467    /// ```
468    #[inline]
469    pub const fn checked_sub(
470        self,
471        seconds: i64,
472        mut nanos: i32,
473    ) -> Result<Timestamp, RangeError> {
474        let Some(mut seconds) = seconds.checked_neg() else {
475            rbail!(b::UnixEpochSeconds::error())
476        };
477
478        // When |nanos| exceeds 1 second, we balance the excess up to seconds.
479        if !(-c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32) {
480            // Never wraps or panics because NANOS_PER_SEC!={0,-1}.
481            let addsecs = nanos / c::NANOS_PER_SEC_32;
482            seconds = match seconds.checked_sub(addsecs as i64) {
483                Some(secs) => secs,
484                None => panic!(
485                    "nanoseconds overflowed seconds in SignedDuration::new"
486                ),
487            };
488            // Never wraps or panics because NANOS_PER_SEC!={0,-1}.
489            nanos = nanos % c::NANOS_PER_SEC_32;
490        }
491        // Negating `nanos` here is OK because the above guarantees that it's
492        // in the inclusive range `[-999_999_999, 999_999_999]`.
493        self.checked_add(seconds, -nanos)
494    }
495
496    /// Implementation of `checked_add` that assumes `|nanos| < 1 second`.
497    #[inline]
498    const fn checked_add_sensible(
499        self,
500        seconds: i64,
501        nanos: i32,
502    ) -> Result<Timestamp, RangeError> {
503        debug_assert!(
504            -c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32
505        );
506
507        let mut second =
508            rtry!(b::UnixEpochSeconds::checked_add(self.as_second(), seconds));
509        // OK because we know both are in the inclusive range
510        // [-999_999_999, 999_999_999] per above math.
511        let mut nanosecond = self.nanosecond + nanos;
512        // When the nanosecond component is zero, we can ignore it and just
513        // return seconds as-is.
514        if nanosecond == 0 {
515            return Ok(Timestamp { second, nanosecond });
516        }
517
518        if nanosecond >= c::NANOS_PER_SEC_32 {
519            nanosecond -= c::NANOS_PER_SEC_32;
520            second = rtry!(b::UnixEpochSeconds::checked_add(second, 1));
521        } else if nanosecond <= -c::NANOS_PER_SEC_32 {
522            nanosecond += c::NANOS_PER_SEC_32;
523            second = rtry!(b::UnixEpochSeconds::checked_add(second, -1));
524        }
525        if second != 0
526            && nanosecond != 0
527            && second.signum() != (nanosecond.signum() as i64)
528        {
529            if second < 0 {
530                debug_assert!(nanosecond > 0);
531                // OK because second<0.
532                second += 1;
533                // OK because nanosecond>0.
534                nanosecond -= c::NANOS_PER_SEC_32;
535            } else {
536                debug_assert!(second > 0);
537                debug_assert!(nanosecond < 0);
538                // OK because second>0.
539                second -= 1;
540                // OK because nanosecond<0.
541                nanosecond += c::NANOS_PER_SEC_32;
542            }
543        }
544        Ok(Timestamp { second, nanosecond })
545    }
546
547    /// Add the given number of seconds to this timestamp.
548    ///
549    /// If this would result in a timestamp outside of its boundaries, then
550    /// this returns an error.
551    ///
552    /// The nanosecond component of the timestamp returned is guaranteed to
553    /// match the nanosecond component of `self`.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// use jiff_core::Timestamp;
559    ///
560    /// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
561    ///
562    /// let ts = mkts(123, 0);
563    /// assert_eq!(ts.checked_add_seconds(0), Ok(mkts(123, 0)));
564    /// assert_eq!(ts.checked_add_seconds(1), Ok(mkts(124, 0)));
565    /// assert_eq!(ts.checked_add_seconds(-1), Ok(mkts(122, 0)));
566    ///
567    /// let ts = mkts(123, 999_999_999);
568    /// assert_eq!(ts.checked_add_seconds(0), Ok(mkts(123, 999_999_999)));
569    /// assert_eq!(ts.checked_add_seconds(1), Ok(mkts(124, 999_999_999)));
570    /// assert_eq!(ts.checked_add_seconds(-1), Ok(mkts(122, 999_999_999)));
571    ///
572    /// assert!(ts.checked_add_seconds(i64::MIN).is_err());
573    /// assert!(ts.checked_add_seconds(i64::MAX).is_err());
574    /// ```
575    #[inline]
576    pub const fn checked_add_seconds(
577        self,
578        seconds: i64,
579    ) -> Result<Timestamp, RangeError> {
580        let second =
581            rtry!(b::UnixEpochSeconds::checked_add(self.as_second(), seconds));
582        Ok(Timestamp { second, ..self })
583    }
584
585    /// Subtracts the given number of seconds from this timestamp.
586    #[inline]
587    pub const fn checked_sub_seconds(
588        self,
589        seconds: i64,
590    ) -> Result<Timestamp, RangeError> {
591        let Some(seconds) = seconds.checked_neg() else {
592            rbail!(b::UnixEpochSeconds::error())
593        };
594        self.checked_add_seconds(seconds)
595    }
596}
597
598impl core::fmt::Debug for Timestamp {
599    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
600        let dt = self.to_datetime(Offset::UTC);
601        core::fmt::Debug::fmt(&dt, f)?;
602        f.write_str("Z")
603    }
604}
605
606impl Default for Timestamp {
607    #[inline]
608    fn default() -> Timestamp {
609        Timestamp::UNIX_EPOCH
610    }
611}
612
613/// Adds a number of seconds to a `Timestamp`.
614///
615/// # Panics
616///
617/// When adding would result in a value outside the boundaries of a
618/// `Timestamp`.
619///
620/// # Example
621///
622/// ```
623/// use jiff_core::Timestamp;
624///
625/// let ts = Timestamp::new(123, 999_999_999).unwrap();;
626/// assert_eq!(ts + 400, Timestamp::new(523, 999_999_999).unwrap());
627/// ```
628impl core::ops::Add<i64> for Timestamp {
629    type Output = Timestamp;
630
631    fn add(self, seconds: i64) -> Timestamp {
632        self.checked_add_seconds(seconds).unwrap()
633    }
634}
635
636/// Adds a number of seconds and nanoseconds to a `Timestamp`.
637///
638/// # Panics
639///
640/// When adding would result in a value outside the boundaries of a
641/// `Timestamp`.
642///
643/// # Example
644///
645/// ```
646/// use jiff_core::Timestamp;
647///
648/// let ts = Timestamp::new(123, 999_999_999).unwrap();;
649/// assert_eq!(ts + (400, 1), Timestamp::new(524, 0).unwrap());
650/// ```
651impl core::ops::Add<(i64, i32)> for Timestamp {
652    type Output = Timestamp;
653
654    fn add(self, (seconds, nanoseconds): (i64, i32)) -> Timestamp {
655        self.checked_add(seconds, nanoseconds).unwrap()
656    }
657}
658
659/// Adds a number of seconds to a `Timestamp`.
660///
661/// # Panics
662///
663/// When adding would result in a value outside the boundaries of a
664/// `Timestamp`.
665impl core::ops::AddAssign<i64> for Timestamp {
666    #[inline]
667    fn add_assign(&mut self, rhs: i64) {
668        *self = *self + rhs;
669    }
670}
671
672/// Adds a number of seconds and nanoseconds to a `Timestamp`.
673///
674/// # Panics
675///
676/// When adding would result in a value outside the boundaries of a
677/// `Timestamp`.
678impl core::ops::AddAssign<(i64, i32)> for Timestamp {
679    #[inline]
680    fn add_assign(&mut self, rhs: (i64, i32)) {
681        *self = *self + rhs;
682    }
683}
684
685/// Subtracts a number of seconds from a `Timestamp`.
686///
687/// # Panics
688///
689/// When adding would result in a value outside the boundaries of a
690/// `Timestamp`.
691///
692/// # Example
693///
694/// ```
695/// use jiff_core::Timestamp;
696///
697/// let ts = Timestamp::new(523, 999_999_999).unwrap();;
698/// assert_eq!(ts - 400, Timestamp::new(123, 999_999_999).unwrap());
699/// ```
700impl core::ops::Sub<i64> for Timestamp {
701    type Output = Timestamp;
702
703    fn sub(self, seconds: i64) -> Timestamp {
704        self.checked_sub_seconds(seconds).unwrap()
705    }
706}
707
708/// Subtracts a number of seconds and nanoseconds from a `Timestamp`.
709///
710/// # Panics
711///
712/// When adding would result in a value outside the boundaries of a
713/// `Timestamp`.
714///
715/// # Example
716///
717/// ```
718/// use jiff_core::Timestamp;
719///
720/// let ts = Timestamp::new(523, 999_999_999).unwrap();;
721/// assert_eq!(ts - (400, 1), Timestamp::new(123, 999_999_998).unwrap());
722/// ```
723impl core::ops::Sub<(i64, i32)> for Timestamp {
724    type Output = Timestamp;
725
726    fn sub(self, (seconds, nanoseconds): (i64, i32)) -> Timestamp {
727        self.checked_sub(seconds, nanoseconds).unwrap()
728    }
729}
730
731/// Subtracts a number of seconds from a `Timestamp`.
732///
733/// # Panics
734///
735/// When subtracting would result in a value outside the boundaries of a
736/// `Timestamp`.
737impl core::ops::SubAssign<i64> for Timestamp {
738    #[inline]
739    fn sub_assign(&mut self, rhs: i64) {
740        *self = *self + rhs;
741    }
742}
743
744/// Subtracts a number of seconds and nanoseconds from a `Timestamp`.
745///
746/// # Panics
747///
748/// When subtracting would result in a value outside the boundaries of a
749/// `Timestamp`.
750impl core::ops::SubAssign<(i64, i32)> for Timestamp {
751    #[inline]
752    fn sub_assign(&mut self, rhs: (i64, i32)) {
753        *self = *self + rhs;
754    }
755}
756
757#[cfg(test)]
758impl quickcheck::Arbitrary for Timestamp {
759    fn arbitrary(g: &mut quickcheck::Gen) -> Timestamp {
760        let secs = b::UnixEpochSeconds::arbitrary(g);
761        let mut nanos = b::SignedSubsecNanosecond::arbitrary(g);
762        // nanoseconds must be zero for the minimum second value,
763        // so just clamp it to 0.
764        if secs == b::UnixEpochSeconds::MIN && nanos < 0 {
765            nanos = 0;
766        }
767        Timestamp::new(secs, nanos).unwrap_or(Timestamp::UNIX_EPOCH)
768    }
769
770    fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
771        let secs = self.as_second();
772        let nanos = self.subsec_nanosecond();
773        alloc::boxed::Box::new((secs, nanos).shrink().filter_map(
774            |(secs, nanos)| {
775                let secs = b::UnixEpochSeconds::check(secs).ok()?;
776                let nanos = b::SignedSubsecNanosecond::check(nanos).ok()?;
777                if secs == b::UnixEpochSeconds::MIN && nanos > 0 {
778                    None
779                } else {
780                    Timestamp::new(secs, nanos).ok()
781                }
782            },
783        ))
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790
791    #[track_caller]
792    fn datetime(
793        year: i16,
794        month: i8,
795        day: i8,
796        hour: i8,
797        minute: i8,
798        second: i8,
799        subsec_nanosecond: i32,
800    ) -> DateTime {
801        DateTime::new(
802            year,
803            month,
804            day,
805            hour,
806            minute,
807            second,
808            subsec_nanosecond,
809        )
810        .unwrap()
811    }
812
813    #[track_caller]
814    fn stamp(second: i64, subsec: i32) -> Timestamp {
815        Timestamp::new(second, subsec).unwrap()
816    }
817
818    #[track_caller]
819    fn offset(second: i32) -> Offset {
820        Offset::from_seconds(second).unwrap()
821    }
822
823    #[test]
824    fn new_ok() {
825        let ts = stamp(0, 0);
826        assert_eq!(ts, Timestamp::UNIX_EPOCH);
827
828        let ts = stamp(0, 123_000_000);
829        assert_eq!(ts.as_second(), 0);
830        assert_eq!(ts.subsec_nanosecond(), 123_000_000);
831
832        let ts = stamp(0, -123_000_000);
833        assert_eq!(ts.as_second(), 0);
834        assert_eq!(ts.subsec_nanosecond(), -123_000_000);
835
836        let ts = stamp(1, 0);
837        assert_eq!(ts.as_second(), 1);
838        assert_eq!(ts.subsec_nanosecond(), 0);
839
840        let ts = stamp(-1, 0);
841        assert_eq!(ts.as_second(), -1);
842        assert_eq!(ts.subsec_nanosecond(), 0);
843
844        let ts = stamp(1, 123_000_000);
845        assert_eq!(ts.as_second(), 1);
846        assert_eq!(ts.subsec_nanosecond(), 123_000_000);
847
848        let ts = stamp(-1, -123_000_000);
849        assert_eq!(ts.as_second(), -1);
850        assert_eq!(ts.subsec_nanosecond(), -123_000_000);
851
852        let ts = stamp(1, -123_000_000);
853        assert_eq!(ts.as_second(), 0);
854        assert_eq!(ts.subsec_nanosecond(), 877_000_000);
855
856        let ts = stamp(-1, 123_000_000);
857        assert_eq!(ts.as_second(), 0);
858        assert_eq!(ts.subsec_nanosecond(), -877_000_000);
859
860        let ts = stamp(-377705023201, 0);
861        assert_eq!(ts, Timestamp::MIN);
862
863        let ts = stamp(253402207200, 999_999_999);
864        assert_eq!(ts, Timestamp::MAX);
865    }
866
867    #[test]
868    fn new_err() {
869        assert!(Timestamp::new(0, 1_000_000_000).is_err());
870        assert!(Timestamp::new(0, -1_000_000_000).is_err());
871        assert!(Timestamp::new(1, 1_000_000_000).is_err());
872        assert!(Timestamp::new(1, -1_000_000_000).is_err());
873        assert!(Timestamp::new(-1, 1_000_000_000).is_err());
874        assert!(Timestamp::new(-1, -1_000_000_000).is_err());
875        assert!(Timestamp::new(0, i32::MAX).is_err());
876        assert!(Timestamp::new(0, i32::MIN).is_err());
877        assert!(Timestamp::new(-377705023201, -1).is_err());
878        assert!(Timestamp::new(253402207201, 0).is_err());
879    }
880
881    #[test]
882    fn to_datetime_no_subsec() {
883        let dt = datetime(1970, 1, 1, 0, 0, 0, 0);
884        assert_eq!(stamp(0, 0).to_datetime(offset(0)), dt);
885        assert_eq!(stamp(-3600, 0).to_datetime(offset(3600)), dt);
886        assert_eq!(stamp(3600, 0).to_datetime(offset(-3600)), dt);
887
888        let dt = datetime(1969, 12, 31, 23, 30, 0, 0);
889        assert_eq!(stamp(-1800, 0).to_datetime(offset(0)), dt);
890        assert_eq!(stamp(-5400, 0).to_datetime(offset(3600)), dt);
891        assert_eq!(stamp(1800, 0).to_datetime(offset(-3600)), dt);
892
893        let dt = datetime(1970, 1, 1, 0, 30, 0, 0);
894        assert_eq!(stamp(1800, 0).to_datetime(offset(0)), dt);
895        assert_eq!(stamp(-1800, 0).to_datetime(offset(3600)), dt);
896        assert_eq!(stamp(5400, 0).to_datetime(offset(-3600)), dt);
897    }
898
899    #[test]
900    fn to_datetime_with_subsec() {
901        let dt = datetime(1970, 1, 1, 0, 0, 0, 123);
902        assert_eq!(stamp(0, 123).to_datetime(offset(0)), dt);
903        assert_eq!(stamp(-3599, -999_999_877).to_datetime(offset(3600)), dt);
904        assert_eq!(stamp(3600, 123).to_datetime(offset(-3600)), dt);
905
906        let dt = datetime(1969, 12, 31, 23, 30, 0, 123);
907        assert_eq!(stamp(-1799, -999_999_877).to_datetime(offset(0)), dt);
908        assert_eq!(stamp(-5399, -999_999_877).to_datetime(offset(3600)), dt);
909        assert_eq!(stamp(1800, 123).to_datetime(offset(-3600)), dt);
910
911        let dt = datetime(1970, 1, 1, 0, 30, 0, 123);
912        assert_eq!(stamp(1800, 123).to_datetime(offset(0)), dt);
913        assert_eq!(stamp(-1799, -999_999_877).to_datetime(offset(3600)), dt);
914        assert_eq!(stamp(5400, 123).to_datetime(offset(-3600)), dt);
915    }
916
917    #[test]
918    fn to_datetime_limits() {
919        assert_eq!(Timestamp::MIN.to_datetime(Offset::MIN), DateTime::MIN);
920        assert_eq!(Timestamp::MAX.to_datetime(Offset::MAX), DateTime::MAX);
921    }
922
923    quickcheck::quickcheck! {
924        fn prop_timestamp_datetime_roundtrip(
925            ts: Timestamp,
926            offset: Offset
927        ) -> bool {
928            let dt = ts.to_datetime(offset);
929            let got = dt.to_timestamp(offset).unwrap();
930            got == ts
931        }
932    }
933}