Skip to content

fix(libc): ignore unavailable positive daylight hints - #12592

Open
JJenkx wants to merge 8 commits into
boykopovar:mainfrom
JJenkx:codex/libc-mktime-unavailable-dst-20261028
Open

JJenkx wants to merge 8 commits into
boykopovar:mainfrom
JJenkx:codex/libc-mktime-unavailable-dst-20261028

Conversation

@JJenkx

@JJenkx JJenkx commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What

When tm_isdst is positive in a zone/year with no nearby daylight adjustment, the host CRT subtracts an hour that the two measured title-bundled libraries do not subtract. For example, 2024-01-15 12:00 in UTC0 with tm_isdst=1 should remain 12:00. Both mktime entrypoints now use automatic resolution when the shared host-zone query finds no available adjustment. Seasonal hints remain effective: an EST5EDT winter daylight hint still shifts an hour, and a summer standard hint still selects standard time.

Share libkernel's existing zone lookup and nearby-daylight calculation through SonyCrt.hpp rather than creating a libc-to-libkernel link cycle or duplicating the rules. The kernel wrappers retain their calculation. Preserve the Buenos Aires historical-DST round trip, errno, normalization and failure behavior. Record remaining transition and host-range limitations in libc debt.

Fulfilled landing order: #12549 has merged, supplying its separate Windows nonordinary-calendar normalization correction. This branch contains this daylight fix and its Windows review follow-up on the original main baseline 319621ba2e578bd053b0baba2877fe70520e0737; it does not copy that PR's implementation. The private combined tree is tested below. No title unblock or FPS improvement is claimed.

Tested

Windows review follow-up at 59d2b51c999b8e228bd51eefeffbfb55c4b3093c: a standalone native UCRT probe shows that a zero process _dstbias survives switching to an explicit EST5EDT zone. All six available-hint conversions then differ in timestamp or normalized hour. The shared Windows refresh now applies the explicit DST zone's one-hour adjustment before conversion; libc's outer zone scope uses it too, so standard-hint and local-time calls do not depend on an earlier positive-hint call. An unset host TZ retains the OS-derived adjustment. This follows the CRT's documented explicit-TZ US daylight rules.

  • Windows 11, WinLibs GCC15.2 UCRT r7, host Eastern Standard Time unchanged: prior PR production plus the new tests (1d527de967) passes 3/4 focused suites and fails guest_mktime at all six injected-zero-bias rows. Fixed production 9fd017708f passes 4/4, zero skips. Final 59d2b51c99 corrects only diagnostic labels; rebuilding the fixture and rerunning all four suites passes, with production module hashes unchanged.
  • Independent Linux Release source/builds at 1d527de967 and 9fd017708f: 4/4 suites per arm, zero skips; the resulting libc binaries are identical. Groups: guest_mktime, guest_time_conversion, guest_time_extras, guest_strftime.
  • Each available-hint record identifies the export, month, input hint, actual/expected timestamp, hour and DST flag. An earlier diagnostic called the second converter libkernel; it is actually libc's mktime_nid_postfix forwarding export. Final labels are libc_mktime and mktime; the archive explicitly maps the earlier negative log labels.

Follow-up source diff, Windows negative/positive logs and JUnit, final-label rerun, Linux paired logs, and process-local CRT reproducer, SHA-256 46ad518ad715f2baadbcc56b0f95a2974b0f82c33b3844c0c26ffea3c7fc32fb (download verified). This is focused module/suite qualification, not a whole-project/game/GPU run. Actual UTC-OS-host validation remains pending: the local negative control injects the zero process bias while leaving the physical Windows timezone unchanged. The fork's hosted diagnostic could not run because Actions is disabled there. Upstream UTC CI is still needed; these local results are not described as a UTC-machine run.

Year-edge follow-up at 7e8465539372e0c6615956deac04adfe5886b30e (contributed by @adriadam10): guard the complete optional daylight-search window before entering it. Linux and actual Windows each pass all six focused groups three times (18 executions per host). Prior a131 production with the new tests throws on Linux; the contributed guard removes that throw. Both-host 216-row boundary probes include INT_MIN, INT_MIN+1, INT_MAX, all months and hints 1/0/-1 in UTC0/EST5EDT. Every tested outer-year row (INT_MIN/INT_MAX, 144 per host) matches main's conversion, without a throw. The full sweep is intentionally not described as identical: the interior INT_MIN+1 positive-hint cases retain the availability correction (12 changed rows per Linux zone; 12 in Windows UTC0 and zero in Windows EST5EDT). These are host-preservation checks, not a console edge oracle. All 21,965 Linux normal-range rows remain unchanged from the prior head.

Guard qualification, negative controls, both-host boundary rows, normal-range preservation and JUnit, SHA-256 1d707eb7d61a0a713e0bc1b9632c0e4ed61430572e8c40e0d9aaf51c2f3f46e5.

Initial measurement head a131f0f6a6f57bd5f7293deddff1099f0fab535f, Release, independent CPU-only source/builds:

Known limits: 52 directed gap/fold/negative-daylight cases remain different; no local macOS execution or console transition-policy qualification. The separately attributed macOS standalone review is not a full local macOS build; console year-edge behavior remains unmeasured. Host lookup failures propagate. This is a bounded correctness improvement, not complete mktime equivalence.

Raw logs, guarded numeric reference rows, probes, patches, manifest and both-host evidence. SHA-256: c9c44603bc56a044ac0c960056f6e117a7519c75138c181f39a0ee9aea8cecd9. No SDK/game binaries are included.

Independent Windows validation, reported by bunbeans2-arch for topic 7e8465539372e0c6615956deac04adfe5886b30e against main f7f03daf52: Windows 11 Pro 22631, i9-9900K, MSYS2 UCRT64 GCC 16.1, Release. guest_mktime passes 5/5 with host zone Eastern Standard Time; retaining the new test while restoring the three production files to main fails 5/5 at the unavailable-daylight-hint timestamp assertion; plain main passes 3/3. This is the contributor's run, not a new local Windows execution.

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 under Silent stubs in their library's technical debt file
  • 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 (fix(libc): normalize a pre-1970 calendar before the Windows host mktime #12549 has merged; earlier private-combination evidence retains its original pins)
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@adriadam10 adriadam10 self-assigned this Oct 28, 2026

@rooklz rooklz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I ran this on macOS (the body has no macOS run). Head is a131f0f6a6, base 319621ba2e.

Setup. macOS 26.2, Apple clang, arm64 and x86_64 (Rosetta). Built with -O1 -fsanitize=address,undefined -fno-sanitize-recover=undefined and the target's -include HostStandardStreams.hpp. Standalone build of Time.cpp, Environment.cpp and Multibyte.cpp with GuestMktime.cpp, plus a one-line __error_nid_postfix → &errno shim.

  • Head passes guest_mktime 3/3 on x86_64 and on arm64, with no sanitizer reports. The macOS-only Lord_Howe/Moscow cases are included.
  • Head's tests with main's Time.cpp fail (unavailable daylight hint changed the timestamp), as the body says for Linux and Windows.
  • Cost: a call with tm_isdst=1 takes about 4.2 µs instead of 3.4 µs on main (-O2, 20k calls, UTC0/EST5EDT/America/New_York). tm_isdst<=0 is unchanged.

Regression: tm_isdst>0 at the representable year edges now throws. The new block only checks utcCalendarFields(local). SonyHostDaylightAmount_nid_no_patch then asks localtime_r about times up to 11 × 2629746 s on each side, plus the zone offset. Close to the int tm_year limits those times are outside what localtime_r can represent, so SonyHostZoneAt_nid_no_patch throws runtime_error and the title aborts. main returns a value for all of these. I swept tm_year ∈ {INT_MIN, INT_MIN+1, INT_MAX}, every month, tm_isdst ∈ {1,0,-1}, in UTC0 and EST5EDT. Exactly 12 rows changed, all tm_isdst=1, and all of them went from a value to a throw:

tm_year=INT_MIN  tm_mon=0..10  tm_isdst=1   main -67768040608487925 ...   head THROW
tm_year=INT_MAX  tm_mon=11     tm_isdst=1   main 67768036190247600        head THROW
                 (no host local time for 67768036192880946)

The debt line does say that a lookup that can't represent its candidates propagates. But before this PR, these inputs didn't do a lookup at all. A guard that only enters the block when the whole search window converts keeps main's result at the edges:

-        GuestTm normalized{};
-        if (utcCalendarFields(local, normalized)) {
+        GuestTm early{};
+        GuestTm late{};
+        constexpr std::int64_t searchSeconds = 12 * 2629746 + SECONDS_PER_DAY;
+        if (utcCalendarFields(local - searchSeconds, early) && utcCalendarFields(local + searchSeconds, late)) {

With this change, head's guest_mktime stays green on both arches, and the 108-row edge sweep is byte-identical to main in both zones. This test kills the throw (head RED with the uncaught runtime_error, guarded version green):

@@ -96,6 +96,22 @@
 
 #endif
 
+static void CheckRepresentableYearEdges(Converter convert) {
+    const struct { int year; int month; } cases[]{
+        {std::numeric_limits<int>::min(), 0},
+        {std::numeric_limits<int>::max(), 11}
+    };
+    for (const auto& item : cases) {
+        GuestTm input{};
+        input.tm_year = item.year;
+        input.tm_mon = item.month;
+        input.tm_mday = 15;
+        input.tm_hour = 12;
+        input.tm_isdst = 1;
+        Require(convert(&input) != -1, "daylight hint at a representable year edge failed");
+    }
+}
+
 static void CheckFailure(Converter convert, int hint = -1) {
     GuestTm input{};
     input.tm_year = std::numeric_limits<int>::max();
@@ -123,6 +139,7 @@
         }
         CheckFailure(convert);
         CheckFailure(convert, 1);
+        CheckRepresentableYearEdges(convert);
         for (const int year : {40, 70, 124}) CheckUnavailableDaylightHint(convert, year);
     }
 }

(I only ran the test on macOS. Please check it on your Linux and Windows hosts before taking it. Whether the console applies the hint at those years is unmeasured. The diff only restores the value main returned and doesn't claim it's correct.)

Mutants (arm64 ASan+UBSan, head tests):

  • Killed: always clearing the hint (available daylight hint was ignored), and entering only for tm_isdst == 1 (the 2/INT_MAX hints catch it).
  • Survived: dropping the errno restore. That's equivalent on macOS, since localtime_r doesn't set errno here. On Windows it may not be equivalent; I couldn't check. Using guess instead of zone, or plain local, as the search origin also survived, as did a forward-only search and a 6-month search. None of the test zones puts the result within a month of a rule boundary. These are only test-strength notes and don't block, because the body's two-library comparison covers the rule behaviour.

Conventions and git merge-tree against 319621ba2e are clean.

@github-actions

github-actions Bot commented Oct 28, 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
PR 10409 core/libs/tests/GuestMktime.cpp L53-55, L168
PR 10987 docs/dev/debt/libc.md L277

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/libs/prx/libc/src/Time.cpp
core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 6819 docs/dev/debt/libc.md
PR 7081 core/libs/prx/libc/include/SonyCrt.hpp
docs/dev/debt/libc.md
PR 7386 docs/dev/debt/libc.md
PR 7447 docs/dev/debt/libc.md
PR 7715 docs/dev/debt/libc.md
PR 7804 core/libs/prx/libkernel/Time/Time.cpp
PR 8150 docs/dev/debt/libc.md
PR 8965 docs/dev/debt/libc.md
PR 9340 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9447 core/libs/prx/libc/src/Time.cpp
docs/dev/debt/libc.md
PR 9466 docs/dev/debt/libc.md
PR 9526 docs/dev/debt/libc.md
PR 10780 docs/dev/debt/libc.md
PR 11172 docs/dev/debt/libc.md
PR 11332 docs/dev/debt/libc.md
PR 11661 docs/dev/debt/libc.md
PR 11689 docs/dev/debt/libc.md
PR 11698 docs/dev/debt/libc.md
PR 11724 docs/dev/debt/libc.md
PR 11726 docs/dev/debt/libc.md
PR 11831 docs/dev/debt/libc.md
PR 12518 docs/dev/debt/libc.md
PR 12642 core/libs/prx/libkernel/Time/Time.cpp
PR 12682 docs/dev/debt/libc.md
PR 12688 docs/dev/debt/libc.md
PR 12700 docs/dev/debt/libc.md
PR 12704 docs/dev/debt/libc.md
PR 12849 docs/dev/debt/libc.md
PR 12965 docs/dev/debt/libc.md
PR 13449 docs/dev/debt/libc.md
PR 13486 docs/dev/debt/libc.md
PR 13794 docs/dev/debt/libc.md
PR 13804 docs/dev/debt/libc.md
PR 13809 docs/dev/debt/libc.md
PR 14212 docs/dev/debt/libc.md
PR 15773 docs/dev/debt/libc.md
PR 15799 core/libs/prx/libkernel/Time/Time.cpp
PR 15800 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 15801 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 15802 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 15803 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 15804 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 15807 core/libs/prx/libkernel/Time/Time.cpp
docs/dev/debt/libc.md
PR 16004 docs/dev/debt/libc.md
PR 16437 core/libs/prx/libkernel/Time/Time.cpp

@JJenkx

JJenkx commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @adriadam10 for the contributed guard in 7e846553937, and @rooklz for the boundary repro. I qualified that exact commit on Linux and actual Windows: all six focused groups pass three times on each host (18 executions each). The unfixed a131 production throws with the new Linux regression; the guarded source restores successful conversion.

Both hosts also completed the 216-row edge sweep. All 144 rows at the outer years INT_MIN and INT_MAX match main, with no throws. I did not label the entire sweep unchanged: at the interior INT_MIN+1 year, positive hints still receive this PR's intended availability correction (12 changed rows per Linux zone; Windows has 12 in UTC0 and zero in EST5EDT). All 21,965 Linux ordinary-range replay rows remain identical to the previous PR head. No console edge behavior is inferred.

The body now names your contributed commit and links the both-host raw evidence. The earlier transition-policy limitations and pending #10409 landing-order coordination remain explicit.

@babyhuey

Copy link
Copy Markdown
Contributor

Windows run of this head 7e8465539372, merged onto main 3a2b9198165 (clean merge, local only), with the selection CI's skipped Windows product job uses: Windows 11, Ryzen 7 5800X, RTX 3080, WinLibs GCC 15.2, Release, BUILD_TESTING=ON. The tree was configured fresh after the merge, then select_tests.py select / ninja -f select.ninja selected / --target libs / select_tests.py ctest --parallel 4 --timeout 120.

AI-assisted: yes (Claude Code)

@bunbeans2-arch

Copy link
Copy Markdown
Contributor

A Windows run with GCC 16: Windows 11 Pro 10.0.22631 (host zone Eastern Standard Time), i9-9900K, MSYS2 UCRT64 GCC 16.1.0, Release, this PR (7e84655393) merged into main f7f03da.

  • With this PR: guest_mktime passed 5 of 5.
  • Same tree with SonyCrt.hpp, libc Time.cpp and libkernel Time.cpp put back to main (this PR's test kept): it failed 5 of 5 at the new check unavailable daylight hint changed the timestamp.
  • main f7f03da alone (its own test and source): guest_mktime passed 3 of 3.

AI-assisted: yes (Claude Code)

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The full Build of this head merged onto main (run 41057229445) fails on windows: guest_mktime aborts (0xc0000409) with available daylight hint was ignored, the check at core/libs/tests/GuestMktime.cpp:83 in CheckAvailableDaylightHints (TZ=EST5EDT, hints 1/0/1 across January and July). linux is green and main does not fail this test.

The Windows runs reported in the thread used hosts in Eastern/Pacific zones; the CI runner's host zone is UTC. The new SonyHostZoneAt_nid_no_patch (SonyCrt.hpp) reads _get_timezone/_get_dstbias and localtime_s, and with a UTC system zone and TZ set from the guest those can give a different daylight amount than on your hosts, so the available-hint case is treated as unavailable (or the reverse).

Please reproduce on Windows with the host zone set to UTC, find which of the six conversions differs, and fix the helper so the seasonal cases of CheckAvailableDaylightHints pass there too. The failing case should be named in the test message so CI shows it.

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Addressed with a normal fast-forward to 59d2b51c999b8e228bd51eefeffbfb55c4b3093c (no merge commits). A native UCRT probe confirms that zero _dstbias persists after selecting explicit EST5EDT. The new controlled regression reproduces all six available-hint differences on prior PR production; refreshing the explicit Windows DST bias fixes them.

Windows: prior production/new tests passes3/4 suites and fails the injected mktime matrix; fixed production and the final named-fixture rerun pass4/4 with zero skips. Linux:4/4 per arm, libc binary unchanged. Updated the description with exact heads, all six named conversions, logs/source diff/reproducer and the earlier diagnostic-label correction.

The physical Windows host remains Eastern; this is a controlled zero-bias reproduction, not an actual UTC-host run. Upstream UTC CI is still needed to verify the original host condition.

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

The new Windows job is green, but its log explicitly reports “No guest core/libs test sources changed”; it did not execute guest_mktime, so I am not treating that build as the requested UTC-host runtime verification.

I traced the selection gap to MinGW’s actual liblibc.dll.a import-library filename: the selector accepted libc.dll.a but missed this spelling. PR #13661 fixes that shared classifier. Its new cases fail against main and pass on Linux and Windows (29/29 focused tests per OS). The previously reported controlled zero-bias Windows reproduction remains valid; actual UTC-host execution is still outstanding. A Windows run with both changes can now select the guest regression.

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

The selector follow-up #13661 at 01a303c56e6881069f2bdf220da33f4d2730971a now passes an actual hosted Windows build/CTest job: 1,613 passed, 879 skipped, 0 failed. Its log confirms main’s guest_mktime executes; it does not include this PR’s six-case change.

Please rerun this head 59d2b51c999b8e228bd51eefeffbfb55c4b3093c on the upstream UTC Windows runner with #13661 included, or after that selector fix lands. This closes the earlier test-selection gap and should expose all six named host-state conversions in the requested environment.

I also prepared a manual UTC qualification workflow on the fork, with prior production 7e84655393 plus the exact named fixture as a negative control, followed by exact-head candidate and all four guest time suites. The repository reports Actions enabled and Build active, but GitHub rejects its dispatch with HTTP422 “Actions has been disabled for this repository.” No hosted run or UTC pass is claimed; the physical desktop remains Eastern.

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Windows run with the host's system zone actually set to UTC, as asked in the review. Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON.

  • This PR's head 59d2b51c99 merged onto main 25f435d7e9 (merge 7368e205fb, local only), with main 25f435d7e9 alone as a control. Each was built in its own build directory.

  • One harness did everything on this machine's test queue, with no game running:

    • recorded the zone (Eastern Standard Time);
    • set it to UTC with tzutil /s UTC, then checked it read back as UTC, offset 00:00:00;
    • ran ctest -R ^guest_mktime$ --repeat until-fail:3 in fresh processes for both builds;
    • restored and verified Eastern Standard Time afterwards.

    The clock was not changed.

Results under the UTC system zone:

  • This PR: guest_mktime passed 3 of 3. All six named available-hint conversions match in both contexts (host-state and zero-host-daylight-bias), for both mktime and libc_mktime:

    month=0 hint=1 actual=1705334400 expected=1705334400 isdst=0
    month=6 hint=0 actual=1721062800 expected=1721062800 isdst=1
    month=6 hint=1 actual=1721059200 expected=1721059200 isdst=1
    

    (standardBias=18000 daylightBias=-3600 for TZ=EST5EDT in every line.)

  • main control: its own guest_mktime passed 3 of 3.

So on a real UTC-zone Windows host the CheckAvailableDaylightHints cases pass with this head. This is a local run, not the upstream full Build, which still has to confirm it on the hosted runner.

AI-assisted: yes (Claude Code).

@JJenkx

JJenkx commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

@adriadam10 The requested real UTC-system-zone Windows reproduction is now recorded in the follow-up. The unchanged public head is 59d2b51c999b8e228bd51eefeffbfb55c4b3093c; the tested local integration 7368e205fbdc254917e0cfa646e43f211324a07b has parents main 25f435d7e9511afc3f7fe8f7d1cf057cc20f43f4 and that head. Both integration and main control passed guest_mktime three times under UTC. Raw named records show all three available-hint inputs matching for both converters in both host-state and zero-host-daylight-bias contexts. The harness recorded Eastern Standard Time → UTC (00:00) → restored Eastern Standard Time; clock unchanged. Could you re-review this follow-up? This local evidence does not claim the hosted full Build passed. AI-assisted: yes (Codex).

Raw Windows receipts, source pins and logs, SHA-256 219a90ce12dc0e99600f5b7fc975725070f372902c2c3ae69499eb798cec8f5a (download verified).

@JJenkx

JJenkx commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@adriadam10 Could you re-review? I'm now the primary owner of this PR. Where it stands at the unchanged head 59d2b51c999b8e228bd51eefeffbfb55c4b3093c:

  • Your request (reproduce on Windows with the host zone set to UTC and fix the helper) is answered in the UTC follow-up. It is a real UTC system-zone run, and guest_mktime passes 3/3 with the seasonal CheckAvailableDaylightHints cases. Your review was on the older commit 7e84655393.
  • CI on this head: every check passes, including windows and relinker (windows-latest, g++), except write-watch (macos-15) and write-watch (macos-15-intel). That CI run is from 2026-10-30, before test(libc): register the macOS write watch fault tests once #13220 (merged 2026-11-01) fixed the duplicate write-watch CTest registrations. The two jobs failed at CMake configure (add_test given test NAME "write_watch_execute-fault" which already exists, likewise read-fault) before any of this PR's libc code built. So it is stale pre-fix CI that needs a rerun, not a current main failure.

A maintainer rerun of full CI on this head would replace that stale result and confirm the UTC runner too.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
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.

5 participants