close
Skip to content

Accept zero dates when NO_ZERO_DATE SQL mode is off#327

Merged
adamziel merged 10 commits intotrunkfrom
adamziel/no-zero-date-mode
Mar 12, 2026
Merged

Accept zero dates when NO_ZERO_DATE SQL mode is off#327
adamziel merged 10 commits intotrunkfrom
adamziel/no-zero-date-mode

Conversation

@adamziel
Copy link
Collaborator

Summary

MySQL controls zero-date acceptance through two SQL modes: NO_ZERO_DATE and NO_ZERO_IN_DATE. Both are on by default, but applications can turn them off to allow values like '0000-00-00 00:00:00' or '2020-00-15'. The SQLite driver wasn't honoring these modes – SQLite's DATE()/DATETIME() functions return NULL for zero dates, so they always fell through to the strict-mode error, even when the zero-date modes were disabled.

This PR adds zero-date acceptance checks to cast_value_for_saving() that inspect the active SQL modes before rejecting a date. When NO_ZERO_DATE is off (or on without strict mode), all-zero dates pass through. When NO_ZERO_IN_DATE is off, dates with zero month/day parts like '2020-00-15' are accepted too. The translate_datetime_literal() method is also updated to preserve zero-part dates instead of truncating them via strtotime().

The behavior matrix now matches MySQL's documentation:

Mode combination '0000-00-00' '2020-00-15'
Both modes off Accepted Accepted
Mode on, non-strict Accepted (warning in MySQL) Stored as '0000-00-00'
Mode on + strict Error Error

Test plan

  • 12 new PHPUnit tests covering all combinations of NO_ZERO_DATE × NO_ZERO_IN_DATE × strict mode
  • Full test suite passes (772 tests, 0 failures)
  • Review that WordPress core's test_insert_empty_post_date still works with default modes

🤖 Generated with Claude Code

@adamziel adamziel changed the base branch from main to trunk March 11, 2026 22:33
adamziel and others added 5 commits March 12, 2026 01:13
… off

MySQL's behavior around zero dates ('0000-00-00') and dates with zero
parts ('2020-00-15') depends on two SQL modes: NO_ZERO_DATE and
NO_ZERO_IN_DATE. Both are enabled by default in MySQL 8.0, but
applications can disable them.

Previously, the SQLite driver always rejected zero dates through
SQLite's DATE()/DATETIME() functions, which return NULL for such
values. This caused incorrect errors when strict mode was on but
the zero-date modes were off.

Now the CASE expression in cast_value_for_saving() checks the active
SQL modes and inserts additional WHEN clauses to accept zero dates
when appropriate. The translate_datetime_literal() method also
preserves dates with zero month/day parts when NO_ZERO_IN_DATE is off,
instead of truncating them via strtotime().
Instead of conditionally building $zero_date_whens in PHP, the WHEN
clauses are always present in the CASE statement with the SQL mode
check embedded as a literal boolean (AND NOT 0/1). This keeps the
generated SQL structure consistent regardless of active modes.
One argument per line in the sprintf call, align equals signs,
and use single quotes for CREATE TABLE string in tests.
Replace sprintf with strtr so each substituted value appears once in the
argument list and the template reads like annotated SQL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, not the raw value

The strtr refactor accidentally changed `WHEN $function_call > '0'`
to `WHEN {value} > '0'`, comparing the wrong operand and breaking
date validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adamziel adamziel force-pushed the adamziel/no-zero-date-mode branch from ca145f8 to 7763982 Compare March 12, 2026 00:13
@adamziel adamziel requested a review from JanJakes March 12, 2026 00:28
adamziel and others added 4 commits March 12, 2026 01:30
Verify that UPDATE statements produce errors for zero dates and
zero-in-dates when strict mode is combined with NO_ZERO_DATE or
NO_ZERO_IN_DATE, matching the existing INSERT rejection tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests verifying that stored zero dates and zero-in-dates can be
selected, compared, ordered, and filtered – matching MySQL behavior.

Fix YEAR(), MONTH(), and DAY() functions to return 0 for zero date
parts. Previously, strtotime() couldn't parse dates like '0000-00-00'
or '2020-00-15', producing wrong results. Now the date parts are
extracted directly from the string when possible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PHP manual references for format specifiers used by gmdate()
were accidentally removed when adding zero-date handling. These
comments document why specific format characters (n, Y, j) are
used in the fallback path and should be preserved.
adamziel added a commit to adamziel/streaming-site-migration that referenced this pull request Mar 12, 2026
The SQLite integration plugin rejects `0000-00-00` dates when the
`STRICT_TRANS_TABLES` mode is on. A fix is [explored in PR
327](WordPress/sqlite-database-integration#327),
but it's not released yet. Until it is, let's disable the strict mode in
the produced SQL dump so the import can work in SQLite environments.
Add ^ anchors to the YEAR/MONTH/DAY regex patterns so they only
match date strings starting at the beginning of the input, not
arbitrary substrings.
@adamziel adamziel merged commit d87bc8f into trunk Mar 12, 2026
16 checks passed
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