From e7746f487cb9cca685ffb1b3d7dccc5554b618a4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 20 Jul 2018 22:03:00 +0200 Subject: php: Added nanosecond support for Timestamp (#3972) * php: Added nanosecond support for Timestamp * php: Fixed compatibility test --- php/ext/google/protobuf/message.c | 102 +++++++++++++++++++++++----------- php/ext/google/protobuf/protobuf.h | 4 ++ php/src/Google/Protobuf/Timestamp.php | 9 +-- php/tests/compatibility_test.sh | 1 + php/tests/memory_leak_test.php | 2 +- php/tests/well_known_test.php | 3 +- 6 files changed, 83 insertions(+), 38 deletions(-) diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c index 9363191d..76d85ab0 100644 --- a/php/ext/google/protobuf/message.c +++ b/php/ext/google/protobuf/message.c @@ -30,6 +30,7 @@ #include #include +#include #include "protobuf.h" #include "utf8.h" @@ -1249,28 +1250,62 @@ PHP_METHOD(Timestamp, fromDateTime) { return; } - // Get timestamp from Datetime object. - zval retval; - zval function_name; - int64_t timestamp; + int64_t timestamp_seconds; + { + zval retval; + zval function_name; #if PHP_MAJOR_VERSION < 7 - INIT_ZVAL(retval); - INIT_ZVAL(function_name); + INIT_ZVAL(retval); + INIT_ZVAL(function_name); #endif - PHP_PROTO_ZVAL_STRING(&function_name, "date_timestamp_get", 1); + PHP_PROTO_ZVAL_STRING(&function_name, "date_timestamp_get", 1); - if (call_user_function(EG(function_table), NULL, &function_name, &retval, 1, - ZVAL_PTR_TO_CACHED_PTR(datetime) TSRMLS_CC) == FAILURE) { - zend_error(E_ERROR, "Cannot get timestamp from DateTime."); - return; + if (call_user_function(EG(function_table), NULL, &function_name, &retval, 1, + ZVAL_PTR_TO_CACHED_PTR(datetime) TSRMLS_CC) == FAILURE) { + zend_error(E_ERROR, "Cannot get timestamp from DateTime."); + return; + } + + protobuf_convert_to_int64(&retval, ×tamp_seconds); + + zval_dtor(&retval); + zval_dtor(&function_name); } - protobuf_convert_to_int64(&retval, ×tamp); + int64_t timestamp_micros; + { + zval retval; + zval function_name; + zval format_string; - zval_dtor(&retval); - zval_dtor(&function_name); +#if PHP_MAJOR_VERSION < 7 + INIT_ZVAL(retval); + INIT_ZVAL(function_name); + INIT_ZVAL(format_string); +#endif + + PHP_PROTO_ZVAL_STRING(&function_name, "date_format", 1); + PHP_PROTO_ZVAL_STRING(&format_string, "u", 1); + + CACHED_VALUE params[2] = { + ZVAL_PTR_TO_CACHED_VALUE(datetime), + ZVAL_TO_CACHED_VALUE(format_string), + }; + + if (call_user_function(EG(function_table), NULL, &function_name, &retval, + ARRAY_SIZE(params), params TSRMLS_CC) == FAILURE) { + zend_error(E_ERROR, "Cannot format DateTime."); + return; + } + + protobuf_convert_to_int64(&retval, ×tamp_micros); + + zval_dtor(&retval); + zval_dtor(&function_name); + zval_dtor(&format_string); + } // Set seconds MessageHeader* self = UNBOX(MessageHeader, getThis()); @@ -1278,13 +1313,13 @@ PHP_METHOD(Timestamp, fromDateTime) { upb_msgdef_ntofz(self->descriptor->msgdef, "seconds"); void* storage = message_data(self); void* memory = slot_memory(self->descriptor->layout, storage, field); - *(int64_t*)memory = timestamp; + *(int64_t*)memory = timestamp_seconds; // Set nanos field = upb_msgdef_ntofz(self->descriptor->msgdef, "nanos"); storage = message_data(self); memory = slot_memory(self->descriptor->layout, storage, field); - *(int32_t*)memory = 0; + *(int32_t*)memory = timestamp_micros * 1000; RETURN_NULL(); } @@ -1303,38 +1338,41 @@ PHP_METHOD(Timestamp, toDateTime) { memory = slot_memory(self->descriptor->layout, storage, field); int32_t nanos = *(int32_t*)memory; - // Get formated time string. - char formated_time[50]; - time_t raw_time = seconds; - struct tm *utc_time = gmtime(&raw_time); - strftime(formated_time, sizeof(formated_time), "%Y-%m-%dT%H:%M:%SUTC", - utc_time); + // Get formatted time string. + char formatted_time[32]; + snprintf(formatted_time, sizeof(formatted_time), "%" PRId64 ".%06" PRId32, + seconds, nanos / 1000); // Create Datetime object. zval datetime; - zval formated_time_php; zval function_name; - int64_t timestamp = 0; + zval format_string; + zval formatted_time_php; #if PHP_MAJOR_VERSION < 7 INIT_ZVAL(function_name); - INIT_ZVAL(formated_time_php); + INIT_ZVAL(format_string); + INIT_ZVAL(formatted_time_php); #endif - PHP_PROTO_ZVAL_STRING(&function_name, "date_create", 1); - PHP_PROTO_ZVAL_STRING(&formated_time_php, formated_time, 1); + PHP_PROTO_ZVAL_STRING(&function_name, "date_create_from_format", 1); + PHP_PROTO_ZVAL_STRING(&format_string, "U.u", 1); + PHP_PROTO_ZVAL_STRING(&formatted_time_php, formatted_time, 1); - CACHED_VALUE params[1] = {ZVAL_TO_CACHED_VALUE(formated_time_php)}; + CACHED_VALUE params[2] = { + ZVAL_TO_CACHED_VALUE(format_string), + ZVAL_TO_CACHED_VALUE(formatted_time_php), + }; - if (call_user_function(EG(function_table), NULL, - &function_name, &datetime, 1, - params TSRMLS_CC) == FAILURE) { + if (call_user_function(EG(function_table), NULL, &function_name, &datetime, + ARRAY_SIZE(params), params TSRMLS_CC) == FAILURE) { zend_error(E_ERROR, "Cannot create DateTime."); return; } - zval_dtor(&formated_time_php); zval_dtor(&function_name); + zval_dtor(&format_string); + zval_dtor(&formatted_time_php); #if PHP_MAJOR_VERSION < 7 zval* datetime_ptr = &datetime; diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h index 20035ab7..3ef4c84b 100644 --- a/php/ext/google/protobuf/protobuf.h +++ b/php/ext/google/protobuf/protobuf.h @@ -42,6 +42,10 @@ #define MAX_LENGTH_OF_INT64 20 #define SIZEOF_INT64 8 +/* From Chromium. */ +#define ARRAY_SIZE(x) \ + ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + // ----------------------------------------------------------------------------- // PHP7 Wrappers // ---------------------------------------------------------------------------- diff --git a/php/src/Google/Protobuf/Timestamp.php b/php/src/Google/Protobuf/Timestamp.php index a793c7e3..6d26f6c5 100644 --- a/php/src/Google/Protobuf/Timestamp.php +++ b/php/src/Google/Protobuf/Timestamp.php @@ -182,18 +182,19 @@ class Timestamp extends \Google\Protobuf\Internal\Message */ public function fromDateTime(\DateTime $datetime) { - $this->seconds = $datetime->format('U'); - $this->nanos = 0; + $this->seconds = $datetime->getTimestamp(); + $this->nanos = 1000 * $datetime->format('u'); } /** - * Converts Timestamp to PHP DateTime. Nano second is ignored. + * Converts Timestamp to PHP DateTime. * * @return \DateTime $datetime */ public function toDateTime() { - return \DateTime::createFromFormat('U', $this->seconds); + $time = sprintf('%s.%06d', $this->seconds, $this->nanos / 1000); + return \DateTime::createFromFormat('U.u', $time); } } diff --git a/php/tests/compatibility_test.sh b/php/tests/compatibility_test.sh index b377d85c..c4d6325d 100755 --- a/php/tests/compatibility_test.sh +++ b/php/tests/compatibility_test.sh @@ -124,6 +124,7 @@ sed -i.bak '/php_implementation_test.php/d' phpunit.xml sed -i.bak '/generated_phpdoc_test.php/d' phpunit.xml sed -i.bak 's/generated_phpdoc_test.php//g' tests/test.sh sed -i.bak '/memory_leak_test.php/d' tests/test.sh +sed -i.bak '/^ public function testTimestamp()$/,/^ }$/d' tests/well_known_test.php for t in "${tests[@]}" do remove_error_test tests/$t diff --git a/php/tests/memory_leak_test.php b/php/tests/memory_leak_test.php index 4e3874b7..f3bcb963 100644 --- a/php/tests/memory_leak_test.php +++ b/php/tests/memory_leak_test.php @@ -152,7 +152,7 @@ date_default_timezone_set('UTC'); $from = new DateTime('2011-01-01T15:03:01.012345UTC'); $timestamp->fromDateTime($from); assert($from->format('U') == $timestamp->getSeconds()); -assert(0 == $timestamp->getNanos()); +assert(1000 * $from->format('u') == $timestamp->getNanos()); $to = $timestamp->toDateTime(); assert(\DateTime::class == get_class($to)); diff --git a/php/tests/well_known_test.php b/php/tests/well_known_test.php index 1e8c4f42..9f2661fa 100644 --- a/php/tests/well_known_test.php +++ b/php/tests/well_known_test.php @@ -312,11 +312,12 @@ class WellKnownTest extends TestBase { $from = new DateTime('2011-01-01T15:03:01.012345UTC'); $timestamp->fromDateTime($from); $this->assertEquals($from->format('U'), $timestamp->getSeconds()); - $this->assertSame(0, $timestamp->getNanos()); + $this->assertEquals(1000 * $from->format('u'), $timestamp->getNanos()); $to = $timestamp->toDateTime(); $this->assertSame(\DateTime::class, get_class($to)); $this->assertSame($from->format('U'), $to->format('U')); + $this->assertSame($from->format('u'), $to->format('u')); } public function testType() -- cgit v1.2.3