Skip to content

Date/Time: Refine WP_Locale parameter types and handle invalid date inputs. - #14827

Open
rootzero00 wants to merge 9 commits into
WordPress:trunkfrom
rootzero00:docs/64522-weekday-range
Open

rootzero00 wants to merge 9 commits into
WordPress:trunkfrom
rootzero00:docs/64522-weekday-range

Conversation

@rootzero00

@rootzero00 rootzero00 commented Oct 24, 2026 •

Copy link
Copy Markdown

Refines weekday, genitive-month, and meridiem parameter types using PHPStan-specific annotations while preserving standard @PARAM tags. get_month() retains its full string|int input type and unknown-month fallback.

Integrates Weston's six follow-up commits from rootzero00#1:

  • Clamp the calendar start-of-week option to 0–6.
  • Handle unavailable post weekdays without displaying Sunday.
  • Narrow get_post_time() return types by date format.
  • Pass an integer weekday in wp_date().
  • Return false for numeric timestamps that cannot be parsed, with regression tests.
  • Remove the overly narrow get_month() annotation.

Trac ticket: https://core.trac.wordpress.org/ticket/64522

Verification

Candidate: e26b0b9

Verification executed in the Codex environment:

  • PHP 8.3.6 syntax checks passed for all four changed PHP files.
  • git diff --check passed against the original trunk base.
  • PHPStan 2.3.1, repository configuration at level 5: no errors.
  • Focused PHPUnit run: 59 tests, 176 assertions, zero failures/errors; one existing warning-expectation deprecation warning.
  • Coding standards: zero errors; six warnings outside the changed lines.
  • A broader 378-test run had three errors and two failures involving unavailable legacy timezone names. That broader run is not claimed as passing.

GitHub Actions results must be assessed for this exact candidate separately.

Contributor: Raul Salvat. Requested WordPress.org props: raulsalvat.
Integrated follow-up commits retain Weston Ruter's authorship and existing attribution.

Use of AI Tools

AI assistance: Yes.
Tools: ChatGPT/Codex; integrated follow-up commits retain their Claude Code attribution.
Used for preparing the original annotation change, reviewing and validating the follow-ups, integrating existing commits, and preparing PR metadata. Original server commands were executed by the contributor; the candidate verification described above ran in the Codex environment.


This pull request is for code review only. Please keep other discussion in the Trac ticket; do not merge this pull request on GitHub.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props raulsalvat, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Comment thread src/wp-includes/class-wp-locale.php Outdated

@westonruter westonruter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While we're here, we should go ahead and update the other methods with similar type narrowings. For example, the get_meridiem() method should add a:

@phpstan-param 'am'|'pm'|'AM'|'PM' $meridiem

Same for the other methods where relevant. I think get_month() and get_month_genitive() are the other candidates.

westonruter and others added 6 commits October 25, 2026 10:30
The `start_of_week` option is cast to an int but otherwise unchecked, so a
negative value makes `( $wdcount + $week_begins ) % 7` negative, and a value
above 6 is out of range for `WP_Locale::get_weekday()`. Either leads to an
undefined array key. Clamping it to 0-6 keeps the weekday headers valid and
satisfies the new `int<0, 6>` type of `WP_Locale::get_weekday()`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kday_date().

`get_post_time()` returns false when the post date cannot be parsed. Both
functions passed that straight to `WP_Locale::get_weekday()`, where it was
coerced to the array key 0, so the post was silently labeled Sunday. Now
nothing is output in that case, and the filterable string result is cast and
clamped to 0-6 to match the `int<0, 6>` type of `WP_Locale::get_weekday()`.

This removes the corresponding entry from the `argument.type` PHPStan baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For each format character whose output has a fixed range, such as `w` for
`'0'` through `'6'` or `m` for `'01'` through `'12'`, the conditional return
type now gives that range, mirroring what PHPStan infers for
`DateTimeInterface::format()`. Formats with an open-ended numeric output give
`numeric-string`. Formats that `wp_date()` translates (`D`, `l`, `M`, `F`, `a`,
and `A`) and the PHP 8.2+ `X` and `x` remain `string`.

With `w` narrowed, the weekday number passed to `WP_Locale::get_weekday()` in
the_weekday() and the_weekday_date() already satisfies `int<0, 6>` after an
int cast, so the `clamp()` calls are no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int in wp_date().

`DateTimeInterface::format( 'w' )` returns a string from `'0'` to `'6'`, which
worked only because the string was coerced to an integer array key. Casting it
to an int matches the `int<0, 6>` parameter type and removes the last
`get_weekday()` entry from the `argument.type` PHPStan baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arsed.

`wp_date()` only checked the timestamp with `is_numeric()`, which accepts
values that `date_create( '@' . $timestamp )` rejects, such as `'1e3'`, `1e20`,
`' 5'`, and `'.5'`. For those, `date_create()` returned false and the following
`setTimezone()` call caused a fatal error. Now false is returned, as documented
for invalid timestamp input and as already done for non-numeric input.

This also resolves the PHPStan `method.nonObject` errors on every use of
`$datetime` in the function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unlike the other `WP_Locale` getters, `get_month()` returns an empty string for
a month number that is not found, which was added so that callers like
`single_month_title()` can pass unvalidated query var values. Narrowing its
parameter to valid month numbers told callers to validate input the method
already handles, surfacing errors at call sites that pass `zeroise()` results,
query vars, or database values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@westonruter

Copy link
Copy Markdown
Member

🤖 Comment by Claude Opus 5.5

Several follow-up changes were made while reviewing the new WP_Locale parameter types. "Allow edits by maintainers" is disabled on this PR, so they could not be pushed to this branch. They are proposed instead as a PR into it: rootzero00#1 (6 commits on top of fefbbea). Merging that PR adds them here.

Changes

  1. Clamp the start of the week in get_calendar(). $week_begins was (int) get_option( 'start_of_week' ) with no bounds, so ( $wdcount + $week_begins ) % 7 could be negative or otherwise outside int<0, 6>, leading to an undefined key in get_weekday(). It is now clamp( (int) get_option( 'start_of_week' ), 0, 6 ), using the clamp() polyfill in compat.php.
  2. Handle a missing post weekday in the_weekday() and the_weekday_date(). get_post_time() returns false when the post date cannot be parsed. That was passed straight to get_weekday(), where it was coerced to key 0, so the post was silently labeled Sunday. Now nothing is output in that case: the_weekday() returns early, and the_weekday_date() skips the weekday along with $before and $after. The filters still apply.
  3. Narrow the get_post_time() return type by date format. The conditional return type now gives a literal union for each format character with a fixed range (w gives '0' through '6', m gives '01' through '12', and so on), mirroring what PHPStan infers for DateTimeInterface::format(). Open-ended numeric formats such as Y give numeric-string. Formats that wp_date() translates when $translate is set (D, l, M, F, a, and A), plus the PHP 8.2+ X and x, remain string. With w narrowed, (int) $weekday_number in the two functions above satisfies int<0, 6> without needing a clamp.
  4. Pass the weekday number to get_weekday() as an int in wp_date(). $datetime->format( 'w' ) returns a string from '0' to '6', which worked only through array key coercion.
  5. Return false from wp_date() when the timestamp cannot be parsed. is_numeric() accepts values that date_create( '@' . $timestamp ) rejects, such as '1e3', 1e20, ' 5', and '.5'. For those, the next line fataled with Call to a member function setTimezone() on false. Now false is returned, as documented for invalid timestamp input and as already done for non-numeric input. A data-provider test covers the four cases; each fails without the fix.
  6. Keep the full month number type for get_month(). r59870 (Core-62824) made get_month() return an empty string for a month number that is not found, so that callers such as single_month_title() can pass unvalidated query var values. Narrowing its parameter to valid month numbers contradicted that and flagged 12 call sites passing zeroise() results, query vars, or database values, so the @phpstan-param was removed from get_month() only. The narrowing is kept on get_weekday(), get_month_genitive(), and get_meridiem(), which have no such fallback (test_get_weekday_undefined_index asserts the warning for an out-of-range weekday).

Upstream PHPStan fix for get_meridiem()

wp_date() passes $datetime->format( 'a' ) and $datetime->format( 'A' ) to get_meridiem(), whose parameter is now 'am'|'pm'|'AM'|'PM'. PHPStan currently infers only non-falsy-string for those formats, even though PHP formats them from hard-coded English values regardless of the locale. This was reported as phpstan/phpstan#15537 and is fixed in phpstan/phpstan-src#6838 (draft), which also covers S, D, l, M, and F. Once that is released and core updates PHPStan, the two remaining get_meridiem() errors go away with no change to core.

PHPStan error breakdown

The 14 files changed here or calling the affected functions were analyzed at three points: the trunk base of this PR (38a613a), the current PR head (fefbbea), and the head with the follow-up commits. Errors were matched by file, identifier, and message, since line numbers shift between versions.

Level 5 (CI)

There are no errors at any of the three points. Relative to trunk, the argument.type baseline loses both of its get_weekday() entries (3 errors):

File Count Trunk message
src/wp-includes/functions.php 1 Parameter #1 $weekday_number of method WP_Locale::get_weekday() expects int, string given.
src/wp-includes/general-template.php 2 Parameter #1 $weekday_number of method WP_Locale::get_weekday() expects int, string|false given.

Level 10

Trunk base PR head With follow-ups
Errors 1955 1964 1950

Fixed relative to the PR head: 14 errors

Identifier Count Where (line numbers at the PR head)
argument.type 6 get_month() given non-empty-string, non-falsy-string, string|false, or int: src/wp-admin/export.php:167, src/wp-admin/includes/class-wp-list-table.php:815, src/wp-admin/includes/media.php:2969, and src/wp-includes/general-template.php:1653, 1951, and 2740
argument.type 1 get_weekday() given int<-6, 6>: src/wp-includes/general-template.php:2749
method.nonObject 7 Cannot call method …() on DateTime|false. on every use of $datetime in wp_date(): src/wp-includes/functions.php:261, 264, 271, 272, 289, 292, and 308

Seven more errors at the PR head are not fixed but go back to their trunk wording, because get_month() again expects int|string: the six mixed arguments from query vars and database rows (general-template.php:1661, 1948, 2381, 2851, and 2863, and media.php:5062) and the zeroise() call inside get_month().

Relative to trunk: 7 fixed, 2 introduced

  • Fixed: the 7 method.nonObject errors in wp_date() listed above.
  • Introduced: 2 argument.type errors for get_meridiem() at src/wp-includes/functions.php:294 and 297, which Infer literal types for word date format characters phpstan/phpstan-src#6838 resolves.
  • Unchanged apart from wording: the zeroise() call in get_month_genitive() now reports int<1, 12>|string given instead of int|string given.

@westonruter

Copy link
Copy Markdown
Member

@rootzero00 please able maintainer edits for this PR and others as that will facilitate getting changes into core.

@rootzero00 rootzero00 changed the title Docs: Clarify the weekday range in WP_Locale::get_weekday(). Date/Time: Refine WP_Locale parameter types and handle invalid date inputs. Oct 27, 2026
@westonruter

Copy link
Copy Markdown
Member

🤖 Comment by Claude Opus 5.5

PHPStan 2.3.3 has been released, and it includes phpstan/phpstan-src#6838 (fixing phpstan/phpstan#15537). PHPStan now infers literal types for the word date format characters (a, A, S, D, l, M, and F).

That resolves the two get_meridiem() errors noted in the earlier comment as pending upstream. PHPStan now infers 'am'|'pm' for a and 'AM'|'PM' for A, which satisfies the new @phpstan-param on WP_Locale::get_meridiem():

  • src/wp-includes/functions.php:294: Parameter #1 $meridiem of method WP_Locale::get_meridiem() expects 'AM'|'am'|'PM'|'pm', non-falsy-string given.
  • src/wp-includes/functions.php:297: the same error.

The branch was analyzed with PHPStan 2.3.2 and then with 2.3.3. The analyzed branch was this PR's head e26b0b9 with trunk merged in.

Config PHPStan 2.3.2 PHPStan 2.3.3
phpstan.neon.dist (CI, level 5) 0 0 (no unmatched baseline entries)
Level 10, whole project 27,327 27,325

The upgrade introduces no errors anywhere else in the codebase. Once PHPStan is bumped to 2.3.3 in composer.json, this PR will no longer introduce any level 10 errors relative to trunk, and its 7 method.nonObject fixes still apply. The upgrade changes no other Composer packages.

@westonruter

Copy link
Copy Markdown
Member

@rootzero00 Please turn on maintainer edits for this PR.

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.

2 participants