Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions library/std/src/sys/pal/hermit/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ struct Timespec {
}

impl Timespec {
const MAX: Timespec = Self::new(i64::MAX, 1_000_000_000 - 1);

const MIN: Timespec = Self::new(i64::MIN, 0);

const fn zero() -> Timespec {
Timespec { t: timespec { tv_sec: 0, tv_nsec: 0 } }
}
Expand Down Expand Up @@ -209,6 +213,10 @@ pub struct SystemTime(Timespec);
pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero());

impl SystemTime {
pub const MAX: SystemTime = SystemTime { t: Timespec::MAX };

pub const MIN: SystemTime = SystemTime { t: Timespec::MIN };

pub fn new(tv_sec: i64, tv_nsec: i32) -> SystemTime {
SystemTime(Timespec::new(tv_sec, tv_nsec))
}
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/sgx/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ impl Instant {
}

impl SystemTime {
pub const MAX: SystemTime = SystemTime(Duration::MAX);

pub const MIN: SystemTime = SystemTime(Duration::ZERO);

pub fn now() -> SystemTime {
SystemTime(usercalls::insecure_time())
}
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/solid/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub struct SystemTime(abi::time_t);
pub const UNIX_EPOCH: SystemTime = SystemTime(0);

impl SystemTime {
pub const MAX: SystemTime = SystemTime(abi::time_t::MAX);

pub const MIN: SystemTime = SystemTime(abi::time_t::MIN);

pub fn now() -> SystemTime {
let rtc = unsafe {
let mut out = MaybeUninit::zeroed();
Expand Down
17 changes: 17 additions & 0 deletions library/std/src/sys/pal/uefi/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ impl Instant {
}

impl SystemTime {
pub const MAX: SystemTime = MAX_UEFI_TIME;

pub const MIN: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 1900,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: -1440,
daylight: 0,
pad1: 0,
pad2: 0,
})
.unwrap();

pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Option<Self> {
match system_time_internal::from_uefi(&t) {
Some(x) => Some(Self(x)),
Expand Down
11 changes: 11 additions & 0 deletions library/std/src/sys/pal/unix/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub(crate) struct Timespec {
}

impl SystemTime {
pub const MAX: SystemTime = SystemTime { t: Timespec::MAX };

pub const MIN: SystemTime = SystemTime { t: Timespec::MIN };

#[cfg_attr(any(target_os = "horizon", target_os = "hurd"), allow(unused))]
pub fn new(tv_sec: i64, tv_nsec: i64) -> Result<SystemTime, io::Error> {
Ok(SystemTime { t: Timespec::new(tv_sec, tv_nsec)? })
Expand Down Expand Up @@ -62,6 +66,13 @@ impl fmt::Debug for SystemTime {
}

impl Timespec {
const MAX: Timespec = unsafe { Self::new_unchecked(i64::MAX, 1_000_000_000 - 1) };

// As described below, on Apple OS, dates before epoch are represented differently.
// This is not an issue here however, because we are using tv_sec = i64::MIN,
// which will cause the compatibility wrapper to not be executed at all.
const MIN: Timespec = unsafe { Self::new_unchecked(i64::MIN, 0) };

const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec {
Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } }
}
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/unsupported/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ impl Instant {
}

impl SystemTime {
pub const MAX: SystemTime = SystemTime(Duration::MAX);

pub const MIN: SystemTime = SystemTime(Duration::ZERO);

pub fn now() -> SystemTime {
panic!("time not implemented on this platform")
}
Expand Down
10 changes: 10 additions & 0 deletions library/std/src/sys/pal/windows/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ impl Instant {
}

impl SystemTime {
pub const MAX: SystemTime = SystemTime {
t: c::FILETIME {
dwLowDateTime: (i64::MAX & 0xFFFFFFFF) as u32,
dwHighDateTime: (i64::MAX >> 32) as u32,
},
};

pub const MIN: SystemTime =
SystemTime { t: c::FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 } };

pub fn now() -> SystemTime {
unsafe {
let mut t: SystemTime = mem::zeroed();
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/xous/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ impl Instant {
}

impl SystemTime {
pub const MAX: SystemTime = SystemTime(Duration::MAX);

pub const MIN: SystemTime = SystemTime(Duration::ZERO);

pub fn now() -> SystemTime {
let result = blocking_scalar(systime_server(), GetUtcTimeMs.into())
.expect("failed to request utc time in ms");
Expand Down
63 changes: 63 additions & 0 deletions library/std/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,69 @@ impl SystemTime {
#[stable(feature = "assoc_unix_epoch", since = "1.28.0")]
pub const UNIX_EPOCH: SystemTime = UNIX_EPOCH;

/// Represents the maximum value representable by [`SystemTime`] on this platform.
///
/// This value differs a lot between platforms, but it is always the case
/// that any positive addition to [`SystemTime::MAX`] will fail.
///
/// # Examples
///
/// ```no_run
/// #![feature(time_systemtime_limits)]
/// use std::time::{Duration, SystemTime};
///
/// // Adding zero will change nothing.
/// assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
///
/// // But adding just 1ns will already fail.
/// assert_eq!(SystemTime::MAX.checked_add(Duration::new(0, 1)), None);
///
/// // Utilize this for saturating arithmetic to improve error handling.
/// // In this case, we will use a certificate with a timestamp in the
/// // future as a practical example.
/// let configured_offset = Duration::from_secs(60 * 60 * 24);
/// let valid_after =
/// SystemTime::now()
/// .checked_add(configured_offset)
/// .unwrap_or(SystemTime::MAX);
/// ```
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
pub const MAX: SystemTime = SystemTime(time::SystemTime::MAX);

/// Represents the minimum value representable by [`SystemTime`] on this platform.
///
/// This value differs a lot between platforms, but it is always the case
/// that any positive subtraction from [`SystemTime::MIN`] will fail.
///
/// Depending on the platform, this may be either less than or equal to
/// [`SystemTime::UNIX_EPOCH`], depending on whether the operating system
/// supports the representation of timestamps before the Unix epoch or not.
/// However, it is always guaranteed that a [`SystemTime::UNIX_EPOCH`] fits
/// between a [`SystemTime::MIN`] and [`SystemTime::MAX`].
///
/// # Examples
///
/// ```
/// #![feature(time_systemtime_limits)]
/// use std::time::{Duration, SystemTime};
///
/// // Subtracting zero will change nothing.
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));
///
/// // But subtracting just 1ns will already fail.
/// assert_eq!(SystemTime::MIN.checked_sub(Duration::new(0, 1)), None);
///
/// // Utilize this for saturating arithmetic to improve error handling.
/// // In this case, we will use a cache expiry as a practical example.
/// let configured_expiry = Duration::from_secs(60 * 3);
/// let expiry_threshold =
/// SystemTime::now()
/// .checked_sub(configured_expiry)
/// .unwrap_or(SystemTime::MIN);
/// ```
#[unstable(feature = "time_systemtime_limits", issue = "149067")]
pub const MIN: SystemTime = SystemTime(time::SystemTime::MIN);

/// Returns the system time corresponding to "now".
///
/// # Examples
Expand Down
19 changes: 19 additions & 0 deletions library/std/tests/time.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![feature(duration_constants)]
#![feature(time_systemtime_limits)]

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

assert_eq!(min, SystemTime::MIN);
assert_eq!(max, SystemTime::MAX);

let delta_a = max.duration_since(min).expect("duration_since overflow");
let delta_b = min.duration_since(max).expect_err("duration_since overflow").duration();

assert_eq!(Duration::MAX, delta_a);
assert_eq!(Duration::MAX, delta_b);
}

#[test]
fn system_time_max_min() {
// First, test everything with checked_* and Duration::ZERO.
assert_eq!(SystemTime::MAX.checked_add(Duration::ZERO), Some(SystemTime::MAX));
assert_eq!(SystemTime::MAX.checked_sub(Duration::ZERO), Some(SystemTime::MAX));
assert_eq!(SystemTime::MIN.checked_add(Duration::ZERO), Some(SystemTime::MIN));
assert_eq!(SystemTime::MIN.checked_sub(Duration::ZERO), Some(SystemTime::MIN));

// Now do the same again with checked_* but try by ± a single nanosecond.
assert!(SystemTime::MAX.checked_add(Duration::new(0, 1)).is_none());
assert!(SystemTime::MAX.checked_sub(Duration::new(0, 1)).is_some());
assert!(SystemTime::MIN.checked_add(Duration::new(0, 1)).is_some());
assert!(SystemTime::MIN.checked_sub(Duration::new(0, 1)).is_none());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concern: This only tests our internal +/- logic, but doesn't actually interface with the system.

Could we add a test in src/fs/tests.rs that calls set_accessed and set_modified with SystemTime::MIN and SystemTime::MAX, and queries it with Metadata::accessed and Metadata::modified, to ensure that these values are actually supported by the OS?

That would give me a higher confidence that they're correct, or at least force us to consider what "correct" means in this context, I don't actually think this will work on platforms where time_t is 32-bit.

Copy link
Author

@cvengler cvengler Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea but this is not possible with an FS test unfortunately, because the boundaries for FS timestamps differ based on the actual FS and are not bound to a SystemTime per se.

For example, the range on my APFS is 1677-09-21 00:12:43.145224192 through 2262-04-11 23:47:16.854775807 and they would differ if I would use the legacy HFS for example.

See: https://dfdatetime.readthedocs.io/en/latest/sources/Date-and-time-values.html

Copy link
Contributor

@ijackson ijackson Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually think this will work on platforms where time_t is 32-bit.

I'm not sure what you mean by "this" (or "work") :-).

According to my reading of unix/time.rs, on systems where time_t is 32-bit, one can still call Duration::from_secs on a 64-bit value and checked_add that to UNIX_EPOCH and get a SystemTime containing the 64-bit value.

One can also observe that (UNIX_EPOCH + Duration::from_secs(i64::MAX)).checked_add(Duration::from_nanos(1) is None.

Functions that try to interface with the OS will have to do "something" with the 64-bit value. I haven't investigated what that is.

All of this is true today. This MR doesn't touch it. If this is a bug, it's a pre-existing bug.

I think we don't want SystemTime::MAX to be based on 32-bit arithmetic., on any platform. Certainly it would be bizarre to have SystemTime::MAX be 32-bit but to be able to create values greater than MAX by addition!

(Eited to fix a u64 that ought to have been i64, and fix parens.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly it would be bizarre to have SystemTime::MAX be 32-bit but to be able to create values greater than MAX by addition!

My feelings exactly! I see SystemTime::MIN and SystemTime::MAX related to an invariant where any further subtraction / addition (except of Duration::ZERO) must fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this is true today. This MR doesn't touch it. If this is a bug, it's a pre-existing bug.

True, but these interactions and limits probably weren't thought of that much before, which is why it makes sense to consider them now.

I think we don't want SystemTime::MAX to be based on 32-bit arithmetic., on any platform.

I disagree. I think it makes sense to have SystemTime::MIN/SystemTime::MAX be "the boundaries in the system APIs we use", not just "some arbitrary1 value used in the current Rust implementation".

For the platforms that have a 32-bit signed tv_sec, SystemTime::now() will on January 19th 2038 start returning weird values one way or another. We can't really do anything about that.

Once that day comes though, I think it makes much more sense that they start returning SystemTime::MIN + delta instead of SystemTime::UNIX_EPOCH - i32::MIN + delta.

Footnotes

  1. For comparison, consider: Why are we even using i64 + u32 nanos, instead of just counting the number of nanoseconds in a i64, that could presumably also be a valid implementation, and that'd change the bounds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't want SystemTime::MAX to be based on 32-bit arithmetic., on any platform.

I disagree. I think it makes sense to have SystemTime::MIN/SystemTime::MAX be "the boundaries in the system APIs we use", not just "some arbitrary1 value used in the current Rust implementation".

The current range of SystemTime is not an "arbitrary value ... used in the current Rust implementation". It's an exposed property of the Rust API. And it is always 64 bit.

We have the following options:

  1. Change SystemTime::UNIX_EPOCH + 2^32 secs to fail on 32-bit time_t systems, even though it currently works. This is obviously unacceptable. It would be a breach of the Rust stability guarantee.

  2. Allow simple addition to routinely create SystemTime values which are > SystemTime::MAX. This is obviously unreasonable (and in practice, would be a footgun).

  3. Have SystemTime::MAX be the largest value that is supported in time arithmetic in Rust, ie, the 64-bit value.

If it is desirable to have a separate value which represents the maximum SystemTime that the operating system can cope with, that needs to not be called MAX precisely because it is sometimes smaller than the maxium value of the SystemTime datatype. Currently the Rust stdlib does not expose this value. It does expose the 64-bit maximum of the Rust std datatype, just not as a constant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that option 2 is bad, and that 3 is preferable to that, but...

  1. Change SystemTime::UNIX_EPOCH + 2^32 secs to fail on 32-bit time_t systems, even though it currently works. This is obviously unacceptable. It would be a breach of the Rust stability guarantee.

I disagree that this isn't an option.

It isn't defined anywhere (to my knowledge) that such an operation is supported? SystemTime::checked_add only says "inside the bounds of the underlying data structure", and the documentation for SystemTime says "the size of a SystemTime struct may vary depending on the target operating system".

Just because the current behaviour is observable by adding to SystemTime doesn't mean that it's intended to be stable.

Or do you know of a concrete case where current users would be broken by SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(i32::MAX as u64 + 1)) returning None in a future version of Rust?

Copy link
Author

@cvengler cvengler Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because the current behaviour is observable by adding to SystemTime doesn't mean that it's intended to be stable.

I disagree there. To me, SystemTime implies that it represents the time as the operating system does and this is in my opinion an assumption users can make. Otherwise, the name System in SystemTime is misleading and a name such as RustTime or StdTime would have been more appropriate in my honest opinion. Also, in this case SystemTime would be no different than chrono's time structs, which do exactly this.

In other words: A person using SystemTime while writing a codebase that targets Unix, should be able to assume that it can represent time values the same way the operating system does, as this is implied already by the name alone.

Loading