Skip to content

Commit 46814a9

Browse files
authored
Rollup merge of #148825 - cvengler:time_systemtime_limits, r=ChrisDenton
Add SystemTime::{MIN, MAX} Accepted ACP: <rust-lang/libs-team#692> Tracking Issue: <#149067> --- This merge request introduces two new constants to `SystemTime`: `MIN` and `MAX`, whose values represent the maximum values for the respective data type, depending upon the platform. Technically, this value is already obtainable during runtime with the following algorithm: Use `SystemTime::UNIX_EPOCH` and call `checked_add` (or `checked_sub`) repeatedly with `Duration::new(0, 1)` on it, until it returns None. Mathematically speaking, this algorithm will terminate after a finite amount of steps, yet it is impractical to run it, as it takes practically forever. Besides, this commit also adds a unit test to verify those values represent the respective minimum and maximum, by letting a `checked_add` and `checked_sub` on it fail. In the future, the hope of the authors lies within the creation of a `SystemTime::saturating_add` and `SystemTime::saturating_sub`, similar to the functions already present in `std::time::Duration`. However, for those, these constants are crucially required, thereby this should be seen as the initial step towards this direction. With this change, implementing these functions oneself outside the standard library becomes feasible in a portable manner for the first time. This feature (and a related saturating version of `checked_{add, sub}` has been requested multiple times over the course of the past few years, most notably: * #100141 * #133525 * #105762 * #71224 * #45448 * #52555
2 parents 01e40d6 + d80348b commit 46814a9

File tree

10 files changed

+178
-2
lines changed

10 files changed

+178
-2
lines changed

library/std/src/sys/pal/hermit/time.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ struct Timespec {
1515
}
1616

1717
impl Timespec {
18+
const MAX: Timespec = Self::new(i64::MAX, 1_000_000_000 - 1);
19+
20+
const MIN: Timespec = Self::new(i64::MIN, 0);
21+
1822
const fn zero() -> Timespec {
1923
Timespec { t: timespec { tv_sec: 0, tv_nsec: 0 } }
2024
}
@@ -209,6 +213,10 @@ pub struct SystemTime(Timespec);
209213
pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero());
210214

211215
impl SystemTime {
216+
pub const MAX: SystemTime = SystemTime { t: Timespec::MAX };
217+
218+
pub const MIN: SystemTime = SystemTime { t: Timespec::MIN };
219+
212220
pub fn new(tv_sec: i64, tv_nsec: i32) -> SystemTime {
213221
SystemTime(Timespec::new(tv_sec, tv_nsec))
214222
}

library/std/src/sys/pal/sgx/time.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ impl Instant {
2828
}
2929

3030
impl SystemTime {
31+
pub const MAX: SystemTime = SystemTime(Duration::MAX);
32+
33+
pub const MIN: SystemTime = SystemTime(Duration::ZERO);
34+
3135
pub fn now() -> SystemTime {
3236
SystemTime(usercalls::insecure_time())
3337
}

library/std/src/sys/pal/solid/time.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ pub struct SystemTime(abi::time_t);
1010
pub const UNIX_EPOCH: SystemTime = SystemTime(0);
1111

1212
impl SystemTime {
13+
pub const MAX: SystemTime = SystemTime(abi::time_t::MAX);
14+
15+
pub const MIN: SystemTime = SystemTime(abi::time_t::MIN);
16+
1317
pub fn now() -> SystemTime {
1418
let rtc = unsafe {
1519
let mut out = MaybeUninit::zeroed();

library/std/src/sys/pal/uefi/time.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ impl Instant {
7070
}
7171

7272
impl SystemTime {
73+
pub const MAX: SystemTime = MAX_UEFI_TIME;
74+
75+
pub const MIN: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
76+
year: 1900,
77+
month: 1,
78+
day: 1,
79+
hour: 0,
80+
minute: 0,
81+
second: 0,
82+
nanosecond: 0,
83+
timezone: -1440,
84+
daylight: 0,
85+
pad1: 0,
86+
pad2: 0,
87+
})
88+
.unwrap();
89+
7390
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Option<Self> {
7491
match system_time_internal::from_uefi(&t) {
7592
Some(x) => Some(Self(x)),

library/std/src/sys/pal/unix/time.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub(crate) struct Timespec {
3030
}
3131

3232
impl SystemTime {
33+
pub const MAX: SystemTime = SystemTime { t: Timespec::MAX };
34+
35+
pub const MIN: SystemTime = SystemTime { t: Timespec::MIN };
36+
3337
#[cfg_attr(any(target_os = "horizon", target_os = "hurd"), allow(unused))]
3438
pub fn new(tv_sec: i64, tv_nsec: i64) -> Result<SystemTime, io::Error> {
3539
Ok(SystemTime { t: Timespec::new(tv_sec, tv_nsec)? })
@@ -62,6 +66,13 @@ impl fmt::Debug for SystemTime {
6266
}
6367

6468
impl Timespec {
69+
const MAX: Timespec = unsafe { Self::new_unchecked(i64::MAX, 1_000_000_000 - 1) };
70+
71+
// As described below, on Apple OS, dates before epoch are represented differently.
72+
// This is not an issue here however, because we are using tv_sec = i64::MIN,
73+
// which will cause the compatibility wrapper to not be executed at all.
74+
const MIN: Timespec = unsafe { Self::new_unchecked(i64::MIN, 0) };
75+
6576
const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec {
6677
Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } }
6778
}

library/std/src/sys/pal/unsupported/time.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ impl Instant {
2727
}
2828

2929
impl SystemTime {
30+
pub const MAX: SystemTime = SystemTime(Duration::MAX);
31+
32+
pub const MIN: SystemTime = SystemTime(Duration::ZERO);
33+
3034
pub fn now() -> SystemTime {
3135
panic!("time not implemented on this platform")
3236
}

library/std/src/sys/pal/windows/time.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ impl Instant {
6464
}
6565

6666
impl SystemTime {
67+
pub const MAX: SystemTime = SystemTime {
68+
t: c::FILETIME {
69+
dwLowDateTime: (i64::MAX & 0xFFFFFFFF) as u32,
70+
dwHighDateTime: (i64::MAX >> 32) as u32,
71+
},
72+
};
73+
74+
pub const MIN: SystemTime =
75+
SystemTime { t: c::FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 } };
76+
6777
pub fn now() -> SystemTime {
6878
unsafe {
6979
let mut t: SystemTime = mem::zeroed();
@@ -101,8 +111,13 @@ impl SystemTime {
101111
}
102112

103113
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
104-
let intervals = self.intervals().checked_sub(checked_dur2intervals(other)?)?;
105-
Some(SystemTime::from_intervals(intervals))
114+
// Windows does not support times before 1601, hence why we don't
115+
// support negatives. In order to tackle this, we try to convert the
116+
// resulting value into an u64, which should obviously fail in the case
117+
// that the value is below zero.
118+
let intervals: u64 =
119+
self.intervals().checked_sub(checked_dur2intervals(other)?)?.try_into().ok()?;
120+
Some(SystemTime::from_intervals(intervals as i64))
106121
}
107122
}
108123

library/std/src/sys/pal/xous/time.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ impl Instant {
3535
}
3636

3737
impl SystemTime {
38+
pub const MAX: SystemTime = SystemTime(Duration::MAX);
39+
40+
pub const MIN: SystemTime = SystemTime(Duration::ZERO);
41+
3842
pub fn now() -> SystemTime {
3943
let result = blocking_scalar(systime_server(), GetUtcTimeMs.into())
4044
.expect("failed to request utc time in ms");

library/std/src/time.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,83 @@ impl SystemTime {
511511
#[stable(feature = "assoc_unix_epoch", since = "1.28.0")]
512512
pub const UNIX_EPOCH: SystemTime = UNIX_EPOCH;
513513

514+
/// Represents the maximum value representable by [`SystemTime`] on this platform.
515+
///
516+
/// This value differs a lot between platforms, but it is always the case
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.
520+
///
521+
/// # Examples
522+
///
523+
/// ```no_run
524+
/// #![feature(time_systemtime_limits)]
525+
/// use std::time::{Duration, SystemTime};
526+
///
527+
/// // Adding zero will change nothing.
528+
/// assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
529+
///
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);
537+
///
538+
/// // Utilize this for saturating arithmetic to improve error handling.
539+
/// // In this case, we will use a certificate with a timestamp in the
540+
/// // future as a practical example.
541+
/// let configured_offset = Duration::from_secs(60 * 60 * 24);
542+
/// let valid_after =
543+
/// SystemTime::now()
544+
/// .checked_add(configured_offset)
545+
/// .unwrap_or(SystemTime::MAX);
546+
/// ```
547+
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
548+
pub const MAX: SystemTime = SystemTime(time::SystemTime::MAX);
549+
550+
/// Represents the minimum value representable by [`SystemTime`] on this platform.
551+
///
552+
/// This value differs a lot between platforms, but it is always the case
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.
556+
///
557+
/// Depending on the platform, this may be either less than or equal to
558+
/// [`SystemTime::UNIX_EPOCH`], depending on whether the operating system
559+
/// supports the representation of timestamps before the Unix epoch or not.
560+
/// However, it is always guaranteed that a [`SystemTime::UNIX_EPOCH`] fits
561+
/// between a [`SystemTime::MIN`] and [`SystemTime::MAX`].
562+
///
563+
/// # Examples
564+
///
565+
/// ```
566+
/// #![feature(time_systemtime_limits)]
567+
/// use std::time::{Duration, SystemTime};
568+
///
569+
/// // Subtracting zero will change nothing.
570+
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
571+
///
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);
579+
///
580+
/// // Utilize this for saturating arithmetic to improve error handling.
581+
/// // In this case, we will use a cache expiry as a practical example.
582+
/// let configured_expiry = Duration::from_secs(60 * 3);
583+
/// let expiry_threshold =
584+
/// SystemTime::now()
585+
/// .checked_sub(configured_expiry)
586+
/// .unwrap_or(SystemTime::MIN);
587+
/// ```
588+
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
589+
pub const MIN: SystemTime = SystemTime(time::SystemTime::MIN);
590+
514591
/// Returns the system time corresponding to "now".
515592
///
516593
/// # Examples
@@ -588,6 +665,9 @@ impl SystemTime {
588665
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
589666
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
590667
/// 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.
591671
#[stable(feature = "time_checked_add", since = "1.34.0")]
592672
pub fn checked_add(&self, duration: Duration) -> Option<SystemTime> {
593673
self.0.checked_add_duration(&duration).map(SystemTime)
@@ -596,6 +676,9 @@ impl SystemTime {
596676
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
597677
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
598678
/// 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.
599682
#[stable(feature = "time_checked_add", since = "1.34.0")]
600683
pub fn checked_sub(&self, duration: Duration) -> Option<SystemTime> {
601684
self.0.checked_sub_duration(&duration).map(SystemTime)

library/std/tests/time.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![feature(duration_constants)]
2+
#![feature(time_systemtime_limits)]
23

34
use std::fmt::Debug;
45
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
@@ -237,9 +238,34 @@ fn system_time_duration_since_max_range_on_unix() {
237238
let min = SystemTime::UNIX_EPOCH - (Duration::new(i64::MAX as u64 + 1, 0));
238239
let max = SystemTime::UNIX_EPOCH + (Duration::new(i64::MAX as u64, 999_999_999));
239240

241+
assert_eq!(min, SystemTime::MIN);
242+
assert_eq!(max, SystemTime::MAX);
243+
240244
let delta_a = max.duration_since(min).expect("duration_since overflow");
241245
let delta_b = min.duration_since(max).expect_err("duration_since overflow").duration();
242246

243247
assert_eq!(Duration::MAX, delta_a);
244248
assert_eq!(Duration::MAX, delta_b);
245249
}
250+
251+
#[test]
252+
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+
260+
// First, test everything with checked_* and Duration::ZERO.
261+
assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
262+
assert_eq!(SystemTime::MAX.checked_sub(Duration::ZERO), Some(SystemTime::MAX));
263+
assert_eq!(SystemTime::MIN.checked_add(Duration::ZERO), Some(SystemTime::MIN));
264+
assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
265+
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());
271+
}

0 commit comments

Comments
 (0)