From 542175ecfad2ac544ab9389b7620f208bd625851 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 5 Feb 2026 16:09:54 +0000 Subject: [PATCH 1/5] ext/standard: mark ext/random as a required dependency (#21130) As the shuffle() and array_rand() functions use part of the Random API --- ...ReflectionExtension_getDependencies_variation2.phpt | 10 ++++++---- ext/standard/basic_functions.c | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/reflection/tests/ReflectionExtension_getDependencies_variation2.phpt b/ext/reflection/tests/ReflectionExtension_getDependencies_variation2.phpt index 60527c0b369f2..84d261e0a00e5 100644 --- a/ext/reflection/tests/ReflectionExtension_getDependencies_variation2.phpt +++ b/ext/reflection/tests/ReflectionExtension_getDependencies_variation2.phpt @@ -7,10 +7,12 @@ Felix De Vliegher $standard = new ReflectionExtension('standard'); var_dump($standard->getDependencies()); ?> ---EXPECTF-- -array(%d) { +--EXPECT-- +array(3) { + ["random"]=> + string(8) "Required" ["uri"]=> - %s(8) "Required" + string(8) "Required" ["session"]=> - %s(8) "Optional" + string(8) "Optional" } diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index d96de3947d5d2..0f802bc128a20 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -140,6 +140,7 @@ static void user_shutdown_function_dtor(zval *zv); static void user_tick_function_dtor(user_tick_function_entry *tick_function_entry); static const zend_module_dep standard_deps[] = { /* {{{ */ + ZEND_MOD_REQUIRED("random") ZEND_MOD_REQUIRED("uri") ZEND_MOD_OPTIONAL("session") ZEND_MOD_END From dcf5cd9195102973b710648f5e6bb02b1cd5c41a Mon Sep 17 00:00:00 2001 From: Arshid Date: Thu, 5 Feb 2026 22:01:53 +0530 Subject: [PATCH 2/5] ext/dom: Returning a boolean value using RETURN_BOOL (#21141) --- ext/dom/element.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ext/dom/element.c b/ext/dom/element.c index 25bd306bcd808..9d70390275cef 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -1326,11 +1326,7 @@ PHP_METHOD(DOMElement, hasAttribute) DOM_GET_OBJ(nodep, id, xmlNodePtr, intern); attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len); - if (attr == NULL) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } + RETURN_BOOL(attr != NULL); } /* }}} end dom_element_has_attribute */ From ee26417b58d0d9f664151f27b3f89f795bc0786e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 23 Dec 2025 00:28:16 +0100 Subject: [PATCH 3/5] Fix timezone offset with seconds losing precision There are two issues: 1. The 'e' formatter doesn't output the seconds of the timezone even if it has seconds. 2. var_dump(), (array) cast, serialization, ... don't include the timezone second offset in the output. This means that, for example, serializing and then unserializing a date object loses the seconds of the timezone. This can be observed by comparing the output of getTimezone() for `$dt` vs the unserialized object in the provided test. Closes GH-20764. --- NEWS | 1 + ext/date/php_date.c | 87 +++++++++++++++++++----------------- ext/date/tests/bug81565.phpt | 2 +- ext/date/tests/gh20764.phpt | 53 ++++++++++++++++++++++ 4 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 ext/date/tests/gh20764.phpt diff --git a/NEWS b/NEWS index dd9fd9a268640..ab6d95471bd64 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,7 @@ PHP NEWS - Date: . Fixed bug GH-20936 (DatePeriod::__set_state() cannot handle null start). (ndossche) + . Fix timezone offset with seconds losing precision. (ndossche) - DOM: . Fixed bug GH-21077 (Accessing Dom\Node::baseURI can throw TypeError). diff --git a/ext/date/php_date.c b/ext/date/php_date.c index a67a58bcf9232..acdd612d04c82 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -795,13 +795,24 @@ static zend_string *date_format(const char *format, size_t format_len, timelib_t case TIMELIB_ZONETYPE_ABBR: length = slprintf(buffer, sizeof(buffer), "%s", offset->abbr); break; - case TIMELIB_ZONETYPE_OFFSET: - length = slprintf(buffer, sizeof(buffer), "%c%02d:%02d", - ((offset->offset < 0) ? '-' : '+'), - abs(offset->offset / 3600), - abs((offset->offset % 3600) / 60) - ); + case TIMELIB_ZONETYPE_OFFSET: { + int seconds = offset->offset % 60; + if (seconds == 0) { + length = slprintf(buffer, sizeof(buffer), "%c%02d:%02d", + ((offset->offset < 0) ? '-' : '+'), + abs(offset->offset / 3600), + abs((offset->offset % 3600) / 60) + ); + } else { + length = slprintf(buffer, sizeof(buffer), "%c%02d:%02d:%02d", + ((offset->offset < 0) ? '-' : '+'), + abs(offset->offset / 3600), + abs((offset->offset % 3600) / 60), + abs(seconds) + ); + } break; + } } } break; @@ -1930,6 +1941,32 @@ static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, return zend_std_get_properties(object); } /* }}} */ +static zend_string *date_create_tz_offset_str(timelib_sll offset) +{ + int seconds = offset % 60; + size_t size; + const char *format; + + if (seconds == 0) { + size = sizeof("+05:00"); + format = "%c%02d:%02d"; + } else { + size = sizeof("+05:00:01"); + format = "%c%02d:%02d:%02d"; + } + + zend_string *tmpstr = zend_string_alloc(size - 1, 0); + + /* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */ + ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format, + offset < 0 ? '-' : '+', + abs((int)(offset / 3600)), + abs((int)(offset % 3600) / 60), + abs(seconds)); + + return tmpstr; +} + static void date_object_to_hash(php_date_obj *dateobj, HashTable *props) { zval zv; @@ -1947,17 +1984,8 @@ static void date_object_to_hash(php_date_obj *dateobj, HashTable *props) case TIMELIB_ZONETYPE_ID: ZVAL_STRING(&zv, dateobj->time->tz_info->name); break; - case TIMELIB_ZONETYPE_OFFSET: { - zend_string *tmpstr = zend_string_alloc(sizeof("UTC+05:00")-1, 0); - int utc_offset = dateobj->time->z; - - ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), sizeof("+05:00"), "%c%02d:%02d", - utc_offset < 0 ? '-' : '+', - abs(utc_offset / 3600), - abs(((utc_offset % 3600) / 60))); - - ZVAL_NEW_STR(&zv, tmpstr); - } + case TIMELIB_ZONETYPE_OFFSET: + ZVAL_NEW_STR(&zv, date_create_tz_offset_str(dateobj->time->z)); break; case TIMELIB_ZONETYPE_ABBR: ZVAL_STRING(&zv, dateobj->time->tz_abbr); @@ -2069,29 +2097,8 @@ static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv) case TIMELIB_ZONETYPE_ID: ZVAL_STRING(zv, tzobj->tzi.tz->name); break; - case TIMELIB_ZONETYPE_OFFSET: { - timelib_sll utc_offset = tzobj->tzi.utc_offset; - int seconds = utc_offset % 60; - size_t size; - const char *format; - if (seconds == 0) { - size = sizeof("+05:00"); - format = "%c%02d:%02d"; - } else { - size = sizeof("+05:00:01"); - format = "%c%02d:%02d:%02d"; - } - zend_string *tmpstr = zend_string_alloc(size - 1, 0); - - /* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */ - ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format, - utc_offset < 0 ? '-' : '+', - abs((int)(utc_offset / 3600)), - abs((int)(utc_offset % 3600) / 60), - abs(seconds)); - - ZVAL_NEW_STR(zv, tmpstr); - } + case TIMELIB_ZONETYPE_OFFSET: + ZVAL_NEW_STR(zv, date_create_tz_offset_str(tzobj->tzi.utc_offset)); break; case TIMELIB_ZONETYPE_ABBR: ZVAL_STRING(zv, tzobj->tzi.z.abbr); diff --git a/ext/date/tests/bug81565.phpt b/ext/date/tests/bug81565.phpt index b23e950eafdf6..fff5766c7ffe8 100644 --- a/ext/date/tests/bug81565.phpt +++ b/ext/date/tests/bug81565.phpt @@ -15,6 +15,6 @@ echo "\n", (new DatetimeZone('+01:45:30'))->getName(); \DateTime::__set_state(array( 'date' => '0021-08-21 00:00:00.000000', 'timezone_type' => 1, - 'timezone' => '+00:49', + 'timezone' => '+00:49:56', )) +01:45:30 diff --git a/ext/date/tests/gh20764.phpt b/ext/date/tests/gh20764.phpt new file mode 100644 index 0000000000000..33963de91b232 --- /dev/null +++ b/ext/date/tests/gh20764.phpt @@ -0,0 +1,53 @@ +--TEST-- +GH-20764 (Timezone offset with seconds loses precision) +--FILE-- +format('e')); + var_dump($dt); + var_dump(unserialize(serialize($dt))->getTimezone()); +} + +?> +--EXPECTF-- +--- Testing timezone +03:00:30 --- +string(9) "+03:00:30" +object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "2025-04-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(9) "+03:00:30" +} +object(DateTimeZone)#%d (2) { + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(9) "+03:00:30" +} +--- Testing timezone -03:00:30 --- +string(9) "-03:00:30" +object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "2025-04-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(9) "-03:00:30" +} +object(DateTimeZone)#%d (2) { + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(9) "-03:00:30" +} From d84ad6a5329e6306dc1cfe6966dfcb1bfdf763ea Mon Sep 17 00:00:00 2001 From: Arshid Date: Thu, 5 Feb 2026 23:22:42 +0530 Subject: [PATCH 4/5] ext/gd: remove _php_image_output unused argument (#21138) --- ext/gd/gd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 135cff7fcc639..81f442f333485 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -120,7 +120,7 @@ static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS); /* End Section filters declarations */ static gdImagePtr _php_image_create_from_string(zend_string *Data, const char *tn, gdImagePtr (*ioctx_func_p)(gdIOCtxPtr)); static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, const char *tn, gdImagePtr (*func_p)(FILE *), gdImagePtr (*ioctx_func_p)(gdIOCtxPtr)); -static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, const char *tn); +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type); static gdIOCtx *create_stream_context(php_stream *stream, int close_stream); static gdIOCtx *create_output_context(zval *to_zval, uint32_t arg_num); static int _php_image_type(zend_string *data); @@ -1729,7 +1729,7 @@ PHP_FUNCTION(imagecreatefromtga) /* }}} */ /* {{{ _php_image_output */ -static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, const char *tn) +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type) { zval *imgind; char *file = NULL; @@ -2102,14 +2102,14 @@ PHP_FUNCTION(imagewbmp) /* {{{ Output GD image to browser or file */ PHP_FUNCTION(imagegd) { - _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD"); + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD); } /* }}} */ /* {{{ Output GD2 image to browser or file */ PHP_FUNCTION(imagegd2) { - _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2"); + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2); } /* }}} */ From 10fb64fed7016c6beb0aad1e39afc5e893277fa1 Mon Sep 17 00:00:00 2001 From: Arshid Date: Thu, 5 Feb 2026 23:53:18 +0530 Subject: [PATCH 5/5] ext/readline: readline_read_history/readline_write_history returning a boolean value using RETURN_BOOL (#21140) --- ext/readline/readline.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/ext/readline/readline.c b/ext/readline/readline.c index 838c74da86ce7..61129194db208 100644 --- a/ext/readline/readline.c +++ b/ext/readline/readline.c @@ -393,13 +393,9 @@ PHP_FUNCTION(readline_read_history) RETURN_FALSE; } - /* XXX from & to NYI */ - if (read_history(arg)) { - /* If filename is NULL, then read from `~/.history' */ - RETURN_FALSE; - } else { - RETURN_TRUE; - } + /* XXX from & to NYI + If filename is NULL, then read from `~/.history' */ + RETURN_BOOL(!read_history(arg)); } /* }}} */ @@ -417,11 +413,7 @@ PHP_FUNCTION(readline_write_history) RETURN_FALSE; } - if (write_history(arg)) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } + RETURN_BOOL(!write_history(arg)); } /* }}} */