Skip to content

Commit 168b257

Browse files
committed
time: Test and document time precision edge-case
There is a slight edge case when adding and subtracting a `Duration` from a `SystemTime`, namely when the duration itself is finer/smaller than the time precision on the operating systems. On most (if not all non-Windows) operating systems, the precision of `Duration` aligns with the `SystemTime`, both being one nanosecond. However, on Windows, this time precision is 100ns, meaning that adding or subtracting a `Duration` whose value is `< Duration::new(0, 100)` will result in that method behaving like an addition/subtracting of `Duration::ZERO`, due to the `Duration` getting rounded-down to the zero value.
1 parent 69ca01f commit 168b257

File tree

2 files changed

+38
-11
lines changed

2 files changed

+38
-11
lines changed

library/std/src/time.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,9 @@ impl SystemTime {
514514
/// Represents the maximum value representable by [`SystemTime`] on this platform.
515515
///
516516
/// This value differs a lot between platforms, but it is always the case
517-
/// that any positive addition to [`SystemTime::MAX`] will fail.
517+
/// that any positive addition of a [`Duration`], whose value is greater
518+
/// than or equal to the time precision of the operating system, to
519+
/// [`SystemTime::MAX`] will fail.
518520
///
519521
/// # Examples
520522
///
@@ -525,8 +527,13 @@ impl SystemTime {
525527
/// // Adding zero will change nothing.
526528
/// assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
527529
///
528-
/// // But adding just 1ns will already fail.
529-
/// assert_eq!(SystemTime::MAX.checked_add(Duration::new(0, 1)), None);
530+
/// // But adding just one second will already fail ...
531+
/// //
532+
/// // Keep in mind that this in fact may succeed, if the Duration is
533+
/// // smaller than the time precision of the operating system, which
534+
/// // happens to be 1ns on most operating systems, with Windows being the
535+
/// // notable exception by using 100ns, hence why this example uses 1s.
536+
/// assert_eq!(SystemTime::MAX.checked_add(Duration::new(1, 0)), None);
530537
///
531538
/// // Utilize this for saturating arithmetic to improve error handling.
532539
/// // In this case, we will use a certificate with a timestamp in the
@@ -543,7 +550,9 @@ impl SystemTime {
543550
/// Represents the minimum value representable by [`SystemTime`] on this platform.
544551
///
545552
/// This value differs a lot between platforms, but it is always the case
546-
/// that any positive subtraction from [`SystemTime::MIN`] will fail.
553+
/// that any positive subtraction of a [`Duration`] from, whose value is
554+
/// greater than or equal to the time precision of the operating system, to
555+
/// [`SystemTime::MIN`] will fail.
547556
///
548557
/// Depending on the platform, this may be either less than or equal to
549558
/// [`SystemTime::UNIX_EPOCH`], depending on whether the operating system
@@ -560,8 +569,13 @@ impl SystemTime {
560569
/// // Subtracting zero will change nothing.
561570
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
562571
///
563-
/// // But subtracting just 1ns will already fail.
564-
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::new(0, 1)), None);
572+
/// // But subtracting just one second will already fail.
573+
/// //
574+
/// // Keep in mind that this in fact may succeed, if the Duration is
575+
/// // smaller than the time precision of the operating system, which
576+
/// // happens to be 1ns on most operating systems, with Windows being the
577+
/// // notable exception by using 100ns, hence why this example uses 1s.
578+
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::new(1, 0)), None);
565579
///
566580
/// // Utilize this for saturating arithmetic to improve error handling.
567581
/// // In this case, we will use a cache expiry as a practical example.
@@ -651,6 +665,9 @@ impl SystemTime {
651665
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
652666
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
653667
/// otherwise.
668+
///
669+
/// In the case that the `duration` is smaller than the time precision of the operating
670+
/// system, `Some(self)` will be returned.
654671
#[stable(feature = "time_checked_add", since = "1.34.0")]
655672
pub fn checked_add(&self, duration: Duration) -> Option<SystemTime> {
656673
self.0.checked_add_duration(&duration).map(SystemTime)
@@ -659,6 +676,9 @@ impl SystemTime {
659676
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
660677
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
661678
/// otherwise.
679+
///
680+
/// In the case that the `duration` is smaller than the time precision of the operating
681+
/// system, `Some(self)` will be returned.
662682
#[stable(feature = "time_checked_add", since = "1.34.0")]
663683
pub fn checked_sub(&self, duration: Duration) -> Option<SystemTime> {
664684
self.0.checked_sub_duration(&duration).map(SystemTime)

library/std/tests/time.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,22 @@ fn system_time_duration_since_max_range_on_unix() {
250250

251251
#[test]
252252
fn system_time_max_min() {
253+
#[cfg(not(target_os = "windows"))]
254+
/// Most (all?) non-Windows systems have nanosecond precision.
255+
const MIN_INTERVAL: Duration = Duration::new(0, 1);
256+
#[cfg(target_os = "windows")]
257+
/// Windows' time precision is at 100ns.
258+
const MIN_INTERVAL: Duration = Duration::new(0, 100);
259+
253260
// First, test everything with checked_* and Duration::ZERO.
254261
assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
255262
assert_eq!(SystemTime::MAX.checked_sub(Duration::ZERO), Some(SystemTime::MAX));
256263
assert_eq!(SystemTime::MIN.checked_add(Duration::ZERO), Some(SystemTime::MIN));
257264
assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
258265

259-
// Now do the same again with checked_* but try by ± a single nanosecond.
260-
assert!(SystemTime::MAX.checked_add(Duration::new(0, 1)).is_none());
261-
assert!(SystemTime::MAX.checked_sub(Duration::new(0, 1)).is_some());
262-
assert!(SystemTime::MIN.checked_add(Duration::new(0, 1)).is_some());
263-
assert!(SystemTime::MIN.checked_sub(Duration::new(0, 1)).is_none());
266+
// Now do the same again with checked_* but try by ± the lowest time precision.
267+
assert!(SystemTime::MAX.checked_add(MIN_INTERVAL).is_none());
268+
assert!(SystemTime::MAX.checked_sub(MIN_INTERVAL).is_some());
269+
assert!(SystemTime::MIN.checked_add(MIN_INTERVAL).is_some());
270+
assert!(SystemTime::MIN.checked_sub(MIN_INTERVAL).is_none());
264271
}

0 commit comments

Comments
 (0)