improve timestamp support
authorMichael Wallner <mike@php.net>
Tue, 7 Dec 2021 14:30:17 +0000 (15:30 +0100)
committerMichael Wallner <mike@php.net>
Tue, 7 Dec 2021 15:01:41 +0000 (16:01 +0100)
.gitignore
ion.c
ion.stub.php
ion_arginfo.h
ion_private.h
tests/Timestamp.phpt [new file with mode: 0644]

index ae434fef9765cfe36e8bd262a2975ed9e158118e..6c67a1e0ccd85ecc9a357a75d2f35e917e6905ae 100644 (file)
@@ -38,4 +38,5 @@ tests/**/*.log
 tests/**/*.sh
 tests/**/*.db
 tests/**/*.mem
+tests/Timestamp/
 tmp-php.ini
diff --git a/ion.c b/ion.c
index 73eee24fbc3d9576c8c25f653e66cb03703aab2f..1b8340d192c46dc59361844b9b901571dcd11df1 100644 (file)
--- a/ion.c
+++ b/ion.c
@@ -88,20 +88,24 @@ ZEND_METHOD(ion_Symbol, equals)
 }
 ZEND_METHOD(ion_Timestamp, __construct)
 {
-       zend_long precision;
-       zend_string *fmt = NULL, *dt = NULL;
-       zval *tz = NULL;
        php_ion_timestamp *obj = php_ion_obj(timestamp, Z_OBJ_P(ZEND_THIS));
        PTR_CHECK(obj);
 
+       zend_long precision;
+       zend_object *precision_obj;
+       zend_string *fmt = NULL, *dt = NULL;
+       zval *tz = NULL;
        ZEND_PARSE_PARAMETERS_START(1, 4)
-               Z_PARAM_LONG(precision)
+               Z_PARAM_OBJ_OF_CLASS_OR_LONG(precision_obj, ce_Timestamp_Precision, precision)
                Z_PARAM_OPTIONAL
                Z_PARAM_STR_OR_NULL(fmt)
-               Z_PARAM_STR(dt)
+               Z_PARAM_STR_OR_NULL(dt)
                Z_PARAM_ZVAL(tz)
        ZEND_PARSE_PARAMETERS_END();
 
+       if (precision_obj) {
+               precision = Z_LVAL_P(zend_enum_fetch_case_value(precision_obj));
+       }
        php_ion_timestamp_ctor(obj, precision, fmt, dt, tz);
 }
 ZEND_METHOD(ion_Timestamp, __toString)
@@ -1472,6 +1476,7 @@ PHP_MINIT_FUNCTION(ion)
        php_ion_register(decimal, Decimal);
        php_ion_register(decimal_ctx, Decimal_Context);
        php_ion_register(timestamp, Timestamp, php_date_get_date_ce());
+       ce_Timestamp_Precision = register_class_ion_Timestamp_Precision();
        php_ion_register(catalog, Catalog);
 
        ce_Reader = register_class_ion_Reader(spl_ce_RecursiveIterator);
index 22504c79231deb772ef237b4909d083920bfb651..cbddd8db5eaada5f61be79f9f74c646453af09aa 100644 (file)
@@ -122,12 +122,24 @@ class Decimal {
     public function toInt() : int {}
 }
 
+namespace ion\Timestamp;
+enum Precision : int {
+    case Year           = 0x1;
+    case Month          = 0x1|0x2;
+    case Day            = 0x1|0x2|0x4;
+    case Min            = 0x1|0x2|0x4|0x10;
+    case Sec            = 0x1|0x2|0x4|0x10|0x20;
+    case Frac           = 0x1|0x2|0x4|0x10|0x20|0x40;
+}
 namespace ion;
 class Timestamp extends \DateTime {
+    public readonly int $precision;
+    public readonly string $format;
+
     public function __construct(
-        public readonly int $precision,
-        public readonly string $format = "c",
-        string $datetime = "now",
+        Timestamp\Precision|int $precision,
+        ?string $format = null,
+        ?string $datetime = null,
         ?\DateTimeZone $timezone = null,
     ) {}
 
index d11e88a68eb1acbe638e6de09e93c698af22d059..1b600b53408d462fb9674465a66844ee3fd10744 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 9972b27bc2f7f20e59aae26777e1a674f1606c11 */
+ * Stub hash: 20996b59177d52516ad45582152909be017ba39d */
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ion_serialize, 0, 1, IS_STRING, 0)
        ZEND_ARG_TYPE_INFO(0, data, IS_MIXED, 0)
@@ -51,9 +51,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ion_Decimal_toInt, 0, 0, I
 ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ion_Timestamp___construct, 0, 0, 1)
-       ZEND_ARG_TYPE_INFO(0, precision, IS_LONG, 0)
-       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, format, IS_STRING, 0, "\"c\"")
-       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, datetime, IS_STRING, 0, "\"now\"")
+       ZEND_ARG_OBJ_TYPE_MASK(0, precision, ion\\Timestamp\\Precision, MAY_BE_LONG, NULL)
+       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, format, IS_STRING, 1, "null")
+       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, datetime, IS_STRING, 1, "null")
        ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timezone, DateTimeZone, 1, "null")
 ZEND_END_ARG_INFO()
 
@@ -595,6 +595,11 @@ static const zend_function_entry class_ion_Decimal_methods[] = {
 };
 
 
+static const zend_function_entry class_ion_Timestamp_Precision_methods[] = {
+       ZEND_FE_END
+};
+
+
 static const zend_function_entry class_ion_Timestamp_methods[] = {
        ZEND_ME(ion_Timestamp, __construct, arginfo_class_ion_Timestamp___construct, ZEND_ACC_PUBLIC)
        ZEND_ME(ion_Timestamp, __toString, arginfo_class_ion_Timestamp___toString, ZEND_ACC_PUBLIC)
@@ -1157,6 +1162,37 @@ static zend_class_entry *register_class_ion_Decimal(void)
        return class_entry;
 }
 
+static zend_class_entry *register_class_ion_Timestamp_Precision(void)
+{
+       zend_class_entry *class_entry = zend_register_internal_enum("ion\\Timestamp\\Precision", IS_LONG, class_ion_Timestamp_Precision_methods);
+
+       zval enum_case_Year_value;
+       ZVAL_LONG(&enum_case_Year_value, 1);
+       zend_enum_add_case_cstr(class_entry, "Year", &enum_case_Year_value);
+
+       zval enum_case_Month_value;
+       ZVAL_LONG(&enum_case_Month_value, 3);
+       zend_enum_add_case_cstr(class_entry, "Month", &enum_case_Month_value);
+
+       zval enum_case_Day_value;
+       ZVAL_LONG(&enum_case_Day_value, 7);
+       zend_enum_add_case_cstr(class_entry, "Day", &enum_case_Day_value);
+
+       zval enum_case_Min_value;
+       ZVAL_LONG(&enum_case_Min_value, 23);
+       zend_enum_add_case_cstr(class_entry, "Min", &enum_case_Min_value);
+
+       zval enum_case_Sec_value;
+       ZVAL_LONG(&enum_case_Sec_value, 55);
+       zend_enum_add_case_cstr(class_entry, "Sec", &enum_case_Sec_value);
+
+       zval enum_case_Frac_value;
+       ZVAL_LONG(&enum_case_Frac_value, 119);
+       zend_enum_add_case_cstr(class_entry, "Frac", &enum_case_Frac_value);
+
+       return class_entry;
+}
+
 static zend_class_entry *register_class_ion_Timestamp(zend_class_entry *class_entry_DateTime)
 {
        zend_class_entry ce, *class_entry;
index 1ac49d9a0d03225b4da35bf832797b6a158ebdb1..5b6a4614510e435148fbc35188aa70976d279687 100644 (file)
@@ -151,6 +151,7 @@ static zend_class_entry
        *ce_Symbol_System_SID,
        *ce_Symbol_Table,
        *ce_Timestamp,
+       *ce_Timestamp_Precision,
        *ce_Type,
        *ce_Writer,
        *ce_Writer_Options,
@@ -430,26 +431,6 @@ static inline void php_ion_decimal_dtor(php_ion_decimal *obj)
 
 typedef php_date_obj php_ion_timestamp;
 
-static inline void php_ion_timestamp_ctor(php_ion_timestamp *obj, zend_long precision, zend_string *fmt, zend_string *dt, zval *tz)
-{
-       if (!obj->time) {
-               php_date_initialize(obj, dt ? dt->val : "now", dt ? dt->len : 3, fmt ? fmt->val : NULL, tz, PHP_DATE_INIT_CTOR);
-       }
-       zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("precision"), precision);
-       if (fmt) {
-               zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("format"), fmt);
-       } else {
-               zend_update_property_stringl(obj->std.ce, &obj->std, ZEND_STRL("format"), ZEND_STRL("c"));
-       }
-}
-
-static inline void php_ion_timestamp_dtor(php_ion_timestamp *obj)
-{
-       if (obj->time) {
-               timelib_time_dtor(obj->time);
-       }
-}
-
 static inline zend_long php_usec_from_ion(const decQuad *frac, decContext *ctx)
 {
        decQuad microsecs, result;
@@ -463,6 +444,26 @@ static inline decQuad *ion_ts_frac_from_usec(decQuad *frac, zend_long usec, decC
        return decQuadDivide(frac, decQuadFromInt32(&us, usec), decQuadFromInt32(&microsecs, 1000000), ctx);
 }
 
+static inline zend_string *php_dt_format_from_precision(uint8_t precision)
+{
+       switch (precision) {
+       case ION_TS_FRAC:
+               return zend_string_init(ZEND_STRL("c"), 0);
+       case ION_TS_SEC:
+               return zend_string_init(ZEND_STRL("Y-m-d\\TH:i:sP"), 0);
+       case ION_TS_MIN:
+               return zend_string_init(ZEND_STRL("Y-m-d\\TH:iP"), 0);
+       case ION_TS_DAY:
+               return zend_string_init(ZEND_STRL("Y-m-d\\T"), 0);
+       case ION_TS_MONTH:
+               return zend_string_init(ZEND_STRL("Y-m\\T"), 0);
+       case ION_TS_YEAR:
+               return zend_string_init(ZEND_STRL("Y\\T"), 0);
+       default:
+               return zend_string_init(ZEND_STRL("c"), 0);
+       }
+}
+
 static inline timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContext *ctx, zend_string **fmt)
 {
        timelib_time *time = timelib_time_ctor();
@@ -470,34 +471,30 @@ static inline timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContex
        switch (ts->precision) {
        case ION_TS_FRAC:
                time->us = php_usec_from_ion(&ts->fraction, ctx);
-               if (fmt) *fmt = zend_string_init(ZEND_STRL("c"), 0);
                /* fallthrough */
        case ION_TS_SEC:
                time->s = ts->seconds;
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\TH:i:sP"), 0);
                /* fallthrough */
        case ION_TS_MIN:
                time->i = ts->minutes;
                time->h = ts->hours;
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\TH:iP"), 0);
                /* fallthrough */
        case ION_TS_DAY:
                time->d = ts->day;
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m-d\\T"), 0);
                /* fallthrough */
        case ION_TS_MONTH:
                time->m = ts->month;
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y-m\\T"), 0);
                /* fallthrough */
        case ION_TS_YEAR:
                time->y = ts->year;
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("Y\\T"), 0);
                /* fallthrough */
        default:
-               if (fmt && !*fmt) *fmt = zend_string_init(ZEND_STRL("c"), 0);
                time->z = ts->tz_offset * 60;
        }
 
+       if (fmt) {
+               fmt = php_dt_format_from_precision(ts->precision);
+       }
        return time;
 }
 
@@ -538,6 +535,25 @@ static inline ION_TIMESTAMP *ion_timestamp_from_php(ION_TIMESTAMP *buf, php_ion_
        return buf;
 }
 
+static inline void php_ion_timestamp_ctor(php_ion_timestamp *obj, zend_long precision, zend_string *fmt, zend_string *dt, zval *tz)
+{
+       if (!obj->time) {
+               php_date_initialize(obj, dt ? dt->val : "", dt ? dt->len : 0, fmt ? fmt->val : NULL, tz, PHP_DATE_INIT_CTOR);
+       }
+       zend_update_property_long(obj->std.ce, &obj->std, ZEND_STRL("precision"), precision);
+
+       fmt = php_dt_format_from_precision(precision);
+       zend_update_property_str(obj->std.ce, &obj->std, ZEND_STRL("format"), fmt);
+       zend_string_release(fmt);
+}
+
+static inline void php_ion_timestamp_dtor(php_ion_timestamp *obj)
+{
+       if (obj->time) {
+               timelib_time_dtor(obj->time);
+       }
+}
+
 typedef struct php_ion_catalog {
        ION_CATALOG *cat;
        zend_object std;
diff --git a/tests/Timestamp.phpt b/tests/Timestamp.phpt
new file mode 100644 (file)
index 0000000..cb50cf0
--- /dev/null
@@ -0,0 +1,53 @@
+--TEST--
+ion\Timestamp
+--EXTENSIONS--
+ion
+--FILE--
+TEST
+<?php
+use ion\Timestamp;
+
+try {
+       var_dump(new Timestamp);
+} catch (Throwable) {
+       echo "caught empty\n";
+}
+$full = "2021-12-07T14:08:51+00:00";
+var_dump($t=new Timestamp(Timestamp\Precision::Day, datetime:$full),(string)$t);
+var_dump($t=new Timestamp(Timestamp\Precision::Day->value, datetime:$full),(string)$t);
+var_dump($t=new Timestamp(Timestamp\Precision::Min, datetime:"2020-10-01"),(string)$t);
+var_dump($t=new Timestamp(Timestamp\Precision::Day, "!Y-m", "2000-10"),(string)$t);
+?>
+DONE
+--EXPECTF--
+TEST
+caught empty
+object(ion\Timestamp)#%d (2) {
+  ["precision"]=>
+  int(7)
+  ["format"]=>
+  string(7) "Y-m-d\T"
+}
+string(11) "2021-12-07T"
+object(ion\Timestamp)#%d (2) {
+  ["precision"]=>
+  int(7)
+  ["format"]=>
+  string(7) "Y-m-d\T"
+}
+string(11) "2021-12-07T"
+object(ion\Timestamp)#%d (2) {
+  ["precision"]=>
+  int(23)
+  ["format"]=>
+  string(11) "Y-m-d\TH:iP"
+}
+string(22) "2020-10-01T00:00+00:00"
+object(ion\Timestamp)#%d (2) {
+  ["precision"]=>
+  int(7)
+  ["format"]=>
+  string(7) "Y-m-d\T"
+}
+string(11) "2000-10-01T"
+DONE