Skip to content

Commit da45ac3

Browse files
Rollup merge of rust-lang#148825 - cvengler:time_systemtime_limits, r=ChrisDenton
Add SystemTime::{MIN, MAX} Accepted ACP: <rust-lang/libs-team#692> Tracking Issue: <rust-lang#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: * rust-lang#100141 * rust-lang#133525 * rust-lang#105762 * rust-lang#71224 * rust-lang#45448 * rust-lang#52555
2 parents 888f4f1 + 3f98375 commit da45ac3

File tree

10 files changed

+143
-0
lines changed

10 files changed

+143
-0
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ 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+
7389
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Option<Self> {
7490
match system_time_internal::from_uefi(&t) {
7591
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 = Duration::MAX;
31+
32+
pub const MIN: 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: 10 additions & 0 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();

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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,69 @@ 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 to [`SystemTime::MAX`] will fail.
518+
///
519+
/// # Examples
520+
///
521+
/// ```no_run
522+
/// #![feature(time_systemtime_limits)]
523+
/// use std::time::{Duration, SystemTime};
524+
///
525+
/// // Adding zero will change nothing.
526+
/// assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
527+
///
528+
/// // But adding just 1ns will already fail.
529+
/// assert_eq!(SystemTime::MAX.checked_add(Duration::new(0, 1)), None);
530+
///
531+
/// // Utilize this for saturating arithmetic to improve error handling.
532+
/// // In this case, we will use a certificate with a timestamp in the
533+
/// // future as a practical example.
534+
/// let configured_offset = Duration::from_secs(60 * 60 * 24);
535+
/// let valid_after =
536+
/// SystemTime::now()
537+
/// .checked_add(configured_offset)
538+
/// .unwrap_or(SystemTime::MAX);
539+
/// ```
540+
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
541+
pub const MAX: SystemTime = SystemTime(time::SystemTime::MAX);
542+
543+
/// Represents the minimum value representable by [`SystemTime`] on this platform.
544+
///
545+
/// This value differs a lot between platforms, but it is always the case
546+
/// that any positive subtraction from [`SystemTime::MIN`] will fail.
547+
///
548+
/// Depending on the platform, this may be either less than or equal to
549+
/// [`SystemTime::UNIX_EPOCH`], depending on whether the operating system
550+
/// supports the representation of timestamps before the Unix epoch or not.
551+
/// However, it is always guaranteed that a [`SystemTime::UNIX_EPOCH`] fits
552+
/// between a [`SystemTime::MIN`] and [`SystemTime::MAX`].
553+
///
554+
/// # Examples
555+
///
556+
/// ```
557+
/// #![feature(time_systemtime_limits)]
558+
/// use std::time::{Duration, SystemTime};
559+
///
560+
/// // Subtracting zero will change nothing.
561+
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
562+
///
563+
/// // But subtracting just 1ns will already fail.
564+
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::new(0, 1)), None);
565+
///
566+
/// // Utilize this for saturating arithmetic to improve error handling.
567+
/// // In this case, we will use a cache expiry as a practical example.
568+
/// let configured_expiry = Duration::from_secs(60 * 3);
569+
/// let expiry_threshold =
570+
/// SystemTime::now()
571+
/// .checked_sub(configured_expiry)
572+
/// .unwrap_or(SystemTime::MIN);
573+
/// ```
574+
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
575+
pub const MIN: SystemTime = SystemTime(time::SystemTime::MIN);
576+
514577
/// Returns the system time corresponding to "now".
515578
///
516579
/// # Examples

library/std/tests/time.rs

Lines changed: 19 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,27 @@ 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+
// First, test everything with checked_* and Duration::ZERO.
254+
assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
255+
assert_eq!(SystemTime::MAX.checked_sub(Duration::ZERO), Some(SystemTime::MAX));
256+
assert_eq!(SystemTime::MIN.checked_add(Duration::ZERO), Some(SystemTime::MIN));
257+
assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
258+
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());
264+
}

0 commit comments

Comments
 (0)