diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 96c37092511fd..509b09ef37ddd 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2500,6 +2500,49 @@ PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, siz return 1; } /* }}} */ +PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec) /* {{{ */ +{ + dateobj->time = timelib_time_ctor(); + dateobj->time->zone_type = TIMELIB_ZONETYPE_OFFSET; + + timelib_unixtime2gmt(dateobj->time, (timelib_sll)sec); + timelib_update_ts(dateobj->time, NULL); + php_date_set_time_fraction(dateobj->time, usec); +} /* }}} */ + +PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts) /* {{{ */ +{ + double sec_dval = trunc(ts); + zend_long sec; + int usec; + + if (UNEXPECTED(isnan(sec_dval) + || sec_dval >= (double)TIMELIB_LONG_MAX + || sec_dval < (double)TIMELIB_LONG_MIN + )) { + zend_throw_error( + date_ce_date_range_error, + "Seconds must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ", %g given", + TIMELIB_LONG_MIN, + TIMELIB_LONG_MAX, + sec_dval + ); + return false; + } + + sec = (zend_long)sec_dval; + usec = (int)(fmod(ts, 1) * 1000000); + + if (UNEXPECTED(usec < 0)) { + sec = sec - 1; + usec = 1000000 + usec; + } + + php_date_initialize_from_ts_long(dateobj, sec, usec); + + return true; +} /* }}} */ + /* {{{ Returns new DateTime object */ PHP_FUNCTION(date_create) { @@ -2564,7 +2607,7 @@ PHP_FUNCTION(date_create_from_format) } /* }}} */ -/* {{{ Returns new DateTime object formatted according to the specified format */ +/* {{{ Returns new DateTimeImmutable object formatted according to the specified format */ PHP_FUNCTION(date_create_immutable_from_format) { zval *timezone_object = NULL; @@ -2662,6 +2705,39 @@ PHP_METHOD(DateTime, createFromInterface) } /* }}} */ +/* {{{ Creates new DateTime object from given unix timetamp */ +PHP_METHOD(DateTime, createFromTimestamp) +{ + zval *value; + zval new_object; + php_date_obj *new_dateobj; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_NUMBER(value) + ZEND_PARSE_PARAMETERS_END(); + + php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, &new_object); + new_dateobj = Z_PHPDATE_P(&new_object); + + switch (Z_TYPE_P(value)) { + case IS_LONG: + php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0); + break; + + case IS_DOUBLE: + if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) { + zval_ptr_dtor(&new_object); + RETURN_THROWS(); + } + break; + + EMPTY_SWITCH_DEFAULT_CASE(); + } + + RETURN_OBJ(Z_OBJ(new_object)); +} +/* }}} */ + /* {{{ Creates new DateTimeImmutable object from an existing mutable DateTime object. */ PHP_METHOD(DateTimeImmutable, createFromMutable) { @@ -2704,6 +2780,39 @@ PHP_METHOD(DateTimeImmutable, createFromInterface) } /* }}} */ +/* {{{ Creates new DateTimeImmutable object from given unix timestamp */ +PHP_METHOD(DateTimeImmutable, createFromTimestamp) +{ + zval *value; + zval new_object; + php_date_obj *new_dateobj; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_NUMBER(value) + ZEND_PARSE_PARAMETERS_END(); + + php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, &new_object); + new_dateobj = Z_PHPDATE_P(&new_object); + + switch (Z_TYPE_P(value)) { + case IS_LONG: + php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0); + break; + + case IS_DOUBLE: + if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) { + zval_ptr_dtor(&new_object); + RETURN_THROWS(); + } + break; + + EMPTY_SWITCH_DEFAULT_CASE(); + } + + RETURN_OBJ(Z_OBJ(new_object)); +} +/* }}} */ + static bool php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht) { zval *z_date; diff --git a/ext/date/php_date.h b/ext/date/php_date.h index a4729ff58ffeb..a67564476bec7 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -142,6 +142,7 @@ PHPAPI zend_class_entry *php_date_get_period_ce(void); PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object); PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, size_t time_str_len, const char *format, zval *timezone_object, int flags); - +PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec); +PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts); #endif /* PHP_DATE_H */ diff --git a/ext/date/php_date.stub.php b/ext/date/php_date.stub.php index e0717fa7f4c85..7ba05b8b6e062 100644 --- a/ext/date/php_date.stub.php +++ b/ext/date/php_date.stub.php @@ -362,6 +362,9 @@ public static function createFromInterface(DateTimeInterface $object): DateTime */ public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false {} + /** @tentative-return-type */ + public static function createFromTimestamp(int|float $timestamp): static {} + /** * @return array|false * @tentative-return-type @@ -466,6 +469,9 @@ public static function __set_state(array $array): DateTimeImmutable {} */ public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTimeImmutable|false {} + /** @tentative-return-type */ + public static function createFromTimestamp(int|float $timestamp): static {} + /** * @return array|false * @tentative-return-type diff --git a/ext/date/php_date_arginfo.h b/ext/date/php_date_arginfo.h index bbede1f39596e..027bec67cb08d 100644 --- a/ext/date/php_date_arginfo.h +++ b/ext/date/php_date_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0f204ac6646be79b515189a384fce9bcea9a4f42 */ + * Stub hash: 1445f6053da5ca9dc7bb618f2eadc4a8ea56a15f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_strtotime, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, datetime, IS_STRING, 0) @@ -280,6 +280,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_DateTime_cre ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timezone, DateTimeZone, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DateTime_createFromTimestamp, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_MASK(0, timestamp, MAY_BE_LONG|MAY_BE_DOUBLE, NULL) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_DateTime_getLastErrors, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_END_ARG_INFO() @@ -348,6 +352,8 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_DateTimeImmu ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timezone, DateTimeZone, 1, "null") ZEND_END_ARG_INFO() +#define arginfo_class_DateTimeImmutable_createFromTimestamp arginfo_class_DateTime_createFromTimestamp + #define arginfo_class_DateTimeImmutable_getLastErrors arginfo_class_DateTime_getLastErrors #define arginfo_class_DateTimeImmutable_format arginfo_class_DateTimeInterface_format @@ -554,6 +560,7 @@ ZEND_METHOD(DateTime, __wakeup); ZEND_METHOD(DateTime, __set_state); ZEND_METHOD(DateTime, createFromImmutable); ZEND_METHOD(DateTime, createFromInterface); +ZEND_METHOD(DateTime, createFromTimestamp); ZEND_METHOD(DateTime, modify); ZEND_METHOD(DateTime, sub); ZEND_METHOD(DateTimeImmutable, __construct); @@ -561,6 +568,7 @@ ZEND_METHOD(DateTimeImmutable, __serialize); ZEND_METHOD(DateTimeImmutable, __unserialize); ZEND_METHOD(DateTimeImmutable, __wakeup); ZEND_METHOD(DateTimeImmutable, __set_state); +ZEND_METHOD(DateTimeImmutable, createFromTimestamp); ZEND_METHOD(DateTimeImmutable, modify); ZEND_METHOD(DateTimeImmutable, add); ZEND_METHOD(DateTimeImmutable, sub); @@ -670,6 +678,7 @@ static const zend_function_entry class_DateTime_methods[] = { ZEND_ME(DateTime, createFromImmutable, arginfo_class_DateTime_createFromImmutable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(DateTime, createFromInterface, arginfo_class_DateTime_createFromInterface, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(createFromFormat, date_create_from_format, arginfo_class_DateTime_createFromFormat, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(DateTime, createFromTimestamp, arginfo_class_DateTime_createFromTimestamp, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(getLastErrors, date_get_last_errors, arginfo_class_DateTime_getLastErrors, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(format, date_format, arginfo_class_DateTime_format, ZEND_ACC_PUBLIC) ZEND_ME(DateTime, modify, arginfo_class_DateTime_modify, ZEND_ACC_PUBLIC) @@ -695,6 +704,7 @@ static const zend_function_entry class_DateTimeImmutable_methods[] = { ZEND_ME(DateTimeImmutable, __wakeup, arginfo_class_DateTimeImmutable___wakeup, ZEND_ACC_PUBLIC) ZEND_ME(DateTimeImmutable, __set_state, arginfo_class_DateTimeImmutable___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(createFromFormat, date_create_immutable_from_format, arginfo_class_DateTimeImmutable_createFromFormat, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(DateTimeImmutable, createFromTimestamp, arginfo_class_DateTimeImmutable_createFromTimestamp, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(getLastErrors, date_get_last_errors, arginfo_class_DateTimeImmutable_getLastErrors, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(format, date_format, arginfo_class_DateTimeImmutable_format, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(getTimezone, date_timezone_get, arginfo_class_DateTimeImmutable_getTimezone, ZEND_ACC_PUBLIC) diff --git a/ext/date/tests/createFromTimestamp.phpt b/ext/date/tests/createFromTimestamp.phpt new file mode 100644 index 0000000000000..7e27e19941beb --- /dev/null +++ b/ext/date/tests/createFromTimestamp.phpt @@ -0,0 +1,283 @@ +--TEST-- +Tests for DateTime[Immutable]::createFromTimestamp +--INI-- +date.timezone=Europe/London +--FILE-- +getMessage() . "\n"; + } + + echo 'DateTimeImmutable::createFromTimestamp(' . var_export($ts, true) . '): '; + try { + var_dump(DateTimeImmutable::createFromTimestamp($ts)); + } catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; + } +} + +echo 'MyDateTime::createFromTimestamp(' . var_export(0, true) . '): '; +try { + var_dump(MyDateTime::createFromTimestamp(0)); +} catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} + +echo 'MyDateTimeImmutable::createFromTimestamp(' . var_export(0, true) . '): '; +try { + var_dump(MyDateTimeImmutable::createFromTimestamp(0)); +} catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} + +?> +--EXPECTF-- +DateTime::createFromTimestamp(1696883232): object(DateTime)#%d (3) { + ["date"]=> + string(26) "2023-10-09 20:27:12.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(1696883232): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "2023-10-09 20:27:12.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-1696883232): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1916-03-25 03:32:48.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-1696883232): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1916-03-25 03:32:48.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(1696883232.013981): object(DateTime)#%d (3) { + ["date"]=> + string(26) "2023-10-09 20:27:12.013981" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(1696883232.013981): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "2023-10-09 20:27:12.013981" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-1696883232.013981): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1916-03-25 03:32:47.986019" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-1696883232.013981): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1916-03-25 03:32:47.986019" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(0.123456): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.123456" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(0.123456): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.123456" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-0.123456): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1969-12-31 23:59:59.876544" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-0.123456): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1969-12-31 23:59:59.876544" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(0): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(0): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(0.0): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(0.0): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-0.0): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-0.0): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(2147483647): object(DateTime)#%d (3) { + ["date"]=> + string(26) "2038-01-19 03:14:07.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(2147483647): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "2038-01-19 03:14:07.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-2147483648): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1901-12-13 20:45:52.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-2147483648): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1901-12-13 20:45:52.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(-2147483648.5): object(DateTime)#%d (3) { + ["date"]=> + string(26) "1901-12-13 20:45:51.500000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTimeImmutable::createFromTimestamp(-2147483648.5): object(DateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1901-12-13 20:45:51.500000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +DateTime::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given +DateTimeImmutable::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given +DateTime::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given +DateTimeImmutable::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given +DateTime::createFromTimestamp(NAN): DateRangeError: Seconds must be a finite number between %i and %i, NAN given +DateTimeImmutable::createFromTimestamp(NAN): DateRangeError: Seconds must be a finite number between %i and %i, NAN given +DateTime::createFromTimestamp(INF): DateRangeError: Seconds must be a finite number between %i and %i, INF given +DateTimeImmutable::createFromTimestamp(INF): DateRangeError: Seconds must be a finite number between %i and %i, INF given +DateTime::createFromTimestamp(-INF): DateRangeError: Seconds must be a finite number between %i and %i, -INF given +DateTimeImmutable::createFromTimestamp(-INF): DateRangeError: Seconds must be a finite number between %i and %i, -INF given +MyDateTime::createFromTimestamp(0): object(MyDateTime)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +} +MyDateTimeImmutable::createFromTimestamp(0): object(MyDateTimeImmutable)#%d (3) { + ["date"]=> + string(26) "1970-01-01 00:00:00.000000" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+00:00" +}