@@ -2370,6 +2370,307 @@ public function testTruncatesInvalidDates() {
23702370 $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [1 ]->option_value );
23712371 }
23722372
2373+ /**
2374+ * Test NO_ZERO_DATE SQL mode behavior.
2375+ *
2376+ * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date):
2377+ *
2378+ * "The NO_ZERO_DATE mode affects whether the server permits '0000-00-00' as a valid date.
2379+ * Its effect also depends on whether strict SQL mode is enabled.
2380+ * - If this mode is not enabled, '0000-00-00' is permitted and inserts produce no warning.
2381+ * - If this mode is enabled, '0000-00-00' is permitted but produces a warning.
2382+ * - If this mode and strict mode are both enabled, '0000-00-00' is not permitted
2383+ * and inserts produce an error, unless IGNORE is also given."
2384+ */
2385+ public function testZeroDateAcceptedWhenNoZeroDateModeIsOff () {
2386+ // With NO_ZERO_DATE disabled, '0000-00-00 00:00:00' should be accepted.
2387+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2388+
2389+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00'); " );
2390+
2391+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2392+ $ results = $ this ->engine ->get_query_results ();
2393+ $ this ->assertCount ( 1 , $ results );
2394+ $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [0 ]->option_value );
2395+ }
2396+
2397+ /**
2398+ * Test that zero dates are rejected in strict mode when NO_ZERO_DATE is active.
2399+ *
2400+ * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date):
2401+ *
2402+ * "If this mode and strict mode are both enabled, '0000-00-00' is not
2403+ * permitted and inserts produce an error."
2404+ */
2405+ public function testZeroDateRejectedWhenNoZeroDateAndStrictModeAreOn () {
2406+ // Default modes include both NO_ZERO_DATE and STRICT_TRANS_TABLES.
2407+ $ this ->assertQueryError (
2408+ "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00'); " ,
2409+ "Incorrect datetime value: '0000-00-00 00:00:00' "
2410+ );
2411+ }
2412+
2413+ /**
2414+ * Test that zero dates are accepted (with warning) when NO_ZERO_DATE is on
2415+ * but strict mode is off.
2416+ *
2417+ * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date):
2418+ *
2419+ * "If this mode is enabled, '0000-00-00' is permitted but produces a warning."
2420+ */
2421+ public function testZeroDateAcceptedWhenNoZeroDateOnButStrictModeOff () {
2422+ $ this ->assertQuery ( "SET sql_mode = 'NO_ZERO_DATE' " );
2423+
2424+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00'); " );
2425+
2426+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2427+ $ results = $ this ->engine ->get_query_results ();
2428+ $ this ->assertCount ( 1 , $ results );
2429+ $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [0 ]->option_value );
2430+ }
2431+
2432+ /**
2433+ * Test that zero dates work with the DATE column type too.
2434+ */
2435+ public function testZeroDateAcceptedForDateColumn () {
2436+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2437+
2438+ $ this ->assertQuery (
2439+ 'CREATE TABLE _date_test (
2440+ ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
2441+ col_date DATE NOT NULL
2442+ ); '
2443+ );
2444+
2445+ $ this ->assertQuery ( "INSERT INTO _date_test (col_date) VALUES ('0000-00-00'); " );
2446+
2447+ $ this ->assertQuery ( 'SELECT * FROM _date_test; ' );
2448+ $ results = $ this ->engine ->get_query_results ();
2449+ $ this ->assertCount ( 1 , $ results );
2450+ $ this ->assertEquals ( '0000-00-00 ' , $ results [0 ]->col_date );
2451+ }
2452+
2453+ /**
2454+ * Test NO_ZERO_IN_DATE SQL mode behavior.
2455+ *
2456+ * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date):
2457+ *
2458+ * "The NO_ZERO_IN_DATE mode affects whether the server permits dates in
2459+ * which the year part is nonzero but the month or day part is 0. (This
2460+ * mode affects dates such as '2010-00-01' or '2010-01-00', but not
2461+ * '0000-00-00'. To control whether the server permits '0000-00-00',
2462+ * use the NO_ZERO_DATE mode.)
2463+ * - If this mode is not enabled, dates with zero parts are permitted
2464+ * and inserts produce no warning.
2465+ * - If this mode is enabled, dates with zero parts are inserted as
2466+ * '0000-00-00' and produce a warning.
2467+ * - If this mode and strict mode are both enabled, dates with zero parts
2468+ * are not permitted and inserts produce an error."
2469+ */
2470+ public function testZeroInDateAcceptedWhenNoZeroInDateModeIsOff () {
2471+ // Disable NO_ZERO_IN_DATE but keep strict mode on.
2472+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2473+
2474+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00'); " );
2475+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-01-00 00:00:00'); " );
2476+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-00-00 00:00:00'); " );
2477+
2478+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2479+ $ results = $ this ->engine ->get_query_results ();
2480+ $ this ->assertCount ( 3 , $ results );
2481+ $ this ->assertEquals ( '2020-00-15 00:00:00 ' , $ results [0 ]->option_value );
2482+ $ this ->assertEquals ( '2020-01-00 00:00:00 ' , $ results [1 ]->option_value );
2483+ $ this ->assertEquals ( '2020-00-00 00:00:00 ' , $ results [2 ]->option_value );
2484+ }
2485+
2486+ /**
2487+ * Test that dates with zero parts are rejected in strict mode when
2488+ * NO_ZERO_IN_DATE is active.
2489+ */
2490+ public function testZeroInDateRejectedWhenNoZeroInDateAndStrictModeAreOn () {
2491+ // Default modes include both NO_ZERO_IN_DATE and STRICT_TRANS_TABLES.
2492+ $ this ->assertQueryError (
2493+ "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00'); " ,
2494+ "Incorrect datetime value: '2020-00-15 00:00:00' "
2495+ );
2496+ }
2497+
2498+ /**
2499+ * Test that dates with zero parts get stored as '0000-00-00 00:00:00'
2500+ * when NO_ZERO_IN_DATE is on but strict mode is off.
2501+ *
2502+ * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date):
2503+ *
2504+ * "If this mode is enabled, dates with zero parts are inserted as
2505+ * '0000-00-00' and produce a warning."
2506+ */
2507+ public function testZeroInDateBecomesZeroDateWhenNoZeroInDateOnButStrictOff () {
2508+ $ this ->assertQuery ( "SET sql_mode = 'NO_ZERO_IN_DATE' " );
2509+
2510+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00'); " );
2511+
2512+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2513+ $ results = $ this ->engine ->get_query_results ();
2514+ $ this ->assertCount ( 1 , $ results );
2515+ $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [0 ]->option_value );
2516+ }
2517+
2518+ /**
2519+ * Test that all modes disabled allows both zero dates and zero-in-dates.
2520+ */
2521+ public function testBothZeroDateModesDisabledAcceptsAll () {
2522+ $ this ->assertQuery ( "SET sql_mode = '' " );
2523+
2524+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00'); " );
2525+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00'); " );
2526+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2020-01-00 00:00:00'); " );
2527+
2528+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2529+ $ results = $ this ->engine ->get_query_results ();
2530+ $ this ->assertCount ( 3 , $ results );
2531+ $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [0 ]->option_value );
2532+ $ this ->assertEquals ( '2020-00-15 00:00:00 ' , $ results [1 ]->option_value );
2533+ $ this ->assertEquals ( '2020-01-00 00:00:00 ' , $ results [2 ]->option_value );
2534+ }
2535+
2536+ /**
2537+ * Test that valid dates still work correctly regardless of zero date modes.
2538+ */
2539+ public function testValidDatesWorkWithZeroDateModes () {
2540+ // Default modes (NO_ZERO_DATE + STRICT_TRANS_TABLES).
2541+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00'); " );
2542+
2543+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2544+ $ results = $ this ->engine ->get_query_results ();
2545+ $ this ->assertCount ( 1 , $ results );
2546+ $ this ->assertEquals ( '2022-01-15 14:30:00 ' , $ results [0 ]->option_value );
2547+ }
2548+
2549+ /**
2550+ * Test zero date handling in UPDATE statements.
2551+ */
2552+ public function testZeroDateInUpdate () {
2553+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2554+
2555+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00'); " );
2556+ $ this ->assertQuery ( "UPDATE _dates SET option_value = '0000-00-00 00:00:00'; " );
2557+
2558+ $ this ->assertQuery ( 'SELECT * FROM _dates; ' );
2559+ $ results = $ this ->engine ->get_query_results ();
2560+ $ this ->assertCount ( 1 , $ results );
2561+ $ this ->assertEquals ( '0000-00-00 00:00:00 ' , $ results [0 ]->option_value );
2562+ }
2563+
2564+ /**
2565+ * Test that zero dates are rejected in UPDATE when NO_ZERO_DATE and strict mode are on.
2566+ */
2567+ public function testZeroDateInUpdateRejectedWhenNoZeroDateAndStrictModeAreOn () {
2568+ // Default modes include both NO_ZERO_DATE and STRICT_TRANS_TABLES.
2569+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00'); " );
2570+ $ this ->assertQueryError (
2571+ "UPDATE _dates SET option_value = '0000-00-00 00:00:00'; " ,
2572+ "Incorrect datetime value: '0000-00-00 00:00:00' "
2573+ );
2574+ }
2575+
2576+ /**
2577+ * Test that dates with zero parts are rejected in UPDATE when
2578+ * NO_ZERO_IN_DATE and strict mode are on.
2579+ */
2580+ public function testZeroInDateInUpdateRejectedWhenNoZeroInDateAndStrictModeAreOn () {
2581+ // Default modes include both NO_ZERO_IN_DATE and STRICT_TRANS_TABLES.
2582+ $ this ->assertQuery ( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00'); " );
2583+ $ this ->assertQueryError (
2584+ "UPDATE _dates SET option_value = '2020-00-15 00:00:00'; " ,
2585+ "Incorrect datetime value: '2020-00-15 00:00:00' "
2586+ );
2587+ }
2588+
2589+ /**
2590+ * Test that stored zero dates can be selected and compared.
2591+ *
2592+ * In MySQL, zero dates are regular values for reads — they can appear in
2593+ * WHERE, ORDER BY, and comparisons regardless of the current SQL mode.
2594+ */
2595+ public function testSelectZeroDatesComparison () {
2596+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2597+
2598+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('zero', '0000-00-00 00:00:00'); " );
2599+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('real', '2022-01-15 14:30:00'); " );
2600+
2601+ // Zero dates compare as less than real dates.
2602+ $ this ->assertQuery ( "SELECT option_name FROM _dates WHERE option_value < '2000-01-01 00:00:00' ORDER BY option_value; " );
2603+ $ results = $ this ->engine ->get_query_results ();
2604+ $ this ->assertCount ( 1 , $ results );
2605+ $ this ->assertEquals ( 'zero ' , $ results [0 ]->option_name );
2606+
2607+ // Equality match on zero date.
2608+ $ this ->assertQuery ( "SELECT option_name FROM _dates WHERE option_value = '0000-00-00 00:00:00'; " );
2609+ $ results = $ this ->engine ->get_query_results ();
2610+ $ this ->assertCount ( 1 , $ results );
2611+ $ this ->assertEquals ( 'zero ' , $ results [0 ]->option_name );
2612+ }
2613+
2614+ /**
2615+ * Test ORDER BY with a mix of zero and non-zero dates.
2616+ */
2617+ public function testSelectZeroDatesOrderBy () {
2618+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2619+
2620+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('b', '2022-06-01 00:00:00'); " );
2621+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('a', '0000-00-00 00:00:00'); " );
2622+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('c', '2023-01-01 00:00:00'); " );
2623+
2624+ $ this ->assertQuery ( 'SELECT option_name FROM _dates ORDER BY option_value ASC; ' );
2625+ $ results = $ this ->engine ->get_query_results ();
2626+ $ this ->assertCount ( 3 , $ results );
2627+ $ this ->assertEquals ( 'a ' , $ results [0 ]->option_name );
2628+ $ this ->assertEquals ( 'b ' , $ results [1 ]->option_name );
2629+ $ this ->assertEquals ( 'c ' , $ results [2 ]->option_name );
2630+ }
2631+
2632+ /**
2633+ * Test that zero-in-dates stored in the database can be read back
2634+ * and filtered in SELECT statements.
2635+ */
2636+ public function testSelectZeroInDates () {
2637+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2638+
2639+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('zero-month', '2020-00-15 00:00:00'); " );
2640+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('zero-day', '2020-01-00 00:00:00'); " );
2641+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('normal', '2020-01-15 00:00:00'); " );
2642+
2643+ // All three rows are readable.
2644+ $ this ->assertQuery ( 'SELECT option_name, option_value FROM _dates ORDER BY option_value ASC; ' );
2645+ $ results = $ this ->engine ->get_query_results ();
2646+ $ this ->assertCount ( 3 , $ results );
2647+ $ this ->assertEquals ( '2020-00-15 00:00:00 ' , $ results [0 ]->option_value );
2648+ $ this ->assertEquals ( '2020-01-00 00:00:00 ' , $ results [1 ]->option_value );
2649+ $ this ->assertEquals ( '2020-01-15 00:00:00 ' , $ results [2 ]->option_value );
2650+
2651+ // Filtering by a zero-in-date value works.
2652+ $ this ->assertQuery ( "SELECT option_name FROM _dates WHERE option_value = '2020-00-15 00:00:00'; " );
2653+ $ results = $ this ->engine ->get_query_results ();
2654+ $ this ->assertCount ( 1 , $ results );
2655+ $ this ->assertEquals ( 'zero-month ' , $ results [0 ]->option_name );
2656+ }
2657+
2658+ /**
2659+ * Test date functions on zero dates — YEAR(), MONTH(), DAY() all return 0.
2660+ */
2661+ public function testDateFunctionsOnZeroDates () {
2662+ $ this ->assertQuery ( "SET sql_mode = 'STRICT_TRANS_TABLES' " );
2663+
2664+ $ this ->assertQuery ( "INSERT INTO _dates (option_name, option_value) VALUES ('zero', '0000-00-00 00:00:00'); " );
2665+
2666+ $ this ->assertQuery ( 'SELECT YEAR(option_value) as y, MONTH(option_value) as m, DAY(option_value) as d FROM _dates; ' );
2667+ $ results = $ this ->engine ->get_query_results ();
2668+ $ this ->assertCount ( 1 , $ results );
2669+ $ this ->assertEquals ( 0 , $ results [0 ]->y );
2670+ $ this ->assertEquals ( 0 , $ results [0 ]->m );
2671+ $ this ->assertEquals ( 0 , $ results [0 ]->d );
2672+ }
2673+
23732674 public function testCaseInsensitiveSelect () {
23742675 $ this ->assertQuery (
23752676 "CREATE TABLE _tmp_table (
0 commit comments