diff --git a/ext/date/config.w32 b/ext/date/config.w32
index 78cca036d380..327242f128ac 100644
--- a/ext/date/config.w32
+++ b/ext/date/config.w32
@@ -1,6 +1,6 @@
// vim:ft=javascript
-EXTENSION("date", "php_date.c", false, "/Iext/date/lib /DHAVE_TIMELIB_CONFIG_H=1");
+EXTENSION("date", "php_date.c php_time.c time_duration.c", false, "/Iext/date/lib /DHAVE_TIMELIB_CONFIG_H=1");
PHP_DATE = "yes";
ADD_SOURCES("ext/date/lib", "astro.c timelib.c dow.c duration.c parse_date.c parse_posix.c parse_tz.c tm2unixtime.c unixtime2tm.c parse_iso_intervals.c interval.c", "date");
diff --git a/ext/date/config0.m4 b/ext/date/config0.m4
index 4853882465e0..aab6d3d139b3 100644
--- a/ext/date/config0.m4
+++ b/ext/date/config0.m4
@@ -18,7 +18,7 @@ timelib_sources="lib/astro.c lib/dow.c lib/duration.c lib/parse_date.c lib/parse
lib/timelib.c lib/tm2unixtime.c lib/unixtime2tm.c lib/parse_iso_intervals.c lib/interval.c"
PHP_NEW_EXTENSION([date],
- [php_date.c],
+ [php_date.c php_time.c time_duration.c],
[no],,
[$PHP_DATE_CFLAGS])
diff --git a/ext/date/php_date.c b/ext/date/php_date.c
index 0e24e4450641..9f9f0a6159ed 100644
--- a/ext/date/php_date.c
+++ b/ext/date/php_date.c
@@ -18,6 +18,7 @@
#include "ext/standard/info.h"
#include "ext/standard/php_versioning.h"
#include "php_date.h"
+#include "php_time.h"
#include "zend_attributes.h"
#include "zend_interfaces.h"
#include "zend_exceptions.h"
@@ -386,6 +387,7 @@ static PHP_GINIT_FUNCTION(date)
date_globals->default_timezone = NULL;
date_globals->timezone = NULL;
date_globals->tzcache = NULL;
+ date_globals->duration_cache = NULL;
}
/* }}} */
@@ -418,6 +420,10 @@ PHP_RSHUTDOWN_FUNCTION(date)
efree(DATEG(timezone));
}
DATEG(timezone) = NULL;
+ if (DATEG(duration_cache)) {
+ zend_object_release(DATEG(duration_cache));
+ }
+ DATEG(duration_cache) = NULL;
return SUCCESS;
}
@@ -447,6 +453,7 @@ PHP_MINIT_FUNCTION(date)
REGISTER_INI_ENTRIES();
date_register_classes();
register_php_date_symbols(module_number);
+ PHP_MINIT(date_time)(INIT_FUNC_ARGS_PASSTHRU);
php_date_global_timezone_db = NULL;
php_date_global_timezone_db_enabled = 0;
diff --git a/ext/date/php_date.h b/ext/date/php_date.h
index 651cc28225fd..97a49974f1a4 100644
--- a/ext/date/php_date.h
+++ b/ext/date/php_date.h
@@ -115,8 +115,11 @@ ZEND_BEGIN_MODULE_GLOBALS(date)
char *timezone;
HashTable *tzcache;
timelib_error_container *last_errors;
+ zend_object *duration_cache;
ZEND_END_MODULE_GLOBALS(date)
+PHPAPI ZEND_EXTERN_MODULE_GLOBALS(date)
+
#define DATEG(v) ZEND_MODULE_GLOBALS_ACCESSOR(date, v)
PHPAPI time_t php_time(void);
diff --git a/ext/date/php_time.c b/ext/date/php_time.c
new file mode 100644
index 000000000000..2c3698f63fbd
--- /dev/null
+++ b/ext/date/php_time.c
@@ -0,0 +1,65 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Derick Rethans |
+ | Tim Düsterhus |
+ +----------------------------------------------------------------------+
+ */
+
+#include "php.h"
+#include "Zend/zend_exceptions.h"
+
+#include "php_time.h"
+#include "time_arginfo.h"
+
+zend_class_entry *php_date_ce_time_duration;
+zend_class_entry *php_date_ce_time_timeexception;
+
+static zend_object_handlers time_duration_object_handlers;
+
+static zend_object *time_duration_object_create(zend_class_entry *ce)
+{
+ php_date_time_duration *obj = zend_object_alloc(sizeof(*obj), ce);
+
+ zend_object_std_init(&obj->std, ce);
+ object_properties_init(&obj->std, ce);
+
+ timelib_duration_ctor_static(&obj->duration, /* seconds */ 0, /* nanoseconds */ 0, /* negative */ false);
+
+ return &obj->std;
+}
+
+static zend_object *time_duration_object_clone(zend_object *object)
+{
+ const php_date_time_duration *obj = php_date_time_duration_from_obj(object);
+
+ php_date_time_duration *new_obj = php_date_time_duration_from_obj(object->ce->create_object(object->ce));
+
+ new_obj->duration = obj->duration;
+ zend_objects_clone_members(&new_obj->std, &obj->std);
+
+ return &new_obj->std;
+}
+
+PHP_MINIT_FUNCTION(date_time)
+{
+ /* Time\TimeException */
+ php_date_ce_time_timeexception = register_class_Time_TimeException(zend_ce_exception);
+
+ /* Time\Duration */
+ memcpy(&time_duration_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+ time_duration_object_handlers.offset = offsetof(php_date_time_duration, std);
+ time_duration_object_handlers.clone_obj = time_duration_object_clone;
+ php_date_ce_time_duration = register_class_Time_Duration();
+ php_date_ce_time_duration->create_object = time_duration_object_create;
+ php_date_ce_time_duration->default_object_handlers = &time_duration_object_handlers;
+
+ return SUCCESS;
+}
diff --git a/ext/date/php_time.h b/ext/date/php_time.h
new file mode 100644
index 000000000000..fad711b519e7
--- /dev/null
+++ b/ext/date/php_time.h
@@ -0,0 +1,48 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Derick Rethans |
+ | Tim Düsterhus |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_DATE_TIME_H
+# define PHP_DATE_TIME_H
+
+# include "php.h"
+# include "lib/timelib.h"
+
+typedef struct php_date_time_duration {
+ timelib_duration duration;
+ zend_object std;
+} php_date_time_duration;
+
+# define php_date_time_duration_from_obj(obj) ZEND_CONTAINER_OF(obj, php_date_time_duration, std)
+
+# define Z_DATE_TIME_DURATION_P(zv) php_date_time_duration_from_obj(Z_OBJ_P((zv)))
+
+# define Z_PARAM_DATE_TIME_DURATION(d) do { \
+ zend_object *__d; \
+ Z_PARAM_OBJ_OF_CLASS(__d, php_date_ce_time_duration); \
+ d = php_date_time_duration_from_obj(__d); \
+ } while (0);
+
+# define Z_PARAM_DATE_TIME_DURATION_OR_NULL(d) do { \
+ zend_object *__d; \
+ Z_PARAM_OBJ_OF_CLASS_OR_NULL(__d, php_date_ce_time_duration); \
+ d = __d ? php_date_time_duration_from_obj(__d) : NULL; \
+ } while (0);
+
+PHPAPI extern zend_class_entry *php_date_ce_time_duration;
+PHPAPI extern zend_class_entry *php_date_ce_time_timeexception;
+
+PHP_MINIT_FUNCTION(date_time);
+
+#endif /* PHP_DATE_TIME_H */
diff --git a/ext/date/tests/time/duration/clone.phpt b/ext/date/tests/time/duration/clone.phpt
new file mode 100644
index 000000000000..06ad23dfe633
--- /dev/null
+++ b/ext/date/tests/time/duration/clone.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Time\Duration: clone
+--FILE--
+negate()), PHP_EOL;
+echo fc(Time\Duration::fromSeconds(1, 1)->negate()), PHP_EOL;
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.000000001
+ +1.000000001
+ -0.000000001
+ -1.000000001
diff --git a/ext/date/tests/time/duration/helper.inc b/ext/date/tests/time/duration/helper.inc
new file mode 100644
index 000000000000..a961315b179b
--- /dev/null
+++ b/ext/date/tests/time/duration/helper.inc
@@ -0,0 +1,31 @@
+negative ? "-" : ($plus_sign ? "+" : " ")), $d->seconds, $d->nanoseconds);
+ if ($pad) {
+ $result = str_pad($result, 21, pad_type: STR_PAD_LEFT);
+ }
+
+ return $result;
+}
+
+function as_int(Time\Duration $d): int {
+ return ($d->negative ? -1 : 1) * ($d->seconds * 1_000_000_000 + $d->nanoseconds);
+}
+
+function negate(Time\Duration $d): Time\Duration {
+ return $d->negate();
+}
+
+function is_negative(Time\Duration $d): bool {
+ return $d->negative;
+}
+
+/** @return Time\Duration[] */
+function negate_all(
+ array $d /** @param Time\Duration[] */,
+): array {
+ return $d
+ |> array_map(negate(?), ?)
+ |> array_filter(?, is_negative(?));
+}
diff --git a/ext/date/tests/time/duration/methods/absolute.phpt b/ext/date/tests/time/duration/methods/absolute.phpt
new file mode 100644
index 000000000000..86578488ae89
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/absolute.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Time\Duration::absolute()
+--FILE--
+absolute()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(0, 0)->negate()->absolute()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(1, 0)->absolute()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(1, 0)->negate()->absolute()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(0, 1)->absolute()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(0, 1)->negate()->absolute()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(1, 1)->absolute()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(1, 1)->negate()->absolute()), PHP_EOL;
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.000000000
+ +1.000000000
+ +1.000000000
+ +0.000000001
+ +0.000000001
+ +1.000000001
+ +1.000000001
diff --git a/ext/date/tests/time/duration/methods/add.phpt b/ext/date/tests/time/duration/methods/add.phpt
new file mode 100644
index 000000000000..825bd45de2fe
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/add.phpt
@@ -0,0 +1,371 @@
+--TEST--
+Time\Duration::add()
+--FILE--
+add($b);
+ echo f($a, pad: false, plus_sign: false), " + ", f($b, pad: false, plus_sign: false), " = ", f($result, pad: false), PHP_EOL;
+ if (PHP_INT_SIZE != 4 && (as_int($a) + as_int($b) !== as_int($result))) {
+ throw new \Exception('Verification failed');
+ }
+ }
+}
+
+?>
+--EXPECT--
+========
+ 0.000000000 + 0.000000000 = +0.000000000
+ 0.000000000 + 0.000000001 = +0.000000001
+ 0.000000000 + 0.000000002 = +0.000000002
+ 0.000000000 + 1.000000000 = +1.000000000
+ 0.000000000 + 1.000000001 = +1.000000001
+ 0.000000000 + 1.000000002 = +1.000000002
+ 0.000000000 + 2.000000000 = +2.000000000
+ 0.000000000 + 2.000000001 = +2.000000001
+ 0.000000000 + 2.000000002 = +2.000000002
+====
+ 0.000000000 + -0.000000001 = -0.000000001
+ 0.000000000 + -0.000000002 = -0.000000002
+ 0.000000000 + -1.000000000 = -1.000000000
+ 0.000000000 + -1.000000001 = -1.000000001
+ 0.000000000 + -1.000000002 = -1.000000002
+ 0.000000000 + -2.000000000 = -2.000000000
+ 0.000000000 + -2.000000001 = -2.000000001
+ 0.000000000 + -2.000000002 = -2.000000002
+========
+ 0.000000001 + 0.000000000 = +0.000000001
+ 0.000000001 + 0.000000001 = +0.000000002
+ 0.000000001 + 0.000000002 = +0.000000003
+ 0.000000001 + 1.000000000 = +1.000000001
+ 0.000000001 + 1.000000001 = +1.000000002
+ 0.000000001 + 1.000000002 = +1.000000003
+ 0.000000001 + 2.000000000 = +2.000000001
+ 0.000000001 + 2.000000001 = +2.000000002
+ 0.000000001 + 2.000000002 = +2.000000003
+====
+ 0.000000001 + -0.000000001 = +0.000000000
+ 0.000000001 + -0.000000002 = -0.000000001
+ 0.000000001 + -1.000000000 = -0.999999999
+ 0.000000001 + -1.000000001 = -1.000000000
+ 0.000000001 + -1.000000002 = -1.000000001
+ 0.000000001 + -2.000000000 = -1.999999999
+ 0.000000001 + -2.000000001 = -2.000000000
+ 0.000000001 + -2.000000002 = -2.000000001
+========
+ 0.000000002 + 0.000000000 = +0.000000002
+ 0.000000002 + 0.000000001 = +0.000000003
+ 0.000000002 + 0.000000002 = +0.000000004
+ 0.000000002 + 1.000000000 = +1.000000002
+ 0.000000002 + 1.000000001 = +1.000000003
+ 0.000000002 + 1.000000002 = +1.000000004
+ 0.000000002 + 2.000000000 = +2.000000002
+ 0.000000002 + 2.000000001 = +2.000000003
+ 0.000000002 + 2.000000002 = +2.000000004
+====
+ 0.000000002 + -0.000000001 = +0.000000001
+ 0.000000002 + -0.000000002 = +0.000000000
+ 0.000000002 + -1.000000000 = -0.999999998
+ 0.000000002 + -1.000000001 = -0.999999999
+ 0.000000002 + -1.000000002 = -1.000000000
+ 0.000000002 + -2.000000000 = -1.999999998
+ 0.000000002 + -2.000000001 = -1.999999999
+ 0.000000002 + -2.000000002 = -2.000000000
+========
+ 1.000000000 + 0.000000000 = +1.000000000
+ 1.000000000 + 0.000000001 = +1.000000001
+ 1.000000000 + 0.000000002 = +1.000000002
+ 1.000000000 + 1.000000000 = +2.000000000
+ 1.000000000 + 1.000000001 = +2.000000001
+ 1.000000000 + 1.000000002 = +2.000000002
+ 1.000000000 + 2.000000000 = +3.000000000
+ 1.000000000 + 2.000000001 = +3.000000001
+ 1.000000000 + 2.000000002 = +3.000000002
+====
+ 1.000000000 + -0.000000001 = +0.999999999
+ 1.000000000 + -0.000000002 = +0.999999998
+ 1.000000000 + -1.000000000 = +0.000000000
+ 1.000000000 + -1.000000001 = -0.000000001
+ 1.000000000 + -1.000000002 = -0.000000002
+ 1.000000000 + -2.000000000 = -1.000000000
+ 1.000000000 + -2.000000001 = -1.000000001
+ 1.000000000 + -2.000000002 = -1.000000002
+========
+ 1.000000001 + 0.000000000 = +1.000000001
+ 1.000000001 + 0.000000001 = +1.000000002
+ 1.000000001 + 0.000000002 = +1.000000003
+ 1.000000001 + 1.000000000 = +2.000000001
+ 1.000000001 + 1.000000001 = +2.000000002
+ 1.000000001 + 1.000000002 = +2.000000003
+ 1.000000001 + 2.000000000 = +3.000000001
+ 1.000000001 + 2.000000001 = +3.000000002
+ 1.000000001 + 2.000000002 = +3.000000003
+====
+ 1.000000001 + -0.000000001 = +1.000000000
+ 1.000000001 + -0.000000002 = +0.999999999
+ 1.000000001 + -1.000000000 = +0.000000001
+ 1.000000001 + -1.000000001 = +0.000000000
+ 1.000000001 + -1.000000002 = -0.000000001
+ 1.000000001 + -2.000000000 = -0.999999999
+ 1.000000001 + -2.000000001 = -1.000000000
+ 1.000000001 + -2.000000002 = -1.000000001
+========
+ 1.000000002 + 0.000000000 = +1.000000002
+ 1.000000002 + 0.000000001 = +1.000000003
+ 1.000000002 + 0.000000002 = +1.000000004
+ 1.000000002 + 1.000000000 = +2.000000002
+ 1.000000002 + 1.000000001 = +2.000000003
+ 1.000000002 + 1.000000002 = +2.000000004
+ 1.000000002 + 2.000000000 = +3.000000002
+ 1.000000002 + 2.000000001 = +3.000000003
+ 1.000000002 + 2.000000002 = +3.000000004
+====
+ 1.000000002 + -0.000000001 = +1.000000001
+ 1.000000002 + -0.000000002 = +1.000000000
+ 1.000000002 + -1.000000000 = +0.000000002
+ 1.000000002 + -1.000000001 = +0.000000001
+ 1.000000002 + -1.000000002 = +0.000000000
+ 1.000000002 + -2.000000000 = -0.999999998
+ 1.000000002 + -2.000000001 = -0.999999999
+ 1.000000002 + -2.000000002 = -1.000000000
+========
+ 2.000000000 + 0.000000000 = +2.000000000
+ 2.000000000 + 0.000000001 = +2.000000001
+ 2.000000000 + 0.000000002 = +2.000000002
+ 2.000000000 + 1.000000000 = +3.000000000
+ 2.000000000 + 1.000000001 = +3.000000001
+ 2.000000000 + 1.000000002 = +3.000000002
+ 2.000000000 + 2.000000000 = +4.000000000
+ 2.000000000 + 2.000000001 = +4.000000001
+ 2.000000000 + 2.000000002 = +4.000000002
+====
+ 2.000000000 + -0.000000001 = +1.999999999
+ 2.000000000 + -0.000000002 = +1.999999998
+ 2.000000000 + -1.000000000 = +1.000000000
+ 2.000000000 + -1.000000001 = +0.999999999
+ 2.000000000 + -1.000000002 = +0.999999998
+ 2.000000000 + -2.000000000 = +0.000000000
+ 2.000000000 + -2.000000001 = -0.000000001
+ 2.000000000 + -2.000000002 = -0.000000002
+========
+ 2.000000001 + 0.000000000 = +2.000000001
+ 2.000000001 + 0.000000001 = +2.000000002
+ 2.000000001 + 0.000000002 = +2.000000003
+ 2.000000001 + 1.000000000 = +3.000000001
+ 2.000000001 + 1.000000001 = +3.000000002
+ 2.000000001 + 1.000000002 = +3.000000003
+ 2.000000001 + 2.000000000 = +4.000000001
+ 2.000000001 + 2.000000001 = +4.000000002
+ 2.000000001 + 2.000000002 = +4.000000003
+====
+ 2.000000001 + -0.000000001 = +2.000000000
+ 2.000000001 + -0.000000002 = +1.999999999
+ 2.000000001 + -1.000000000 = +1.000000001
+ 2.000000001 + -1.000000001 = +1.000000000
+ 2.000000001 + -1.000000002 = +0.999999999
+ 2.000000001 + -2.000000000 = +0.000000001
+ 2.000000001 + -2.000000001 = +0.000000000
+ 2.000000001 + -2.000000002 = -0.000000001
+========
+ 2.000000002 + 0.000000000 = +2.000000002
+ 2.000000002 + 0.000000001 = +2.000000003
+ 2.000000002 + 0.000000002 = +2.000000004
+ 2.000000002 + 1.000000000 = +3.000000002
+ 2.000000002 + 1.000000001 = +3.000000003
+ 2.000000002 + 1.000000002 = +3.000000004
+ 2.000000002 + 2.000000000 = +4.000000002
+ 2.000000002 + 2.000000001 = +4.000000003
+ 2.000000002 + 2.000000002 = +4.000000004
+====
+ 2.000000002 + -0.000000001 = +2.000000001
+ 2.000000002 + -0.000000002 = +2.000000000
+ 2.000000002 + -1.000000000 = +1.000000002
+ 2.000000002 + -1.000000001 = +1.000000001
+ 2.000000002 + -1.000000002 = +1.000000000
+ 2.000000002 + -2.000000000 = +0.000000002
+ 2.000000002 + -2.000000001 = +0.000000001
+ 2.000000002 + -2.000000002 = +0.000000000
+========
+-0.000000001 + 0.000000000 = -0.000000001
+-0.000000001 + 0.000000001 = +0.000000000
+-0.000000001 + 0.000000002 = +0.000000001
+-0.000000001 + 1.000000000 = +0.999999999
+-0.000000001 + 1.000000001 = +1.000000000
+-0.000000001 + 1.000000002 = +1.000000001
+-0.000000001 + 2.000000000 = +1.999999999
+-0.000000001 + 2.000000001 = +2.000000000
+-0.000000001 + 2.000000002 = +2.000000001
+====
+-0.000000001 + -0.000000001 = -0.000000002
+-0.000000001 + -0.000000002 = -0.000000003
+-0.000000001 + -1.000000000 = -1.000000001
+-0.000000001 + -1.000000001 = -1.000000002
+-0.000000001 + -1.000000002 = -1.000000003
+-0.000000001 + -2.000000000 = -2.000000001
+-0.000000001 + -2.000000001 = -2.000000002
+-0.000000001 + -2.000000002 = -2.000000003
+========
+-0.000000002 + 0.000000000 = -0.000000002
+-0.000000002 + 0.000000001 = -0.000000001
+-0.000000002 + 0.000000002 = +0.000000000
+-0.000000002 + 1.000000000 = +0.999999998
+-0.000000002 + 1.000000001 = +0.999999999
+-0.000000002 + 1.000000002 = +1.000000000
+-0.000000002 + 2.000000000 = +1.999999998
+-0.000000002 + 2.000000001 = +1.999999999
+-0.000000002 + 2.000000002 = +2.000000000
+====
+-0.000000002 + -0.000000001 = -0.000000003
+-0.000000002 + -0.000000002 = -0.000000004
+-0.000000002 + -1.000000000 = -1.000000002
+-0.000000002 + -1.000000001 = -1.000000003
+-0.000000002 + -1.000000002 = -1.000000004
+-0.000000002 + -2.000000000 = -2.000000002
+-0.000000002 + -2.000000001 = -2.000000003
+-0.000000002 + -2.000000002 = -2.000000004
+========
+-1.000000000 + 0.000000000 = -1.000000000
+-1.000000000 + 0.000000001 = -0.999999999
+-1.000000000 + 0.000000002 = -0.999999998
+-1.000000000 + 1.000000000 = +0.000000000
+-1.000000000 + 1.000000001 = +0.000000001
+-1.000000000 + 1.000000002 = +0.000000002
+-1.000000000 + 2.000000000 = +1.000000000
+-1.000000000 + 2.000000001 = +1.000000001
+-1.000000000 + 2.000000002 = +1.000000002
+====
+-1.000000000 + -0.000000001 = -1.000000001
+-1.000000000 + -0.000000002 = -1.000000002
+-1.000000000 + -1.000000000 = -2.000000000
+-1.000000000 + -1.000000001 = -2.000000001
+-1.000000000 + -1.000000002 = -2.000000002
+-1.000000000 + -2.000000000 = -3.000000000
+-1.000000000 + -2.000000001 = -3.000000001
+-1.000000000 + -2.000000002 = -3.000000002
+========
+-1.000000001 + 0.000000000 = -1.000000001
+-1.000000001 + 0.000000001 = -1.000000000
+-1.000000001 + 0.000000002 = -0.999999999
+-1.000000001 + 1.000000000 = -0.000000001
+-1.000000001 + 1.000000001 = +0.000000000
+-1.000000001 + 1.000000002 = +0.000000001
+-1.000000001 + 2.000000000 = +0.999999999
+-1.000000001 + 2.000000001 = +1.000000000
+-1.000000001 + 2.000000002 = +1.000000001
+====
+-1.000000001 + -0.000000001 = -1.000000002
+-1.000000001 + -0.000000002 = -1.000000003
+-1.000000001 + -1.000000000 = -2.000000001
+-1.000000001 + -1.000000001 = -2.000000002
+-1.000000001 + -1.000000002 = -2.000000003
+-1.000000001 + -2.000000000 = -3.000000001
+-1.000000001 + -2.000000001 = -3.000000002
+-1.000000001 + -2.000000002 = -3.000000003
+========
+-1.000000002 + 0.000000000 = -1.000000002
+-1.000000002 + 0.000000001 = -1.000000001
+-1.000000002 + 0.000000002 = -1.000000000
+-1.000000002 + 1.000000000 = -0.000000002
+-1.000000002 + 1.000000001 = -0.000000001
+-1.000000002 + 1.000000002 = +0.000000000
+-1.000000002 + 2.000000000 = +0.999999998
+-1.000000002 + 2.000000001 = +0.999999999
+-1.000000002 + 2.000000002 = +1.000000000
+====
+-1.000000002 + -0.000000001 = -1.000000003
+-1.000000002 + -0.000000002 = -1.000000004
+-1.000000002 + -1.000000000 = -2.000000002
+-1.000000002 + -1.000000001 = -2.000000003
+-1.000000002 + -1.000000002 = -2.000000004
+-1.000000002 + -2.000000000 = -3.000000002
+-1.000000002 + -2.000000001 = -3.000000003
+-1.000000002 + -2.000000002 = -3.000000004
+========
+-2.000000000 + 0.000000000 = -2.000000000
+-2.000000000 + 0.000000001 = -1.999999999
+-2.000000000 + 0.000000002 = -1.999999998
+-2.000000000 + 1.000000000 = -1.000000000
+-2.000000000 + 1.000000001 = -0.999999999
+-2.000000000 + 1.000000002 = -0.999999998
+-2.000000000 + 2.000000000 = +0.000000000
+-2.000000000 + 2.000000001 = +0.000000001
+-2.000000000 + 2.000000002 = +0.000000002
+====
+-2.000000000 + -0.000000001 = -2.000000001
+-2.000000000 + -0.000000002 = -2.000000002
+-2.000000000 + -1.000000000 = -3.000000000
+-2.000000000 + -1.000000001 = -3.000000001
+-2.000000000 + -1.000000002 = -3.000000002
+-2.000000000 + -2.000000000 = -4.000000000
+-2.000000000 + -2.000000001 = -4.000000001
+-2.000000000 + -2.000000002 = -4.000000002
+========
+-2.000000001 + 0.000000000 = -2.000000001
+-2.000000001 + 0.000000001 = -2.000000000
+-2.000000001 + 0.000000002 = -1.999999999
+-2.000000001 + 1.000000000 = -1.000000001
+-2.000000001 + 1.000000001 = -1.000000000
+-2.000000001 + 1.000000002 = -0.999999999
+-2.000000001 + 2.000000000 = -0.000000001
+-2.000000001 + 2.000000001 = +0.000000000
+-2.000000001 + 2.000000002 = +0.000000001
+====
+-2.000000001 + -0.000000001 = -2.000000002
+-2.000000001 + -0.000000002 = -2.000000003
+-2.000000001 + -1.000000000 = -3.000000001
+-2.000000001 + -1.000000001 = -3.000000002
+-2.000000001 + -1.000000002 = -3.000000003
+-2.000000001 + -2.000000000 = -4.000000001
+-2.000000001 + -2.000000001 = -4.000000002
+-2.000000001 + -2.000000002 = -4.000000003
+========
+-2.000000002 + 0.000000000 = -2.000000002
+-2.000000002 + 0.000000001 = -2.000000001
+-2.000000002 + 0.000000002 = -2.000000000
+-2.000000002 + 1.000000000 = -1.000000002
+-2.000000002 + 1.000000001 = -1.000000001
+-2.000000002 + 1.000000002 = -1.000000000
+-2.000000002 + 2.000000000 = -0.000000002
+-2.000000002 + 2.000000001 = -0.000000001
+-2.000000002 + 2.000000002 = +0.000000000
+====
+-2.000000002 + -0.000000001 = -2.000000003
+-2.000000002 + -0.000000002 = -2.000000004
+-2.000000002 + -1.000000000 = -3.000000002
+-2.000000002 + -1.000000001 = -3.000000003
+-2.000000002 + -1.000000002 = -3.000000004
+-2.000000002 + -2.000000000 = -4.000000002
+-2.000000002 + -2.000000001 = -4.000000003
+-2.000000002 + -2.000000002 = -4.000000004
diff --git a/ext/date/tests/time/duration/methods/add_32.phpt b/ext/date/tests/time/duration/methods/add_32.phpt
new file mode 100644
index 000000000000..f436d0a63ffa
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/add_32.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::add() (32 bit variation)
+--SKIPIF--
+
+--FILE--
+add($b);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
diff --git a/ext/date/tests/time/duration/methods/add_64.phpt b/ext/date/tests/time/duration/methods/add_64.phpt
new file mode 100644
index 000000000000..98448409bf19
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/add_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::add() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+add($b);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/compare.phpt b/ext/date/tests/time/duration/methods/compare.phpt
new file mode 100644
index 000000000000..488a39c2fc16
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/compare.phpt
@@ -0,0 +1,369 @@
+--TEST--
+Time\Duration::absolute()
+--FILE--
+')), " ", f($b, pad: false), PHP_EOL;
+ }
+}
+
+?>
+--EXPECT--
+========
++0.000000000 = +0.000000000
++0.000000000 < +0.000000001
++0.000000000 < +0.000000002
++0.000000000 < +1.000000000
++0.000000000 < +1.000000001
++0.000000000 < +1.000000002
++0.000000000 < +2.000000000
++0.000000000 < +2.000000001
++0.000000000 < +2.000000002
+====
++0.000000000 > -0.000000001
++0.000000000 > -0.000000002
++0.000000000 > -1.000000000
++0.000000000 > -1.000000001
++0.000000000 > -1.000000002
++0.000000000 > -2.000000000
++0.000000000 > -2.000000001
++0.000000000 > -2.000000002
+========
++0.000000001 > +0.000000000
++0.000000001 = +0.000000001
++0.000000001 < +0.000000002
++0.000000001 < +1.000000000
++0.000000001 < +1.000000001
++0.000000001 < +1.000000002
++0.000000001 < +2.000000000
++0.000000001 < +2.000000001
++0.000000001 < +2.000000002
+====
++0.000000001 > -0.000000001
++0.000000001 > -0.000000002
++0.000000001 > -1.000000000
++0.000000001 > -1.000000001
++0.000000001 > -1.000000002
++0.000000001 > -2.000000000
++0.000000001 > -2.000000001
++0.000000001 > -2.000000002
+========
++0.000000002 > +0.000000000
++0.000000002 > +0.000000001
++0.000000002 = +0.000000002
++0.000000002 < +1.000000000
++0.000000002 < +1.000000001
++0.000000002 < +1.000000002
++0.000000002 < +2.000000000
++0.000000002 < +2.000000001
++0.000000002 < +2.000000002
+====
++0.000000002 > -0.000000001
++0.000000002 > -0.000000002
++0.000000002 > -1.000000000
++0.000000002 > -1.000000001
++0.000000002 > -1.000000002
++0.000000002 > -2.000000000
++0.000000002 > -2.000000001
++0.000000002 > -2.000000002
+========
++1.000000000 > +0.000000000
++1.000000000 > +0.000000001
++1.000000000 > +0.000000002
++1.000000000 = +1.000000000
++1.000000000 < +1.000000001
++1.000000000 < +1.000000002
++1.000000000 < +2.000000000
++1.000000000 < +2.000000001
++1.000000000 < +2.000000002
+====
++1.000000000 > -0.000000001
++1.000000000 > -0.000000002
++1.000000000 > -1.000000000
++1.000000000 > -1.000000001
++1.000000000 > -1.000000002
++1.000000000 > -2.000000000
++1.000000000 > -2.000000001
++1.000000000 > -2.000000002
+========
++1.000000001 > +0.000000000
++1.000000001 > +0.000000001
++1.000000001 > +0.000000002
++1.000000001 > +1.000000000
++1.000000001 = +1.000000001
++1.000000001 < +1.000000002
++1.000000001 < +2.000000000
++1.000000001 < +2.000000001
++1.000000001 < +2.000000002
+====
++1.000000001 > -0.000000001
++1.000000001 > -0.000000002
++1.000000001 > -1.000000000
++1.000000001 > -1.000000001
++1.000000001 > -1.000000002
++1.000000001 > -2.000000000
++1.000000001 > -2.000000001
++1.000000001 > -2.000000002
+========
++1.000000002 > +0.000000000
++1.000000002 > +0.000000001
++1.000000002 > +0.000000002
++1.000000002 > +1.000000000
++1.000000002 > +1.000000001
++1.000000002 = +1.000000002
++1.000000002 < +2.000000000
++1.000000002 < +2.000000001
++1.000000002 < +2.000000002
+====
++1.000000002 > -0.000000001
++1.000000002 > -0.000000002
++1.000000002 > -1.000000000
++1.000000002 > -1.000000001
++1.000000002 > -1.000000002
++1.000000002 > -2.000000000
++1.000000002 > -2.000000001
++1.000000002 > -2.000000002
+========
++2.000000000 > +0.000000000
++2.000000000 > +0.000000001
++2.000000000 > +0.000000002
++2.000000000 > +1.000000000
++2.000000000 > +1.000000001
++2.000000000 > +1.000000002
++2.000000000 = +2.000000000
++2.000000000 < +2.000000001
++2.000000000 < +2.000000002
+====
++2.000000000 > -0.000000001
++2.000000000 > -0.000000002
++2.000000000 > -1.000000000
++2.000000000 > -1.000000001
++2.000000000 > -1.000000002
++2.000000000 > -2.000000000
++2.000000000 > -2.000000001
++2.000000000 > -2.000000002
+========
++2.000000001 > +0.000000000
++2.000000001 > +0.000000001
++2.000000001 > +0.000000002
++2.000000001 > +1.000000000
++2.000000001 > +1.000000001
++2.000000001 > +1.000000002
++2.000000001 > +2.000000000
++2.000000001 = +2.000000001
++2.000000001 < +2.000000002
+====
++2.000000001 > -0.000000001
++2.000000001 > -0.000000002
++2.000000001 > -1.000000000
++2.000000001 > -1.000000001
++2.000000001 > -1.000000002
++2.000000001 > -2.000000000
++2.000000001 > -2.000000001
++2.000000001 > -2.000000002
+========
++2.000000002 > +0.000000000
++2.000000002 > +0.000000001
++2.000000002 > +0.000000002
++2.000000002 > +1.000000000
++2.000000002 > +1.000000001
++2.000000002 > +1.000000002
++2.000000002 > +2.000000000
++2.000000002 > +2.000000001
++2.000000002 = +2.000000002
+====
++2.000000002 > -0.000000001
++2.000000002 > -0.000000002
++2.000000002 > -1.000000000
++2.000000002 > -1.000000001
++2.000000002 > -1.000000002
++2.000000002 > -2.000000000
++2.000000002 > -2.000000001
++2.000000002 > -2.000000002
+========
+-0.000000001 < +0.000000000
+-0.000000001 < +0.000000001
+-0.000000001 < +0.000000002
+-0.000000001 < +1.000000000
+-0.000000001 < +1.000000001
+-0.000000001 < +1.000000002
+-0.000000001 < +2.000000000
+-0.000000001 < +2.000000001
+-0.000000001 < +2.000000002
+====
+-0.000000001 = -0.000000001
+-0.000000001 > -0.000000002
+-0.000000001 > -1.000000000
+-0.000000001 > -1.000000001
+-0.000000001 > -1.000000002
+-0.000000001 > -2.000000000
+-0.000000001 > -2.000000001
+-0.000000001 > -2.000000002
+========
+-0.000000002 < +0.000000000
+-0.000000002 < +0.000000001
+-0.000000002 < +0.000000002
+-0.000000002 < +1.000000000
+-0.000000002 < +1.000000001
+-0.000000002 < +1.000000002
+-0.000000002 < +2.000000000
+-0.000000002 < +2.000000001
+-0.000000002 < +2.000000002
+====
+-0.000000002 < -0.000000001
+-0.000000002 = -0.000000002
+-0.000000002 > -1.000000000
+-0.000000002 > -1.000000001
+-0.000000002 > -1.000000002
+-0.000000002 > -2.000000000
+-0.000000002 > -2.000000001
+-0.000000002 > -2.000000002
+========
+-1.000000000 < +0.000000000
+-1.000000000 < +0.000000001
+-1.000000000 < +0.000000002
+-1.000000000 < +1.000000000
+-1.000000000 < +1.000000001
+-1.000000000 < +1.000000002
+-1.000000000 < +2.000000000
+-1.000000000 < +2.000000001
+-1.000000000 < +2.000000002
+====
+-1.000000000 < -0.000000001
+-1.000000000 < -0.000000002
+-1.000000000 = -1.000000000
+-1.000000000 > -1.000000001
+-1.000000000 > -1.000000002
+-1.000000000 > -2.000000000
+-1.000000000 > -2.000000001
+-1.000000000 > -2.000000002
+========
+-1.000000001 < +0.000000000
+-1.000000001 < +0.000000001
+-1.000000001 < +0.000000002
+-1.000000001 < +1.000000000
+-1.000000001 < +1.000000001
+-1.000000001 < +1.000000002
+-1.000000001 < +2.000000000
+-1.000000001 < +2.000000001
+-1.000000001 < +2.000000002
+====
+-1.000000001 < -0.000000001
+-1.000000001 < -0.000000002
+-1.000000001 < -1.000000000
+-1.000000001 = -1.000000001
+-1.000000001 > -1.000000002
+-1.000000001 > -2.000000000
+-1.000000001 > -2.000000001
+-1.000000001 > -2.000000002
+========
+-1.000000002 < +0.000000000
+-1.000000002 < +0.000000001
+-1.000000002 < +0.000000002
+-1.000000002 < +1.000000000
+-1.000000002 < +1.000000001
+-1.000000002 < +1.000000002
+-1.000000002 < +2.000000000
+-1.000000002 < +2.000000001
+-1.000000002 < +2.000000002
+====
+-1.000000002 < -0.000000001
+-1.000000002 < -0.000000002
+-1.000000002 < -1.000000000
+-1.000000002 < -1.000000001
+-1.000000002 = -1.000000002
+-1.000000002 > -2.000000000
+-1.000000002 > -2.000000001
+-1.000000002 > -2.000000002
+========
+-2.000000000 < +0.000000000
+-2.000000000 < +0.000000001
+-2.000000000 < +0.000000002
+-2.000000000 < +1.000000000
+-2.000000000 < +1.000000001
+-2.000000000 < +1.000000002
+-2.000000000 < +2.000000000
+-2.000000000 < +2.000000001
+-2.000000000 < +2.000000002
+====
+-2.000000000 < -0.000000001
+-2.000000000 < -0.000000002
+-2.000000000 < -1.000000000
+-2.000000000 < -1.000000001
+-2.000000000 < -1.000000002
+-2.000000000 = -2.000000000
+-2.000000000 > -2.000000001
+-2.000000000 > -2.000000002
+========
+-2.000000001 < +0.000000000
+-2.000000001 < +0.000000001
+-2.000000001 < +0.000000002
+-2.000000001 < +1.000000000
+-2.000000001 < +1.000000001
+-2.000000001 < +1.000000002
+-2.000000001 < +2.000000000
+-2.000000001 < +2.000000001
+-2.000000001 < +2.000000002
+====
+-2.000000001 < -0.000000001
+-2.000000001 < -0.000000002
+-2.000000001 < -1.000000000
+-2.000000001 < -1.000000001
+-2.000000001 < -1.000000002
+-2.000000001 < -2.000000000
+-2.000000001 = -2.000000001
+-2.000000001 > -2.000000002
+========
+-2.000000002 < +0.000000000
+-2.000000002 < +0.000000001
+-2.000000002 < +0.000000002
+-2.000000002 < +1.000000000
+-2.000000002 < +1.000000001
+-2.000000002 < +1.000000002
+-2.000000002 < +2.000000000
+-2.000000002 < +2.000000001
+-2.000000002 < +2.000000002
+====
+-2.000000002 < -0.000000001
+-2.000000002 < -0.000000002
+-2.000000002 < -1.000000000
+-2.000000002 < -1.000000001
+-2.000000002 < -1.000000002
+-2.000000002 < -2.000000000
+-2.000000002 < -2.000000001
+-2.000000002 = -2.000000002
diff --git a/ext/date/tests/time/duration/methods/divideBy.phpt b/ext/date/tests/time/duration/methods/divideBy.phpt
new file mode 100644
index 000000000000..440107d33ed4
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/divideBy.phpt
@@ -0,0 +1,133 @@
+--TEST--
+Time\Duration::divideBy()
+--FILE--
+divideBy($divisor);
+ echo f($d), " / ", sprintf("%10d", $divisor), " = ", f($result), PHP_EOL;
+
+ if (PHP_INT_SIZE != 4 && (intdiv(as_int($d), $divisor) !== as_int($result))) {
+ throw new \Exception('Verification failed');
+ }
+ }
+}
+
+echo "========", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(1, 0);
+try {
+ $d->divideBy(0);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+========
+ +0.000000000 / 1 = +0.000000000
+ +0.000000000 / 2 = +0.000000000
+ +0.000000000 / 3 = +0.000000000
+ +0.000000000 / 4 = +0.000000000
+ +0.000000000 / 999999999 = +0.000000000
+ +0.000000000 / 1000000000 = +0.000000000
+ +0.000000000 / 2147483647 = +0.000000000
+========
+ +0.000000001 / 1 = +0.000000001
+ +0.000000001 / 2 = +0.000000000
+ +0.000000001 / 3 = +0.000000000
+ +0.000000001 / 4 = +0.000000000
+ +0.000000001 / 999999999 = +0.000000000
+ +0.000000001 / 1000000000 = +0.000000000
+ +0.000000001 / 2147483647 = +0.000000000
+========
+ +0.999999999 / 1 = +0.999999999
+ +0.999999999 / 2 = +0.499999999
+ +0.999999999 / 3 = +0.333333333
+ +0.999999999 / 4 = +0.249999999
+ +0.999999999 / 999999999 = +0.000000001
+ +0.999999999 / 1000000000 = +0.000000000
+ +0.999999999 / 2147483647 = +0.000000000
+========
+ +1.000000000 / 1 = +1.000000000
+ +1.000000000 / 2 = +0.500000000
+ +1.000000000 / 3 = +0.333333333
+ +1.000000000 / 4 = +0.250000000
+ +1.000000000 / 999999999 = +0.000000001
+ +1.000000000 / 1000000000 = +0.000000001
+ +1.000000000 / 2147483647 = +0.000000000
+========
++2147483647.999999999 / 1 = +2147483647.999999999
++2147483647.999999999 / 2 = +1073741823.999999999
++2147483647.999999999 / 3 = +715827882.666666666
++2147483647.999999999 / 4 = +536870911.999999999
++2147483647.999999999 / 999999999 = +2.147483650
++2147483647.999999999 / 1000000000 = +2.147483647
++2147483647.999999999 / 2147483647 = +1.000000000
+========
+ -0.000000001 / 1 = -0.000000001
+ -0.000000001 / 2 = +0.000000000
+ -0.000000001 / 3 = +0.000000000
+ -0.000000001 / 4 = +0.000000000
+ -0.000000001 / 999999999 = +0.000000000
+ -0.000000001 / 1000000000 = +0.000000000
+ -0.000000001 / 2147483647 = +0.000000000
+========
+ -0.999999999 / 1 = -0.999999999
+ -0.999999999 / 2 = -0.499999999
+ -0.999999999 / 3 = -0.333333333
+ -0.999999999 / 4 = -0.249999999
+ -0.999999999 / 999999999 = -0.000000001
+ -0.999999999 / 1000000000 = +0.000000000
+ -0.999999999 / 2147483647 = +0.000000000
+========
+ -1.000000000 / 1 = -1.000000000
+ -1.000000000 / 2 = -0.500000000
+ -1.000000000 / 3 = -0.333333333
+ -1.000000000 / 4 = -0.250000000
+ -1.000000000 / 999999999 = -0.000000001
+ -1.000000000 / 1000000000 = -0.000000001
+ -1.000000000 / 2147483647 = +0.000000000
+========
+-2147483647.999999999 / 1 = -2147483647.999999999
+-2147483647.999999999 / 2 = -1073741823.999999999
+-2147483647.999999999 / 3 = -715827882.666666666
+-2147483647.999999999 / 4 = -536870911.999999999
+-2147483647.999999999 / 999999999 = -2.147483650
+-2147483647.999999999 / 1000000000 = -2.147483647
+-2147483647.999999999 / 2147483647 = -1.000000000
+========
+DivisionByZeroError: Division by zero
diff --git a/ext/date/tests/time/duration/methods/divideBy_64.phpt b/ext/date/tests/time/duration/methods/divideBy_64.phpt
new file mode 100644
index 000000000000..21c54538f40b
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/divideBy_64.phpt
@@ -0,0 +1,78 @@
+--TEST--
+Time\Duration::divideBy() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+divideBy($factor)), PHP_EOL;
+ }
+}
+
+?>
+--EXPECT--
+========
++2147483648.000000000 / 1 = +2147483648.000000000
++2147483648.000000000 / 2 = +1073741824.000000000
++2147483648.000000000 / 3 = +715827882.666666666
++2147483648.000000000 / 4 = +536870912.000000000
++2147483648.000000000 / 999999999 = +2.147483650
++2147483648.000000000 / 1000000000 = +2.147483648
++2147483648.000000000 / 9223372035999999999 = +0.000000000
+========
++9223372035.999999999 / 1 = +9223372035.999999999
++9223372035.999999999 / 2 = +4611686017.999999999
++9223372035.999999999 / 3 = +3074457345.333333333
++9223372035.999999999 / 4 = +2305843008.999999999
++9223372035.999999999 / 999999999 = +9.223372045
++9223372035.999999999 / 1000000000 = +9.223372035
++9223372035.999999999 / 9223372035999999999 = +0.000000001
+========
+-2147483648.000000000 / 1 = -2147483648.000000000
+-2147483648.000000000 / 2 = -1073741824.000000000
+-2147483648.000000000 / 3 = -715827882.666666666
+-2147483648.000000000 / 4 = -536870912.000000000
+-2147483648.000000000 / 999999999 = -2.147483650
+-2147483648.000000000 / 1000000000 = -2.147483648
+-2147483648.000000000 / 9223372035999999999 = +0.000000000
+========
+-9223372035.999999999 / 1 = -9223372035.999999999
+-9223372035.999999999 / 2 = -4611686017.999999999
+-9223372035.999999999 / 3 = -3074457345.333333333
+-9223372035.999999999 / 4 = -2305843008.999999999
+-9223372035.999999999 / 999999999 = -9.223372045
+-9223372035.999999999 / 1000000000 = -9.223372035
+-9223372035.999999999 / 9223372035999999999 = -0.000000001
diff --git a/ext/date/tests/time/duration/methods/fromHours.phpt b/ext/date/tests/time/duration/methods/fromHours.phpt
new file mode 100644
index 000000000000..658288036433
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromHours.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromHours()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +3600.000000000
++2147482800.000000000
+ValueError: Time\Duration::fromHours(): Argument #1 ($hours) must be greater than or equal to 0
diff --git a/ext/date/tests/time/duration/methods/fromHours_32.phpt b/ext/date/tests/time/duration/methods/fromHours_32.phpt
new file mode 100644
index 000000000000..998ab5d9beaf
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromHours_32.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromHours() (32 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECTF--
++2147482800.000000000
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
diff --git a/ext/date/tests/time/duration/methods/fromHours_64.phpt b/ext/date/tests/time/duration/methods/fromHours_64.phpt
new file mode 100644
index 000000000000..719da0e88e20
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromHours_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromHours() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223369200.000000000
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromIso8601DurationString.phpt b/ext/date/tests/time/duration/methods/fromIso8601DurationString.phpt
new file mode 100644
index 000000000000..109a1dd1307a
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromIso8601DurationString.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Time\Duration::fromIso8601DurationString()
+--FILE--
+getMessage(), PHP_EOL;
+ }
+}
+
+?>
+--EXPECT--
+PT0S : +0.000000000
+PT1S : +1.000000000
+PT60S : +60.000000000
+PT2147483647S : +2147483647.000000000
+PT0.1S : Time\TimeException: The ISO 8601 duration string could not be parsed
+PT0M : +0.000000000
+PT1M : +60.000000000
+PT1M1S : +61.000000000
+PT1M60S : +120.000000000
+PT0H : +0.000000000
+PT1H : +3600.000000000
+PT1H1M : +3660.000000000
+PT1H1M1S : +3661.000000000
+PT1H60M : +7200.000000000
+PT1H60M60S : +7260.000000000
+ : Time\TimeException: The ISO 8601 duration string could not be parsed
+P : Time\TimeException: The ISO 8601 duration string could not be parsed
+PT : Time\TimeException: The ISO 8601 duration string could not be parsed
+P1D : Time\TimeException: The ISO 8601 duration string may only contain the time (T) aspect
+P1W : Time\TimeException: The ISO 8601 duration string may only contain the time (T) aspect
+P1M : Time\TimeException: The ISO 8601 duration string may only contain the time (T) aspect
+P1DT0S : Time\TimeException: The ISO 8601 duration string may only contain the time (T) aspect
+2000-01-01T00:00:00Z : Time\TimeException: The ISO 8601 duration string is missing the period (P) aspect
+2000-01-01T00:00:00Z/PT1H: Time\TimeException: The ISO 8601 duration string may only contain the period (P) aspect
diff --git a/ext/date/tests/time/duration/methods/fromIso8601DurationString_64.phpt b/ext/date/tests/time/duration/methods/fromIso8601DurationString_64.phpt
new file mode 100644
index 000000000000..04ee27db4660
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromIso8601DurationString_64.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Time\Duration::fromIso8601DurationString() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+ }
+}
+
+?>
+--EXPECT--
+PT2147483648S : +2147483648.000000000
+PT9223372035S : +9223372035.000000000
+PT9223372036S : Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromMicroseconds.phpt b/ext/date/tests/time/duration/methods/fromMicroseconds.phpt
new file mode 100644
index 000000000000..aef4092b9094
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMicroseconds.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Time\Duration::fromMicroseconds()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.000001000
+ +0.999999000
+ +1.000000000
+ +2147.483647000
+ValueError: Time\Duration::fromMicroseconds(): Argument #1 ($microseconds) must be greater than or equal to 0
diff --git a/ext/date/tests/time/duration/methods/fromMicroseconds_64.phpt b/ext/date/tests/time/duration/methods/fromMicroseconds_64.phpt
new file mode 100644
index 000000000000..84f317425a44
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMicroseconds_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromMicroseconds() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372035.999999000
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromMilliseconds.phpt b/ext/date/tests/time/duration/methods/fromMilliseconds.phpt
new file mode 100644
index 000000000000..d63f20535b4b
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMilliseconds.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Time\Duration::fromMilliseconds()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.001000000
+ +0.999000000
+ +1.000000000
+ +2147483.647000000
+ValueError: Time\Duration::fromMilliseconds(): Argument #1 ($milliseconds) must be greater than or equal to 0
diff --git a/ext/date/tests/time/duration/methods/fromMilliseconds_64.phpt b/ext/date/tests/time/duration/methods/fromMilliseconds_64.phpt
new file mode 100644
index 000000000000..4ed7250fb38a
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMilliseconds_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromMilliseconds() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372035.999000000
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromMinutes.phpt b/ext/date/tests/time/duration/methods/fromMinutes.phpt
new file mode 100644
index 000000000000..7674ea95f54c
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMinutes.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromMinutes()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +60.000000000
++2147483640.000000000
+ValueError: Time\Duration::fromMinutes(): Argument #1 ($minutes) must be greater than or equal to 0
diff --git a/ext/date/tests/time/duration/methods/fromMinutes_32.phpt b/ext/date/tests/time/duration/methods/fromMinutes_32.phpt
new file mode 100644
index 000000000000..705823165df6
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMinutes_32.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromMinutes() (32 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECTF--
++2147483640.000000000
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
diff --git a/ext/date/tests/time/duration/methods/fromMinutes_64.phpt b/ext/date/tests/time/duration/methods/fromMinutes_64.phpt
new file mode 100644
index 000000000000..4b3eaf4ad26c
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromMinutes_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromMinutes() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372020.000000000
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromNanoseconds.phpt b/ext/date/tests/time/duration/methods/fromNanoseconds.phpt
new file mode 100644
index 000000000000..7b425d2eb562
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromNanoseconds.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Time\Duration::fromNanoseconds()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.000000001
+ +0.999999999
+ +1.000000000
+ +2.147483647
+ValueError: Time\Duration::fromNanoseconds(): Argument #1 ($nanoseconds) must be greater than or equal to 0
diff --git a/ext/date/tests/time/duration/methods/fromNanoseconds_64.phpt b/ext/date/tests/time/duration/methods/fromNanoseconds_64.phpt
new file mode 100644
index 000000000000..e68adc28e4a3
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromNanoseconds_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::fromNanoseconds() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372035.999999999
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/fromSeconds.phpt b/ext/date/tests/time/duration/methods/fromSeconds.phpt
new file mode 100644
index 000000000000..8cd26ce4c725
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromSeconds.phpt
@@ -0,0 +1,46 @@
+--TEST--
+Time\Duration::fromSeconds()
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+try {
+ Time\Duration::fromSeconds(0, -1);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+try {
+ Time\Duration::fromSeconds(0, 1000000000);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ +0.000000000
+ +1.000000000
++2147483647.000000000
+ +0.000000000
+ +0.000000001
+ +1.000000001
++2147483647.999999999
+ValueError: Time\Duration::fromSeconds(): Argument #1 ($seconds) must be greater than or equal to 0
+ValueError: Time\Duration::fromSeconds(): Argument #2 ($nanoseconds) must be greater than or equal to 0
+ValueError: Time\Duration::fromSeconds(): Argument #2 ($nanoseconds) must be less than 1_000_000_000
diff --git a/ext/date/tests/time/duration/methods/fromSeconds_64.phpt b/ext/date/tests/time/duration/methods/fromSeconds_64.phpt
new file mode 100644
index 000000000000..2deed85ec0e8
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/fromSeconds_64.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Time\Duration::fromSeconds() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372035.000000000
++9223372035.999999999
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/multiplyBy.phpt b/ext/date/tests/time/duration/methods/multiplyBy.phpt
new file mode 100644
index 000000000000..70b374d28c2b
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/multiplyBy.phpt
@@ -0,0 +1,206 @@
+--TEST--
+Time\Duration::multiplyBy()
+--FILE--
+multiplyBy($factor);
+ echo f($d), " * ", sprintf("%10d", $factor), " = ", f($result), PHP_EOL;
+
+ if (PHP_INT_SIZE != 4 && (as_int($d) * $factor !== as_int($result))) {
+ throw new \Exception('Verification failed');
+ }
+ }
+}
+
+echo "========", PHP_EOL;
+$d = Time\Duration::fromSeconds(0, 1);
+$factor = 2_147_483_647;
+$result = $d->multiplyBy($factor);
+echo f($d), " * ", sprintf("%10d", $factor), " = ", f($result), PHP_EOL;
+
+echo "========", PHP_EOL;
+$d = Time\Duration::fromSeconds(1, 0);
+$factor = 2_147_483_647;
+$result = $d->multiplyBy($factor);
+echo f($d), " * ", sprintf("%10d", $factor), " = ", f($result), PHP_EOL;
+
+?>
+--EXPECT--
+========
+ +0.000000000 * 0 = +0.000000000
+ +0.000000000 * 1 = +0.000000000
+ +0.000000000 * 2 = +0.000000000
+ +0.000000000 * 3 = +0.000000000
+ +0.000000000 * 4 = +0.000000000
+ +0.000000000 * 999999999 = +0.000000000
+ +0.000000000 * 1000000000 = +0.000000000
+========
+ +0.000000001 * 0 = +0.000000000
+ +0.000000001 * 1 = +0.000000001
+ +0.000000001 * 2 = +0.000000002
+ +0.000000001 * 3 = +0.000000003
+ +0.000000001 * 4 = +0.000000004
+ +0.000000001 * 999999999 = +0.999999999
+ +0.000000001 * 1000000000 = +1.000000000
+========
+ +0.000000002 * 0 = +0.000000000
+ +0.000000002 * 1 = +0.000000002
+ +0.000000002 * 2 = +0.000000004
+ +0.000000002 * 3 = +0.000000006
+ +0.000000002 * 4 = +0.000000008
+ +0.000000002 * 999999999 = +1.999999998
+ +0.000000002 * 1000000000 = +2.000000000
+========
+ +1.000000000 * 0 = +0.000000000
+ +1.000000000 * 1 = +1.000000000
+ +1.000000000 * 2 = +2.000000000
+ +1.000000000 * 3 = +3.000000000
+ +1.000000000 * 4 = +4.000000000
+ +1.000000000 * 999999999 = +999999999.000000000
+ +1.000000000 * 1000000000 = +1000000000.000000000
+========
+ +1.000000001 * 0 = +0.000000000
+ +1.000000001 * 1 = +1.000000001
+ +1.000000001 * 2 = +2.000000002
+ +1.000000001 * 3 = +3.000000003
+ +1.000000001 * 4 = +4.000000004
+ +1.000000001 * 999999999 = +999999999.999999999
+ +1.000000001 * 1000000000 = +1000000001.000000000
+========
+ +1.000000002 * 0 = +0.000000000
+ +1.000000002 * 1 = +1.000000002
+ +1.000000002 * 2 = +2.000000004
+ +1.000000002 * 3 = +3.000000006
+ +1.000000002 * 4 = +4.000000008
+ +1.000000002 * 999999999 = +1000000000.999999998
+ +1.000000002 * 1000000000 = +1000000002.000000000
+========
+ +2.000000000 * 0 = +0.000000000
+ +2.000000000 * 1 = +2.000000000
+ +2.000000000 * 2 = +4.000000000
+ +2.000000000 * 3 = +6.000000000
+ +2.000000000 * 4 = +8.000000000
+ +2.000000000 * 999999999 = +1999999998.000000000
+ +2.000000000 * 1000000000 = +2000000000.000000000
+========
+ +2.000000001 * 0 = +0.000000000
+ +2.000000001 * 1 = +2.000000001
+ +2.000000001 * 2 = +4.000000002
+ +2.000000001 * 3 = +6.000000003
+ +2.000000001 * 4 = +8.000000004
+ +2.000000001 * 999999999 = +1999999998.999999999
+ +2.000000001 * 1000000000 = +2000000001.000000000
+========
+ +2.000000002 * 0 = +0.000000000
+ +2.000000002 * 1 = +2.000000002
+ +2.000000002 * 2 = +4.000000004
+ +2.000000002 * 3 = +6.000000006
+ +2.000000002 * 4 = +8.000000008
+ +2.000000002 * 999999999 = +1999999999.999999998
+ +2.000000002 * 1000000000 = +2000000002.000000000
+========
+ -0.000000001 * 0 = +0.000000000
+ -0.000000001 * 1 = -0.000000001
+ -0.000000001 * 2 = -0.000000002
+ -0.000000001 * 3 = -0.000000003
+ -0.000000001 * 4 = -0.000000004
+ -0.000000001 * 999999999 = -0.999999999
+ -0.000000001 * 1000000000 = -1.000000000
+========
+ -0.000000002 * 0 = +0.000000000
+ -0.000000002 * 1 = -0.000000002
+ -0.000000002 * 2 = -0.000000004
+ -0.000000002 * 3 = -0.000000006
+ -0.000000002 * 4 = -0.000000008
+ -0.000000002 * 999999999 = -1.999999998
+ -0.000000002 * 1000000000 = -2.000000000
+========
+ -1.000000000 * 0 = +0.000000000
+ -1.000000000 * 1 = -1.000000000
+ -1.000000000 * 2 = -2.000000000
+ -1.000000000 * 3 = -3.000000000
+ -1.000000000 * 4 = -4.000000000
+ -1.000000000 * 999999999 = -999999999.000000000
+ -1.000000000 * 1000000000 = -1000000000.000000000
+========
+ -1.000000001 * 0 = +0.000000000
+ -1.000000001 * 1 = -1.000000001
+ -1.000000001 * 2 = -2.000000002
+ -1.000000001 * 3 = -3.000000003
+ -1.000000001 * 4 = -4.000000004
+ -1.000000001 * 999999999 = -999999999.999999999
+ -1.000000001 * 1000000000 = -1000000001.000000000
+========
+ -1.000000002 * 0 = +0.000000000
+ -1.000000002 * 1 = -1.000000002
+ -1.000000002 * 2 = -2.000000004
+ -1.000000002 * 3 = -3.000000006
+ -1.000000002 * 4 = -4.000000008
+ -1.000000002 * 999999999 = -1000000000.999999998
+ -1.000000002 * 1000000000 = -1000000002.000000000
+========
+ -2.000000000 * 0 = +0.000000000
+ -2.000000000 * 1 = -2.000000000
+ -2.000000000 * 2 = -4.000000000
+ -2.000000000 * 3 = -6.000000000
+ -2.000000000 * 4 = -8.000000000
+ -2.000000000 * 999999999 = -1999999998.000000000
+ -2.000000000 * 1000000000 = -2000000000.000000000
+========
+ -2.000000001 * 0 = +0.000000000
+ -2.000000001 * 1 = -2.000000001
+ -2.000000001 * 2 = -4.000000002
+ -2.000000001 * 3 = -6.000000003
+ -2.000000001 * 4 = -8.000000004
+ -2.000000001 * 999999999 = -1999999998.999999999
+ -2.000000001 * 1000000000 = -2000000001.000000000
+========
+ -2.000000002 * 0 = +0.000000000
+ -2.000000002 * 1 = -2.000000002
+ -2.000000002 * 2 = -4.000000004
+ -2.000000002 * 3 = -6.000000006
+ -2.000000002 * 4 = -8.000000008
+ -2.000000002 * 999999999 = -1999999999.999999998
+ -2.000000002 * 1000000000 = -2000000002.000000000
+========
+ +0.000000001 * 2147483647 = +2.147483647
+========
+ +1.000000000 * 2147483647 = +2147483647.000000000
diff --git a/ext/date/tests/time/duration/methods/multiplyBy_32.phpt b/ext/date/tests/time/duration/methods/multiplyBy_32.phpt
new file mode 100644
index 000000000000..7a1cbcb56065
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/multiplyBy_32.phpt
@@ -0,0 +1,55 @@
+--TEST--
+Time\Duration::multiplyBy() (32 bit variation)
+--SKIPIF--
+
+--FILE--
+multiplyBy(1)), PHP_EOL;
+
+try {
+ $d->multiplyBy(2);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+echo "====", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(1_073_741_823, 999999999);
+
+echo f($d->multiplyBy(2)), PHP_EOL;
+
+try {
+ $d->multiplyBy(3);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+echo "====", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(715_827_882, 666666666);
+
+echo f($d->multiplyBy(3)), PHP_EOL;
+
+try {
+ $d->multiplyBy(4);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++2147483647.999999999
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
+====
++2147483647.999999998
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
+====
++2147483647.999999998
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
diff --git a/ext/date/tests/time/duration/methods/multiplyBy_64.phpt b/ext/date/tests/time/duration/methods/multiplyBy_64.phpt
new file mode 100644
index 000000000000..04b220fe2dbc
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/multiplyBy_64.phpt
@@ -0,0 +1,70 @@
+--TEST--
+Time\Duration::multiplyBy() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+multiplyBy(1)), PHP_EOL;
+
+try {
+ $d->multiplyBy(2);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+echo "====", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(4_611_686_017, 999999999);
+
+echo f($d->multiplyBy(2)), PHP_EOL;
+
+try {
+ $d->multiplyBy(3);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+echo "====", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(3_074_457_345, 333333333);
+
+echo f($d->multiplyBy(3)), PHP_EOL;
+
+try {
+ $d->multiplyBy(4);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+echo "====", PHP_EOL;
+
+$d = Time\Duration::fromSeconds(0, 1);
+
+echo f($d->multiplyBy(9_223_372_035_999999999)), PHP_EOL;
+
+try {
+ $d->multiplyBy(9_223_372_036_000000000);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
++9223372035.999999999
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
+====
++9223372035.999999998
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
+====
++9223372035.999999999
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
+====
++9223372035.999999999
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/methods/negate.phpt b/ext/date/tests/time/duration/methods/negate.phpt
new file mode 100644
index 000000000000..ce156cd20374
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/negate.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Time\Duration::negate()
+--FILE--
+negate()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(0, 0)->negate()->negate()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(1, 0)->negate()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(1, 0)->negate()->negate()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(0, 1)->negate()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(0, 1)->negate()->negate()), PHP_EOL;
+
+echo f(Time\Duration::fromSeconds(1, 1)->negate()), PHP_EOL;
+echo f(Time\Duration::fromSeconds(1, 1)->negate()->negate()), PHP_EOL;
+
+?>
+--EXPECT--
+ +0.000000000
+ +0.000000000
+ -1.000000000
+ +1.000000000
+ -0.000000001
+ +0.000000001
+ -1.000000001
+ +1.000000001
diff --git a/ext/date/tests/time/duration/methods/sub.phpt b/ext/date/tests/time/duration/methods/sub.phpt
new file mode 100644
index 000000000000..5552a249d9bf
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/sub.phpt
@@ -0,0 +1,371 @@
+--TEST--
+Time\Duration::sub()
+--FILE--
+sub($b);
+ echo f($a, pad: false, plus_sign: false), " - ", f($b, pad: false, plus_sign: false), " = ", f($result, pad: false), PHP_EOL;
+ if (PHP_INT_SIZE != 4 && (as_int($a) - as_int($b) !== as_int($result))) {
+ throw new \Exception('Verification failed');
+ }
+ }
+}
+
+?>
+--EXPECT--
+========
+ 0.000000000 - 0.000000000 = +0.000000000
+ 0.000000000 - 0.000000001 = -0.000000001
+ 0.000000000 - 0.000000002 = -0.000000002
+ 0.000000000 - 1.000000000 = -1.000000000
+ 0.000000000 - 1.000000001 = -1.000000001
+ 0.000000000 - 1.000000002 = -1.000000002
+ 0.000000000 - 2.000000000 = -2.000000000
+ 0.000000000 - 2.000000001 = -2.000000001
+ 0.000000000 - 2.000000002 = -2.000000002
+====
+ 0.000000000 - -0.000000001 = +0.000000001
+ 0.000000000 - -0.000000002 = +0.000000002
+ 0.000000000 - -1.000000000 = +1.000000000
+ 0.000000000 - -1.000000001 = +1.000000001
+ 0.000000000 - -1.000000002 = +1.000000002
+ 0.000000000 - -2.000000000 = +2.000000000
+ 0.000000000 - -2.000000001 = +2.000000001
+ 0.000000000 - -2.000000002 = +2.000000002
+========
+ 0.000000001 - 0.000000000 = +0.000000001
+ 0.000000001 - 0.000000001 = +0.000000000
+ 0.000000001 - 0.000000002 = -0.000000001
+ 0.000000001 - 1.000000000 = -0.999999999
+ 0.000000001 - 1.000000001 = -1.000000000
+ 0.000000001 - 1.000000002 = -1.000000001
+ 0.000000001 - 2.000000000 = -1.999999999
+ 0.000000001 - 2.000000001 = -2.000000000
+ 0.000000001 - 2.000000002 = -2.000000001
+====
+ 0.000000001 - -0.000000001 = +0.000000002
+ 0.000000001 - -0.000000002 = +0.000000003
+ 0.000000001 - -1.000000000 = +1.000000001
+ 0.000000001 - -1.000000001 = +1.000000002
+ 0.000000001 - -1.000000002 = +1.000000003
+ 0.000000001 - -2.000000000 = +2.000000001
+ 0.000000001 - -2.000000001 = +2.000000002
+ 0.000000001 - -2.000000002 = +2.000000003
+========
+ 0.000000002 - 0.000000000 = +0.000000002
+ 0.000000002 - 0.000000001 = +0.000000001
+ 0.000000002 - 0.000000002 = +0.000000000
+ 0.000000002 - 1.000000000 = -0.999999998
+ 0.000000002 - 1.000000001 = -0.999999999
+ 0.000000002 - 1.000000002 = -1.000000000
+ 0.000000002 - 2.000000000 = -1.999999998
+ 0.000000002 - 2.000000001 = -1.999999999
+ 0.000000002 - 2.000000002 = -2.000000000
+====
+ 0.000000002 - -0.000000001 = +0.000000003
+ 0.000000002 - -0.000000002 = +0.000000004
+ 0.000000002 - -1.000000000 = +1.000000002
+ 0.000000002 - -1.000000001 = +1.000000003
+ 0.000000002 - -1.000000002 = +1.000000004
+ 0.000000002 - -2.000000000 = +2.000000002
+ 0.000000002 - -2.000000001 = +2.000000003
+ 0.000000002 - -2.000000002 = +2.000000004
+========
+ 1.000000000 - 0.000000000 = +1.000000000
+ 1.000000000 - 0.000000001 = +0.999999999
+ 1.000000000 - 0.000000002 = +0.999999998
+ 1.000000000 - 1.000000000 = +0.000000000
+ 1.000000000 - 1.000000001 = -0.000000001
+ 1.000000000 - 1.000000002 = -0.000000002
+ 1.000000000 - 2.000000000 = -1.000000000
+ 1.000000000 - 2.000000001 = -1.000000001
+ 1.000000000 - 2.000000002 = -1.000000002
+====
+ 1.000000000 - -0.000000001 = +1.000000001
+ 1.000000000 - -0.000000002 = +1.000000002
+ 1.000000000 - -1.000000000 = +2.000000000
+ 1.000000000 - -1.000000001 = +2.000000001
+ 1.000000000 - -1.000000002 = +2.000000002
+ 1.000000000 - -2.000000000 = +3.000000000
+ 1.000000000 - -2.000000001 = +3.000000001
+ 1.000000000 - -2.000000002 = +3.000000002
+========
+ 1.000000001 - 0.000000000 = +1.000000001
+ 1.000000001 - 0.000000001 = +1.000000000
+ 1.000000001 - 0.000000002 = +0.999999999
+ 1.000000001 - 1.000000000 = +0.000000001
+ 1.000000001 - 1.000000001 = +0.000000000
+ 1.000000001 - 1.000000002 = -0.000000001
+ 1.000000001 - 2.000000000 = -0.999999999
+ 1.000000001 - 2.000000001 = -1.000000000
+ 1.000000001 - 2.000000002 = -1.000000001
+====
+ 1.000000001 - -0.000000001 = +1.000000002
+ 1.000000001 - -0.000000002 = +1.000000003
+ 1.000000001 - -1.000000000 = +2.000000001
+ 1.000000001 - -1.000000001 = +2.000000002
+ 1.000000001 - -1.000000002 = +2.000000003
+ 1.000000001 - -2.000000000 = +3.000000001
+ 1.000000001 - -2.000000001 = +3.000000002
+ 1.000000001 - -2.000000002 = +3.000000003
+========
+ 1.000000002 - 0.000000000 = +1.000000002
+ 1.000000002 - 0.000000001 = +1.000000001
+ 1.000000002 - 0.000000002 = +1.000000000
+ 1.000000002 - 1.000000000 = +0.000000002
+ 1.000000002 - 1.000000001 = +0.000000001
+ 1.000000002 - 1.000000002 = +0.000000000
+ 1.000000002 - 2.000000000 = -0.999999998
+ 1.000000002 - 2.000000001 = -0.999999999
+ 1.000000002 - 2.000000002 = -1.000000000
+====
+ 1.000000002 - -0.000000001 = +1.000000003
+ 1.000000002 - -0.000000002 = +1.000000004
+ 1.000000002 - -1.000000000 = +2.000000002
+ 1.000000002 - -1.000000001 = +2.000000003
+ 1.000000002 - -1.000000002 = +2.000000004
+ 1.000000002 - -2.000000000 = +3.000000002
+ 1.000000002 - -2.000000001 = +3.000000003
+ 1.000000002 - -2.000000002 = +3.000000004
+========
+ 2.000000000 - 0.000000000 = +2.000000000
+ 2.000000000 - 0.000000001 = +1.999999999
+ 2.000000000 - 0.000000002 = +1.999999998
+ 2.000000000 - 1.000000000 = +1.000000000
+ 2.000000000 - 1.000000001 = +0.999999999
+ 2.000000000 - 1.000000002 = +0.999999998
+ 2.000000000 - 2.000000000 = +0.000000000
+ 2.000000000 - 2.000000001 = -0.000000001
+ 2.000000000 - 2.000000002 = -0.000000002
+====
+ 2.000000000 - -0.000000001 = +2.000000001
+ 2.000000000 - -0.000000002 = +2.000000002
+ 2.000000000 - -1.000000000 = +3.000000000
+ 2.000000000 - -1.000000001 = +3.000000001
+ 2.000000000 - -1.000000002 = +3.000000002
+ 2.000000000 - -2.000000000 = +4.000000000
+ 2.000000000 - -2.000000001 = +4.000000001
+ 2.000000000 - -2.000000002 = +4.000000002
+========
+ 2.000000001 - 0.000000000 = +2.000000001
+ 2.000000001 - 0.000000001 = +2.000000000
+ 2.000000001 - 0.000000002 = +1.999999999
+ 2.000000001 - 1.000000000 = +1.000000001
+ 2.000000001 - 1.000000001 = +1.000000000
+ 2.000000001 - 1.000000002 = +0.999999999
+ 2.000000001 - 2.000000000 = +0.000000001
+ 2.000000001 - 2.000000001 = +0.000000000
+ 2.000000001 - 2.000000002 = -0.000000001
+====
+ 2.000000001 - -0.000000001 = +2.000000002
+ 2.000000001 - -0.000000002 = +2.000000003
+ 2.000000001 - -1.000000000 = +3.000000001
+ 2.000000001 - -1.000000001 = +3.000000002
+ 2.000000001 - -1.000000002 = +3.000000003
+ 2.000000001 - -2.000000000 = +4.000000001
+ 2.000000001 - -2.000000001 = +4.000000002
+ 2.000000001 - -2.000000002 = +4.000000003
+========
+ 2.000000002 - 0.000000000 = +2.000000002
+ 2.000000002 - 0.000000001 = +2.000000001
+ 2.000000002 - 0.000000002 = +2.000000000
+ 2.000000002 - 1.000000000 = +1.000000002
+ 2.000000002 - 1.000000001 = +1.000000001
+ 2.000000002 - 1.000000002 = +1.000000000
+ 2.000000002 - 2.000000000 = +0.000000002
+ 2.000000002 - 2.000000001 = +0.000000001
+ 2.000000002 - 2.000000002 = +0.000000000
+====
+ 2.000000002 - -0.000000001 = +2.000000003
+ 2.000000002 - -0.000000002 = +2.000000004
+ 2.000000002 - -1.000000000 = +3.000000002
+ 2.000000002 - -1.000000001 = +3.000000003
+ 2.000000002 - -1.000000002 = +3.000000004
+ 2.000000002 - -2.000000000 = +4.000000002
+ 2.000000002 - -2.000000001 = +4.000000003
+ 2.000000002 - -2.000000002 = +4.000000004
+========
+-0.000000001 - 0.000000000 = -0.000000001
+-0.000000001 - 0.000000001 = -0.000000002
+-0.000000001 - 0.000000002 = -0.000000003
+-0.000000001 - 1.000000000 = -1.000000001
+-0.000000001 - 1.000000001 = -1.000000002
+-0.000000001 - 1.000000002 = -1.000000003
+-0.000000001 - 2.000000000 = -2.000000001
+-0.000000001 - 2.000000001 = -2.000000002
+-0.000000001 - 2.000000002 = -2.000000003
+====
+-0.000000001 - -0.000000001 = +0.000000000
+-0.000000001 - -0.000000002 = +0.000000001
+-0.000000001 - -1.000000000 = +0.999999999
+-0.000000001 - -1.000000001 = +1.000000000
+-0.000000001 - -1.000000002 = +1.000000001
+-0.000000001 - -2.000000000 = +1.999999999
+-0.000000001 - -2.000000001 = +2.000000000
+-0.000000001 - -2.000000002 = +2.000000001
+========
+-0.000000002 - 0.000000000 = -0.000000002
+-0.000000002 - 0.000000001 = -0.000000003
+-0.000000002 - 0.000000002 = -0.000000004
+-0.000000002 - 1.000000000 = -1.000000002
+-0.000000002 - 1.000000001 = -1.000000003
+-0.000000002 - 1.000000002 = -1.000000004
+-0.000000002 - 2.000000000 = -2.000000002
+-0.000000002 - 2.000000001 = -2.000000003
+-0.000000002 - 2.000000002 = -2.000000004
+====
+-0.000000002 - -0.000000001 = -0.000000001
+-0.000000002 - -0.000000002 = +0.000000000
+-0.000000002 - -1.000000000 = +0.999999998
+-0.000000002 - -1.000000001 = +0.999999999
+-0.000000002 - -1.000000002 = +1.000000000
+-0.000000002 - -2.000000000 = +1.999999998
+-0.000000002 - -2.000000001 = +1.999999999
+-0.000000002 - -2.000000002 = +2.000000000
+========
+-1.000000000 - 0.000000000 = -1.000000000
+-1.000000000 - 0.000000001 = -1.000000001
+-1.000000000 - 0.000000002 = -1.000000002
+-1.000000000 - 1.000000000 = -2.000000000
+-1.000000000 - 1.000000001 = -2.000000001
+-1.000000000 - 1.000000002 = -2.000000002
+-1.000000000 - 2.000000000 = -3.000000000
+-1.000000000 - 2.000000001 = -3.000000001
+-1.000000000 - 2.000000002 = -3.000000002
+====
+-1.000000000 - -0.000000001 = -0.999999999
+-1.000000000 - -0.000000002 = -0.999999998
+-1.000000000 - -1.000000000 = +0.000000000
+-1.000000000 - -1.000000001 = +0.000000001
+-1.000000000 - -1.000000002 = +0.000000002
+-1.000000000 - -2.000000000 = +1.000000000
+-1.000000000 - -2.000000001 = +1.000000001
+-1.000000000 - -2.000000002 = +1.000000002
+========
+-1.000000001 - 0.000000000 = -1.000000001
+-1.000000001 - 0.000000001 = -1.000000002
+-1.000000001 - 0.000000002 = -1.000000003
+-1.000000001 - 1.000000000 = -2.000000001
+-1.000000001 - 1.000000001 = -2.000000002
+-1.000000001 - 1.000000002 = -2.000000003
+-1.000000001 - 2.000000000 = -3.000000001
+-1.000000001 - 2.000000001 = -3.000000002
+-1.000000001 - 2.000000002 = -3.000000003
+====
+-1.000000001 - -0.000000001 = -1.000000000
+-1.000000001 - -0.000000002 = -0.999999999
+-1.000000001 - -1.000000000 = -0.000000001
+-1.000000001 - -1.000000001 = +0.000000000
+-1.000000001 - -1.000000002 = +0.000000001
+-1.000000001 - -2.000000000 = +0.999999999
+-1.000000001 - -2.000000001 = +1.000000000
+-1.000000001 - -2.000000002 = +1.000000001
+========
+-1.000000002 - 0.000000000 = -1.000000002
+-1.000000002 - 0.000000001 = -1.000000003
+-1.000000002 - 0.000000002 = -1.000000004
+-1.000000002 - 1.000000000 = -2.000000002
+-1.000000002 - 1.000000001 = -2.000000003
+-1.000000002 - 1.000000002 = -2.000000004
+-1.000000002 - 2.000000000 = -3.000000002
+-1.000000002 - 2.000000001 = -3.000000003
+-1.000000002 - 2.000000002 = -3.000000004
+====
+-1.000000002 - -0.000000001 = -1.000000001
+-1.000000002 - -0.000000002 = -1.000000000
+-1.000000002 - -1.000000000 = -0.000000002
+-1.000000002 - -1.000000001 = -0.000000001
+-1.000000002 - -1.000000002 = +0.000000000
+-1.000000002 - -2.000000000 = +0.999999998
+-1.000000002 - -2.000000001 = +0.999999999
+-1.000000002 - -2.000000002 = +1.000000000
+========
+-2.000000000 - 0.000000000 = -2.000000000
+-2.000000000 - 0.000000001 = -2.000000001
+-2.000000000 - 0.000000002 = -2.000000002
+-2.000000000 - 1.000000000 = -3.000000000
+-2.000000000 - 1.000000001 = -3.000000001
+-2.000000000 - 1.000000002 = -3.000000002
+-2.000000000 - 2.000000000 = -4.000000000
+-2.000000000 - 2.000000001 = -4.000000001
+-2.000000000 - 2.000000002 = -4.000000002
+====
+-2.000000000 - -0.000000001 = -1.999999999
+-2.000000000 - -0.000000002 = -1.999999998
+-2.000000000 - -1.000000000 = -1.000000000
+-2.000000000 - -1.000000001 = -0.999999999
+-2.000000000 - -1.000000002 = -0.999999998
+-2.000000000 - -2.000000000 = +0.000000000
+-2.000000000 - -2.000000001 = +0.000000001
+-2.000000000 - -2.000000002 = +0.000000002
+========
+-2.000000001 - 0.000000000 = -2.000000001
+-2.000000001 - 0.000000001 = -2.000000002
+-2.000000001 - 0.000000002 = -2.000000003
+-2.000000001 - 1.000000000 = -3.000000001
+-2.000000001 - 1.000000001 = -3.000000002
+-2.000000001 - 1.000000002 = -3.000000003
+-2.000000001 - 2.000000000 = -4.000000001
+-2.000000001 - 2.000000001 = -4.000000002
+-2.000000001 - 2.000000002 = -4.000000003
+====
+-2.000000001 - -0.000000001 = -2.000000000
+-2.000000001 - -0.000000002 = -1.999999999
+-2.000000001 - -1.000000000 = -1.000000001
+-2.000000001 - -1.000000001 = -1.000000000
+-2.000000001 - -1.000000002 = -0.999999999
+-2.000000001 - -2.000000000 = -0.000000001
+-2.000000001 - -2.000000001 = +0.000000000
+-2.000000001 - -2.000000002 = +0.000000001
+========
+-2.000000002 - 0.000000000 = -2.000000002
+-2.000000002 - 0.000000001 = -2.000000003
+-2.000000002 - 0.000000002 = -2.000000004
+-2.000000002 - 1.000000000 = -3.000000002
+-2.000000002 - 1.000000001 = -3.000000003
+-2.000000002 - 1.000000002 = -3.000000004
+-2.000000002 - 2.000000000 = -4.000000002
+-2.000000002 - 2.000000001 = -4.000000003
+-2.000000002 - 2.000000002 = -4.000000004
+====
+-2.000000002 - -0.000000001 = -2.000000001
+-2.000000002 - -0.000000002 = -2.000000000
+-2.000000002 - -1.000000000 = -1.000000002
+-2.000000002 - -1.000000001 = -1.000000001
+-2.000000002 - -1.000000002 = -1.000000000
+-2.000000002 - -2.000000000 = -0.000000002
+-2.000000002 - -2.000000001 = -0.000000001
+-2.000000002 - -2.000000002 = +0.000000000
diff --git a/ext/date/tests/time/duration/methods/sub_32.phpt b/ext/date/tests/time/duration/methods/sub_32.phpt
new file mode 100644
index 000000000000..298b2bfe4953
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/sub_32.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::sub() (32 bit variation)
+--SKIPIF--
+
+--FILE--
+negate();
+$b = Time\Duration::fromSeconds(2_147_483_647, 999999999);
+
+try {
+ $a->sub($b);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Time\TimeException: The maximum representable range is 2_147_483_647 seconds (roughly 68 years)
diff --git a/ext/date/tests/time/duration/methods/sub_64.phpt b/ext/date/tests/time/duration/methods/sub_64.phpt
new file mode 100644
index 000000000000..c8f8fac503bf
--- /dev/null
+++ b/ext/date/tests/time/duration/methods/sub_64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Time\Duration::sub() (64 bit variation)
+--SKIPIF--
+
+--FILE--
+negate();
+$b = Time\Duration::fromSeconds(9_223_372_035, 999999999);
+
+try {
+ $a->sub($b);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Time\TimeException: The maximum representable range is 9_223_372_035 seconds (roughly 292 years)
diff --git a/ext/date/tests/time/duration/new.phpt b/ext/date/tests/time/duration/new.phpt
new file mode 100644
index 000000000000..312752374e73
--- /dev/null
+++ b/ext/date/tests/time/duration/new.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Time\Duration: new
+--FILE--
+getMessage(), PHP_EOL;
+}
+
+try {
+ (new ReflectionClass(Time\Duration::class))->newInstance();
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+try {
+ (new ReflectionClass(Time\Duration::class))->newInstanceWithoutConstructor();
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Error: Call to private Time\Duration::__construct() from global scope
+ReflectionException: Access to non-public constructor of class Time\Duration
+ReflectionException: Class Time\Duration is an internal class marked as final that cannot be instantiated without invoking its constructor
diff --git a/ext/date/time.stub.php b/ext/date/time.stub.php
new file mode 100644
index 000000000000..b9d0a01b39d6
--- /dev/null
+++ b/ext/date/time.stub.php
@@ -0,0 +1,80 @@
+. |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Derick Rethans |
+ | Tim Düsterhus |
+ +----------------------------------------------------------------------+
+ */
+
+#include "php.h"
+#include "Zend/zend_exceptions.h"
+
+#include "php_date.h"
+#include "php_time.h"
+
+#define NANOS_IN_SEC 1000000000
+#define NANOS_IN_MICRO 1000
+#define MICROS_IN_SEC 1000000
+#define NANOS_IN_MILLI 1000000
+#define MILLIS_IN_SEC 1000
+
+ZEND_STATIC_ASSERT(NANOS_IN_MICRO * MICROS_IN_SEC == NANOS_IN_SEC, "");
+ZEND_STATIC_ASSERT(NANOS_IN_MILLI * MILLIS_IN_SEC == NANOS_IN_SEC, "");
+
+#define Z_PARAM_ULONG(l) { \
+ zend_long __l; \
+ Z_PARAM_LONG(__l); \
+ if (__l < 0) { \
+ zend_argument_value_error(_i, "must be greater than or equal to 0"); \
+ _error_code = ZPP_ERROR_FAILURE; \
+ break; \
+ } \
+ l = __l; \
+ }
+
+ZEND_COLD static void throw_out_of_range_exception(void)
+{
+#if SIZEOF_ZEND_LONG != 4
+ zend_throw_exception(php_date_ce_time_timeexception, "The maximum representable range is 9_223_372_035 seconds (roughly 292 years)", 0);
+#else
+ zend_throw_exception(php_date_ce_time_timeexception, "The maximum representable range is 2_147_483_647 seconds (roughly 68 years)", 0);
+#endif
+}
+
+ZEND_COLD static void throw_timelib_error(int error)
+{
+ switch (error) {
+ case TIMELIB_ERROR_SECONDS_OUT_OF_RANGE:
+ throw_out_of_range_exception();
+ break;
+ case TIMELIB_ERROR_DIVISION_BY_ZERO:
+ zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
+ break;
+ case TIMELIB_ERROR_ISO8601_DURATION_PARSE_FAILURE:
+ case TIMELIB_ERROR_DURATION_MISSING_PERIOD:
+ case TIMELIB_ERROR_DURATION_ONLY_PERIOD_ALLOWED:
+ case TIMELIB_ERROR_DURATION_DAYS_FOUND:
+ zend_throw_exception(php_date_ce_time_timeexception, timelib_get_error_message(error), 0);
+ break;
+ default:
+ /* This should be unreachable in practice. */
+ zend_throw_exception_ex(php_date_ce_time_timeexception, 0, "Failed to create a Time\\Duration: %s", timelib_get_error_message(error));
+ break;
+ }
+}
+
+static inline php_date_time_duration *create_duration_shell(zval *target)
+{
+ object_init_ex(target, php_date_ce_time_duration);
+
+ return Z_DATE_TIME_DURATION_P(target);
+}
+
+ZEND_ATTRIBUTE_NODISCARD static inline zend_result sync_properties(php_date_time_duration *object)
+{
+ if (
+ /* Check if the duration would overflow the $seconds property. */
+ object->duration.seconds > ((uint64_t)ZEND_LONG_MAX)
+ /* This constraint is an explicit part of PHP's API. While it is currently also
+ * enforced by timelib, this might change in a future version of timelib, thus
+ * we also enforce it manually. */
+ || object->duration.seconds > UINT64_C(9223372035)
+ ) {
+ throw_out_of_range_exception();
+ return FAILURE;
+ }
+
+ ZEND_ASSERT(Z_ISUNDEF_P(OBJ_PROP_NUM(&object->std, 0)));
+ ZEND_ASSERT(Z_ISUNDEF_P(OBJ_PROP_NUM(&object->std, 1)));
+ ZEND_ASSERT(Z_ISUNDEF_P(OBJ_PROP_NUM(&object->std, 2)));
+
+ ZVAL_LONG(OBJ_PROP_NUM(&object->std, 0), object->duration.seconds);
+ ZVAL_LONG(OBJ_PROP_NUM(&object->std, 1), object->duration.nanoseconds);
+ ZVAL_BOOL(OBJ_PROP_NUM(&object->std, 2), object->duration.negative);
+
+ return SUCCESS;
+}
+
+ZEND_ATTRIBUTE_NODISCARD static zend_result create_duration(zval *target, zend_ulong seconds, zend_ulong nanoseconds)
+{
+ ZEND_ASSERT(nanoseconds < NANOS_IN_SEC);
+
+ if (EXPECTED(DATEG(duration_cache))) {
+ php_date_time_duration *cached = php_date_time_duration_from_obj(DATEG(duration_cache));
+ ZEND_ASSERT(!cached->duration.negative);
+
+ if (cached->duration.seconds == seconds && cached->duration.nanoseconds == nanoseconds) {
+ ZVAL_OBJ_COPY(target, &cached->std);
+ return SUCCESS;
+ }
+ }
+
+ php_date_time_duration *obj = create_duration_shell(target);
+
+ int error = timelib_duration_ctor_static(&obj->duration, seconds, nanoseconds, /* negative */ false);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ return FAILURE;
+ }
+
+ if (sync_properties(obj) == FAILURE) {
+ return FAILURE;
+ }
+
+ if (DATEG(duration_cache)) {
+ zend_object_release(DATEG(duration_cache));
+ }
+ GC_ADDREF(&obj->std);
+ DATEG(duration_cache) = &obj->std;
+
+ return SUCCESS;
+}
+
+PHP_METHOD(Time_Duration, __construct)
+{
+}
+
+PHP_METHOD(Time_Duration, fromSeconds)
+{
+ zend_ulong seconds;
+ zend_ulong nanoseconds = 0;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_ULONG(seconds);
+ Z_PARAM_OPTIONAL;
+ Z_PARAM_ULONG(nanoseconds);
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (nanoseconds >= NANOS_IN_SEC) {
+ zend_argument_value_error(2, "must be less than 1_000_000_000");
+ RETURN_THROWS();
+ }
+
+ if (create_duration(return_value, seconds, nanoseconds) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromNanoseconds)
+{
+ zend_ulong nanoseconds;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(nanoseconds);
+ ZEND_PARSE_PARAMETERS_END();
+
+ zend_ulong seconds = nanoseconds / NANOS_IN_SEC;
+ nanoseconds %= NANOS_IN_SEC;
+
+ if (create_duration(return_value, seconds, nanoseconds) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromMicroseconds)
+{
+ zend_ulong microseconds;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(microseconds);
+ ZEND_PARSE_PARAMETERS_END();
+
+ zend_ulong seconds = microseconds / MICROS_IN_SEC;
+ zend_ulong nanoseconds = (microseconds % MICROS_IN_SEC) * NANOS_IN_MICRO;
+
+ if (create_duration(return_value, seconds, nanoseconds) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromMilliseconds)
+{
+ zend_ulong milliseconds;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(milliseconds);
+ ZEND_PARSE_PARAMETERS_END();
+
+ zend_ulong seconds = milliseconds / MILLIS_IN_SEC;
+ zend_ulong nanoseconds = (milliseconds % MILLIS_IN_SEC) * NANOS_IN_MILLI;
+
+ if (create_duration(return_value, seconds, nanoseconds) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromMinutes)
+{
+ zend_ulong minutes;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(minutes);
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (minutes > (ZEND_ULONG_MAX / 60)) {
+ throw_out_of_range_exception();
+ RETURN_THROWS();
+ }
+
+ if (create_duration(return_value, minutes * 60, /* nanoseconds */ 0) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromHours)
+{
+ zend_ulong hours;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(hours);
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (hours > (ZEND_ULONG_MAX / 3600)) {
+ throw_out_of_range_exception();
+ RETURN_THROWS();
+ }
+
+ if (create_duration(return_value, hours * 3600, /* nanoseconds */ 0) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, fromIso8601DurationString)
+{
+ zend_string *specification;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(specification);
+ ZEND_PARSE_PARAMETERS_END();
+
+ int error;
+ timelib_duration *d = timelib_duration_create_from_iso8601string(ZSTR_VAL(specification), &error);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+ new->duration = *d;
+ timelib_duration_dtor(d);
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, negate)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+
+ int error = timelib_duration_negate_static(&new->duration, &original->duration);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, absolute)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ if (!original->duration.negative) {
+ RETURN_COPY(ZEND_THIS);
+ }
+
+ if (create_duration(return_value, original->duration.seconds, original->duration.nanoseconds) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, add)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ const php_date_time_duration *additional;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_DATE_TIME_DURATION(additional);
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+
+ int error = timelib_duration_add_static(&new->duration, &original->duration, &additional->duration);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, sub)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ const php_date_time_duration *minus;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_DATE_TIME_DURATION(minus);
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+
+ int error = timelib_duration_sub_static(&new->duration, &original->duration, &minus->duration);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, multiplyBy)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ zend_ulong factor;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(factor);
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+
+ int error = timelib_duration_mul_static(&new->duration, &original->duration, factor);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, divideBy)
+{
+ const php_date_time_duration *original = Z_DATE_TIME_DURATION_P(ZEND_THIS);
+
+ zend_ulong divisor;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ULONG(divisor);
+ ZEND_PARSE_PARAMETERS_END();
+
+ php_date_time_duration *new = create_duration_shell(return_value);
+
+ int error = timelib_duration_div_static(&new->duration, &original->duration, divisor);
+ if (error != TIMELIB_ERROR_NO_ERROR) {
+ throw_timelib_error(error);
+ RETURN_THROWS();
+ }
+
+ if (sync_properties(new) == FAILURE) {
+ RETURN_THROWS();
+ }
+}
+
+PHP_METHOD(Time_Duration, compare)
+{
+ const php_date_time_duration *a;
+ const php_date_time_duration *b;
+
+ ZEND_PARSE_PARAMETERS_START(2, 2)
+ Z_PARAM_DATE_TIME_DURATION(a);
+ Z_PARAM_DATE_TIME_DURATION(b);
+ ZEND_PARSE_PARAMETERS_END();
+
+ RETURN_LONG(timelib_duration_compare(&a->duration, &b->duration));
+}
diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c
index ca363c7b37d9..c29ccfee04f2 100644
--- a/ext/standard/io_poll.c
+++ b/ext/standard/io_poll.c
@@ -19,6 +19,7 @@
#include "php_poll.h"
#include "io_poll_arginfo.h"
#include "io_poll_decl.h"
+#include "ext/date/php_time.h"
/* Class entries */
static zend_class_entry *php_io_poll_backend_class_entry;
@@ -774,38 +775,28 @@ PHP_METHOD(Io_Poll_Context, add)
PHP_METHOD(Io_Poll_Context, wait)
{
- zend_long timeout_seconds = -1;
- bool timeout_seconds_is_null = true;
- zend_long timeout_microseconds = 0;
+ php_date_time_duration *timeout = NULL;
zend_long max_events = 0;
bool max_events_is_null = true;
- ZEND_PARSE_PARAMETERS_START(0, 3)
+ ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
- Z_PARAM_LONG_OR_NULL(timeout_seconds, timeout_seconds_is_null)
- Z_PARAM_LONG(timeout_microseconds)
+ Z_PARAM_DATE_TIME_DURATION_OR_NULL(timeout)
Z_PARAM_LONG_OR_NULL(max_events, max_events_is_null)
ZEND_PARSE_PARAMETERS_END();
php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());
- /* Build timespec from seconds + microseconds, or NULL for indefinite */
- struct timespec ts;
- const struct timespec *timeout = NULL;
- if (timeout_seconds >= 0) {
- if (timeout_microseconds < 0) {
- zend_argument_value_error(2, "must be greater than or equal to 0");
+ /* Build timespec from php_date_time_duration, or NULL for indefinite */
+ struct timespec timeout_ts;
+ if (timeout) {
+ if (timeout->duration.negative) {
+ zend_argument_value_error(2, "must not be negative");
RETURN_THROWS();
}
- /* Allow microseconds >= 1000000, carry overflow into seconds
- * (same behavior as stream_select) */
- ts.tv_sec = (time_t) (timeout_seconds + (timeout_microseconds / 1000000));
- ts.tv_nsec = (long) ((timeout_microseconds % 1000000) * 1000);
- timeout = &ts;
- } else if (!timeout_seconds_is_null) {
- zend_argument_value_error(1, "must be greater than or equal to 0");
- RETURN_THROWS();
+ timeout_ts.tv_sec = timeout->duration.seconds;
+ timeout_ts.tv_nsec = timeout->duration.nanoseconds;
}
if (max_events_is_null) {
@@ -814,12 +805,12 @@ PHP_METHOD(Io_Poll_Context, wait)
max_events = 64;
}
} else if (max_events <= 0) {
- zend_argument_value_error(3, "must be greater than 0");
+ zend_argument_value_error(2, "must be greater than 0");
RETURN_THROWS();
}
php_poll_event *events = safe_emalloc(max_events, sizeof(*events), 0);
- int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout);
+ int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout ? &timeout_ts : NULL);
if (num_events < 0) {
php_poll_error err = php_poll_get_error(intern->ctx);
diff --git a/ext/standard/io_poll.stub.php b/ext/standard/io_poll.stub.php
index 83c1ba5cbe2e..e3bcbf11c871 100644
--- a/ext/standard/io_poll.stub.php
+++ b/ext/standard/io_poll.stub.php
@@ -86,7 +86,7 @@ public function __construct(Backend $backend = Backend::Auto) {}
public function add(Handle $handle, array $events, mixed $data = null): Watcher {}
/** @return list */
- public function wait(?int $timeoutSeconds = null, int $timeoutMicroseconds = 0, ?int $maxEvents = null): array {}
+ public function wait(?\Time\Duration $timeout = null, ?int $maxEvents = null): array {}
public function getBackend(): Backend {}
}
diff --git a/ext/standard/io_poll_arginfo.h b/ext/standard/io_poll_arginfo.h
index 5fc62f629564..801df235c26b 100644
--- a/ext/standard/io_poll_arginfo.h
+++ b/ext/standard/io_poll_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit io_poll.stub.php instead.
- * Stub hash: a7450146c5b3b3f3486611c83a55cf0cc932b27a
+ * Stub hash: 2f52b00fd6dfc62291e0dd288ffd68547b29bdaa
* Has decl header: yes */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Io_Poll_Backend_getAvailableBackends, 0, 0, IS_ARRAY, 0)
@@ -56,8 +56,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Io_Poll_Context_add, 0, 2,
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Io_Poll_Context_wait, 0, 0, IS_ARRAY, 0)
- ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutSeconds, IS_LONG, 1, "null")
- ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMicroseconds, IS_LONG, 0, "0")
+ ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timeout, Time\\Duration, 1, "null")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, maxEvents, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
diff --git a/ext/standard/io_poll_decl.h b/ext/standard/io_poll_decl.h
index 2b09e3665f58..de49880392a7 100644
--- a/ext/standard/io_poll_decl.h
+++ b/ext/standard/io_poll_decl.h
@@ -1,8 +1,8 @@
/* This is a generated file, edit io_poll.stub.php instead.
- * Stub hash: a7450146c5b3b3f3486611c83a55cf0cc932b27a */
+ * Stub hash: 2f52b00fd6dfc62291e0dd288ffd68547b29bdaa */
-#ifndef ZEND_IO_POLL_DECL_a7450146c5b3b3f3486611c83a55cf0cc932b27a_H
-#define ZEND_IO_POLL_DECL_a7450146c5b3b3f3486611c83a55cf0cc932b27a_H
+#ifndef ZEND_IO_POLL_DECL_2f52b00fd6dfc62291e0dd288ffd68547b29bdaa_H
+#define ZEND_IO_POLL_DECL_2f52b00fd6dfc62291e0dd288ffd68547b29bdaa_H
typedef enum zend_enum_Io_Poll_Backend {
ZEND_ENUM_Io_Poll_Backend_Auto = 1,
@@ -23,4 +23,4 @@ typedef enum zend_enum_Io_Poll_Event {
ZEND_ENUM_Io_Poll_Event_EdgeTriggered = 7,
} zend_enum_Io_Poll_Event;
-#endif /* ZEND_IO_POLL_DECL_a7450146c5b3b3f3486611c83a55cf0cc932b27a_H */
+#endif /* ZEND_IO_POLL_DECL_2f52b00fd6dfc62291e0dd288ffd68547b29bdaa_H */
diff --git a/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt b/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt
index 4f32ec28c88d..3e64d950c19e 100644
--- a/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt
@@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
$watcher = pt_stream_poll_add($poll_ctx, $socket2, [Io\Poll\Event::Write], "socket_data");
$watcher->modify([Io\Poll\Event::Write], "modified_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'modified_data']
]);
?>
diff --git a/ext/standard/tests/poll/poll_stream_sock_read.phpt b/ext/standard/tests/poll/poll_stream_sock_read.phpt
index 70aebc9cd926..0b44a3d14eac 100644
--- a/ext/standard/tests/poll/poll_stream_sock_read.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_read.phpt
@@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket_data");
fwrite($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket_data', 'read' => 'test data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt b/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt
index c5fec5391652..2662a4e1f4c1 100644
--- a/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt
@@ -11,14 +11,14 @@ $poll_ctx = pt_new_stream_poll();
$watcher1w = pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket_data_1");
pt_stream_poll_add($poll_ctx, $socket2w, [Io\Poll\Event::Write], "socket_data_2");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_1'],
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_2']
]);
$watcher1w->remove();
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_2']
]);
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt
index 3909dea58637..f81ac40bcfab 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt
@@ -13,7 +13,7 @@ pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data")
fwrite($socket1w, "test data");
fclose($socket1r);
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => [
'default' => [Io\Poll\Event::Write, Io\Poll\Event::Error, Io\Poll\Event::HangUp],
@@ -28,7 +28,7 @@ pt_expect_events($poll_ctx->wait(0, 100000), [
], $poll_ctx);
fclose($socket1w);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_max_events.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_max_events.phpt
index d78d3f4fde35..c7374d2986e0 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_max_events.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_max_events.phpt
@@ -23,14 +23,14 @@ for ($i = 0; $i < 4; $i++) {
];
}
-pt_expect_events($poll_ctx->wait(0, 100000, 8), $expected);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 8), $expected);
// All read data was drained above, so only write events remain
$expected = [];
for ($i = 0; $i < 4; $i++) {
$expected[] = ['events' => [Io\Poll\Event::Write], 'data' => "sock$i"];
}
-pt_expect_events($poll_ctx->wait(0, 100000, 8), $expected);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 8), $expected);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt
index f25cb973ddce..63b22bd94941 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt
@@ -15,32 +15,32 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read, Io\Poll\Event::EdgeTriggered], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write, Io\Poll\Event::EdgeTriggered], "socket2_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
-pt_expect_events($poll_ctx->wait(0), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);
fwrite($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);
fwrite($socket1w, "more data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
fwrite($socket1w, " and even more data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'more data and even more data']
]);
fclose($socket1r);
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => ['default' => [Io\Poll\Event::Write, Io\Poll\Event::HangUp]],
'data' => 'socket2_data'
@@ -48,7 +48,7 @@ pt_expect_events($poll_ctx->wait(0, 100000), [
], $poll_ctx);
fclose($socket1w);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt
index 8bc8ea7fcc3d..639420872614 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt
@@ -10,39 +10,39 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
fwrite($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);
fwrite($socket1w, "more data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);
fwrite($socket1w, " and even more data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'more data and even more data']
]);
fclose($socket1r);
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => [
'default' => [Io\Poll\Event::Write, Io\Poll\Event::HangUp],
@@ -56,7 +56,7 @@ pt_expect_events($poll_ctx->wait(0, 100000), [
], $poll_ctx);
fclose($socket1w);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt
index 4a7b67b8243f..29432b7f95af 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt
@@ -15,11 +15,11 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read, Io\Poll\Event::EdgeTriggered], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write, Io\Poll\Event::EdgeTriggered], "socket2_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
fwrite($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt
index a807e573c5fe..f0a64fc06387 100644
--- a/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt
@@ -10,11 +10,11 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
fwrite($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_sock_write.phpt b/ext/standard/tests/poll/poll_stream_sock_write.phpt
index 2d937d7e9a3b..1bfb0524ec87 100644
--- a/ext/standard/tests/poll/poll_stream_sock_write.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_write.phpt
@@ -9,7 +9,7 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket_data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_sock_write_close.phpt b/ext/standard/tests/poll/poll_stream_sock_write_close.phpt
index 8927e017b05d..49e452907f20 100644
--- a/ext/standard/tests/poll/poll_stream_sock_write_close.phpt
+++ b/ext/standard/tests/poll/poll_stream_sock_write_close.phpt
@@ -13,7 +13,7 @@ pt_stream_poll_add($poll_ctx, $socket2w, [Io\Poll\Event::Write], "socket2w_data"
fclose($socket1w);
fclose($socket2w);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_read.phpt b/ext/standard/tests/poll/poll_stream_tcp_read.phpt
index 71c7af0ca734..e9296a266e04 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_read.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_read.phpt
@@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket_data");
pt_write_sleep($socket1w, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket_data', 'read' => 'test data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_max_events.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_max_events.phpt
index f927e73ed4d3..bb2c734cbf2c 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_read_max_events.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_read_max_events.phpt
@@ -19,7 +19,7 @@ for ($i = 0; $i < count($clients); $i++) {
// events must be delivered by the following wait() without any duplicates.
$seen = [];
for ($round = 1; $round <= 2; $round++) {
- $watchers = $poll_ctx->wait(0, 100000, 4);
+ $watchers = $poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 4);
echo "Round $round count: " . count($watchers) . "\n";
foreach ($watchers as $watcher) {
$data = $watcher->getData();
@@ -35,7 +35,7 @@ for ($round = 1; $round <= 2; $round++) {
ksort($seen);
echo "Seen: " . implode(',', array_keys($seen)) . "\n";
-pt_expect_events($poll_ctx->wait(0), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt
index db2ff71fb7c8..bc49571ca8ef 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt
@@ -11,7 +11,7 @@ for ($i = 0; $i < count($servers); $i++) {
pt_stream_poll_add($poll_ctx, $servers[$i], [Io\Poll\Event::Read], "server{$i}_data");
}
-pt_expect_events($poll_ctx->wait(0), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);
for ($i = 0; $i < count($clients); $i++) {
pt_write_sleep($clients[$i], "test $i data");
@@ -22,16 +22,16 @@ $expected_events = [];
for ($i = 0; $i < 20; $i++) {
$expected_events[] = ['events' => [Io\Poll\Event::Read], 'data' => "server{$i}_data", 'read' => "test $i data"];
}
-pt_expect_events($poll_ctx->wait(0, 100000), $expected_events);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), $expected_events);
pt_write_sleep($clients[1], "more data");
pt_write_sleep($clients[2], "more data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'server1_data', 'read' => 'more data'],
['events' => [Io\Poll\Event::Read], 'data' => 'server2_data', 'read' => 'more data']
]);
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt
index 02db6355ca1a..c8f7c424b3dc 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt
@@ -13,18 +13,18 @@ pt_stream_poll_add($poll_ctx, $server1, [Io\Poll\Event::Read, Io\Poll\Event::One
pt_stream_poll_add($poll_ctx, $client2, [Io\Poll\Event::Read, Io\Poll\Event::OneShot], "client2_data");
pt_stream_poll_add($poll_ctx, $server2, [Io\Poll\Event::Read, Io\Poll\Event::OneShot], "server2_data");
-pt_expect_events($poll_ctx->wait(0), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);
pt_write_sleep($client1, "test data");
pt_write_sleep($client2, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'server1_data', 'read' => 'test data'],
['events' => [Io\Poll\Event::Read], 'data' => 'server2_data', 'read' => 'test data']
]);
pt_write_sleep($client1, "more data");
pt_write_sleep($client2, "more data");
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_one_shot_max_events.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot_max_events.phpt
index d7a2e48be6f4..73142b16f636 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_read_one_shot_max_events.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot_max_events.phpt
@@ -20,7 +20,7 @@ for ($i = 0; $i < count($clients); $i++) {
// them, and each oneshot event must be delivered exactly once.
$seen = [];
for ($round = 1; $round <= 2; $round++) {
- $watchers = $poll_ctx->wait(0, 100000, 4);
+ $watchers = $poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 4);
echo "Round $round count: " . count($watchers) . "\n";
foreach ($watchers as $watcher) {
$data = $watcher->getData();
@@ -36,7 +36,7 @@ echo "Seen: " . implode(',', array_keys($seen)) . "\n";
// Every oneshot watcher fired once, so new data must not be reported
pt_write_sleep($clients[0], "more data");
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt
index 6132975e1b50..79db5608b974 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt
@@ -10,16 +10,16 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $client, [Io\Poll\Event::Read, Io\Poll\Event::Write, Io\Poll\Event::OneShot], "client_data");
pt_stream_poll_add($poll_ctx, $server, [Io\Poll\Event::Read, Io\Poll\Event::Write, Io\Poll\Event::OneShot], "server_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'client_data'],
['events' => [Io\Poll\Event::Write], 'data' => 'server_data']
]);
pt_write_sleep($client, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
pt_write_sleep($client, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot_mixed.phpt b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot_mixed.phpt
index c480caf1b73d..b8646f252e70 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot_mixed.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot_mixed.phpt
@@ -11,13 +11,13 @@ pt_stream_poll_add($poll_ctx, $client, [Io\Poll\Event::Read, Io\Poll\Event::Writ
pt_stream_poll_add($poll_ctx, $server, [Io\Poll\Event::Read, Io\Poll\Event::Write, Io\Poll\Event::OneShot], "server_data");
pt_write_sleep($client, "test data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'client_data'],
['events' => [Io\Poll\Event::Read, Io\Poll\Event::Write], 'data' => 'server_data']
]);
pt_write_sleep($client, "test data");
-pt_expect_events($poll_ctx->wait(0, 100000), []);
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);
?>
--EXPECT--
diff --git a/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt b/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt
index f29a74f19edd..9300e2266f37 100644
--- a/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt
+++ b/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt
@@ -10,14 +10,14 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $client, [Io\Poll\Event::Read, Io\Poll\Event::Write], "client_data");
pt_stream_poll_add($poll_ctx, $server, [Io\Poll\Event::Read, Io\Poll\Event::Write], "server_data");
-pt_expect_events($poll_ctx->wait(0), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'client_data'],
['events' => [Io\Poll\Event::Write], 'data' => 'server_data']
]);
fwrite($client, "test data");
usleep(10000);
-pt_expect_events($poll_ctx->wait(0, 100000), [
+pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'client_data'],
['events' => [Io\Poll\Event::Read, Io\Poll\Event::Write], 'data' => 'server_data', 'read' => 'test data']
]);
diff --git a/ext/standard/tests/poll/poll_stream_wait_no_add.phpt b/ext/standard/tests/poll/poll_stream_wait_no_add.phpt
index a297fa5e1162..14789d709f6b 100644
--- a/ext/standard/tests/poll/poll_stream_wait_no_add.phpt
+++ b/ext/standard/tests/poll/poll_stream_wait_no_add.phpt
@@ -4,7 +4,7 @@ Poll stream - only wait
wait(0, 100000);
+$events = $poll_ctx->wait(Time\Duration::fromMicroseconds(100000));
pt_print_events($events);
?>