Skip to content
Merged
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
14 changes: 7 additions & 7 deletions crates/host_env/src/fileutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ pub mod windows {
pub st_gid: i32,
pub st_rdev: libc::c_ulong,
pub st_size: u64,
pub st_atime: libc::time_t,
pub st_atime: crate::time::TimeT,
pub st_atime_nsec: i32,
pub st_mtime: libc::time_t,
pub st_mtime: crate::time::TimeT,
pub st_mtime_nsec: i32,
pub st_ctime: libc::time_t,
pub st_ctime: crate::time::TimeT,
pub st_ctime_nsec: i32,
pub st_birthtime: libc::time_t,
pub st_birthtime: crate::time::TimeT,
pub st_birthtime_nsec: i32,
pub st_file_attributes: libc::c_ulong,
pub st_reparse_tag: u32,
Expand Down Expand Up @@ -155,13 +155,13 @@ pub mod windows {
))
}

fn large_integer_to_time_t_nsec(input: i64) -> (libc::time_t, libc::c_int) {
fn large_integer_to_time_t_nsec(input: i64) -> (crate::time::TimeT, libc::c_int) {
let nsec_out = (input % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
let time_out = ((input / 10_000_000) - SECS_BETWEEN_EPOCHS) as libc::time_t;
let time_out = (input / 10_000_000) - SECS_BETWEEN_EPOCHS;
(time_out, nsec_out as _)
}

fn file_time_to_time_t_nsec(in_ptr: FILETIME) -> (libc::time_t, libc::c_int) {
fn file_time_to_time_t_nsec(in_ptr: FILETIME) -> (crate::time::TimeT, libc::c_int) {
let in_val: i64 = unsafe { core::mem::transmute_copy(&in_ptr) };
let nsec_out = (in_val % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
let time_out = (in_val / 10_000_000) - SECS_BETWEEN_EPOCHS;
Expand Down
12 changes: 6 additions & 6 deletions crates/host_env/src/nt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,16 @@ struct FileAttributeTagInfo {
reparse_tag: u32,
}

const fn win32_large_integer_to_time(li: i64) -> (libc::time_t, i32) {
const fn win32_large_integer_to_time(li: i64) -> (crate::time::TimeT, i32) {
let nsec = ((li % 10_000_000) * 100) as i32;
let sec = (li / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t;
let sec = li / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS;
(sec, nsec)
}

const fn win32_filetime_to_time(ft_low: u32, ft_high: u32) -> (libc::time_t, i32) {
const fn win32_filetime_to_time(ft_low: u32, ft_high: u32) -> (crate::time::TimeT, i32) {
let ticks = ((ft_high as i64) << 32) | (ft_low as i64);
let nsec = ((ticks % 10_000_000) * 100) as i32;
let sec = (ticks / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t;
let sec = ticks / 10_000_000 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS;
(sec, nsec)
}

Expand Down Expand Up @@ -591,10 +591,10 @@ impl ScandirEntry {
if reparse_tag == IO_REPARSE_TAG_SYMLINK {
mode = (mode & !libc::S_IFMT) | S_IFLNK;
}
let to_time = |ticks: u64| -> (libc::time_t, i32) {
let to_time = |ticks: u64| -> (crate::time::TimeT, i32) {
let ticks = ticks as i64;
(
(ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t,
ticks / 10_000_000 - SECS_BETWEEN_EPOCHS,
((ticks % 10_000_000) * 100) as i32,
)
};
Expand Down
12 changes: 7 additions & 5 deletions crates/host_env/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn duration_since_system_now() -> Result<Duration, SystemTimeError> {
pub type TimeT = libc::time_t;

#[cfg(windows)]
pub type TimeT = libc::time_t;
pub type TimeT = libc::time64_t;

#[cfg(unix)]
#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -132,16 +132,18 @@ pub fn mktime(tm: &mut libc::tm) -> TimeT {

#[cfg(windows)]
unsafe extern "C" {
fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
fn _localtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
fn _gmtime64_s(tm: *mut libc::tm, time: *const TimeT) -> libc::c_int;
fn _localtime64_s(tm: *mut libc::tm, time: *const TimeT) -> libc::c_int;
#[link_name = "_mktime64"]
fn c_mktime(tm: *mut libc::tm) -> libc::time_t;
fn c_mktime(tm: *mut libc::tm) -> TimeT;
#[link_name = "_time64"]
fn c_time(time: *mut TimeT) -> TimeT;
}

#[cfg(windows)]
#[cfg_attr(target_env = "musl", allow(deprecated))]
pub fn current_time_t() -> TimeT {
unsafe { libc::time(core::ptr::null_mut()) }
unsafe { c_time(core::ptr::null_mut()) }
}

#[cfg(windows)]
Expand Down
3 changes: 2 additions & 1 deletion crates/stdlib/src/_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,7 +1821,8 @@ mod _datetime {
fn platform_time(t: i64, local: bool, vm: &VirtualMachine) -> PyResult<Tm> {
#[cfg(any(unix, windows))]
{
let t_c = libc::time_t::try_from(t).map_err(|_| time_t_overflow(vm))?;
let t_c =
rustpython_host_env::time::TimeT::try_from(t).map_err(|_| time_t_overflow(vm))?;
let tm = if local {
rustpython_host_env::time::localtime_from_timestamp(t_c)
} else {
Expand Down
8 changes: 4 additions & 4 deletions crates/vm/src/stdlib/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -927,27 +927,27 @@ mod decl {

#[cfg(any(unix, windows))]
#[cfg_attr(target_env = "musl", allow(deprecated))]
fn pyobj_to_time_t(value: Either<f64, i64>, vm: &VirtualMachine) -> PyResult<libc::time_t> {
fn pyobj_to_time_t(value: Either<f64, i64>, vm: &VirtualMachine) -> PyResult<host_time::TimeT> {
match value {
Either::A(float) => {
if !float.is_finite() {
return Err(vm.new_value_error("Invalid value for timestamp"));
}
let secs = float.floor();
#[cfg_attr(target_env = "musl", allow(deprecated))]
let minimum = libc::time_t::MIN as f64;
let minimum = host_time::TimeT::MIN as f64;
// The signed maximum rounds up when represented by f64.
if !(minimum..-minimum).contains(&secs) {
return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
}
#[cfg_attr(target_env = "musl", allow(deprecated))]
Ok(secs as libc::time_t)
Ok(secs as host_time::TimeT)
}
Either::B(int) => {
// try_into is needed on 32-bit platforms where time_t != i64
#[allow(clippy::useless_conversion)]
#[cfg_attr(target_env = "musl", allow(deprecated))]
let ts: libc::time_t = int.try_into().map_err(|_| {
let ts: host_time::TimeT = int.try_into().map_err(|_| {
vm.new_overflow_error("timestamp out of range for platform time_t")
})?;
Ok(ts)
Expand Down
11 changes: 11 additions & 0 deletions extra_tests/snippets/stdlib_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -3941,6 +3941,16 @@ class Name(str):
assert reference() is None


def test_windows_time64():
if sys.platform == "win32":
for timestamp in (2_208_988_800, 2_208_988_800.0):
assert datetime_module.datetime.fromtimestamp(
timestamp, datetime_module.timezone.utc
) == datetime_module.datetime(
2040, 1, 1, tzinfo=datetime_module.timezone.utc
)


def test_timestamp_errno():
if sys.platform == "win32":
try:
Expand Down Expand Up @@ -3970,6 +3980,7 @@ def test_timestamp_errno():

test_astimezone_subclass_state()
test_timezone_cycles()
test_windows_time64()
test_timestamp_errno()


Expand Down
22 changes: 22 additions & 0 deletions extra_tests/snippets/stdlib_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@
assert os.name == "posix" or os.name == "nt"


def windows_stat_preserves_time64():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove the prohibited snippet-test additions

The new windows_stat_preserves_time64 function adds fresh assertions and test logic to an existing test file, as do test_windows_time64 in stdlib_datetime.py and the new Windows block in stdlib_time.py; repository policy limits test-file changes to adding or removing expected-failure markers, so these regression additions need to be removed or relocated before this patch can comply.

AGENTS.md reference: AGENTS.md:L275-L279

Useful? React with 👍 / 👎.

import tempfile

with tempfile.TemporaryDirectory() as directory:
filename = os.path.join(directory, "time64")
with open(filename, "wb"):
pass
timestamp = 2_208_988_800 # 2040-01-01, beyond signed 32-bit seconds.
os.utime(filename, (timestamp, timestamp))
with open(filename, "rb") as file:
results = [os.stat(filename), os.fstat(file.fileno())]
with os.scandir(directory) as entries:
results.append(next(entries).stat())
for result in results:
assert result.st_mtime == timestamp
assert result.st_mtime_ns == timestamp * 1_000_000_000


if sys.platform == "win32":
windows_stat_preserves_time64()


def stat_rejects_negative_file_descriptors():
import errno
import os
Expand Down
6 changes: 6 additions & 0 deletions extra_tests/snippets/stdlib_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@
assert local[:6] == time.gmtime(timestamp + local.tm_gmtoff)[:6]
assert time.ctime(timestamp) == time.asctime(local)

if sys.platform == "win32":
# The explicit 64-bit CRT ABI must accept timestamps beyond 2038.
for timestamp in (2_208_988_800, 2_208_988_800.0):
assert time.gmtime(timestamp) == (2040, 1, 1, 0, 0, 0, 6, 1, 0)


x = time.gmtime(1000)

assert x.tm_year == 1970
Expand Down
Loading