Browse Source

php: Added nanosecond support for Timestamp (#3972)

* php: Added nanosecond support for Timestamp

* php: Fixed compatibility test
Leonard Hecker 7 years ago
parent
commit
e7746f487c

+ 70 - 32
php/ext/google/protobuf/message.c

@@ -30,6 +30,7 @@
 
 
 #include <php.h>
 #include <php.h>
 #include <stdlib.h>
 #include <stdlib.h>
+#include <inttypes.h>
 
 
 #include "protobuf.h"
 #include "protobuf.h"
 #include "utf8.h"
 #include "utf8.h"
@@ -1249,28 +1250,62 @@ PHP_METHOD(Timestamp, fromDateTime) {
     return;
     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
 #if PHP_MAJOR_VERSION < 7
-  INIT_ZVAL(retval);
-  INIT_ZVAL(function_name);
+    INIT_ZVAL(retval);
+    INIT_ZVAL(function_name);
 #endif
 #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, &timestamp_seconds);
+
+    zval_dtor(&retval);
+    zval_dtor(&function_name);
   }
   }
 
 
-  protobuf_convert_to_int64(&retval, &timestamp);
+  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, &timestamp_micros);
+
+    zval_dtor(&retval);
+    zval_dtor(&function_name);
+    zval_dtor(&format_string);
+  }
 
 
   // Set seconds
   // Set seconds
   MessageHeader* self = UNBOX(MessageHeader, getThis());
   MessageHeader* self = UNBOX(MessageHeader, getThis());
@@ -1278,13 +1313,13 @@ PHP_METHOD(Timestamp, fromDateTime) {
       upb_msgdef_ntofz(self->descriptor->msgdef, "seconds");
       upb_msgdef_ntofz(self->descriptor->msgdef, "seconds");
   void* storage = message_data(self);
   void* storage = message_data(self);
   void* memory = slot_memory(self->descriptor->layout, storage, field);
   void* memory = slot_memory(self->descriptor->layout, storage, field);
-  *(int64_t*)memory = timestamp;
+  *(int64_t*)memory = timestamp_seconds;
 
 
   // Set nanos
   // Set nanos
   field = upb_msgdef_ntofz(self->descriptor->msgdef, "nanos");
   field = upb_msgdef_ntofz(self->descriptor->msgdef, "nanos");
   storage = message_data(self);
   storage = message_data(self);
   memory = slot_memory(self->descriptor->layout, storage, field);
   memory = slot_memory(self->descriptor->layout, storage, field);
-  *(int32_t*)memory = 0;
+  *(int32_t*)memory = timestamp_micros * 1000;
 
 
   RETURN_NULL();
   RETURN_NULL();
 }
 }
@@ -1303,38 +1338,41 @@ PHP_METHOD(Timestamp, toDateTime) {
   memory = slot_memory(self->descriptor->layout, storage, field);
   memory = slot_memory(self->descriptor->layout, storage, field);
   int32_t nanos = *(int32_t*)memory;
   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.
   // Create Datetime object.
   zval datetime;
   zval datetime;
-  zval formated_time_php;
   zval function_name;
   zval function_name;
-  int64_t timestamp = 0;
+  zval format_string;
+  zval formatted_time_php;
 
 
 #if PHP_MAJOR_VERSION < 7
 #if PHP_MAJOR_VERSION < 7
   INIT_ZVAL(function_name);
   INIT_ZVAL(function_name);
-  INIT_ZVAL(formated_time_php);
+  INIT_ZVAL(format_string);
+  INIT_ZVAL(formatted_time_php);
 #endif
 #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.");
     zend_error(E_ERROR, "Cannot create DateTime.");
     return;
     return;
   }
   }
 
 
-  zval_dtor(&formated_time_php);
   zval_dtor(&function_name);
   zval_dtor(&function_name);
+  zval_dtor(&format_string);
+  zval_dtor(&formatted_time_php);
 
 
 #if PHP_MAJOR_VERSION < 7
 #if PHP_MAJOR_VERSION < 7
   zval* datetime_ptr = &datetime;
   zval* datetime_ptr = &datetime;

+ 4 - 0
php/ext/google/protobuf/protobuf.h

@@ -42,6 +42,10 @@
 #define MAX_LENGTH_OF_INT64 20
 #define MAX_LENGTH_OF_INT64 20
 #define SIZEOF_INT64 8
 #define SIZEOF_INT64 8
 
 
+/* From Chromium. */
+#define ARRAY_SIZE(x) \
+    ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 // PHP7 Wrappers
 // PHP7 Wrappers
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------

+ 5 - 4
php/src/Google/Protobuf/Timestamp.php

@@ -182,18 +182,19 @@ class Timestamp extends \Google\Protobuf\Internal\Message
      */
      */
     public function fromDateTime(\DateTime $datetime)
     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
      * @return \DateTime $datetime
      */
      */
     public function toDateTime()
     public function toDateTime()
     {
     {
-        return \DateTime::createFromFormat('U', $this->seconds);
+        $time = sprintf('%s.%06d', $this->seconds, $this->nanos / 1000);
+        return \DateTime::createFromFormat('U.u', $time);
     }
     }
 }
 }
 
 

+ 1 - 0
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 '/generated_phpdoc_test.php/d' phpunit.xml
 sed -i.bak 's/generated_phpdoc_test.php//g' tests/test.sh
 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 '/memory_leak_test.php/d' tests/test.sh
+sed -i.bak '/^    public function testTimestamp()$/,/^    }$/d' tests/well_known_test.php
 for t in "${tests[@]}"
 for t in "${tests[@]}"
 do
 do
   remove_error_test tests/$t
   remove_error_test tests/$t

+ 1 - 1
php/tests/memory_leak_test.php

@@ -152,7 +152,7 @@ date_default_timezone_set('UTC');
 $from = new DateTime('2011-01-01T15:03:01.012345UTC');
 $from = new DateTime('2011-01-01T15:03:01.012345UTC');
 $timestamp->fromDateTime($from);
 $timestamp->fromDateTime($from);
 assert($from->format('U') == $timestamp->getSeconds());
 assert($from->format('U') == $timestamp->getSeconds());
-assert(0 == $timestamp->getNanos());
+assert(1000 * $from->format('u') == $timestamp->getNanos());
 
 
 $to = $timestamp->toDateTime();
 $to = $timestamp->toDateTime();
 assert(\DateTime::class == get_class($to));
 assert(\DateTime::class == get_class($to));

+ 2 - 1
php/tests/well_known_test.php

@@ -312,11 +312,12 @@ class WellKnownTest extends TestBase {
         $from = new DateTime('2011-01-01T15:03:01.012345UTC');
         $from = new DateTime('2011-01-01T15:03:01.012345UTC');
         $timestamp->fromDateTime($from);
         $timestamp->fromDateTime($from);
         $this->assertEquals($from->format('U'), $timestamp->getSeconds());
         $this->assertEquals($from->format('U'), $timestamp->getSeconds());
-        $this->assertSame(0, $timestamp->getNanos());
+        $this->assertEquals(1000 * $from->format('u'), $timestamp->getNanos());
 
 
         $to = $timestamp->toDateTime();
         $to = $timestamp->toDateTime();
         $this->assertSame(\DateTime::class, get_class($to));
         $this->assertSame(\DateTime::class, get_class($to));
         $this->assertSame($from->format('U'), $to->format('U'));
         $this->assertSame($from->format('U'), $to->format('U'));
+        $this->assertSame($from->format('u'), $to->format('u'));
     }
     }
 
 
     public function testType()
     public function testType()