Skip to content

Use CRT functions for time module on Windows#7090

Open
youknowone wants to merge 2 commits intoRustPython:mainfrom
youknowone:time
Open

Use CRT functions for time module on Windows#7090
youknowone wants to merge 2 commits intoRustPython:mainfrom
youknowone:time

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 11, 2026

  • Replace chrono with CRT functions (wcsftime, _gmtime64_s, _localtime64_s, _mktime64) on Windows for correct behavior
  • Fix timezone name corruption: use take_while instead of filter for null-terminated wide strings in TIME_ZONE_INFORMATION
  • Fix wcsftime symbol name (_wcsftime -> wcsftime)
  • Fix CString import cfg to unix-only
  • Remove 13 expectedFailure markers from test_time for Windows

Summary by CodeRabbit

  • Refactor

    • Unified time/timezone handling across Unix, Windows, and other targets; centralized mktime/gmtime/localtime/struct_time conversion and formatting for consistent cross-platform behavior.
  • Bug Fixes

    • More robust timezone name extraction and safer local/UTC conversions.
    • Improved strftime/strptime with OS-specific validation, proper wide/ASCII handling on Windows, and preserved non-Unix/non-Windows fallbacks.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Broadened platform guards and refactored stdlib time handling for cross-platform behavior: added Windows FFI/bindings and helpers (wcsftime, win_mktime, tz extraction), centralized CheckedTm/StructTime conversions and pyobj_to_time_t, unified strftime/mktime paths across Unix and Windows, and preserved non-Unix/Windows fallbacks.

Changes

Cohort / File(s) Summary
Core time module
crates/vm/src/stdlib/time.rs
Large refactor: many #[cfg(unix)] widened to #[cfg(any(unix, windows))], added #[cfg(not(any(unix, windows)))] branches, reorganized control flow for time conversion, timezone extraction, and formatting.
Windows bindings & helpers
crates/vm/src/stdlib/time.rs (Windows branches)
Added unsafe extern "C" wcsftime, promoted get_tz_info visibility, added current_time_t, gmtime_from_timestamp, localtime_from_timestamp, win_mktime, and Windows-specific year-range and wide-char strftime handling.
Timestamp conversion helpers
crates/vm/src/stdlib/time.rs (cross-platform parts)
Introduced pyobj_to_time_t, unified pyobj-to-time conversions, centralized mktime dispatch to Windows/Unix-specific implementations, and added pyobj_to_date_time usage sites across cfg boundaries.
CheckedTm / StructTime conversion
crates/vm/src/stdlib/time.rs (cross-platform parts)
Reworked CheckedTm to be platform-aware (conditional zone/gmtoff), added tm_from_struct_time/checked_tm_from_struct_time, and adjusted StructTimeData construction to match per-OS fields.
Formatting / strftime paths
crates/vm/src/stdlib/time.rs (formatting paths)
Added strftime_crt as a cross-platform entry with per-OS inner logic: Unix ASCII strftime and Windows wide wcsftime path, with null-safe TZ name extraction and year validation.
Fallback / other targets
crates/vm/src/stdlib/time.rs (non-unix/windows branches)
Preserved and adapted fallback implementations under cfg(not(any(unix, windows))) to reuse CheckedTm and cross-platform helpers, minimizing duplication.

Sequence Diagram(s)

sequenceDiagram
    participant VM as VirtualMachine
    participant Time as stdlib::time
    participant OS as libc / Windows API

    VM->>Time: call strftime/asctime/ctime/mktime with Py args
    Time->>Time: pyobj_to_time_t / tm_from_struct_time / CheckedTm
    alt Windows
        Time->>OS: win_mktime / wcsftime / GetTimeZoneInformation
        OS-->>Time: time_t / wide string / TIME_ZONE_INFORMATION
    else Unix
        Time->>OS: mktime / libc::strftime / localtime/gmtime
        OS-->>Time: time_t / C string / tm struct
    end
    Time-->>VM: return Python string/StructTime/float
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh

Poem

🐇
I hop through timestamps, wide and small,
Unix ticks and Windows calls, one and all,
I trim the null, I bind with care,
Zones aligned and strings laid bare,
I chew the bugs and scurry on my way.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main objective of the PR: replacing chrono with CRT functions for the time module specifically on Windows, which aligns with the substantial refactoring shown in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 87.88% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin time

@youknowone youknowone force-pushed the time branch 2 times, most recently from b7251ab to 7870255 Compare February 13, 2026 06:12
@youknowone youknowone marked this pull request as ready for review February 13, 2026 06:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/time.rs (1)

498-531: 🛠️ Refactor suggestion | 🟠 Major

Merge duplicate unix / windows blocks into a single cfg(any(unix, windows)) block.

The #[cfg(unix)] and #[cfg(windows)] blocks in gmtime are identical. localtime (line 539) already uses a merged cfg(any(unix, windows)) — do the same here.

♻️ Suggested diff
-        #[cfg(unix)]
-        {
-            let ts = match secs {
-                OptionalArg::Present(Some(value)) => pyobj_to_time_t(value, vm)?,
-                OptionalArg::Present(None) | OptionalArg::Missing => current_time_t(),
-            };
-            gmtime_from_timestamp(ts, vm)
-        }
-
-        #[cfg(windows)]
+        #[cfg(any(unix, windows))]
         {
             let ts = match secs {
                 OptionalArg::Present(Some(value)) => pyobj_to_time_t(value, vm)?,
                 OptionalArg::Present(None) | OptionalArg::Missing => current_time_t(),
             };
             gmtime_from_timestamp(ts, vm)
         }

As per coding guidelines: "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."

🧹 Nitpick comments (3)
crates/vm/src/stdlib/time.rs (3)

1350-1367: pyobj_to_time_t is duplicated verbatim across Unix and Windows platform modules.

The Unix (line 1020) and Windows (line 1350) implementations are identical. Consider extracting this into a shared #[cfg(any(unix, windows))] helper to reduce duplication.


1415-1419: get_tz_info() is duplicated between the decl module (line 202) and the platform module (line 1415).

Both are identical functions wrapping GetTimeZoneInformation. Consider making one call the other, or extracting it to a shared location.


1390-1396: current_time_t() uses .unwrap() which panics if the system clock is before UNIX_EPOCH.

This is extremely unlikely in practice, but the Unix counterpart (line 1064) uses libc::time() which doesn't panic. Consider using unwrap_or(0) or saturating to 0 for defensive consistency.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/time.rs (1)

607-610: ⚠️ Potential issue | 🟡 Minor

Wrong function name in error message: "asctime" should be "ctime".

Line 609 passes "asctime" as func_name to checked_tm_from_struct_time, but this is the ctime function. If the validation ever fails, the error message would incorrectly say asctime(): illegal time tuple argument.

Proposed fix
-            let tm = checked_tm_from_struct_time(&local, vm, "asctime")?.tm;
+            let tm = checked_tm_from_struct_time(&local, vm, "ctime")?.tm;
🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/time.rs`:
- Around line 1313-1316: The Windows platform module is cfg(gated) only by
#[cfg(windows)] but imports get_tz_info and TIME_ZONE_INFORMATION which are
currently #[cfg(target_env = "msvc")], causing build failures on MinGW/gnu
targets; fix by making the cfgs consistent: either change the platform module's
attribute to #[cfg(all(windows, target_env = "msvc"))] so the whole module is
only compiled for MSVC, or relax get_tz_info and TIME_ZONE_INFORMATION (and
associated windows_sys imports) to #[cfg(windows)] so they are available on all
Windows targets; update the declarations for get_tz_info and
TIME_ZONE_INFORMATION (and any windows_sys uses) or the module attribute for mod
platform accordingly so the compiler sees matching cfgs for imports and usage
(e.g., references: get_tz_info, TIME_ZONE_INFORMATION, mod platform, and the
call site that uses get_tz_info).
- Around line 1329-1334: The Windows-specific extern declarations for
_gmtime64_s, _localtime64_s and c_mktime currently use libc::time_t and lack an
architecture guard, which is unsound on 32-bit Windows; add
#[cfg(target_pointer_width = "64")] to the Windows platform module containing
the extern "C" block (the functions named _gmtime64_s, _localtime64_s, and the
linked c_mktime/_mktime64) so these 64-bit CRT bindings are only compiled on
64-bit targets, or alternatively change the function signatures to use explicit
i64 types for the time parameters to ensure correctness on all targets.
🧹 Nitpick comments (2)
crates/vm/src/stdlib/time.rs (2)

470-486: Potential panic on out-of-range tm_wday / tm_mon indices.

asctime_from_tm indexes arrays of size 7 and 12 using tm.tm_wday as usize and tm.tm_mon as usize with no bounds check. While callers typically validate these, if asctime_from_tm is ever reached with invalid values (e.g., via a future code path), it will panic with an index-out-of-bounds. Consider a defensive .get() or a debug assertion.

This is a minor concern since checked_tm_from_struct_time validates month in [0, 11] and wday >= 0 (plus % 7 caps it at 6), and localtime_from_timestamp delegates to the C library which produces valid values.


1408-1465: Significant code duplication with unix_mktime and checked_tm_from_struct_time.

The field extraction pattern (try_into_value + map_err(|_| invalid_tuple())) is repeated nearly identically in win_mktime (here), unix_mktime (Line 1080), and checked_tm_from_struct_time (Line 332). While the validation semantics differ (mktime normalizes, strftime validates), the extraction logic is the same.

Consider extracting a shared helper that converts StructTimeData fields into raw (year, mon, mday, hour, min, sec, wday, yday, isdst) tuple, then let each caller apply its own validation/adjustments. This would remove ~50 lines of duplicated code. As per coding guidelines: "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."

Comment on lines +1329 to +1334
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;
#[link_name = "_mktime64"]
fn c_mktime(tm: *mut libc::tm) -> libc::time_t;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the specific code in question
sed -n '1320,1340p' crates/vm/src/stdlib/time.rs

Repository: RustPython/RustPython

Length of output: 844


🏁 Script executed:

# Check what targets the crate supports in Cargo.toml
cat Cargo.toml | grep -A 20 "targets\|platform" || echo "No explicit targets found in root Cargo.toml"

# Check the vm crate's Cargo.toml
cat crates/vm/Cargo.toml | head -30

Repository: RustPython/RustPython

Length of output: 969


🏁 Script executed:

# Search for any 32-bit Windows or i686 target configuration in the codebase
rg -i 'target_pointer_width.*32|i686.*windows|x86.*windows' --type=rust

Repository: RustPython/RustPython

Length of output: 722


🏁 Script executed:

# Search for conditional compilation for Windows architecture
rg -i 'cfg.*windows|target_os.*windows' crates/vm/src/stdlib/time.rs -B2 -A2

Repository: RustPython/RustPython

Length of output: 5592


🏁 Script executed:

# Check how libc::time_t is used elsewhere in the file
rg -n 'libc::time_t' crates/vm/src/stdlib/time.rs

Repository: RustPython/RustPython

Length of output: 829


🏁 Script executed:

# Check the full Windows platform module to see if there are any 32-bit checks
sed -n '1300,1400p' crates/vm/src/stdlib/time.rs

Repository: RustPython/RustPython

Length of output: 3836


🏁 Script executed:

# Search for any documentation or comments about 32-bit Windows support in the time module
rg -i '32.*bit|32-bit|x86' crates/vm/src/stdlib/time.rs -B3 -A3

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check if there's any cfg guard on the Windows module declaration
rg -B10 'mod platform' crates/vm/src/stdlib/time.rs | grep -A10 '#\[cfg'

Repository: RustPython/RustPython

Length of output: 438


🏁 Script executed:

# Verify whether the crate targets 32-bit Windows by checking build scripts or CI config
find . -name "*.yml" -o -name "*.yaml" -o -name "build.rs" | xargs grep -l "windows.*32\|i686.*windows" 2>/dev/null | head -5

Repository: RustPython/RustPython

Length of output: 96


🏁 Script executed:

# Check libc crate version and documentation reference for Windows time_t
rg 'libc.*=|libc =' crates/vm/Cargo.toml

Repository: RustPython/RustPython

Length of output: 92


🏁 Script executed:

# Check the root Cargo.toml to see workspace dependencies for libc
cat Cargo.toml | grep -A 5 '\[workspace\]' | head -20

Repository: RustPython/RustPython

Length of output: 130


🏁 Script executed:

# Check if there's explicit documentation about supported platforms
find . -name "README*" -o -name "CONTRIBUTING*" | xargs grep -i "32.*bit\|platform\|windows" 2>/dev/null | head -10

Repository: RustPython/RustPython

Length of output: 697


🏁 Script executed:

# Check GitHub Actions workflows for 32-bit Windows builds
cat .github/workflows/release.yml | grep -i "windows\|32\|i686\|x86" | head -20

Repository: RustPython/RustPython

Length of output: 1016


🏁 Script executed:

# Check the actual libc version to understand time_t on Windows
rg 'libc.*=' Cargo.toml

Repository: RustPython/RustPython

Length of output: 109


🏁 Script executed:

# Verify if the Windows module has any 64-bit specific guards
grep -B5 -A15 '#\[cfg(windows)\]' crates/vm/src/stdlib/time.rs | grep -A15 'mod platform'

Repository: RustPython/RustPython

Length of output: 631


🏁 Script executed:

# Create a small test to understand libc types on Windows
python3 << 'EOF'
# According to libc crate documentation:
# On Windows 64-bit: time_t is i64
# On Windows 32-bit: time_t is i32
# But _gmtime64_s always expects __time64_t which is i64 (64-bit signed)
# This is a mismatch on 32-bit platforms

print("On Windows 64-bit:")
print("  libc::time_t = i64")
print("  _gmtime64_s expects __time64_t = i64")
print("  Parameter: *const libc::time_t = *const i64 ✓ CORRECT")
print()
print("On Windows 32-bit:")
print("  libc::time_t = i32")
print("  _gmtime64_s expects __time64_t = i64") 
print("  Parameter: *const libc::time_t = *const i32 ✗ INCORRECT")
print()
print("ISSUE: If 32-bit Windows is supported, the binding is UNSOUND")
EOF

Repository: RustPython/RustPython

Length of output: 400


Add a 64-bit Windows guard to the platform module declarations.

The extern bindings for _gmtime64_s, _localtime64_s, and _mktime64 are correct for 64-bit Windows where libc::time_t is i64, matching the required __time64_t. However, the Windows platform module lacks any target_pointer_width guard. On 32-bit Windows, libc::time_t is i32, making these bindings unsound—the parameter type *const libc::time_t would become *const i32 instead of the required *const i64. While 32-bit Windows is not currently tested in CI (the i686-pc-windows-msvc target is commented out), the code could still compile and cause undefined behavior if someone targets it.

Add #[cfg(target_pointer_width = "64")] to the Windows platform module to prevent unsound compilation on 32-bit architectures, or replace the use of libc::time_t with explicit i64 types for these 64-bit CRT functions.

🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/time.rs` around lines 1329 - 1334, The Windows-specific
extern declarations for _gmtime64_s, _localtime64_s and c_mktime currently use
libc::time_t and lack an architecture guard, which is unsound on 32-bit Windows;
add #[cfg(target_pointer_width = "64")] to the Windows platform module
containing the extern "C" block (the functions named _gmtime64_s,
_localtime64_s, and the linked c_mktime/_mktime64) so these 64-bit CRT bindings
are only compiled on 64-bit targets, or alternatively change the function
signatures to use explicit i64 types for the time parameters to ensure
correctness on all targets.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/time.rs`:
- Around line 634-649: The format-scanner currently only detects the literal
sequence "%y" and thus misses modified forms like "%#y" when tm.tm_year < 0;
update the loop that iterates over format (the code using fmt.as_str(),
chars.next(), and checking tm.tm_year) to skip valid strftime
flag/width/precision characters (e.g., '#', '-', '_', '0', field width digits
and optional '.' precision) between '%' and the conversion specifier, and then
treat the final conversion letter as the thing to check for 'y'; if that
conversion letter is 'y' and tm.tm_year < 0, return the same
vm.new_value_error("format %y requires year >= 1900 on Windows".to_owned()).
Ensure the change is applied in the same block where tm.tm_year is inspected so
all modified forms like "%#y" are rejected.
🧹 Nitpick comments (3)
crates/vm/src/stdlib/time.rs (3)

1424-1481: win_mktime and unix_mktime are near-identical; extract shared logic.

Both functions parse the same StructTimeData fields into a libc::tm, differing only in the final mktime call (libc::mktime vs c_mktime with suppress_iph!). As per coding guidelines: "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."

Consider extracting the common StructTimeData → libc::tm parsing into a shared helper (e.g., tm_from_struct_time_for_mktime), then have each platform function call it and dispatch to its respective mktime.

Also applies to: 1096-1155


956-971: Lossy i64 → f64 → time_t round-trip for integer timestamps.

Line 965 converts i64 to f64, losing precision for values > 2⁵³. The subsequent secs as libc::time_t on line 970 then converts back, potentially yielding a different timestamp than the user passed in. For the integer case, you could short-circuit directly:

Suggested fix
     fn pyobj_to_time_t(value: Either<f64, i64>, vm: &VirtualMachine) -> PyResult<libc::time_t> {
-        let secs = match value {
+        match value {
             Either::A(float) => {
                 if !float.is_finite() {
                     return Err(vm.new_value_error("Invalid value for timestamp"));
                 }
-                float.floor()
+                let secs = float.floor();
+                if secs < libc::time_t::MIN as f64 || secs > libc::time_t::MAX as f64 {
+                    return Err(
+                        vm.new_overflow_error("timestamp out of range for platform time_t"),
+                    );
+                }
+                Ok(secs as libc::time_t)
             }
-            Either::B(int) => int as f64,
-        };
-        if secs < libc::time_t::MIN as f64 || secs > libc::time_t::MAX as f64 {
-            return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
+            Either::B(int) => Ok(int as libc::time_t),
         }
-        Ok(secs as libc::time_t)
     }

470-486: asctime_from_tm indexes arrays without bounds checks — panics on invalid tm_wday/tm_mon.

While current callers go through checked_tm_from_struct_time which validates ranges, asctime_from_tm itself is pub-accessible within the module and will panic with an index-out-of-bounds if given a tm with tm_wday >= 7 or tm_mon >= 12. A defensive .get().unwrap_or(...) or debug assertion would harden this against future misuse.

Comment on lines 634 to 649
// %y requires year >= 1900 on Windows
if tm.tm_year < 0 {
let fmt = format.as_str();
let mut chars = fmt.chars();
while let Some(ch) = chars.next() {
if ch == '%' {
if let Some(next) = chars.next() {
if next == 'y' {
return Err(vm.new_value_error(
"format %y requires year >= 1900 on Windows".to_owned(),
));
}
}
}
size = size.saturating_mul(2);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

%y check doesn't account for Windows modifier flags like %#y.

The scan rejects %y when year < 1900, but Windows MSVC strftime also supports %#y (and potentially other modified forms). The current check only looks for the exact sequence %y, so %#y would slip through and could cause MSVC's strftime to produce incorrect output or crash for years before 1900.

Consider extending the scan to skip over flag characters (#, -, 0, etc.) between % and the conversion specifier:

Suggested approach
                     if ch == '%' {
-                        if let Some(next) = chars.next() {
-                            if next == 'y' {
+                        // Skip optional modifier flags (e.g., %#y)
+                        let mut next = chars.next();
+                        while let Some(c) = next {
+                            if c == '#' || c == '-' || c == '0' {
+                                next = chars.next();
+                            } else {
+                                break;
+                            }
+                        }
+                        if let Some(spec) = next {
+                            if spec == 'y' {
                                 return Err(vm.new_value_error(
                                     "format %y requires year >= 1900 on Windows".to_owned(),
                                 ));
                             }
                         }
🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/time.rs` around lines 634 - 649, The format-scanner
currently only detects the literal sequence "%y" and thus misses modified forms
like "%#y" when tm.tm_year < 0; update the loop that iterates over format (the
code using fmt.as_str(), chars.next(), and checking tm.tm_year) to skip valid
strftime flag/width/precision characters (e.g., '#', '-', '_', '0', field width
digits and optional '.' precision) between '%' and the conversion specifier, and
then treat the final conversion letter as the thing to check for 'y'; if that
conversion letter is 'y' and tm.tm_year < 0, return the same
vm.new_value_error("format %y requires year >= 1900 on Windows".to_owned()).
Ensure the change is applied in the same block where tm.tm_year is inspected so
all modified forms like "%#y" are rejected.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/time.rs`:
- Around line 1357-1363: The current_time_t function silently returns 0 on clock
errors because duration_since(UNIX_EPOCH).unwrap_or_default() masks failures;
update current_time_t to mirror the Unix behavior by returning an error sentinel
instead of 0 on failure: either (A) on Windows use libc::time(null) under
cfg(target_os = "windows") (call libc::time with a null pointer and return its
result cast to libc::time_t) or (B) keep the SystemTime approach but detect Err
from duration_since and return -1 as a libc::time_t on error; change
current_time_t to perform that branching so callers receive the same -1-on-error
semantics as the Unix implementation.
- Around line 940-955: pyobj_to_time_t currently maps the integer branch
(Either::B) through an f64 which can lose precision for i64 values beyond 2^53;
change the Either::B(int) handling in pyobj_to_time_t to perform a direct
integer range check against libc::time_t::MIN and libc::time_t::MAX (by casting
those bounds to i64 or comparing using i128 if needed) and then cast int ->
libc::time_t directly, keeping the float branch behavior (floor + finite check)
unchanged; update error paths to return vm.new_overflow_error("timestamp out of
range for platform time_t") when the integer is outside time_t bounds.
🧹 Nitpick comments (1)
crates/vm/src/stdlib/time.rs (1)

1408-1465: win_mktime largely duplicates unix_mktime — consider extracting shared logic.

The field extraction and error handling in win_mktime (lines 1408–1465) mirrors unix_mktime (lines 1080–1139) almost identically. As per coding guidelines, shared logic should be extracted to avoid duplication. The platform-specific parts (extra tm_gmtoff/tm_zone fields on Unix, suppress_iph! on Windows) could be handled in small platform-conditional blocks after a shared field-extraction helper.

- Replace chrono with CRT functions (wcsftime, _gmtime64_s,
  _localtime64_s, _mktime64) on Windows for correct behavior
- Fix timezone name corruption: use take_while instead of filter
  for null-terminated wide strings in TIME_ZONE_INFORMATION
- Fix wcsftime symbol name (_wcsftime -> wcsftime)
- Fix CString import cfg to unix-only
- Remove 13 expectedFailure markers from test_time for Windows
@github-actions
Copy link
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_time.py (TODO: 2)
[x] test: cpython/Lib/test/test_strftime.py

dependencies:

dependent tests: (134 tests)

  • time: test_android test_asyncio test_calendar test_compileall test_concurrent_futures test_context test_dummy_thread test_dummy_threading test_email test_epoll test_file_eintr test_float test_fork1 test_ftplib test_functools test_http_cookiejar test_httpservers test_imaplib test_importlib test_inspect test_io test_kqueue test_logging test_mailbox test_multiprocessing_main_handling test_os test_plistlib test_poll test_posix test_pydoc test_queue test_random test_resource test_sched test_selectors test_signal test_smtplib test_socket test_ssl test_strftime test_structseq test_subprocess test_sys test_syslog test_thread test_threading test_threadsignals test_time test_timeout test_urllibnet test_weakref test_xmlrpc test_zipfile test_zipfile64 test_zipimport
    • email.generator: test_email
      • mailbox: test_genericalias
      • smtplib: test_smtpnet
    • email.utils:
      • http.server: test_robotparser test_urllib2_localnet
    • http.server:
      • wsgiref.simple_server: test_wsgiref
      • xmlrpc.server: test_docxmlrpc
    • multiprocessing: test_concurrent_futures test_fcntl
    • pydoc: test_enum
    • queue: test_asyncio
    • socketserver: test_socketserver
    • subprocess: test_audit test_bz2 test_c_locale_coercion test_cmd_line test_cmd_line_script test_ctypes test_dtrace test_faulthandler test_gzip test_json test_msvcrt test_ntpath test_osx_env test_platform test_py_compile test_regrtest test_repl test_runpy test_script_helper test_select test_shutil test_site test_sqlite3 test_support test_sysconfig test_tempfile test_unittest test_urllib2 test_utf8_mode test_venv test_wait3 test_webbrowser
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
    • tarfile: test_tarfile
    • threading: test_asyncio test_code test_concurrent_futures test_contextlib test_decimal test_hashlib test_httplib test_importlib test_itertools test_largefile test_linecache test_opcache test_pathlib test_super test_termios test_threadedtempfile test_threading_local test_winreg test_zstd
      • sysconfig: test_pyexpat test_tools test_urllib2net
      • trace: test_trace
      • zipfile: test_pkgutil test_zipapp test_zipfile
    • timeit: test_timeit
    • urllib.request: test_pathlib test_urllib
    • zipfile:
      • importlib.metadata: test_importlib test_zoneinfo
    • zipimport: test_importlib

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/time.rs`:
- Around line 1327-1329: The Windows platform module is annotated with plain
#[pymodule] but should be a submodule like the Unix one; change the attribute on
the Windows module declaration from #[pymodule] to #[pymodule(sub)] for the
module named platform so its items are merged into the parent module (i.e.,
update the module declaration "mod platform" to use #[pymodule(sub)]).
🧹 Nitpick comments (1)
crates/vm/src/stdlib/time.rs (1)

940-997: Consider extracting shared field-extraction logic from tm_from_struct_time and checked_tm_from_struct_time.

Both functions independently extract the same ~8 fields from StructTimeData into libc::tm using the identical clone().try_into_value(vm).map_err(...) pattern. A shared helper that populates a libc::tm from StructTimeData (with a configurable error-message prefix) could be called by both, with checked_tm_from_struct_time adding its range validation on top.

As per coding guidelines: "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."

Comment on lines 1327 to 1329
#[cfg(windows)]
#[pymodule]
mod platform {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the current line count and find all #[pymodule] attributes
echo "=== All #[pymodule] attributes in time.rs ==="
rg -n '#\[pymodule' crates/vm/src/stdlib/time.rs

echo ""
echo "=== Lines around 1043 (Unix platform module) ==="
sed -n '1040,1050p' crates/vm/src/stdlib/time.rs

echo ""
echo "=== Lines around 1328 (Windows platform module) ==="
sed -n '1325,1335p' crates/vm/src/stdlib/time.rs

echo ""
echo "=== Check the main module definition to understand module structure ==="
sed -n '20,35p' crates/vm/src/stdlib/time.rs

Repository: RustPython/RustPython

Length of output: 1533


🏁 Script executed:

# Search for other #[pymodule(sub)] usage to understand the pattern
echo "=== Other uses of #[pymodule(sub)] in the codebase ==="
rg -n '#\[pymodule\(sub\)' crates/ | head -20

echo ""
echo "=== Check if there are Windows-specific pyfunction declarations ==="
sed -n '1328,1450p' crates/vm/src/stdlib/time.rs | grep -n "#\[pyfunction\|fn "

echo ""
echo "=== Check how Unix platform module is structured ==="
sed -n '1043,1150p' crates/vm/src/stdlib/time.rs | grep -n "#\[pyfunction\|fn " | head -15

Repository: RustPython/RustPython

Length of output: 2025


🏁 Script executed:

# Check if Windows platform module functions are being properly registered
echo "=== Looking for #[pyfunction] in Windows platform module ==="
sed -n '1328,1500p' crates/vm/src/stdlib/time.rs | grep -n "pyfunction"

echo ""
echo "=== Check module initialization or expose patterns ==="
grep -n "pub fn\|pub async fn" crates/vm/src/stdlib/time.rs | grep -i "clock\|perf" 

echo ""
echo "=== Verify test files reference time.perf_counter on Windows ==="
rg "time\.perf_counter|time\.get_clock_info" --type py | head -10

Repository: RustPython/RustPython

Length of output: 985


🏁 Script executed:

# Get full Windows platform module to see structure
echo "=== Full Windows platform module ==="
sed -n '1328,1410p' crates/vm/src/stdlib/time.rs

echo ""
echo "=== Look for perf_counter and get_clock_info implementation ==="
rg -n "fn perf_counter|fn get_clock_info" crates/vm/src/stdlib/time.rs -A 3

Repository: RustPython/RustPython

Length of output: 4226


🏁 Script executed:

# Check if Windows platform module functions are exported as #[pyfunction]
echo "=== Windows platform module functions (1328-1600 range) ==="
sed -n '1328,1600p' crates/vm/src/stdlib/time.rs | grep -E "#\[pyfunction\]|^[[:space:]]*(pub )?fn " | head -30

Repository: RustPython/RustPython

Length of output: 754


🏁 Script executed:

# Check the complete context of Windows get_clock_info function
echo "=== Windows platform module get_clock_info and surrounding context ==="
sed -n '1500,1530p' crates/vm/src/stdlib/time.rs

echo ""
echo "=== Compare with Unix platform module structure ==="
sed -n '1043,1060p' crates/vm/src/stdlib/time.rs

Repository: RustPython/RustPython

Length of output: 1843


Add sub attribute to Windows platform module: #[pymodule(sub)].

The Windows platform module uses plain #[pymodule] while the Unix platform module uses #[pymodule(sub)]. This is inconsistent with every other sub-module in the codebase, which all use #[pymodule(sub)] to merge their items into the parent module.

Suggested fix:
 #[cfg(windows)]
-#[pymodule]
+#[pymodule(sub)]
 mod platform {
🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/time.rs` around lines 1327 - 1329, The Windows platform
module is annotated with plain #[pymodule] but should be a submodule like the
Unix one; change the attribute on the Windows module declaration from
#[pymodule] to #[pymodule(sub)] for the module named platform so its items are
merged into the parent module (i.e., update the module declaration "mod
platform" to use #[pymodule(sub)]).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant