Skip to content

fix(libc): convert local times before 1970 and after 3000 on Windows - #4811

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:carry/localtime-range-windows-4807
Oct 13, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:carry/localtime-range-windows-4807

Conversation

@milescoviello

@milescoviello milescoviello commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes #4807. Carries @DavidSamir's fix branch from that issue (DavidSamir:fix/libc-localtime-outside-1970-3000-windows, 9cb4e363), keeping the commit and its authorship; it cherry-picks onto current main without changes.

On Windows, localtime, localtime_s, mktime and ctime failed for every time before 1970 and after about January 3001, because they call the C runtime's localtime_s and mktime, which only accept that range: localtime returned NULL, mktime -1 and ctime ??? ??? ?? ??:??:?? ????, where the console and the Linux build convert. docs/dev/debt/libc.md recorded the gap. The Gregorian calendar repeats every 400 years (146097 days, a whole number of weeks), so when the C runtime refuses a time, localtime_s converts the same moment a whole number of cycles away inside 1970 to 2370 and moves the year back, and mktime does the reverse. The path is chosen before the one C runtime call, from the time value for localtime_s and from the broken-down time for mktime, so no call is retried after an error and errno is not saved and restored; a conversion inside the range is made by the same single call as before. The debt entry now records what remains: a time outside the range gets the daylight-saving rule the C runtime applies to the year it is converted in. Linux is unchanged.

guest_time_conversion gains CheckLocalRange (ten times from year 1 to 9999 through localtime_s, localtime, mktime and ctime, normalisation across 1969/1970 and 3100/3101, localtime_s(INT64_MIN) still failing), and guest_mktime loses its three Windows-only exceptions.

Tested

  • From the issue and its update for the second version: Windows 11, WinLibs MinGW-w64 GCC 15.2.0, Release: with main's Time.cpp both tests fail and with the fix both pass 5/5; a probe of 2,360,002 conversions under nine TZ settings has 1,374,526 failures with main's libraries and none with the branch's, every localtime matching gmtime plus tm_gmtoff and every mktime round-tripping; eleven catalog titles end the same way with both; full ctest 1001/1001; no measurable cost inside the range.
  • Linux, added for this carry: Ubuntu 24.04, GCC 13.3 (CI's compiler), Release with CI's configure flags, on main 0c3dc9c. guest_time_conversion, guest_mktime, guest_utime, guest_rtc, guest_file_timestamps and guest_freebsd_clocks pass, and all 324 guest_* tests pass in two runs. The new checks also pass with main's Time.cpp, as expected since only the Windows path changes. I could not run Windows myself.
  • tools/check_conventions.py --base upstream/main passes. The debt entry it rewrites sits next to the one my fix(libc): keep a trailing slash on paths under a mount alias #4681 adds in docs/dev/debt/libc.md, so whichever of the two lands second needs a one-line rebase, which I'll do.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#4015 core/libs/prx/libc/src/Time.cpp L277-280, L289-290
#4543 docs/dev/debt/libc.md L54-55

@DavidSamir

Copy link
Copy Markdown
Contributor

The branch in #4807 has been updated to 9cb4e36: mktime no longer calls the C runtime a second time after a failure, it chooses the path from the broken-down time before the one call (the rule from the review of #4532). Details and the rerun results are in my comment on #4807. This PR still carries a133bd4; could you take the new commit?

AI-assisted: yes

@milescoviello
milescoviello force-pushed the carry/localtime-range-windows-4807 branch from 66eb387 to 7126090 Compare October 13, 2026 01:50
@milescoviello

Copy link
Copy Markdown
Contributor Author

Updated #4811 to your 9cb4e36 (now 7126090 on current main). Linux rechecked on the new version: all 324 guest_* tests pass in two runs, guest_time_conversion and guest_mktime also pass with main's Time.cpp, and a MinGW-w64 -fsyntax-only -Wall -Wextra compile of Time.cpp is clean. The PR body now describes the single-call version.

AI-assisted: yes

@oneandonlydean oneandonlydean self-assigned this Oct 13, 2026
@oneandonlydean
oneandonlydean merged commit 9b7c8ea into boykopovar:main Oct 13, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libc: localtime, mktime and ctime fail before 1970 and after 3000 on Windows; fix branch ready

3 participants