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}