From 34ab0270d0fe548cb87a86a5a16c7aa239451c00 Mon Sep 17 00:00:00 2001 From: Yoshio HANAWA Date: Wed, 4 Jan 2017 17:58:17 +0900 Subject: [PATCH] Fix `timecop_date_create_from_format()` for supportting time travelling / Refactoring / Add tests --- README.md | 25 +- circle.yml | 3 +- php-timecop.spec | 2 +- php_timecop.h | 42 +- tests/006.phpt | 27 -- tests/008.phpt | 22 - tests/009.phpt | 23 - tests/core_001.phpt | 16 +- tests/core_005.phpt | 21 +- tests/core_009.phpt | 17 + tests/core_010.phpt | 18 + tests/core_011.phpt | 22 + tests/{007.phpt => core_012.phpt} | 12 +- tests/core_013.phpt | 22 + tests/date_001.phpt | 34 +- tests/date_002.phpt | 8 +- tests/date_003.phpt | 3 +- tests/date_007.phpt | 96 ++-- tests/date_008.phpt | 67 +++ tests/date_override_001.phpt | 36 +- tests/date_override_003.phpt | 1 + tests/date_override_007.phpt | 93 ++-- tests/date_override_008.phpt | 68 +++ tests/immutable_001.phpt | 8 +- tests/immutable_007.phpt | 86 ++-- tests/immutable_008.phpt | 67 +++ tests/immutable_override_001.phpt | 61 +++ tests/immutable_override_007.phpt | 67 +++ tests/immutable_override_008.phpt | 68 +++ tests/issue_014.phpt | 1 + tests/nooverload01.phpt | 18 - timecop_php5.c | 728 ++++++++++++++++-------------- timecop_php7.c | 597 +++++++++++++----------- 33 files changed, 1507 insertions(+), 872 deletions(-) delete mode 100644 tests/006.phpt delete mode 100644 tests/008.phpt delete mode 100644 tests/009.phpt create mode 100644 tests/core_009.phpt create mode 100644 tests/core_010.phpt create mode 100644 tests/core_011.phpt rename tests/{007.phpt => core_012.phpt} (54%) create mode 100644 tests/core_013.phpt create mode 100644 tests/date_008.phpt create mode 100644 tests/date_override_008.phpt create mode 100644 tests/immutable_008.phpt create mode 100644 tests/immutable_override_001.phpt create mode 100644 tests/immutable_override_007.phpt create mode 100644 tests/immutable_override_008.phpt delete mode 100644 tests/nooverload01.phpt diff --git a/README.md b/README.md index 5c1115f..b75c0c5 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ extension=timecop.so ## SYSTEM REQUIREMENTS -- OS: Linux, FreeBSD, MacOSX -- PHP: 5.3.x, 5.4.x, 5.5.x, 5.6.x, 7.0.x, 7.1.x +- OS: Linux, macOS +- PHP: 5.3.1 - 7.1.x (might work on 5.2.x and 5.3.0, but not tested enough) - SAPI: Apache, CLI - Other SAPIs are not tested, but there is no SAPI-dependent code. - non-ZTS(recommended), ZTS @@ -50,9 +50,13 @@ extension=timecop.so - `gettimeofday()` - `unixtojd()` - `DateTime::_construct()` - - `DateTime::createFromFormat()` (PHP >= 5.3.4) + - `DateTime::createFromFormat()` (PHP >= 5.3.0) + - `DateTimeImmutable::_construct()` (PHP >= 5.5.0) + - `DateTimeImmutable::createFromFormat()` (PHP >= 5.5.0) - `date_create()` - - `date_create_from_format()` (PHP >= 5.3.4) + - `date_create_from_format()` (PHP >= 5.3.0) + - `date_create_immutable()` (PHP >= 5.5.0) + - `date_create_immutable_from_format()` (PHP >= 5.5.0) - Rewrite value of the following global variables when the time has been moved. - `$_SERVER['REQUEST_TIME']` @@ -98,12 +102,15 @@ var_dump((new DateTime())->format("c")); // string(25) "2017-01-01T00:00:05+00:0 ## CHANGELOG -### version 1.2.2(alpha), 2017/1/4 -- Implement Implement `TimecopDateTimeImmutable` class and `timecop_date_create_immutable()`, `timecop_date_create_immutable_from_format()` functions. -- Now `timecop_date_create_from_format()` returns DateTime instance +### version 1.2.3(beta), 2017/1/8 +- Fix `timecop_date_create_from_format()`: support time travelling + - Now portions of the generated time not provided in `format` will be set to the travelled time + - The previous version is completely same behavior as `date_create_from_format()`. +- Remove `TimecopDateTime::getTimestamp()`, `TimecopDateTime::setTimestamp()` on PHP 5.2.x -### version 1.2.1(alpha), 2016/12/30 -- Fix the year 2038 problem for PHP 7.x on 64bit Windows. +### version 1.2.2(alpha), 2017/1/4 +- Implement `TimecopDateTimeImmutable` class and `timecop_date_create_immutable()`, `timecop_date_create_immutable_from_format()` functions. +- Now `timecop_date_create_from_format()` returns `DateTime` instance ### version 1.2.0(alpha), 2016/12/30 - Big internal change (without BC break): handle microseconds accurately in time traveling. diff --git a/circle.yml b/circle.yml index e074cc8..3180984 100644 --- a/circle.yml +++ b/circle.yml @@ -13,4 +13,5 @@ dependencies: test: override: - - make test REPORT_EXIT_STATUS=1 NO_INTERACTION=1 TESTS="--show-all" | tee tests-output.txt && if grep -q 'TEST SUMMARY$' tests-output.txt ; then exit 1 ; fi + - make test REPORT_EXIT_STATUS=1 NO_INTERACTION=1 TESTS="--show-all" | tee tests-output.txt && if grep -q 'TEST SUMMARY$' tests-output.txt ; then exit 1 ; fi: + parallel: true diff --git a/php-timecop.spec b/php-timecop.spec index 2e6be25..7cd858c 100644 --- a/php-timecop.spec +++ b/php-timecop.spec @@ -1,6 +1,6 @@ %define __ext_name timecop Name: php-%{__ext_name} -Version: 1.2.1 +Version: 1.2.3 Release: 1%{?dist} Summary: php-timecop module diff --git a/php_timecop.h b/php_timecop.h index e787788..a84f31c 100644 --- a/php_timecop.h +++ b/php_timecop.h @@ -15,7 +15,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #ifndef PHP_TIMECOP_H #define PHP_TIMECOP_H -#define PHP_TIMECOP_VERSION "1.2.2" +#define PHP_TIMECOP_VERSION "1.2.3" extern zend_module_entry timecop_module_entry; #define phpext_timecop_ptr &timecop_module_entry @@ -68,6 +68,10 @@ PHP_FUNCTION(timecop_date_create_from_format); PHP_FUNCTION(timecop_date_create_immutable); PHP_FUNCTION(timecop_date_create_immutable_from_format); #endif +#if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 +PHP_FUNCTION(date_timestamp_set); +PHP_FUNCTION(date_timestamp_get); +#endif PHP_METHOD(TimecopDateTime, __construct); PHP_METHOD(TimecopOrigDateTime, __construct); @@ -80,11 +84,6 @@ PHP_METHOD(TimecopOrigDateTimeImmutable, __construct); PHP_METHOD(Timecop, freeze); PHP_METHOD(Timecop, travel); -#if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 -PHP_METHOD(TimecopDateTime, getTimestamp); -PHP_METHOD(TimecopDateTime, setTimestamp); -#endif - typedef enum timecop_mode_t { TIMECOP_MODE_REALTIME, TIMECOP_MODE_FREEZE, @@ -104,6 +103,7 @@ ZEND_BEGIN_MODULE_GLOBALS(timecop) tc_timeval travel_origin; tc_timeval travel_offset; tc_timeval_long scaling_factor; + zend_class_entry *ce_DateTimeZone; zend_class_entry *ce_DateTimeInterface; zend_class_entry *ce_DateTime; zend_class_entry *ce_TimecopDateTime; @@ -125,9 +125,6 @@ ZEND_END_MODULE_GLOBALS(timecop) #define ORIG_FUNC_NAME(fname) \ (TIMECOP_G(func_override) ? (SAVE_FUNC_PREFIX fname) : fname) -#define ORIG_FUNC_NAME_SIZEOF(fname) \ - (TIMECOP_G(func_override) ? sizeof(SAVE_FUNC_PREFIX fname) : sizeof(fname)) - #define TIMECOP_OFE(fname) {fname, OVRD_FUNC_PREFIX fname, SAVE_FUNC_PREFIX fname} #define TIMECOP_OCE(cname, mname) \ {cname, mname, OVRD_CLASS_PREFIX cname, SAVE_FUNC_PREFIX mname} @@ -145,14 +142,29 @@ struct timecop_override_class_entry { char *save_method; }; -#define timecop_call_orig_method_with_0_params(obj, obj_ce, fn_proxy, function_name, retval) \ - zend_call_method(obj, obj_ce, fn_proxy, ORIG_FUNC_NAME(function_name), ORIG_FUNC_NAME_SIZEOF(function_name)-1, retval, 0, NULL, NULL TSRMLS_CC) +#define call_php_method_with_0_params(obj, ce, method_name, retval) \ + _call_php_method_with_0_params(obj, ce, method_name, retval TSRMLS_CC) + +#define call_php_method_with_1_params(obj, ce, method_name, retval, arg1) \ + _call_php_method_with_1_params(obj, ce, method_name, retval, arg1 TSRMLS_CC) + +#define call_php_method_with_2_params(obj, ce, method_name, retval, arg1, arg2) \ + _call_php_method_with_2_params(obj, ce, method_name, retval, arg1, arg2 TSRMLS_CC) + +#define call_php_function_with_0_params(function_name, retval) \ + _call_php_function_with_0_params(function_name, retval TSRMLS_CC) + +#define call_php_function_with_1_params(function_name, retval, arg1) \ + _call_php_function_with_1_params(function_name, retval, arg1 TSRMLS_CC) + +#define call_php_function_with_2_params(function_name, retval, arg1, arg2) \ + _call_php_function_with_2_params(function_name, retval, arg1, arg2 TSRMLS_CC) -#define timecop_call_orig_method_with_1_params(obj, obj_ce, fn_proxy, function_name, retval, arg1) \ - zend_call_method(obj, obj_ce, fn_proxy, ORIG_FUNC_NAME(function_name), ORIG_FUNC_NAME_SIZEOF(function_name)-1, retval, 1, arg1, NULL TSRMLS_CC) +#define call_php_function_with_3_params(function_name, retval, arg1, arg2, arg3) \ + _call_php_function_with_3_params(function_name, retval, arg1, arg2, arg3 TSRMLS_CC) -#define timecop_call_orig_method_with_2_params(obj, obj_ce, fn_proxy, function_name, retval, arg1, arg2) \ - zend_call_method(obj, obj_ce, fn_proxy, ORIG_FUNC_NAME(function_name), ORIG_FUNC_NAME_SIZEOF(function_name)-1, retval, 2, arg1, arg2 TSRMLS_CC) +#define call_php_function_with_params(function_name, retval, param_count, params) \ + _call_php_function_with_params(function_name, retval, param_count, params TSRMLS_CC) /* In every utility function you add that needs to use variables in php_timecop_globals, call TSRMLS_FETCH(); after declaring other diff --git a/tests/006.phpt b/tests/006.phpt deleted file mode 100644 index 3fa1c07..0000000 --- a/tests/006.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Check for timecop.sync_request_time=1 and $_SERVER['REQUEST_TIME'] ---SKIPIF-- - ---INI-- -date.timezone=UTC ---FILE-- -= 0) { - die("skip this is for PHP 5.2.x"); - } - $required_class = array("timecopdatetime"); - foreach ($required_class as $class_name) { - if (!class_exists($class_name)) { - die("skip $class_name class is not available."); - } - } ---INI-- -date.timezone=America/Los_Angeles ---FILE-- -getTimestamp()); ---EXPECT-- -int(1000) diff --git a/tests/009.phpt b/tests/009.phpt deleted file mode 100644 index 4ddcb6c..0000000 --- a/tests/009.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Check for TimecopDateTime::setTimestampfor PHP 5.2 ---SKIPIF-- -= 0) { - die("skip this is for PHP 5.2.x"); - } - $required_class = array("timecopdatetime"); - foreach ($required_class as $class_name) { - if (!class_exists($class_name)) { - die("skip $class_name class is not available."); - } - } ---INI-- -date.timezone=America/Los_Angeles ---FILE-- -setTimestamp(2000); -var_dump($dt->format("U")); ---EXPECT-- -string(4) "2000" \ No newline at end of file diff --git a/tests/core_001.phpt b/tests/core_001.phpt index 7a3551b..c333c3b 100644 --- a/tests/core_001.phpt +++ b/tests/core_001.phpt @@ -37,18 +37,18 @@ if (PHP_INT_SIZE === 8) { timecop_freeze(1); $dt7 =new TimecopDateTime(); -var_dump($dt2->format("Y-m-d H:i:s.u")); -var_dump($dt4->format("Y-m-d H:i:s.u")); -var_dump($dt6->format("Y-m-d H:i:s.u")); +var_dump($dt2->format("Y-m-d H:i:s.uP")); +var_dump($dt4->format("Y-m-d H:i:s.uP")); +var_dump($dt6->format("Y-m-d H:i:s.uP")); var_dump($dt1 == $dt2); var_dump($dt3 == $dt4); var_dump($dt5 == $dt6); -var_dump($dt7->format("Y-m-d H:i:s.u")); +var_dump($dt7->format("Y-m-d H:i:s.uP")); --EXPECT-- -string(26) "1969-12-31 16:00:00.000000" -string(26) "1970-01-01 01:00:00.000000" -string(26) "2039-12-31 16:00:00.000000" +string(32) "1969-12-31 16:00:00.000000-08:00" +string(32) "1970-01-01 01:00:00.000000-08:00" +string(32) "2039-12-31 16:00:00.000000-08:00" bool(true) bool(true) bool(true) -string(26) "1969-12-31 16:00:01.000000" +string(32) "1969-12-31 16:00:01.000000-08:00" diff --git a/tests/core_005.phpt b/tests/core_005.phpt index 56efa08..a36be2c 100644 --- a/tests/core_005.phpt +++ b/tests/core_005.phpt @@ -1,13 +1,14 @@ --TEST-- -Check for Timecop::freeze() +Test for Timecop::freeze() --SKIPIF-- setTimeZone(new DateTimezone("America/Los_Angeles")); + $dt6->setTimeZone(new DateTimezone("Pacific/Honolulu")); } -Timecop::freeze(1); +Timecop::freeze(7); $dt7 =new TimecopDateTime(); var_dump($dt2->format("Y-m-d H:i:s.u")); @@ -45,10 +46,10 @@ var_dump($dt3 == $dt4); var_dump($dt5 == $dt6); var_dump($dt7->format("Y-m-d H:i:s.u")); --EXPECT-- -string(26) "1969-12-31 16:00:00.000000" +string(26) "1969-12-31 14:00:00.000000" string(26) "1970-01-01 01:00:00.000000" -string(26) "2039-12-31 16:00:00.000000" +string(26) "2039-12-31 14:00:05.000000" bool(true) bool(true) bool(true) -string(26) "1969-12-31 16:00:01.000000" +string(26) "1969-12-31 14:00:07.000000" diff --git a/tests/core_009.phpt b/tests/core_009.phpt new file mode 100644 index 0000000..515de32 --- /dev/null +++ b/tests/core_009.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test for date_timestamp_get() on PHP 5.2 +--SKIPIF-- += 0) { + die("skip this test is for PHP 5.2.x"); +} +$required_class = array("timecopdatetime"); +include(__DIR__."/../tests-skipcheck.inc.php"); +--INI-- +date.timezone=America/Los_Angeles +--FILE-- += 0) { + die("skip this test is for PHP 5.2.x"); +} +$required_class = array("timecopdatetime"); +include(__DIR__."/../tests-skipcheck.inc.php"); +--INI-- +date.timezone=America/Los_Angeles +--FILE-- +format("U")); +--EXPECT-- +string(4) "2000" diff --git a/tests/core_011.phpt b/tests/core_011.phpt new file mode 100644 index 0000000..827f4b0 --- /dev/null +++ b/tests/core_011.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for $_SERVER['REQUEST_TIME'] when timecop.sync_request_time=1 +--SKIPIF-- + +$required_func = array("timecop_freeze", "timecop_travel", "timecop_return"); +include(__DIR__."/../tests-skipcheck.inc.php"); --INI-- date.timezone=UTC timecop.sync_request_time=0 diff --git a/tests/core_013.phpt b/tests/core_013.phpt new file mode 100644 index 0000000..9cae48b --- /dev/null +++ b/tests/core_013.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for phpinfo() +--SKIPIF-- + enabled +Version => \d+\.\d+\.\d+ + +Directive => Local Value => Master Value +timecop\.func_override => 1 => 1 +timecop\.sync_request_time => 1 => 1 +.* diff --git a/tests/date_001.phpt b/tests/date_001.phpt index e59f407..50eaf13 100644 --- a/tests/date_001.phpt +++ b/tests/date_001.phpt @@ -1,5 +1,5 @@ --TEST-- -Check for DateTime/TimecopDateTime/TimecopOrigDateTime inheritance +Test for DateTime/TimecopDateTime/TimecopOrigDateTime inheritance --SKIPIF-- = 0) { + var_dump($dt1 instanceof DateTimeInterface); + var_dump($dt2 instanceof DateTimeInterface); + var_dump($dt3 instanceof DateTimeInterface); +} else { + // force pass if PHP < 5.5.0 + var_dump(true); + var_dump(true); + var_dump(true); +} +echo "===\n"; +var_dump($dt1->format("c") === $freezed_time); +var_dump($dt2->format("c") === $freezed_time); +var_dump($dt3->format("c") === $freezed_time); --EXPECT-- string(8) "DateTime" string(15) "TimecopDateTime" string(19) "TimecopOrigDateTime" +bool(true) +bool(true) +bool(false) bool(false) bool(false) +bool(false) +=== +bool(true) +bool(true) bool(true) +=== bool(false) bool(true) bool(false) diff --git a/tests/date_002.phpt b/tests/date_002.phpt index d570f72..362f806 100644 --- a/tests/date_002.phpt +++ b/tests/date_002.phpt @@ -50,9 +50,11 @@ foreach ($strs as $str) { printf("TimecopDatetime::format is differ from Datetime::format for str=%s: %s !== %s\n", $str, $dt1->format("c"), $dt2->format("c")); } - if ($dt1->format("U") != $dt2->getTimestamp()) { - printf("TimecopDatetime::getTimestamp is differ from Datetime::getTimestamp for str=%s: %s !== %s\n", - $str, $dt1->format("U"), $dt2->getTimestamp()); + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + if ($dt1->format("U") != $dt2->getTimestamp()) { + printf("TimecopDatetime::getTimestamp is differ from Datetime::getTimestamp for str=%s: %s !== %s\n", + $str, $dt1->format("U"), $dt2->getTimestamp()); + } } } } diff --git a/tests/date_003.phpt b/tests/date_003.phpt index 9d2c9ce..bd84439 100644 --- a/tests/date_003.phpt +++ b/tests/date_003.phpt @@ -2,6 +2,7 @@ Test for serialize/unserialize TimecopDateTime instance --SKIPIF-- format("c")); var_dump($dt2->format("c")); diff --git a/tests/date_007.phpt b/tests/date_007.phpt index 1945346..49e9cbb 100644 --- a/tests/date_007.phpt +++ b/tests/date_007.phpt @@ -1,56 +1,66 @@ --TEST-- -Test for timecop_date_create_from_format +Test for timecop_date_create_from_format() --SKIPIF-- format("c")); - var_dump($dt->getTimezone()->getName()); +$dt0 = timecop_date_create_from_format("Y-m-d H:i:s.u", "2010-01-02 03:04:05.678000"); +var_dump(get_class($dt0)); +var_dump($dt0->format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array("timecop_date_create_from_format", $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new DateTime()); + $dt2 = call_user_func_array("timecop_date_create_from_format", $args); + $dt3 = call_user_func_array("date_create_from_format", $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("timecop_date_create_from_format('%s', '%s') is differ from date_create_from_format() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } } - ---EXPECT-- -string(8) "DateTime" -string(8) "DateTime" -string(25) "2012-03-31T12:34:56-07:00" -string(19) "America/Los_Angeles" -string(25) "1970-01-01T19:00:00-05:00" -string(3) "EST" -string(25) "1970-01-02T00:00:00+00:00" -string(6) "+00:00" -string(25) "2012-04-01T00:00:00+09:00" -string(10) "Asia/Tokyo" +--EXPECTREGEX-- +string\(8\) "DateTime" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/date_008.phpt b/tests/date_008.phpt new file mode 100644 index 0000000..3cbe4c7 --- /dev/null +++ b/tests/date_008.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test for TimecopDateTime::createFromFormat() +--SKIPIF-- +format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array(array("TimecopDateTime","createFromFormat"), $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new DateTime()); + $dt2 = call_user_func_array(array("TimecopDateTime","createFromFormat"), $args); + $dt3 = call_user_func_array(array("DateTime","createFromFormat"), $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("TimecopDateTime::createFromFormat('%s', '%s') is differ from DateTime::createFromFormat() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } +} +--EXPECTREGEX-- +string\(8\) "DateTime" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/date_override_001.phpt b/tests/date_override_001.phpt index 1cc7795..15e0a8d 100644 --- a/tests/date_override_001.phpt +++ b/tests/date_override_001.phpt @@ -1,5 +1,5 @@ --TEST-- -Check for DateTime/TimecopDateTime/TimecopOrigDateTime inheritance when function override is enabled +Test for DateTime/TimecopDateTime/TimecopOrigDateTime inheritance when function override is enabled --SKIPIF-- = 0) { + var_dump($dt1 instanceof DateTimeInterface); + var_dump($dt2 instanceof DateTimeInterface); + var_dump($dt3 instanceof DateTimeInterface); +} else { + // force pass if PHP < 5.5.0 + var_dump(true); + var_dump(true); + var_dump(true); +} +echo "===\n"; +var_dump($dt1->format("c") === $freezed_time); +var_dump($dt2->format("c") === $freezed_time); +var_dump($dt3->format("c") === $freezed_time); --EXPECT-- string(8) "DateTime" string(15) "TimecopDateTime" string(19) "TimecopOrigDateTime" +bool(true) +bool(true) bool(false) bool(false) -bool(true) bool(false) +bool(false) +=== +bool(true) +bool(true) +bool(true) +=== +bool(true) bool(true) bool(false) diff --git a/tests/date_override_003.phpt b/tests/date_override_003.phpt index 1c32f7d..f80bd24 100644 --- a/tests/date_override_003.phpt +++ b/tests/date_override_003.phpt @@ -2,6 +2,7 @@ Test for serialize/unserialize overridden DateTime instance --SKIPIF-- format("c")); - var_dump($dt->getTimezone()->getName()); +$dt0 = date_create_from_format("Y-m-d H:i:s.u", "2010-01-02 03:04:05.678000"); +var_dump(get_class($dt0)); +var_dump($dt0->format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array("date_create_from_format", $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new TimecopOrigDateTime()); + $dt2 = call_user_func_array("date_create_from_format", $args); + $dt3 = call_user_func_array("timecop_orig_date_create_from_format", $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("date_create_from_format('%s', '%s') is differ from timecop_orig_date_create_from_format() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } } - ---EXPECT-- -string(8) "DateTime" -string(8) "DateTime" -string(25) "2012-03-31T12:34:56-07:00" -string(19) "America/Los_Angeles" -string(25) "1970-01-01T19:00:00-05:00" -string(3) "EST" -string(25) "1970-01-02T00:00:00+00:00" -string(6) "+00:00" -string(25) "2012-04-01T00:00:00+09:00" -string(10) "Asia/Tokyo" +--EXPECTREGEX-- +string\(8\) "DateTime" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/date_override_008.phpt b/tests/date_override_008.phpt new file mode 100644 index 0000000..44283e1 --- /dev/null +++ b/tests/date_override_008.phpt @@ -0,0 +1,68 @@ +--TEST-- +Method overrideing test for DateTime::createFromFormat() +--SKIPIF-- +format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array(array("DateTime","createFromFormat"), $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new TimecopOrigDateTime()); + $dt2 = call_user_func_array(array("DateTime","createFromFormat"), $args); + $dt3 = call_user_func_array(array("TimecopOrigDateTime","createFromFormat"), $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("DateTime::createFromFormat('%s', '%s') is differ from TimecopOrigDateTime::createFromFormat() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } +} +--EXPECTREGEX-- +string\(8\) "DateTime" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/immutable_001.phpt b/tests/immutable_001.phpt index a0d8a8e..ade4907 100644 --- a/tests/immutable_001.phpt +++ b/tests/immutable_001.phpt @@ -1,5 +1,5 @@ --TEST-- -Check for DateTimeImmutable/TimecopDateTimeImmutable/TimecopOrigDateTimeImmutable inheritance +Test for DateTimeImmutable/TimecopDateTimeImmutable/TimecopOrigDateTimeImmutable inheritance --SKIPIF-- = 0) { var_dump($dt1 instanceof DateTimeInterface); @@ -47,6 +49,8 @@ bool(true) bool(true) bool(false) bool(false) +bool(false) +bool(false) === bool(true) bool(true) diff --git a/tests/immutable_007.phpt b/tests/immutable_007.phpt index da44890..ee3d2ff 100644 --- a/tests/immutable_007.phpt +++ b/tests/immutable_007.phpt @@ -1,46 +1,66 @@ --TEST-- -Check for timecop_date_create_immutable_from_format +Check for timecop_date_create_immutable_from_format() --SKIPIF-- format("c")); - var_dump($dt->getTimezone()->getName()); +$dt0 = timecop_date_create_immutable_from_format("Y-m-d H:i:s.u", "2010-01-02 03:04:05.678"); +var_dump(get_class($dt0)); +var_dump($dt0->format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array("timecop_date_create_immutable_from_format", $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_immutable_from_format() and date_create_immutale_from_format() */ + $start_time = time(); + timecop_freeze(new DateTime()); + $dt2 = call_user_func_array("timecop_date_create_immutable_from_format", $args); + $dt3 = call_user_func_array("date_create_immutable_from_format", $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("timecop_date_create_immutable_from_format('%s', '%s') is differ from date_create_immutable_from_format() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } } - ---EXPECT-- -string(17) "DateTimeImmutable" -string(25) "2012-03-31T12:34:56-07:00" -string(19) "America/Los_Angeles" -string(25) "1970-01-01T19:00:00-05:00" -string(3) "EST" -string(25) "1970-01-02T00:00:00+00:00" -string(6) "+00:00" -string(25) "2012-04-01T00:00:00+09:00" -string(10) "Asia/Tokyo" +--EXPECTREGEX-- +string\(17\) "DateTimeImmutable" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/immutable_008.phpt b/tests/immutable_008.phpt new file mode 100644 index 0000000..870e024 --- /dev/null +++ b/tests/immutable_008.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test for TimecopDateTimeImmutable::createFromFormat() +--SKIPIF-- +format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array(array("TimecopDateTimeImmutable","createFromFormat"), $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new DateTime()); + $dt2 = call_user_func_array(array("TimecopDateTimeImmutable","createFromFormat"), $args); + $dt3 = call_user_func_array(array("DateTimeImmutable","createFromFormat"), $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("TimecopDateTimeImmutable::createFromFormat('%s', '%s') is differ from DateTimeImmutable::createFromFormat() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } +} +--EXPECTREGEX-- +string\(17\) "DateTimeImmutable" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/immutable_override_001.phpt b/tests/immutable_override_001.phpt new file mode 100644 index 0000000..c6b2f7a --- /dev/null +++ b/tests/immutable_override_001.phpt @@ -0,0 +1,61 @@ +--TEST-- +Test for DateTimeImmutable/TimecopDateTimeImmutable/TimecopOrigDateTimeImmutable inheritance when function override is enabled +--SKIPIF-- += 0) { + var_dump($dt1 instanceof DateTimeInterface); + var_dump($dt2 instanceof DateTimeInterface); + var_dump($dt3 instanceof DateTimeInterface); +} else { + // force pass if PHP < 5.5.0 + var_dump(true); + var_dump(true); + var_dump(true); +} +echo "===\n"; +var_dump($dt1->format("c") === $dt0->format("c")); +var_dump($dt2->format("c") === $dt0->format("c")); +var_dump($dt3->format("c") === $dt0->format("c")); +--EXPECT-- +string(17) "DateTimeImmutable" +string(24) "TimecopDateTimeImmutable" +string(28) "TimecopOrigDateTimeImmutable" +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +=== +bool(true) +bool(true) +bool(true) +=== +bool(true) +bool(true) +bool(false) diff --git a/tests/immutable_override_007.phpt b/tests/immutable_override_007.phpt new file mode 100644 index 0000000..b66dca6 --- /dev/null +++ b/tests/immutable_override_007.phpt @@ -0,0 +1,67 @@ +--TEST-- +Function overrideing test for date_create_immutable_from_format() +--SKIPIF-- +format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array("date_create_immutable_from_format", $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new TimecopOrigDateTime()); + $dt2 = call_user_func_array("date_create_immutable_from_format", $args); + $dt3 = call_user_func_array("timecop_orig_date_create_immutable_from_format", $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("date_create_immutable_from_format('%s', '%s') is differ from timecop_orig_date_create_immutable_from_format() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } +} +--EXPECTREGEX-- +string\(17\) "DateTimeImmutable" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/immutable_override_008.phpt b/tests/immutable_override_008.phpt new file mode 100644 index 0000000..c0335ae --- /dev/null +++ b/tests/immutable_override_008.phpt @@ -0,0 +1,68 @@ +--TEST-- +Method overrideing test for DateTimeImmutable::createFromFormat() +--SKIPIF-- +format("Y-m-d H:i:s.uP")); +foreach ($tests_args as $args) { + timecop_freeze($dt0); + $dt1 = call_user_func_array(array("DateTimeImmutable","createFromFormat"), $args); + var_dump($dt1->format("Y-m-d H:i:s.uP")); + while (true) { + /* test for equality between timecop_date_create_from_format() and date_create_from_format() */ + $start_time = time(); + timecop_freeze(new TimecopOrigDateTime()); + $dt2 = call_user_func_array(array("DateTimeImmutable","createFromFormat"), $args); + $dt3 = call_user_func_array(array("TimecopOrigDateTimeImmutable","createFromFormat"), $args); + if ($start_time === time()) { + if ($dt2 && $dt3 && ($dt2->format("c") !== $dt3->format("c"))) { + printf("DateTimeImmutable::createFromFormat('%s', '%s') is differ from TimecopOrigDateTimeImmutable::createFromFormat() : %s !== %s\n", + $args[0], $args[1], $dt2->format("c"), $dt3->format("c")); + } + break; + } + } +} +--EXPECTREGEX-- +string\(17\) "DateTimeImmutable" +string\(32\) "2010-01-02 03:04:05\.678000-08:00" +string\(32\) "2010-01-02 03:04:05\.(000|678)000-08:00" +string\(32\) "2010-01-02 03:04:05\.(\1)000-08:00" +string\(32\) "1990-01-02 03:04:05\.000000-08:00" +string\(32\) "2010-09-02 03:04:05\.000000-07:00" +string\(32\) "2010-12-24 03:04:05\.000000-08:00" +string\(32\) "2010-01-02 05:00:00\.000000\+09:00" +string\(32\) "2010-01-02 00:00:59\.000000-08:00" +string\(32\) "2010-01-02 03:04:05\.654321-08:00" +string\(32\) "2012-03-31 12:34:56\.000000-07:00" +string\(32\) "1970-01-01 12:34:56\.000000-08:00" +string\(32\) "1970-01-01 19:00:00\.000000-05:00" +string\(32\) "1970-01-02 00:00:00\.000000\+00:00" +string\(32\) "2012-04-01 00:00:00\.000000\+09:00" diff --git a/tests/issue_014.phpt b/tests/issue_014.phpt index c56dcb4..e714acd 100644 --- a/tests/issue_014.phpt +++ b/tests/issue_014.phpt @@ -2,6 +2,7 @@ Check for issue #14 (All PHPUnit assertions involving DateTime comparison fail with PHP 7.1) --SKIPIF-- travel_offset.sec = 0; globals->travel_offset.usec = 0; globals->scaling_factor = 1; + globals->ce_DateTimeZone = NULL; globals->ce_DateTimeInterface = NULL; globals->ce_DateTime = NULL; globals->ce_TimecopDateTime = NULL; @@ -83,8 +83,12 @@ static const struct timecop_override_func_entry timecop_override_func_table[] = static const struct timecop_override_class_entry timecop_override_class_table[] = { TIMECOP_OCE("datetime", "__construct"), +#if PHP_VERSION_ID >= 50300 + TIMECOP_OCE("datetime", "createfromformat"), +#endif #if PHP_VERSION_ID >= 50500 TIMECOP_OCE("datetimeimmutable", "__construct"), + TIMECOP_OCE("datetimeimmutable", "createfromformat"), #endif {NULL, NULL, NULL, NULL} }; @@ -192,11 +196,13 @@ ZEND_END_ARG_INFO() #endif #if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 -ZEND_BEGIN_ARG_INFO_EX(arginfo_timecop_date_method_timestamp_set, 0, 0, 1) +ZEND_BEGIN_ARG_INFO_EX(arginfo_date_timestamp_set, 0, 0, 2) + ZEND_ARG_INFO(0, object) ZEND_ARG_INFO(0, unixtimestamp) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO(arginfo_timecop_date_method_timestamp_get, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_date_timestamp_get, 0, 0, 1) + ZEND_ARG_INFO(0, object) ZEND_END_ARG_INFO() #endif @@ -229,6 +235,10 @@ const zend_function_entry timecop_functions[] = { #if PHP_VERSION_ID >= 50500 PHP_FE(timecop_date_create_immutable, arginfo_timecop_date_create) PHP_FE(timecop_date_create_immutable_from_format, arginfo_timecop_date_create_from_format) +#endif +#if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 + PHP_FE(date_timestamp_set, arginfo_date_timestamp_set) + PHP_FE(date_timestamp_get, arginfo_date_timestamp_get) #endif {NULL, NULL, NULL} }; @@ -251,12 +261,6 @@ static zend_function_entry timecop_funcs_date[] = { #if PHP_VERSION_ID >= 50300 PHP_ME_MAPPING(createFromFormat, timecop_date_create_from_format, arginfo_timecop_date_create_from_format, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) -#endif -#if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 - PHP_ME(TimecopDateTime, getTimestamp, - arginfo_timecop_date_method_timestamp_get, 0) - PHP_ME(TimecopDateTime, setTimestamp, - arginfo_timecop_date_method_timestamp_set, 0) #endif {NULL, NULL, NULL} }; @@ -269,17 +273,17 @@ static zend_function_entry timecop_funcs_orig_date[] = { #if PHP_VERSION_ID >= 50500 static zend_function_entry timecop_funcs_immutable[] = { - PHP_ME(TimecopDateTimeImmutable, __construct, arginfo_timecop_date_create, - ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) - PHP_ME_MAPPING(createFromFormat, timecop_date_create_immutable_from_format, arginfo_timecop_date_create_from_format, - ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) - {NULL, NULL, NULL} + PHP_ME(TimecopDateTimeImmutable, __construct, arginfo_timecop_date_create, + ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) + PHP_ME_MAPPING(createFromFormat, timecop_date_create_immutable_from_format, arginfo_timecop_date_create_from_format, + ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + {NULL, NULL, NULL} }; static zend_function_entry timecop_funcs_orig_immutable[] = { - PHP_ME(TimecopOrigDateTimeImmutable, __construct, arginfo_timecop_date_create, - ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) - {NULL, NULL, NULL} + PHP_ME(TimecopOrigDateTimeImmutable, __construct, arginfo_timecop_date_create, + ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} }; #endif @@ -321,22 +325,31 @@ static long mocked_timestamp(); static int fill_mktime_params(zval ***params, const char *date_function_name, int from TSRMLS_DC); static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval_time, zval **retval_timezone TSRMLS_DC); +static long get_mock_fraction(zval *time, zval *timezone_obj TSRMLS_DC); static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *function_name, int index_to_fill_timestamp); static void _timecop_call_mktime(INTERNAL_FUNCTION_PARAMETERS, const char *mktime_function_name, const char *date_function_name); -static int get_mock_time(tc_timeval *fixed, const tc_timeval *now TSRMLS_DC); +static int get_mock_timeval(tc_timeval *fixed, const tc_timeval *now TSRMLS_DC); static inline int get_mock_timestamp(long *fixed_timestamp TSRMLS_DC); -static int get_time_from_datetime(tc_timeval *tp, zval *dt TSRMLS_DC); +static int get_timeval_from_datetime(tc_timeval *tp, zval *dt TSRMLS_DC); static int get_current_time(tc_timeval *now TSRMLS_DC); -static inline void timecop_call_original_constructor(zval **obj, zend_class_entry *ce, zval ***params, int param_count TSRMLS_DC); -static inline void timecop_call_constructor(zval **obj, zend_class_entry *ce, zval ***params, int param_count TSRMLS_DC); -static void timecop_call_constructor_ex(zval **obj, zend_class_entry *ce, zval ***params, int param_count, int call_original TSRMLS_DC); - -static void simple_call_function(const char *function_name, zval **retval_ptr_ptr, zend_uint param_count, zval **params[] TSRMLS_DC); -static zval *php_timecop_date_instantiate(zend_class_entry *pce, zval *object TSRMLS_DC); +static void _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static inline void _timecop_date_create(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static inline void _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static void _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAMETERS, zval *obj, int immutable); + +static inline zval* _call_php_method_with_0_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr TSRMLS_DC); +static inline zval* _call_php_method_with_1_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, zval *arg1 TSRMLS_DC); +static inline zval* _call_php_method_with_2_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2 TSRMLS_DC); +static inline zval* _call_php_method(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC); +static inline void _call_php_function_with_0_params(const char *function_name, zval **retval_ptr_ptr TSRMLS_DC); +static inline void _call_php_function_with_1_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1 TSRMLS_DC); +static inline void _call_php_function_with_2_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2 TSRMLS_DC); +static void _call_php_function_with_3_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2, zval *arg3 TSRMLS_DC); +static inline void _call_php_function_with_params(const char *function_name, zval **retval_ptr_ptr, zend_uint param_count, zval **params[] TSRMLS_DC); /* {{{ timecop_module_entry */ @@ -443,7 +456,7 @@ static int register_timecop_classes(TSRMLS_D) { zend_class_entry **pce; zend_class_entry ce; - zend_class_entry *tmp, *date_ce, *immutable_ce = NULL, *interface_ce = NULL; + zend_class_entry *tmp, *date_ce, *timezone_ce, *immutable_ce = NULL, *interface_ce = NULL; if (zend_hash_find(CG(class_table), "datetime", sizeof("datetime"), (void **) &pce) == FAILURE) { /* DateTime must be initialized before */ @@ -453,6 +466,14 @@ static int register_timecop_classes(TSRMLS_D) } date_ce = *pce; + if (zend_hash_find(CG(class_table), "datetimezone", sizeof("datetimezone"), (void **) &pce) == FAILURE) { + /* DateTimeImmutable must be initialized before */ + php_error_docref("https://github.com/hnw/php-timecop" TSRMLS_CC, E_WARNING, + "timecop couldn't find class %s.", "DateTimeZone"); + return SUCCESS; + } + timezone_ce = *pce; + #if PHP_VERSION_ID >= 50500 if (zend_hash_find(CG(class_table), "datetimeimmutable", sizeof("datetimeimmutable"), (void **) &pce) == FAILURE) { /* DateTimeImmutable must be initialized before */ @@ -474,6 +495,7 @@ static int register_timecop_classes(TSRMLS_D) INIT_CLASS_ENTRY(ce, "Timecop", timecop_funcs_timecop); zend_register_internal_class(&ce TSRMLS_CC); + TIMECOP_G(ce_DateTimeZone) = timezone_ce; if (interface_ce) { TIMECOP_G(ce_DateTimeInterface) = interface_ce; } else { @@ -755,7 +777,7 @@ static int fill_mktime_params(zval ***params, const char *date_function_name, in INIT_ZVAL(format); ZVAL_STRING(&format, formats[i], 0); - simple_call_function(date_function_name, &retval_ptr, 2, date_params TSRMLS_CC); + call_php_function_with_params(date_function_name, &retval_ptr, 2, date_params); if (retval_ptr) { ZVAL_ZVAL(*params[i], retval_ptr, 1, 1); } @@ -773,7 +795,7 @@ static int fill_mktime_params(zval ***params, const char *date_function_name, in * if ($time === null || $time === false || $time === "") { * $time = "now"; * } - * $now = get_mock_time(); + * $now = get_mock_timeval(); * if ($timezone_obj) { * // save default timezone * $zonename = $timezone_obj->getName() @@ -788,17 +810,14 @@ static int fill_mktime_params(zval ***params, const char *date_function_name, in * if ($fixed_sec === FALSE) { * return false; * } - * $dt1 = date_create($time, $timezone_obj); - * $dt2 = date_create($time, $timezone_obj); - * $usec1 = $dt1->format("u"); - * $usec2 = $dt2->format("u"); - * if ($usec1 === $usec2) { - * $fixed_usec = $usec1; - * } else { + * $fixed_usec = get_mock_fraction($time, $timezone_obj); + * if ($fixed_usec === -1) { * $fixed_usec = $now->usec; * } + * $dt = date_create($time, $timezone_obj); + * $dt->setTimestamp($fixed_sec); * $format = sprintf("Y-m-d H:i:s.%06d", $fixed_usec); - * $formatted_time = date($format, $fixed_sec); + * $formatted_time = $dt->format($format); * return $formatted_time; * } */ @@ -807,6 +826,7 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval zval *fixed_sec, *orig_zonename; zval str_now, now_timestamp; tc_timeval now; + long fixed_usec; if (TIMECOP_G(timecop_mode) == TIMECOP_MODE_REALTIME) { MAKE_STD_ZVAL(*retval_time); @@ -824,15 +844,15 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval time = &str_now; } - get_mock_time(&now, NULL TSRMLS_CC); + get_mock_timeval(&now, NULL TSRMLS_CC); if (timezone_obj && Z_TYPE_P(timezone_obj) == IS_OBJECT) { zval *zonename; - zend_call_method_with_0_params(&timezone_obj, Z_OBJCE_PP(&timezone_obj), NULL, "getname", &zonename); + call_php_method_with_0_params(&timezone_obj, Z_OBJCE_PP(&timezone_obj), "getname", &zonename); if (zonename) { - zend_call_method_with_0_params(NULL, NULL, NULL, "date_default_timezone_get", &orig_zonename); + call_php_function_with_0_params("date_default_timezone_get", &orig_zonename); if (orig_zonename) { - zend_call_method_with_1_params(NULL, NULL, NULL, "date_default_timezone_set", NULL, zonename); + call_php_function_with_1_params("date_default_timezone_set", NULL, zonename); } zval_ptr_dtor(&zonename); } @@ -840,14 +860,15 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval INIT_ZVAL(now_timestamp); ZVAL_LONG(&now_timestamp, now.sec); - zend_call_method_with_2_params(NULL, NULL, NULL, "strtotime", &fixed_sec, time, &now_timestamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("strtotime"), &fixed_sec, time, &now_timestamp); if (timezone_obj && Z_TYPE_P(timezone_obj) == IS_OBJECT) { - zend_call_method_with_1_params(NULL, NULL, NULL, "date_default_timezone_set", NULL, orig_zonename); + call_php_function_with_1_params("date_default_timezone_set", NULL, orig_zonename); zval_ptr_dtor(&orig_zonename); } - if (Z_TYPE_P(fixed_sec) == IS_BOOL && !Z_BVAL_P(fixed_sec)) { /* $fixed_sec === false */ + if (Z_TYPE_P(fixed_sec) == IS_BOOL && !Z_BVAL_P(fixed_sec)) { + /* $fixed_sec === false */ MAKE_STD_ZVAL(*retval_time); ZVAL_FALSE(*retval_time); MAKE_STD_ZVAL(*retval_timezone); @@ -855,30 +876,18 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval return -1; } + fixed_usec = get_mock_fraction(time, timezone_obj TSRMLS_CC); + if (fixed_usec == -1) { + fixed_usec = now.usec; + } + { - zval *dt1, *dt2, *usec1, *usec2; - zval null_val, u_str, format_str; - long fixed_usec; + zval *dt; + zval format_str; char buf[64]; - if (timezone_obj == NULL) { - INIT_ZVAL(null_val); - ZVAL_NULL(&null_val); - timezone_obj = &null_val; - } - - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date_create", &dt1, time, timezone_obj); - if (Z_TYPE_P(dt1) == IS_BOOL && !Z_BVAL_P(time)) { - MAKE_STD_ZVAL(*retval_time); - ZVAL_FALSE(*retval_time); - MAKE_STD_ZVAL(*retval_timezone); - ZVAL_NULL(*retval_timezone); - return -1; - } - - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date_create", &dt2, time, timezone_obj); - if (Z_TYPE_P(dt2) == IS_BOOL && !Z_BVAL_P(time)) { - zval_ptr_dtor(&dt1); + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt, time, timezone_obj); + if (Z_TYPE_P(dt) == IS_BOOL && !Z_BVAL_P(dt)) { MAKE_STD_ZVAL(*retval_time); ZVAL_FALSE(*retval_time); MAKE_STD_ZVAL(*retval_timezone); @@ -886,27 +895,14 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval return -1; } - INIT_ZVAL(u_str); - ZVAL_STRING(&u_str, "u", 0); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "format", &usec1, &u_str); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "format", &usec2, &u_str); - convert_to_long(usec1); - convert_to_long(usec2); - if (Z_LVAL_P(usec1) == Z_LVAL_P(usec2)) { - fixed_usec = Z_LVAL_P(usec1); - } else { - fixed_usec = now.usec; - } sprintf(buf, "Y-m-d H:i:s.%06ld", fixed_usec); INIT_ZVAL(format_str); ZVAL_STRING(&format_str, buf, 0); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "settimestamp", NULL, fixed_sec); - zend_call_method_with_0_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "gettimezone", retval_timezone); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "format", retval_time, &format_str); - zval_ptr_dtor(&dt1); - zval_ptr_dtor(&dt2); - zval_ptr_dtor(&usec1); - zval_ptr_dtor(&usec2); + call_php_function_with_2_params("date_timestamp_set", NULL, dt, fixed_sec); + call_php_method_with_0_params(&dt, TIMECOP_G(ce_DateTime), "gettimezone", retval_timezone); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "format", retval_time, &format_str); + + zval_ptr_dtor(&dt); } if (fixed_sec) { @@ -916,6 +912,59 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval **retval return 0; } +/* + * get_mock_fraction() + * + * pseudo code: + * + * function get_mock_fraction($time, $timezone_obj) { + * $dt1 = date_create($time, $timezone_obj); + * $dt2 = date_create($time, $timezone_obj); + * $usec1 = $dt1->format("u"); + * $usec2 = $dt2->format("u"); + * if ($usec1 === $usec2) { + * $fixed_usec = $usec1; + * } else { + * $fixed_usec = -1; + * } + * return $fixed_usec; + * } + */ +static long get_mock_fraction(zval *time, zval *timezone_obj TSRMLS_DC) +{ + zval *dt1, *dt2, *usec1, *usec2; + long fixed_usec; + zval u_str; + + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt1, time, timezone_obj); + if (Z_TYPE_P(dt1) == IS_BOOL && !Z_BVAL_P(dt1)) { + return -1; + } + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt2, time, timezone_obj); + if (Z_TYPE_P(dt2) == IS_BOOL && !Z_BVAL_P(dt2)) { + zval_ptr_dtor(&dt1); + return -1; + } + INIT_ZVAL(u_str); + ZVAL_STRING(&u_str, "u", 0); + call_php_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), "format", &usec1, &u_str); + call_php_method_with_1_params(&dt2, TIMECOP_G(ce_DateTime), "format", &usec2, &u_str); + convert_to_long(usec1); + convert_to_long(usec2); + + if (Z_LVAL_P(usec1) == Z_LVAL_P(usec2)) { + fixed_usec = Z_LVAL_P(usec1); + } else { + fixed_usec = -1; + } + zval_ptr_dtor(&dt1); + zval_ptr_dtor(&dt2); + zval_ptr_dtor(&usec1); + zval_ptr_dtor(&usec2); + + return fixed_usec; +} + static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *function_name, int index_to_fill_timestamp) { int params_size; @@ -940,7 +989,7 @@ static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *fun argc++; } - simple_call_function(function_name, &retval_ptr, argc, params TSRMLS_CC); + call_php_function_with_params(function_name, &retval_ptr, argc, params); efree(params); @@ -976,7 +1025,7 @@ static void _timecop_call_mktime(INTERNAL_FUNCTION_PARAMETERS, const char *mktim php_error_docref(NULL TSRMLS_CC, E_STRICT, "You should be using the time() function instead"); } - simple_call_function(mktime_function_name, &retval_ptr, params_size, params TSRMLS_CC); + call_php_function_with_params(mktime_function_name, &retval_ptr, params_size, params); for (i = argc; i < MKTIME_NUM_ARGS; i++) { zval_ptr_dtor(&filled_value[i]); @@ -998,7 +1047,7 @@ PHP_FUNCTION(timecop_freeze) tc_timeval freezed_tv; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "O", &dt, TIMECOP_G(ce_DateTimeInterface)) != FAILURE) { - get_time_from_datetime(&freezed_tv, dt TSRMLS_CC); + get_timeval_from_datetime(&freezed_tv, dt TSRMLS_CC); } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "l", ×tamp) != FAILURE) { freezed_tv.sec = timestamp; freezed_tv.usec = 0; @@ -1027,7 +1076,7 @@ PHP_FUNCTION(timecop_travel) tc_timeval now, mock_tv; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "O", &dt, TIMECOP_G(ce_DateTimeInterface)) != FAILURE) { - get_time_from_datetime(&mock_tv, dt TSRMLS_CC); + get_timeval_from_datetime(&mock_tv, dt TSRMLS_CC); } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "l", ×tamp) != FAILURE) { mock_tv.sec = timestamp; mock_tv.usec = 0; @@ -1063,7 +1112,7 @@ PHP_FUNCTION(timecop_scale) RETURN_FALSE; } get_current_time(&now TSRMLS_CC); - get_mock_time(&mock_time, &now TSRMLS_CC); + get_mock_timeval(&mock_time, &now TSRMLS_CC); TIMECOP_G(timecop_mode) = TIMECOP_MODE_TRAVEL; TIMECOP_G(travel_origin) = now; tc_timeval_sub(&TIMECOP_G(travel_offset), &mock_time, &now); @@ -1180,19 +1229,8 @@ PHP_FUNCTION(timecop_gmstrftime) } /* }}} */ -static inline int get_mock_timestamp(long *fixed_timestamp TSRMLS_DC) -{ - tc_timeval tv; - int ret; - ret = get_mock_time(&tv, NULL TSRMLS_CC); - if (ret == 0) { - *fixed_timestamp = tv.sec; - } - return ret; -} - /* - * get_mock_time(fixed, now) + * get_mock_timeval(fixed, now) * * * delta @@ -1208,7 +1246,7 @@ static inline int get_mock_timestamp(long *fixed_timestamp TSRMLS_DC) * delta = orig_time - travel_origin * traveled_time = travel_origin + travel_offset + delta * scaling_factor */ -static int get_mock_time(tc_timeval *fixed, const tc_timeval *now TSRMLS_DC) +static int get_mock_timeval(tc_timeval *fixed, const tc_timeval *now TSRMLS_DC) { if (TIMECOP_G(timecop_mode) == TIMECOP_MODE_FREEZE) { *fixed = TIMECOP_G(freezed_time); @@ -1234,15 +1272,26 @@ static int get_mock_time(tc_timeval *fixed, const tc_timeval *now TSRMLS_DC) return 0; } -static int get_time_from_datetime(tc_timeval *tp, zval *dt TSRMLS_DC) +static inline int get_mock_timestamp(long *fixed_timestamp TSRMLS_DC) +{ + tc_timeval tv; + int ret; + ret = get_mock_timeval(&tv, NULL TSRMLS_CC); + if (ret == 0) { + *fixed_timestamp = tv.sec; + } + return ret; +} + +static int get_timeval_from_datetime(tc_timeval *tp, zval *dt TSRMLS_DC) { zval *sec, *usec; zval u_str; - zend_call_method_with_0_params(&dt, Z_OBJCE_P(dt), NULL, "gettimestamp", &sec); + call_php_function_with_1_params("date_timestamp_get", &sec, dt); INIT_ZVAL(u_str); ZVAL_STRING(&u_str, "u", 0); - zend_call_method_with_1_params(&dt, Z_OBJCE_P(dt), NULL, "format", &usec, &u_str); + call_php_method_with_1_params(&dt, Z_OBJCE_P(dt), "format", &usec, &u_str); convert_to_long(usec); tp->sec = Z_LVAL_P(sec); @@ -1290,7 +1339,7 @@ static void _timecop_gettimeofday(INTERNAL_FUNCTION_PARAMETERS, int mode) return; } - get_mock_time(&fixed, NULL TSRMLS_CC); + get_mock_timeval(&fixed, NULL TSRMLS_CC); if (get_as_float) { RETURN_DOUBLE((double)(fixed.sec + fixed.usec / MICRO_IN_SEC)); @@ -1305,14 +1354,14 @@ static void _timecop_gettimeofday(INTERNAL_FUNCTION_PARAMETERS, int mode) /* offset */ INIT_ZVAL(format); ZVAL_STRING(&format, "Z", 0); - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date", &zv_offset, &format, ×tamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("date"), &zv_offset, &format, ×tamp); convert_to_long(zv_offset); offset = Z_LVAL_P(zv_offset); zval_ptr_dtor(&zv_offset); /* is_dst */ ZVAL_STRING(&format, "I", 0); - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date", &zv_dst, &format, ×tamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("date"), &zv_dst, &format, ×tamp); convert_to_long(zv_dst); is_dst = Z_LVAL_P(zv_dst); zval_ptr_dtor(&zv_dst); @@ -1354,74 +1403,185 @@ PHP_FUNCTION(timecop_unixtojd) } /* }}} */ -/* {{{ proto DateTime timecop_date_create([string time[, DateTimeZone object]]) - Returns new DateTime object initialized with traveled time */ -PHP_FUNCTION(timecop_date_create) +static void _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable) { - zval ***params; + zval *arg1 = NULL, *arg2 = NULL; + zend_class_entry *real_ce; - params = (zval ***) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval **), 0); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|zz", &arg1, &arg2) == FAILURE) { + RETURN_FALSE; + } - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); + if (immutable) { + real_ce = TIMECOP_G(ce_DateTimeImmutable); + } else { + real_ce = TIMECOP_G(ce_DateTime); + } + + call_php_method_with_2_params(&getThis(), real_ce, ORIG_FUNC_NAME("__construct"), NULL, arg1, arg2); +} + +static inline void _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable) +{ + _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, getThis(), immutable); +} + +static inline void _timecop_date_create(INTERNAL_FUNCTION_PARAMETERS, int immutable) +{ + _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, immutable); +} + +static void _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAMETERS, zval *obj, int immutable) +{ + zval orig_time, *orig_timezone = NULL; + zval *fixed_time, *fixed_timezone, *dt, *arg1, *arg2; + char *orig_time_str = NULL; + int orig_time_len = 0; + const char *real_func; + zend_class_entry *real_ce; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sO!", &orig_time_str, &orig_time_len, &orig_timezone, TIMECOP_G(ce_DateTimeZone)) == FAILURE) { RETURN_FALSE; } - php_timecop_date_instantiate(TIMECOP_G(ce_DateTime), return_value TSRMLS_CC); + INIT_ZVAL(orig_time); + if (orig_time_str == NULL) { + ZVAL_NULL(&orig_time); + } else { + ZVAL_STRINGL(&orig_time, orig_time_str, orig_time_len, 0); + } + if (immutable) { + real_func = ORIG_FUNC_NAME("date_create_immutable"); + real_ce = TIMECOP_G(ce_DateTimeImmutable); + } else { + real_func = ORIG_FUNC_NAME("date_create"); + real_ce = TIMECOP_G(ce_DateTime); + } + + if (get_formatted_mock_time(&orig_time, orig_timezone, &fixed_time, &fixed_timezone TSRMLS_CC) == 0) { + arg1 = fixed_time; + arg2 = fixed_timezone; + } else { + arg1 = &orig_time; + arg2 = orig_timezone; + } + if (obj == NULL) { + call_php_function_with_2_params(real_func, &dt, arg1, arg2); + } else { + call_php_method_with_2_params(&obj, real_ce, ORIG_FUNC_NAME("__construct"), NULL, arg1, arg2); + } + + zval_ptr_dtor(&fixed_time); + zval_ptr_dtor(&fixed_timezone); - /* call TimecopDateTime::__construct() */ - timecop_call_constructor(&return_value, TIMECOP_G(ce_TimecopDateTime), params, ZEND_NUM_ARGS() TSRMLS_CC); + if (obj == NULL) { + RETURN_ZVAL(dt, 1, 1); + } +} - efree(params); +/* {{{ proto DateTime timecop_date_create([string time[, DateTimeZone object]]) + Returns new DateTime object initialized with traveled time */ +PHP_FUNCTION(timecop_date_create) +{ + _timecop_date_create(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ #if PHP_VERSION_ID >= 50300 -/* {{{ proto DateTime timecop_date_create_from_format(string format, string time[, DateTimeZone object]) - Returns new DateTime object initialized with traveled time */ -PHP_FUNCTION(timecop_date_create_from_format) +static void _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAMETERS, int immutable) { - zval *timezone_object = NULL; - char *time_str = NULL, *format_str = NULL; - int time_str_len = 0, format_str_len = 0; + zval *orig_timezone = NULL; + zval orig_format, orig_time, fixed_format, *fixed_time, *new_format, *new_time; + zval *dt, *new_dt, now_timestamp, tmp; + char *orig_format_str, *orig_time_str; + int orig_format_len, orig_time_len; + tc_timeval now; + char buf[64]; + const char *real_func; -#if PHP_VERSION_ID <= 50303 - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Currently this method is unsupported on PHP 5.3.0-5.3.3. Please upgrade PHP to 5.3.4+."); -#else - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|O", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, php_date_get_timezone_ce()) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|O!", &orig_format_str, &orig_format_len, &orig_time_str, &orig_time_len, &orig_timezone, TIMECOP_G(ce_DateTimeZone)) == FAILURE) { RETURN_FALSE; } - php_timecop_date_instantiate(TIMECOP_G(ce_DateTime), return_value TSRMLS_CC); - if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, format_str, timezone_object, 0 TSRMLS_CC)) { + INIT_ZVAL(orig_format); + ZVAL_STRINGL(&orig_format, orig_format_str, orig_format_len, 0); + INIT_ZVAL(orig_time); + ZVAL_STRINGL(&orig_time, orig_time_str, orig_time_len, 0); + + call_php_function_with_3_params(ORIG_FUNC_NAME("date_create_from_format"), &dt, &orig_format, &orig_time, orig_timezone); + if (Z_TYPE_P(dt) == IS_BOOL && !Z_BVAL_P(dt)) { RETURN_FALSE; } -#endif + + get_mock_timeval(&now, NULL TSRMLS_CC); + + INIT_ZVAL(now_timestamp); + ZVAL_LONG(&now_timestamp, now.sec); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "settimestamp", NULL, &now_timestamp); + sprintf(buf, "Y-m-d H:i:s.%06ld ", now.usec); + INIT_ZVAL(tmp); + ZVAL_STRINGL(&tmp, buf, strlen(buf), 0); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "format", &fixed_time, &tmp); + + INIT_ZVAL(fixed_format); + if (memchr(orig_format_str, '!', orig_format_len)) { + ZVAL_STRING(&fixed_format, "???\?-?\?-?? ??:??:??.??????", 0); + } else if (memchr(orig_format_str, 'g', orig_format_len) || + memchr(orig_format_str, 'h', orig_format_len) || + memchr(orig_format_str, 'G', orig_format_len) || + memchr(orig_format_str, 'H', orig_format_len) || + memchr(orig_format_str, 'i', orig_format_len) || + memchr(orig_format_str, 's', orig_format_len)) { + ZVAL_STRING(&fixed_format, "Y-m-d ??:??:??.??????", 0); + } else if (memchr(orig_format_str, 'Y', orig_format_len) || + memchr(orig_format_str, 'y', orig_format_len) || + memchr(orig_format_str, 'F', orig_format_len) || + memchr(orig_format_str, 'M', orig_format_len) || + memchr(orig_format_str, 'm', orig_format_len) || + memchr(orig_format_str, 'n', orig_format_len) || + memchr(orig_format_str, 'd', orig_format_len) || + memchr(orig_format_str, 'j', orig_format_len) || + memchr(orig_format_str, 'D', orig_format_len) || + memchr(orig_format_str, 'l', orig_format_len) || + memchr(orig_format_str, 'U', orig_format_len)) { + ZVAL_STRING(&fixed_format, "Y-m-d H:i:s.??????", 0); + } else { + ZVAL_STRING(&fixed_format, "Y-m-d H:i:s.??????", 0); + } + + ZVAL_STRING(&tmp, "%s %s", 0); + call_php_function_with_3_params("sprintf", &new_format, &tmp, &fixed_format, &orig_format); + call_php_function_with_3_params("sprintf", &new_time, &tmp, fixed_time, &orig_time); + + if (immutable) { + real_func = ORIG_FUNC_NAME("date_create_immutable_from_format"); + } else { + real_func = ORIG_FUNC_NAME("date_create_from_format"); + } + call_php_function_with_3_params(real_func, &new_dt, new_format, new_time, orig_timezone); + + zval_ptr_dtor(&dt); + zval_ptr_dtor(&fixed_time); + zval_ptr_dtor(&new_format); + zval_ptr_dtor(&new_time); + RETURN_ZVAL(new_dt, 1, 1); +} + +/* {{{ proto DateTime timecop_date_create_from_format(string format, string time[, DateTimeZone object]) + Returns new DateTime object initialized with traveled time */ +PHP_FUNCTION(timecop_date_create_from_format) +{ + _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ #endif #if PHP_VERSION_ID >= 50500 - /* {{{ proto DateTimeImmutable timecop_date_create_immutable([string time[, DateTimeZone object]]) Returns new DateTimeImmutable object initialized with traveled time */ PHP_FUNCTION(timecop_date_create_immutable) { - zval ***params; - - params = (zval ***) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - RETURN_FALSE; - } - - php_timecop_date_instantiate(TIMECOP_G(ce_DateTimeImmutable), return_value TSRMLS_CC); - - /* call TimecopDateTime::__construct() */ - timecop_call_constructor(&return_value, TIMECOP_G(ce_TimecopDateTimeImmutable), params, ZEND_NUM_ARGS() TSRMLS_CC); - - efree(params); + _timecop_date_create(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1429,18 +1589,7 @@ PHP_FUNCTION(timecop_date_create_immutable) Returns new DateTimeImmutable object initialized with traveled time */ PHP_FUNCTION(timecop_date_create_immutable_from_format) { - zval *timezone_object = NULL; - char *time_str = NULL, *format_str = NULL; - int time_str_len = 0, format_str_len = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|O", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, php_date_get_timezone_ce()) == FAILURE) { - RETURN_FALSE; - } - - php_timecop_date_instantiate(TIMECOP_G(ce_DateTimeImmutable), return_value TSRMLS_CC); - if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, format_str, timezone_object, 0 TSRMLS_CC)) { - RETURN_FALSE; - } + _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ #endif @@ -1449,43 +1598,7 @@ PHP_FUNCTION(timecop_date_create_immutable_from_format) Creates new TimecopDateTime object */ PHP_METHOD(TimecopDateTime, __construct) { - zval ***params; - int nparams; - zval *fixed_time, *fixed_timezone; - zval *obj = getThis(); - - nparams = ZEND_NUM_ARGS(); - if (nparams < 2) { - nparams = 2; - } - params = (zval ***) safe_emalloc(nparams, sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - RETURN_FALSE; - } - - zval *time = NULL, *timezone_obj = NULL; - if (ZEND_NUM_ARGS() >= 1) { - time = *params[0]; - } - if (ZEND_NUM_ARGS() >= 2) { - timezone_obj = *params[1]; - } - - nparams = ZEND_NUM_ARGS(); - if (get_formatted_mock_time(time, timezone_obj, &fixed_time, &fixed_timezone TSRMLS_CC) == 0) { - params[0] = &fixed_time; - params[1] = &fixed_timezone; - nparams = 2; - } - - /* call original DateTime::__construct() */ - timecop_call_original_constructor(&obj, TIMECOP_G(ce_DateTime), params, nparams TSRMLS_CC); - - zval_ptr_dtor(&fixed_time); - zval_ptr_dtor(&fixed_timezone); - efree(params); + _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ @@ -1493,20 +1606,7 @@ PHP_METHOD(TimecopDateTime, __construct) Creates new TimecopOrigDateTime object */ PHP_METHOD(TimecopOrigDateTime, __construct) { - zval ***params; - zval *obj = getThis(); - - params = (zval ***) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - RETURN_FALSE; - } - - /* call original DateTime::__construct() */ - timecop_call_original_constructor(&obj, TIMECOP_G(ce_DateTime), params, ZEND_NUM_ARGS() TSRMLS_CC); - - efree(params); + _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ @@ -1515,44 +1615,7 @@ PHP_METHOD(TimecopOrigDateTime, __construct) Creates new TimecopDateTimeImmutable object */ PHP_METHOD(TimecopDateTimeImmutable, __construct) { - zval ***params; - int nparams; - zval *fixed_time, *fixed_timezone; - zval *obj = getThis(); - - nparams = ZEND_NUM_ARGS(); - if (nparams < 2) { - nparams = 2; - } - params = (zval ***) safe_emalloc(nparams, sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - //zend_throw_error(NULL, "Cannot get arguments for TimecopDateTimeImmutable::__construct"); - RETURN_FALSE; - } - - zval *time = NULL, *timezone_obj = NULL; - if (ZEND_NUM_ARGS() >= 1) { - time = *params[0]; - } - if (ZEND_NUM_ARGS() >= 2) { - timezone_obj = *params[1]; - } - - nparams = ZEND_NUM_ARGS(); - if (get_formatted_mock_time(time, timezone_obj, &fixed_time, &fixed_timezone TSRMLS_CC) == 0) { - params[0] = &fixed_time; - params[1] = &fixed_timezone; - nparams = 2; - } - - /* call original DateTimeImmutable::__construct() */ - timecop_call_original_constructor(&obj, TIMECOP_G(ce_DateTimeImmutable), params, nparams TSRMLS_CC); - - zval_ptr_dtor(&fixed_time); - zval_ptr_dtor(&fixed_timezone); - efree(params); + _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1560,111 +1623,115 @@ PHP_METHOD(TimecopDateTimeImmutable, __construct) Creates new TimecopOrigDateTimeImmutable object */ PHP_METHOD(TimecopOrigDateTimeImmutable, __construct) { - zval ***params; - zval *obj = getThis(); - - params = (zval ***) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - //zend_throw_error(NULL, "Cannot get arguments for TimecopOrigDateTimeImmutable::__construct"); - RETURN_FALSE; - } - - /* call original DateTimeImmutable::__construct() */ - timecop_call_original_constructor(&obj, TIMECOP_G(ce_DateTimeImmutable), params, ZEND_NUM_ARGS() TSRMLS_CC); - - efree(params); + _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ #endif #if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 - -/* {{{ proto long TimecopDateTime::getTimestamp() - Gets the Unix timestamp. +/* {{{ proto DateTime date_timestamp_set(DateTime object, long unixTimestamp) + Sets the date and time based on an Unix timestamp. */ -PHP_METHOD(TimecopDateTime, getTimestamp) +PHP_FUNCTION(date_timestamp_set) { - zval *object; - php_date_obj *dateobj; - long timestamp; - int error; + zval *object; + php_date_obj *dateobj; + long timestamp; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, TIMECOP_G(ce_TimecopDateTime)) == FAILURE) { - RETURN_FALSE; - } - dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC); - DATE_CHECK_INITIALIZED(dateobj->time, DateTime); - timelib_update_ts(dateobj->time, NULL); - - timestamp = timelib_date_to_int(dateobj->time, &error); - if (error) { - RETURN_FALSE; - } else { - RETVAL_LONG(timestamp); - } + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, TIMECOP_G(ce_DateTime), ×tamp) == FAILURE) { + RETURN_FALSE; + } + dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC); + DATE_CHECK_INITIALIZED(dateobj->time, DateTime); + timelib_unixtime2local(dateobj->time, (timelib_sll)timestamp); + timelib_update_ts(dateobj->time, NULL); + + RETURN_ZVAL(object, 1, 0); } /* }}} */ -/* {{{ proto DateTime TimecopDateTime::getTimestamp(long unixTimestamp) - Sets the date and time based on an Unix timestamp. */ -PHP_METHOD(TimecopDateTime, setTimestamp) +/* {{{ proto long date_timestamp_get(DateTime object) + Gets the Unix timestamp. +*/ +PHP_FUNCTION(date_timestamp_get) { - zval *object; - php_date_obj *dateobj; - long timestamp; + zval *object; + php_date_obj *dateobj; + long timestamp; + int error; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, TIMECOP_G(ce_TimecopDateTime), ×tamp) == FAILURE) { - RETURN_FALSE; - } - dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC); - DATE_CHECK_INITIALIZED(dateobj->time, DateTime); - timelib_unixtime2local(dateobj->time, (timelib_sll)timestamp); - timelib_update_ts(dateobj->time, NULL); + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, TIMECOP_G(ce_DateTime)) == FAILURE) { + RETURN_FALSE; + } + dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC); + DATE_CHECK_INITIALIZED(dateobj->time, DateTime); + timelib_update_ts(dateobj->time, NULL); - RETURN_ZVAL(object, 1, 0); + timestamp = timelib_date_to_int(dateobj->time, &error); + if (error) { + RETURN_FALSE; + } else { + RETVAL_LONG(timestamp); + } } /* }}} */ #endif -static inline void timecop_call_original_constructor(zval **obj, zend_class_entry *ce, zval ***params, int param_count TSRMLS_DC) { - timecop_call_constructor_ex(obj, ce, params, param_count, 1 TSRMLS_CC); -} -static inline void timecop_call_constructor(zval **obj, zend_class_entry *ce, zval ***params, int param_count TSRMLS_DC) { - timecop_call_constructor_ex(obj, ce, params, param_count, 0 TSRMLS_CC); +static inline zval* _call_php_method_with_0_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr TSRMLS_DC) +{ + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr_ptr, 0, NULL, NULL TSRMLS_CC); } -static void timecop_call_constructor_ex(zval **obj, zend_class_entry *ce, zval ***params, int param_count, int call_original TSRMLS_DC) +static inline zval* _call_php_method_with_1_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, zval *arg1 TSRMLS_DC) { - zval *arg1 = NULL, *arg2 = NULL; - const char *func_name; - size_t func_name_len; - - if (param_count > 2) { - zend_error(E_ERROR, "INTERNAL ERROR: too many parameters for constructor."); - return; + int nparams = 1; + if (arg1 == NULL) { + nparams = 0; } + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr_ptr, nparams, arg1, NULL TSRMLS_CC); +} - if (param_count == 1) { - arg1 = *params[0]; - } else if (param_count == 2) { - arg1 = *params[0]; - arg2 = *params[1]; +static inline zval* _call_php_method_with_2_params(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2 TSRMLS_DC) +{ + int nparams = 2; + if (arg1 == NULL) { + nparams = 0; + } else if (arg2 == NULL) { + nparams = 1; } + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr_ptr, nparams, arg1, arg2 TSRMLS_CC); +} + +static inline zval* _call_php_method(zval **object_pp, zend_class_entry *obj_ce, const char *method_name, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC) +{ + return zend_call_method(object_pp, obj_ce, NULL, method_name, strlen(method_name), retval_ptr_ptr, param_count, arg1, arg2 TSRMLS_CC); +} + +static inline void _call_php_function_with_0_params(const char *function_name, zval **retval_ptr_ptr TSRMLS_DC) +{ + _call_php_method_with_0_params(NULL, NULL, function_name, retval_ptr_ptr TSRMLS_CC); +} - if (call_original) { - func_name = ORIG_FUNC_NAME("__construct"); - func_name_len = ORIG_FUNC_NAME_SIZEOF("__construct")-1; +static inline void _call_php_function_with_1_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1 TSRMLS_DC) +{ + _call_php_method_with_1_params(NULL, NULL, function_name, retval_ptr_ptr, arg1 TSRMLS_CC); +} +static inline void _call_php_function_with_2_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2 TSRMLS_DC) +{ + _call_php_method_with_2_params(NULL, NULL, function_name, retval_ptr_ptr, arg1, arg2 TSRMLS_CC); +} +static void _call_php_function_with_3_params(const char *function_name, zval **retval_ptr_ptr, zval *arg1, zval *arg2, zval *arg3 TSRMLS_DC) +{ + if (arg3 == NULL) { + _call_php_function_with_2_params(function_name, retval_ptr_ptr, arg1, arg2 TSRMLS_CC); } else { - func_name = "__construct"; - func_name_len = sizeof("__construct")-1; + zval *zps[3] = {arg1, arg2, arg3}; + zval **params[3] = {&zps[0], &zps[1], &zps[2]}; + _call_php_function_with_params(function_name, retval_ptr_ptr, 3, params TSRMLS_CC); } - - zend_call_method(obj, ce, NULL, func_name, func_name_len, NULL, param_count, arg1, arg2 TSRMLS_CC); } -static void simple_call_function(const char *function_name, zval **retval_ptr_ptr, zend_uint param_count, zval **params[] TSRMLS_DC) +static inline void _call_php_function_with_params(const char *function_name, zval **retval_ptr_ptr, zend_uint param_count, zval **params[] TSRMLS_DC) { zval callable; @@ -1674,21 +1741,6 @@ static void simple_call_function(const char *function_name, zval **retval_ptr_pt call_user_function_ex(EG(function_table), NULL, &callable, retval_ptr_ptr, param_count, params, 1, NULL TSRMLS_CC); } -/* Advanced Interface */ -static zval *php_timecop_date_instantiate(zend_class_entry *pce, zval *object TSRMLS_DC) -{ - Z_TYPE_P(object) = IS_OBJECT; - object_init_ex(object, pce); -#if !defined(PHP_VERSION_ID) || PHP_VERSION_ID < 50300 - object->refcount = 1; - object->is_ref = 0; -#else - Z_SET_REFCOUNT_P(object, 1); - Z_UNSET_ISREF_P(object); -#endif - return object; -} - /* * Local variables: * tab-width: 4 diff --git a/timecop_php7.c b/timecop_php7.c index 83bad62..db949b6 100644 --- a/timecop_php7.c +++ b/timecop_php7.c @@ -19,7 +19,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "ext/date/php_date.h" #include "php_timecop.h" @@ -42,6 +41,7 @@ static void timecop_globals_ctor(zend_timecop_globals *globals) { globals->travel_offset.sec = 0; globals->travel_offset.usec = 0; globals->scaling_factor = 1; + globals->ce_DateTimeZone = NULL; globals->ce_DateTimeInterface = NULL; globals->ce_DateTime = NULL; globals->ce_TimecopDateTime = NULL; @@ -75,7 +75,9 @@ static const struct timecop_override_func_entry timecop_override_func_table[] = static const struct timecop_override_class_entry timecop_override_class_table[] = { TIMECOP_OCE("datetime", "__construct"), + TIMECOP_OCE("datetime", "createfromformat"), TIMECOP_OCE("datetimeimmutable", "__construct"), + TIMECOP_OCE("datetimeimmutable", "createfromformat"), {NULL, NULL, NULL, NULL} }; @@ -271,20 +273,31 @@ static zend_long mocked_timestamp(); static int fill_mktime_params(zval *fill_params, const char *date_function_name, int from); static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval *retval_time, zval *retval_timezone); +static long get_mock_fraction(zval *time, zval *timezone_obj); static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *function_name, int index_to_fill_timestamp); static void _timecop_call_mktime(INTERNAL_FUNCTION_PARAMETERS, const char *mktime_function_name, const char *date_function_name); -static int get_mock_time(tc_timeval *fixed, const tc_timeval *now); +static int get_mock_timeval(tc_timeval *fixed, const tc_timeval *now); static inline int get_mock_timestamp(zend_long *fixed_timestamp); -static int get_time_from_datetime(tc_timeval *tp, zval *dt); +static int get_timeval_from_datetime(tc_timeval *tp, zval *dt); static int get_current_time(tc_timeval *now); -static inline void timecop_call_original_constructor(zval *obj, zend_class_entry *ce, zval *params, int param_count); -static inline void timecop_call_constructor(zval *obj, zend_class_entry *ce, zval *params, int param_count); -static void timecop_call_constructor_ex(zval *obj, zend_class_entry *ce, zval *params, int param_count, int call_original); -static void simple_call_function(const char *function_name, zval *retval_ptr, uint32_t param_count, zval params[]); +static void _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static inline void _timecop_date_create(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static inline void _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable); +static void _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAMETERS, zval *obj, int immutable); + +static inline zval* _call_php_method_with_0_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr); +static inline zval* _call_php_method_with_1_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, zval *arg1); +static inline zval* _call_php_method_with_2_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, zval *arg1, zval *arg2); +static inline zval* _call_php_method(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, int param_count, zval* arg1, zval* arg2); +static inline void _call_php_function_with_0_params(const char *function_name, zval *retval_ptr); +static inline void _call_php_function_with_1_params(const char *function_name, zval *retval_ptr, zval *arg1); +static inline void _call_php_function_with_2_params(const char *function_name, zval *retval_ptr, zval *arg1, zval *arg2); +static void _call_php_function_with_3_params(const char *function_name, zval *retval_ptr, zval *arg1, zval *arg2, zval *arg3); +static inline void _call_php_function_with_params(const char *function_name, zval *retval_ptr, uint32_t param_count, zval params[]); /* {{{ timecop_module_entry */ @@ -397,7 +410,7 @@ PHP_MINFO_FUNCTION(timecop) static int register_timecop_classes() { zend_class_entry ce; - zend_class_entry *tmp, *date_ce, *immutable_ce, *interface_ce; + zend_class_entry *tmp, *date_ce, *timezone_ce, *immutable_ce, *interface_ce; date_ce = zend_hash_str_find_ptr(CG(class_table), "datetime", sizeof("datetime")-1); if (date_ce == NULL) { @@ -407,6 +420,14 @@ static int register_timecop_classes() return SUCCESS; } + timezone_ce = zend_hash_str_find_ptr(CG(class_table), "datetimezone", sizeof("datetimezone")-1); + if (timezone_ce == NULL) { + /* DateTime must be initialized before */ + php_error_docref("https://github.com/hnw/php-timecop", E_WARNING, + "timecop couldn't find class %s.", "DateTimeZone"); + return SUCCESS; + } + immutable_ce = zend_hash_str_find_ptr(CG(class_table), "datetimeimmutable", sizeof("datetimeimmutable")-1); if (immutable_ce == NULL) { /* DateTimeImmutable must be initialized before */ @@ -426,6 +447,7 @@ static int register_timecop_classes() INIT_CLASS_ENTRY(ce, "Timecop", timecop_funcs_timecop); zend_register_internal_class(&ce); + TIMECOP_G(ce_DateTimeZone) = timezone_ce; TIMECOP_G(ce_DateTimeInterface) = interface_ce; /* replace DateTime */ @@ -703,7 +725,7 @@ static int fill_mktime_params(zval *fill_params, const char *date_function_name, for (i = from; i < MKTIME_NUM_ARGS; i++) { ZVAL_STRING(¶ms[0], formats[i]); - simple_call_function(date_function_name, &fill_params[i], 2, params); + call_php_function_with_params(date_function_name, &fill_params[i], 2, params); zval_ptr_dtor(¶ms[0]); } @@ -719,7 +741,7 @@ static int fill_mktime_params(zval *fill_params, const char *date_function_name, * if ($time === null || $time === false || $time === "") { * $time = "now"; * } - * $now = get_mock_time(); + * $now = get_mock_timeval(); * if ($timezone_obj) { * // save default timezone * $zonename = $timezone_obj->getName() @@ -734,17 +756,14 @@ static int fill_mktime_params(zval *fill_params, const char *date_function_name, * if ($fixed_sec === FALSE) { * return false; * } - * $dt1 = date_create($time, $timezone_obj); - * $dt2 = date_create($time, $timezone_obj); - * $usec1 = $dt1->format("u"); - * $usec2 = $dt2->format("u"); - * if ($usec1 === $usec2) { - * $fixed_usec = $usec1; - * } else { + * $fixed_usec = get_mock_fraction($time, $timezone_obj); + * if ($fixed_usec === -1) { * $fixed_usec = $now->usec; * } + * $dt = date_create($time, $timezone_obj); + * $dt->setTimestamp($fixed_sec); * $format = sprintf("Y-m-d H:i:s.%06d", $fixed_usec); - * $formatted_time = date($format, $fixed_sec); + * $formatted_time = $dt->format($format); * return $formatted_time; * } */ @@ -753,6 +772,7 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval *retval_ zval fixed_sec, orig_zonename; zval now_timestamp, str_now; tc_timeval now; + long fixed_usec; if (TIMECOP_G(timecop_mode) == TIMECOP_MODE_REALTIME) { ZVAL_FALSE(retval_time); @@ -767,21 +787,21 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval *retval_ time = &str_now; } - get_mock_time(&now, NULL); + get_mock_timeval(&now, NULL); if (timezone_obj && Z_TYPE_P(timezone_obj) == IS_OBJECT) { zval zonename; - zend_call_method_with_0_params(timezone_obj, Z_OBJCE_P(timezone_obj), NULL, "getname", &zonename); - zend_call_method_with_0_params(NULL, NULL, NULL, "date_default_timezone_get", &orig_zonename); - zend_call_method_with_1_params(NULL, NULL, NULL, "date_default_timezone_set", NULL, &zonename); + call_php_method_with_0_params(timezone_obj, Z_OBJCE_P(timezone_obj), "getname", &zonename); + call_php_function_with_0_params("date_default_timezone_get", &orig_zonename); + call_php_function_with_1_params("date_default_timezone_set", NULL, &zonename); zval_ptr_dtor(&zonename); } ZVAL_LONG(&now_timestamp, now.sec); - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "strtotime", &fixed_sec, time, &now_timestamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("strtotime"), &fixed_sec, time, &now_timestamp); if (timezone_obj && Z_TYPE_P(timezone_obj) == IS_OBJECT) { - zend_call_method_with_1_params(NULL, NULL, NULL, "date_default_timezone_set", NULL, &orig_zonename); + call_php_function_with_1_params("date_default_timezone_set", NULL, &orig_zonename); zval_ptr_dtor(&orig_zonename); } @@ -791,51 +811,31 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval *retval_ return -1; } - { - zval dt1, dt2, usec1, usec2; - zval null_val, u_str, format_str; - zend_long fixed_usec; - char buf[64]; + fixed_usec = get_mock_fraction(time, timezone_obj); + if (fixed_usec == -1) { + fixed_usec = now.usec; + } - if (timezone_obj == NULL) { - ZVAL_NULL(&null_val); - timezone_obj = &null_val; - } + { + zval dt; + zval format_str; - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date_create", &dt1, time, timezone_obj); - if (Z_TYPE(dt1) == IS_FALSE) { - ZVAL_FALSE(retval_time); - ZVAL_NULL(retval_timezone); - return -1; - } + char buf[64]; - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date_create", &dt2, time, timezone_obj); - if (Z_TYPE(dt2) == IS_FALSE) { - zval_ptr_dtor(&dt1); + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt, time, timezone_obj); + if (Z_TYPE(dt) == IS_FALSE) { ZVAL_FALSE(retval_time); ZVAL_NULL(retval_timezone); return -1; } - ZVAL_STRING(&u_str, "u"); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "format", &usec1, &u_str); - zend_call_method_with_1_params(&dt2, TIMECOP_G(ce_DateTime), NULL, "format", &usec2, &u_str); - convert_to_long(&usec1); - convert_to_long(&usec2); - if (Z_LVAL(usec1) == Z_LVAL(usec2)) { - fixed_usec = Z_LVAL(usec1); - } else { - fixed_usec = now.usec; - } - sprintf(buf, "Y-m-d H:i:s.%06ld", fixed_usec); ZVAL_STRING(&format_str, buf); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "settimestamp", NULL, &fixed_sec); - zend_call_method_with_0_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "gettimezone", retval_timezone); - zend_call_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), NULL, "format", retval_time, &format_str); - zval_ptr_dtor(&dt1); - zval_ptr_dtor(&dt2); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "settimestamp", NULL, &fixed_sec); + call_php_method_with_0_params(&dt, TIMECOP_G(ce_DateTime), "gettimezone", retval_timezone); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "format", retval_time, &format_str); + zval_ptr_dtor(&fixed_sec); zval_ptr_dtor(&format_str); - zval_ptr_dtor(&u_str); + zval_ptr_dtor(&dt); } if (time == &str_now) { @@ -844,6 +844,59 @@ static int get_formatted_mock_time(zval *time, zval *timezone_obj, zval *retval_ return 0; } +/* + * get_mock_fraction() + * + * pseudo code: + * + * function get_mock_fraction($time, $timezone_obj) { + * $dt1 = date_create($time, $timezone_obj); + * $dt2 = date_create($time, $timezone_obj); + * $usec1 = $dt1->format("u"); + * $usec2 = $dt2->format("u"); + * if ($usec1 === $usec2) { + * $fixed_usec = $usec1; + * } else { + * $fixed_usec = -1; + * } + * return $fixed_usec; + * } + */ +static long get_mock_fraction(zval *time, zval *timezone_obj TSRMLS_DC) +{ + zval dt1, dt2, usec1, usec2; + long fixed_usec; + zval u_str; + + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt1, time, timezone_obj); + if (Z_TYPE(dt1) == IS_FALSE) { + return -1; + } + + call_php_function_with_2_params(ORIG_FUNC_NAME("date_create"), &dt2, time, timezone_obj); + if (Z_TYPE(dt2) == IS_FALSE) { + zval_ptr_dtor(&dt1); + return -1; + } + ZVAL_STRING(&u_str, "u"); + call_php_method_with_1_params(&dt1, TIMECOP_G(ce_DateTime), "format", &usec1, &u_str); + call_php_method_with_1_params(&dt2, TIMECOP_G(ce_DateTime), "format", &usec2, &u_str); + convert_to_long(&usec1); + convert_to_long(&usec2); + + if (Z_LVAL(usec1) == Z_LVAL(usec2)) { + fixed_usec = Z_LVAL(usec1); + } else { + fixed_usec = -1; + } + zval_ptr_dtor(&dt1); + zval_ptr_dtor(&dt2); + zval_ptr_dtor(&u_str); + + return fixed_usec; +} + + static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *function_name, int index_to_fill_timestamp) { zval *params; @@ -863,7 +916,7 @@ static void _timecop_call_function(INTERNAL_FUNCTION_PARAMETERS, const char *fun param_count++; } - simple_call_function(function_name, return_value, param_count, params); + call_php_function_with_params(function_name, return_value, param_count, params); efree(params); } @@ -895,7 +948,7 @@ static void _timecop_call_mktime(INTERNAL_FUNCTION_PARAMETERS, const char *mktim php_error_docref(NULL, E_STRICT, "You should be using the time() function instead"); } - simple_call_function(mktime_function_name, return_value, param_count, params); + call_php_function_with_params(mktime_function_name, return_value, param_count, params); for (i = ZEND_NUM_ARGS(); i < MKTIME_NUM_ARGS; i++) { zval_ptr_dtor(¶ms[i]); @@ -913,7 +966,7 @@ PHP_FUNCTION(timecop_freeze) tc_timeval freezed_tv; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "O", &dt, TIMECOP_G(ce_DateTimeInterface)) != FAILURE) { - get_time_from_datetime(&freezed_tv, dt); + get_timeval_from_datetime(&freezed_tv, dt); } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "l", ×tamp) != FAILURE) { freezed_tv.sec = timestamp; freezed_tv.usec = 0; @@ -942,7 +995,7 @@ PHP_FUNCTION(timecop_travel) tc_timeval now, mock_tv; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "O", &dt, TIMECOP_G(ce_DateTimeInterface)) != FAILURE) { - get_time_from_datetime(&mock_tv, dt); + get_timeval_from_datetime(&mock_tv, dt); } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "l", ×tamp) != FAILURE) { mock_tv.sec = timestamp; mock_tv.usec = 0; @@ -978,7 +1031,7 @@ PHP_FUNCTION(timecop_scale) RETURN_FALSE; } get_current_time(&now); - get_mock_time(&mock_time, &now); + get_mock_timeval(&mock_time, &now); TIMECOP_G(timecop_mode) = TIMECOP_MODE_TRAVEL; TIMECOP_G(travel_origin) = now; tc_timeval_sub(&TIMECOP_G(travel_offset), &mock_time, &now); @@ -1095,19 +1148,8 @@ PHP_FUNCTION(timecop_gmstrftime) } /* }}} */ -static inline int get_mock_timestamp(zend_long *fixed_timestamp) -{ - tc_timeval tv; - int ret; - ret = get_mock_time(&tv, NULL); - if (ret == 0) { - *fixed_timestamp = tv.sec; - } - return ret; -} - /* - * get_mock_time(fixed, now) + * get_mock_timeval(fixed, now) * * * delta @@ -1123,7 +1165,7 @@ static inline int get_mock_timestamp(zend_long *fixed_timestamp) * delta = orig_time - travel_origin * traveled_time = travel_origin + travel_offset + delta * scaling_factor */ -static int get_mock_time(tc_timeval *fixed, const tc_timeval *now) +static int get_mock_timeval(tc_timeval *fixed, const tc_timeval *now) { if (TIMECOP_G(timecop_mode) == TIMECOP_MODE_FREEZE) { *fixed = TIMECOP_G(freezed_time); @@ -1149,14 +1191,25 @@ static int get_mock_time(tc_timeval *fixed, const tc_timeval *now) return 0; } -static int get_time_from_datetime(tc_timeval *tp, zval *dt) +static inline int get_mock_timestamp(zend_long *fixed_timestamp) +{ + tc_timeval tv; + int ret; + ret = get_mock_timeval(&tv, NULL); + if (ret == 0) { + *fixed_timestamp = tv.sec; + } + return ret; +} + +static int get_timeval_from_datetime(tc_timeval *tp, zval *dt) { zval sec, usec; zval u_str; - zend_call_method_with_0_params(dt, Z_OBJCE_P(dt), NULL, "gettimestamp", &sec); + call_php_method_with_0_params(dt, Z_OBJCE_P(dt), "gettimestamp", &sec); ZVAL_STRING(&u_str, "u"); - zend_call_method_with_1_params(dt, Z_OBJCE_P(dt), NULL, "format", &usec, &u_str); + call_php_method_with_1_params(dt, Z_OBJCE_P(dt), "format", &usec, &u_str); zval_ptr_dtor(&u_str); convert_to_long(&usec); @@ -1202,7 +1255,7 @@ static void _timecop_gettimeofday(INTERNAL_FUNCTION_PARAMETERS, int mode) RETURN_FALSE; } - if (get_mock_time(&fixed, NULL)) { + if (get_mock_timeval(&fixed, NULL)) { RETURN_FALSE; } @@ -1217,7 +1270,7 @@ static void _timecop_gettimeofday(INTERNAL_FUNCTION_PARAMETERS, int mode) /* offset */ ZVAL_STRING(&format, "Z"); - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date", &zv_offset, &format, ×tamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("date"), &zv_offset, &format, ×tamp); convert_to_long(&zv_offset); offset = Z_LVAL(zv_offset); zval_ptr_dtor(&zv_offset); @@ -1225,7 +1278,7 @@ static void _timecop_gettimeofday(INTERNAL_FUNCTION_PARAMETERS, int mode) /* is_dst */ ZVAL_STRING(&format, "I"); - timecop_call_orig_method_with_2_params(NULL, NULL, NULL, "date", &zv_dst, &format, ×tamp); + call_php_function_with_2_params(ORIG_FUNC_NAME("date"), &zv_dst, &format, ×tamp); convert_to_long(&zv_dst); is_dst = Z_LVAL(zv_dst); zval_ptr_dtor(&zv_dst); @@ -1268,67 +1321,180 @@ PHP_FUNCTION(timecop_unixtojd) } /* }}} */ -/* {{{ proto DateTime timecop_date_create([string time[, DateTimeZone object]]) - Returns new DateTime object initialized with traveled time */ -PHP_FUNCTION(timecop_date_create) +static void _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable) { - zval *params; - - params = (zval *) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval), 0); + zval *arg1 = NULL, *arg2 = NULL; + zend_class_entry *real_ce; - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|zz", &arg1, &arg2) == FAILURE) { RETURN_FALSE; } - object_init_ex(return_value, TIMECOP_G(ce_DateTime)); + if (immutable) { + real_ce = TIMECOP_G(ce_DateTimeImmutable); + } else { + real_ce = TIMECOP_G(ce_DateTime); + } - /* call TimecopDateTime::__construct() */ - timecop_call_constructor(return_value, TIMECOP_G(ce_TimecopDateTime), params, ZEND_NUM_ARGS()); + call_php_method_with_2_params(getThis(), real_ce, ORIG_FUNC_NAME("__construct"), NULL, arg1, arg2); +} - efree(params); +static inline void _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAMETERS, int immutable) +{ + _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, getThis(), immutable); } -/* }}} */ -/* {{{ proto DateTime timecop_date_create_from_format(string format, string time[, DateTimeZone object]) - Returns new DateTime object initialized with traveled time */ -PHP_FUNCTION(timecop_date_create_from_format) +static inline void _timecop_date_create(INTERNAL_FUNCTION_PARAMETERS, int immutable) { - zval *timezone_object = NULL; - char *time_str = NULL, *format_str = NULL; - size_t time_str_len = 0, format_str_len = 0; + _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, immutable); +} - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|O", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, php_date_get_timezone_ce()) == FAILURE) { +static void _timecop_datetime_constructor_ex(INTERNAL_FUNCTION_PARAMETERS, zval *obj, int immutable) +{ + zval orig_time, *orig_timezone = NULL; + zval fixed_time, fixed_timezone, dt, *arg1, *arg2; + char *orig_time_str = NULL; + size_t orig_time_len = 0; + const char *real_func; + zend_class_entry *real_ce; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sO!", &orig_time_str, &orig_time_len, &orig_timezone, TIMECOP_G(ce_DateTimeZone)) == FAILURE) { RETURN_FALSE; } - object_init_ex(return_value, TIMECOP_G(ce_DateTime)); + if (orig_time_str == NULL) { + ZVAL_NULL(&orig_time); + } else { + ZVAL_STRINGL(&orig_time, orig_time_str, orig_time_len); + } + if (immutable) { + real_func = ORIG_FUNC_NAME("date_create_immutable"); + real_ce = TIMECOP_G(ce_DateTimeImmutable); + } else { + real_func = ORIG_FUNC_NAME("date_create"); + real_ce = TIMECOP_G(ce_DateTime); + } - if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, 0)) { - RETURN_FALSE; + if (get_formatted_mock_time(&orig_time, orig_timezone, &fixed_time, &fixed_timezone TSRMLS_CC) == 0) { + arg1 = &fixed_time; + arg2 = &fixed_timezone; + } else { + arg1 = &orig_time; + arg2 = orig_timezone; + } + if (obj == NULL) { + call_php_function_with_2_params(real_func, return_value, arg1, arg2); + } else { + call_php_method_with_2_params(obj, real_ce, ORIG_FUNC_NAME("__construct"), NULL, arg1, arg2); } + zval_ptr_dtor(&orig_time); + zval_ptr_dtor(&fixed_time); + zval_ptr_dtor(&fixed_timezone); +} + +/* {{{ proto DateTime timecop_date_create([string time[, DateTimeZone object]]) + Returns new DateTime object initialized with traveled time */ +PHP_FUNCTION(timecop_date_create) +{ + _timecop_date_create(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ proto DateTimeImmutable timecop_date_create_immutable([string time[, DateTimeZone object]]) - Returns new DateTimeImmutable object initialized with traveled time */ -PHP_FUNCTION(timecop_date_create_immutable) +static void _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAMETERS, int immutable) { - zval *params; + zval *orig_timezone = NULL; + zval orig_format, orig_time, fixed_format, fixed_time, new_format, new_time; + zval dt, now_timestamp, tmp; + char *orig_format_str, *orig_time_str; + size_t orig_format_len, orig_time_len; + tc_timeval now; + char buf[64]; + const char *real_func; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|O!", &orig_format_str, &orig_format_len, &orig_time_str, &orig_time_len, &orig_timezone, TIMECOP_G(ce_DateTimeZone)) == FAILURE) { + RETURN_FALSE; + } - params = (zval *) safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval), 0); + ZVAL_STRINGL(&orig_format, orig_format_str, orig_format_len); + ZVAL_STRINGL(&orig_time, orig_time_str, orig_time_len); - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); + call_php_function_with_3_params(ORIG_FUNC_NAME("date_create_from_format"), &dt, &orig_format, &orig_time, orig_timezone); + if (Z_TYPE(dt) == IS_FALSE) { RETURN_FALSE; } - object_init_ex(return_value, TIMECOP_G(ce_DateTimeImmutable)); + get_mock_timeval(&now, NULL); - /* call TimecopDateTime::__construct() */ - timecop_call_constructor(return_value, TIMECOP_G(ce_TimecopDateTimeImmutable), params, ZEND_NUM_ARGS()); + ZVAL_LONG(&now_timestamp, now.sec); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "settimestamp", NULL, &now_timestamp); + sprintf(buf, "Y-m-d H:i:s.%06ld ", now.usec); + ZVAL_STRING(&tmp, buf); + call_php_method_with_1_params(&dt, TIMECOP_G(ce_DateTime), "format", &fixed_time, &tmp); + zval_ptr_dtor(&tmp); + + if (memchr(orig_format_str, '!', orig_format_len)) { + ZVAL_STRING(&fixed_format, "???\?-?\?-?? ??:??:??.??????"); + } else if (memchr(orig_format_str, 'g', orig_format_len) || + memchr(orig_format_str, 'h', orig_format_len) || + memchr(orig_format_str, 'G', orig_format_len) || + memchr(orig_format_str, 'H', orig_format_len) || + memchr(orig_format_str, 'i', orig_format_len) || + memchr(orig_format_str, 's', orig_format_len)) { + ZVAL_STRING(&fixed_format, "Y-m-d ??:??:??.??????"); + } else if (memchr(orig_format_str, 'Y', orig_format_len) || + memchr(orig_format_str, 'y', orig_format_len) || + memchr(orig_format_str, 'F', orig_format_len) || + memchr(orig_format_str, 'M', orig_format_len) || + memchr(orig_format_str, 'm', orig_format_len) || + memchr(orig_format_str, 'n', orig_format_len) || + memchr(orig_format_str, 'd', orig_format_len) || + memchr(orig_format_str, 'j', orig_format_len) || + memchr(orig_format_str, 'D', orig_format_len) || + memchr(orig_format_str, 'l', orig_format_len) || + memchr(orig_format_str, 'U', orig_format_len)) { + ZVAL_STRING(&fixed_format, "Y-m-d H:i:s.??????"); + } else { +#if PHP_VERSION_ID >= 70100 + ZVAL_STRING(&fixed_format, "Y-m-d H:i:s.u"); +#else + ZVAL_STRING(&fixed_format, "Y-m-d H:i:s.??????"); +#endif + } - efree(params); + ZVAL_STRING(&tmp, "%s %s"); + call_php_function_with_3_params("sprintf", &new_format, &tmp, &fixed_format, &orig_format); + call_php_function_with_3_params("sprintf", &new_time, &tmp, &fixed_time, &orig_time); + zval_ptr_dtor(&tmp); + + if (immutable) { + real_func = ORIG_FUNC_NAME("date_create_immutable_from_format"); + } else { + real_func = ORIG_FUNC_NAME("date_create_from_format"); + } + call_php_function_with_3_params(real_func, return_value, &new_format, &new_time, orig_timezone); + + zval_ptr_dtor(&dt); + zval_ptr_dtor(&orig_format); + zval_ptr_dtor(&orig_time); + zval_ptr_dtor(&fixed_format); + zval_ptr_dtor(&fixed_time); + zval_ptr_dtor(&new_format); + zval_ptr_dtor(&new_time); +} + +/* {{{ proto DateTime timecop_date_create_from_format(string format, string time[, DateTimeZone object]) + Returns new DateTime object initialized with traveled time */ +PHP_FUNCTION(timecop_date_create_from_format) +{ + _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto DateTimeImmutable timecop_date_create_immutable([string time[, DateTimeZone object]]) + Returns new DateTimeImmutable object initialized with traveled time */ +PHP_FUNCTION(timecop_date_create_immutable) +{ + _timecop_date_create(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1336,19 +1502,7 @@ PHP_FUNCTION(timecop_date_create_immutable) Returns new DateTimeImmutable object initialized with traveled time */ PHP_FUNCTION(timecop_date_create_immutable_from_format) { - zval *timezone_object = NULL; - char *time_str = NULL, *format_str = NULL; - size_t time_str_len = 0, format_str_len = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|O", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, php_date_get_timezone_ce()) == FAILURE) { - RETURN_FALSE; - } - - object_init_ex(return_value, TIMECOP_G(ce_DateTimeImmutable)); - - if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, 0)) { - RETURN_FALSE; - } + _timecop_date_create_from_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1356,43 +1510,7 @@ PHP_FUNCTION(timecop_date_create_immutable_from_format) Creates new TimecopDateTime object */ PHP_METHOD(TimecopDateTime, __construct) { - zval *params; - int nparams; - zval fixed_time, fixed_timezone; - zval *obj = getThis(); - - nparams = ZEND_NUM_ARGS(); - if (nparams < 2) { - nparams = 2; - } - params = (zval *)safe_emalloc(nparams, sizeof(zval), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - zend_throw_error(NULL, "Cannot get arguments for TimecopDateTime::__construct"); - RETURN_FALSE; - } - - zval *time = NULL, *timezone_obj = NULL; - if (ZEND_NUM_ARGS() >= 1) { - time = ¶ms[0]; - } - if (ZEND_NUM_ARGS() >= 2) { - timezone_obj = ¶ms[1]; - } - - nparams = ZEND_NUM_ARGS(); - if (get_formatted_mock_time(time, timezone_obj, &fixed_time, &fixed_timezone) == 0) { - ZVAL_COPY_VALUE(¶ms[0], &fixed_time); - ZVAL_COPY_VALUE(¶ms[1], &fixed_timezone); - nparams = 2; - } - - timecop_call_original_constructor(obj, TIMECOP_G(ce_DateTime), params, nparams); - - zval_ptr_dtor(&fixed_time); - zval_ptr_dtor(&fixed_timezone); - efree(params); + _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ @@ -1400,21 +1518,7 @@ PHP_METHOD(TimecopDateTime, __construct) Creates new TimecopOrigDateTime object */ PHP_METHOD(TimecopOrigDateTime, __construct) { - zval *params; - zval *obj = getThis(); - - params = (zval *)safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - zend_throw_error(NULL, "Cannot get arguments for TimecopOrigDateTime::__construct"); - RETURN_FALSE; - } - - /* call original DateTime::__construct() */ - timecop_call_original_constructor(obj, TIMECOP_G(ce_DateTime), params, ZEND_NUM_ARGS()); - - efree(params); + _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ @@ -1422,44 +1526,7 @@ PHP_METHOD(TimecopOrigDateTime, __construct) Creates new TimecopDateTimeImmutable object */ PHP_METHOD(TimecopDateTimeImmutable, __construct) { - zval *params; - int nparams; - zval fixed_time, fixed_timezone; - zval *obj = getThis(); - - nparams = ZEND_NUM_ARGS(); - if (nparams < 2) { - nparams = 2; - } - params = (zval *)safe_emalloc(nparams, sizeof(zval), 0); - - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - zend_throw_error(NULL, "Cannot get arguments for TimecopDateTimeImmutable::__construct"); - RETURN_FALSE; - } - - zval *time = NULL, *timezone_obj = NULL; - if (ZEND_NUM_ARGS() >= 1) { - time = ¶ms[0]; - } - if (ZEND_NUM_ARGS() >= 2) { - timezone_obj = ¶ms[1]; - } - - nparams = ZEND_NUM_ARGS(); - if (get_formatted_mock_time(time, timezone_obj, &fixed_time, &fixed_timezone) == 0) { - ZVAL_COPY_VALUE(¶ms[0], &fixed_time); - ZVAL_COPY_VALUE(¶ms[1], &fixed_timezone); - nparams = 2; - } - - /* call original DateTimeImmutable::__construct() */ - timecop_call_original_constructor(obj, TIMECOP_G(ce_DateTimeImmutable), params, nparams); - - zval_ptr_dtor(&fixed_time); - zval_ptr_dtor(&fixed_timezone); - efree(params); + _timecop_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1467,60 +1534,72 @@ PHP_METHOD(TimecopDateTimeImmutable, __construct) Creates new TimecopOrigDateTimeImmutable object */ PHP_METHOD(TimecopOrigDateTimeImmutable, __construct) { - zval *params; - zval *obj = getThis(); + _timecop_orig_datetime_constructor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ - params = (zval *)safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval), 0); +static inline zval* _call_php_method_with_0_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr) +{ + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr, 0, NULL, NULL); +} - if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), params) == FAILURE) { - efree(params); - zend_throw_error(NULL, "Cannot get arguments for TimecopOrigDateTimeImmutable::__construct"); - RETURN_FALSE; +static inline zval* _call_php_method_with_1_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, zval *arg1) +{ + int nparams = 1; + if (arg1 == NULL) { + nparams = 0; } - - /* call original DateTimeImmutable::__construct() */ - timecop_call_original_constructor(obj, TIMECOP_G(ce_DateTimeImmutable), params, ZEND_NUM_ARGS()); - - efree(params); + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr, nparams, arg1, NULL); } -/* }}} */ -static inline void timecop_call_original_constructor(zval *obj, zend_class_entry *ce, zval *params, int param_count) { - timecop_call_constructor_ex(obj, ce, params, param_count, 1); +static inline zval* _call_php_method_with_2_params(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, zval *arg1, zval *arg2) +{ + int nparams = 2; + if (arg1 == NULL) { + nparams = 0; + } else if (arg2 == NULL) { + nparams = 1; + } + return _call_php_method(object_pp, obj_ce, method_name, retval_ptr, nparams, arg1, arg2); } -static inline void timecop_call_constructor(zval *obj, zend_class_entry *ce, zval *params, int param_count) { - timecop_call_constructor_ex(obj, ce, params, param_count, 0); + +static inline zval* _call_php_method(zval *object_pp, zend_class_entry *obj_ce, const char *method_name, zval *retval_ptr, int param_count, zval* arg1, zval* arg2) +{ + return zend_call_method(object_pp, obj_ce, NULL, method_name, strlen(method_name), retval_ptr, param_count, arg1, arg2); } -static void timecop_call_constructor_ex(zval *obj, zend_class_entry *ce, zval *params, int param_count, int call_original) { - zval *arg1 = NULL, *arg2 = NULL; - const char *func_name; - size_t func_name_len; +static inline void _call_php_function_with_0_params(const char *function_name, zval *retval_ptr) +{ + _call_php_method_with_0_params(NULL, NULL, function_name, retval_ptr); +} - if (param_count > 2) { - zend_error(E_ERROR, "INTERNAL ERROR: too many parameters for constructor."); - return; - } +static inline void _call_php_function_with_1_params(const char *function_name, zval *retval_ptr, zval *arg1) +{ + _call_php_method_with_1_params(NULL, NULL, function_name, retval_ptr, arg1); +} - if (param_count == 1) { - arg1 = ¶ms[0]; - } else if (param_count == 2) { - arg1 = ¶ms[0]; - arg2 = ¶ms[1]; - } +static inline void _call_php_function_with_2_params(const char *function_name, zval *retval_ptr, zval *arg1, zval *arg2) +{ + _call_php_method_with_2_params(NULL, NULL, function_name, retval_ptr, arg1, arg2); +} - if (call_original) { - func_name = ORIG_FUNC_NAME("__construct"); - func_name_len = ORIG_FUNC_NAME_SIZEOF("__construct")-1; +static inline void _call_php_function_with_3_params(const char *function_name, zval *retval_ptr, zval *arg1, zval *arg2, zval *arg3) +{ + if (arg3 == NULL) { + _call_php_function_with_2_params(function_name, retval_ptr, arg1, arg2); } else { - func_name = "__construct"; - func_name_len = sizeof("__construct")-1; + zval params[3]; + ZVAL_COPY(¶ms[0], arg1); + ZVAL_COPY(¶ms[1], arg2); + ZVAL_COPY(¶ms[2], arg3); + call_php_function_with_params(function_name, retval_ptr, 3, params); + zval_ptr_dtor(¶ms[0]); + zval_ptr_dtor(¶ms[1]); + zval_ptr_dtor(¶ms[2]); } - - zend_call_method(obj, ce, NULL, func_name, func_name_len, NULL, param_count, arg1, arg2); } -static void simple_call_function(const char *function_name, zval *retval_ptr, uint32_t param_count, zval *params) +static inline void _call_php_function_with_params(const char *function_name, zval *retval_ptr, uint32_t param_count, zval *params) { zval callable; ZVAL_STRING(&callable, function_name);