Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 45 additions & 40 deletions ext/date/php_date.c
Original file line number Diff line number Diff line change
Expand Up @@ -797,13 +797,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)
);
}
Comment on lines +800 to +815
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: these lines mix tabs and spaces

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah this was already the case to align the switch case. It's a bit annoying to work with.

break;
}
}
}
break;
Expand Down Expand Up @@ -1921,6 +1932,30 @@ 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;
Expand All @@ -1938,17 +1973,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);
Expand Down Expand Up @@ -2060,29 +2086,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);
Expand Down
2 changes: 1 addition & 1 deletion ext/date/tests/bug81565.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 28 additions & 0 deletions ext/date/tests/gh20764.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
GH-20764 (Timezone offset with seconds loses precision)
--FILE--
<?php

$tz = new DateTimeZone('+03:00:30');
$dt = new DateTimeImmutable('2025-04-01', $tz);
var_dump($dt->format('e'));
var_dump($dt);
var_dump(unserialize(serialize($dt))->getTimezone());

?>
--EXPECT--
string(9) "+03:00:30"
object(DateTimeImmutable)#2 (3) {
["date"]=>
string(26) "2025-04-01 00:00:00.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(9) "+03:00:30"
}
object(DateTimeZone)#4 (2) {
["timezone_type"]=>
int(1)
["timezone"]=>
string(9) "+03:00:30"
}
Loading