fix timestamp formats
authorMichael Wallner <mike@php.net>
Mon, 10 Jan 2022 19:32:26 +0000 (20:32 +0100)
committerMichael Wallner <mike@php.net>
Mon, 10 Jan 2022 19:32:26 +0000 (20:32 +0100)
ion.c
ion.stub.php
ion_arginfo.h
ion_private.h
tests/Timestamp.phpt
tests/serialize/timestamp.phpt

diff --git a/ion.c b/ion.c
index 81231d653c5baba9c8f59c530e2bd4ea03f3c457..8bf7e0a3e11b63a5f2ab0361fb3ee629ee1f2726 100644 (file)
--- a/ion.c
+++ b/ion.c
@@ -409,13 +409,13 @@ static ZEND_METHOD(ion_Timestamp, __construct)
        PTR_CHECK(obj);
 
        zend_long precision;
-       zend_object *precision_obj;
+       zend_object *precision_obj = NULL, *format_obj = NULL;
        zend_string *fmt = NULL, *dt = NULL;
        zval *tz = NULL;
        ZEND_PARSE_PARAMETERS_START(1, 4)
                Z_PARAM_OBJ_OF_CLASS_OR_LONG(precision_obj, ce_Timestamp_Precision, precision)
                Z_PARAM_OPTIONAL
-               Z_PARAM_STR_OR_NULL(fmt)
+               Z_PARAM_OBJ_OF_CLASS_OR_STR_OR_NULL(format_obj, ce_Timestamp_Format, fmt)
                Z_PARAM_STR_OR_NULL(dt)
                Z_PARAM_ZVAL(tz)
        ZEND_PARSE_PARAMETERS_END();
@@ -423,6 +423,9 @@ static ZEND_METHOD(ion_Timestamp, __construct)
        if (precision_obj) {
                precision = Z_LVAL_P(zend_enum_fetch_case_value(precision_obj));
        }
+       if (format_obj) {
+               fmt = Z_STR_P(zend_enum_fetch_case_value(format_obj));
+       }
        php_ion_timestamp_ctor(obj, precision, fmt, dt, tz);
 }
 static ZEND_METHOD(ion_Timestamp, __toString)
@@ -1785,6 +1788,7 @@ PHP_MINIT_FUNCTION(ion)
        if (SUCCESS != g_sym_init())  {
                return FAILURE;
        }
+       g_intern_str_init();
 
        // Catalog
        php_ion_register(catalog, Catalog, zend_ce_countable);
@@ -1826,6 +1830,7 @@ PHP_MINIT_FUNCTION(ion)
 
        // Timestamp
        ce_Timestamp = register_class_ion_Timestamp(php_date_get_date_ce());
+       ce_Timestamp_Format = register_class_ion_Timestamp_Format();
        ce_Timestamp_Precision = register_class_ion_Timestamp_Precision();
 
        // Type
index 158553e54aab169f525113fd9c9fcfe2ad0eb572..fd9022499c660d71f1b7063ab6481b1ae7ae01c0 100644 (file)
@@ -234,6 +234,19 @@ enum Precision : int {
     case SecTZ          = 0x1|0x2|0x4|0x10|0x20|0x80;
     case FracTZ         = 0x1|0x2|0x4|0x10|0x20|0x40|0x80;
 }
+
+namespace ion\Timestamp;
+enum Format : string {
+    case Year           = "Y\T";
+    case Month          = "Y-m\T";
+    case Day            = "Y-m-d\T";
+    case Min            = "Y-m-d\TH:i";
+    case Sec            = "Y-m-d\TH:i:s";
+    case Frac           = "Y-m-d\TH:i:s.v";
+    case MinTZ          = "Y-m-d\TH:iP";
+    case SecTZ          = "Y-m-d\TH:i:sP";
+    case FracTZ         = "Y-m-d\TH:i:s.vP";
+}
 namespace ion;
 class Timestamp extends \DateTime {
     public readonly int $precision;
@@ -241,7 +254,7 @@ class Timestamp extends \DateTime {
 
     public function __construct(
         Timestamp\Precision|int $precision,
-        ?string $format = null,
+        Timestamp\Format|string|null $format = null,
         ?string $datetime = null,
         ?\DateTimeZone $timezone = null,
     ) {}
index cdb4a179308d3c7b4a46388b994ca3cd64a81175..895bb87f70ebdabe7781dc065e17361cf3eda988 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: ab42c2a137aa474d0da60bcc65144da7a9472948 */
+ * Stub hash: 8e04adcc2af90243429a756e14d48507f8411309 */
 
 ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_ion_Symbol_Table_PHP, 0, 0, ion\\Symbol\\Table, 0)
 ZEND_END_ARG_INFO()
@@ -158,7 +158,7 @@ ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ion_Timestamp___construct, 0, 0, 1)
        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_OBJ_TYPE_MASK(0, format, ion\\Timestamp\\Format, MAY_BE_STRING|MAY_BE_NULL, "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()
@@ -776,6 +776,11 @@ static const zend_function_entry class_ion_Timestamp_Precision_methods[] = {
 };
 
 
+static const zend_function_entry class_ion_Timestamp_Format_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)
@@ -1496,6 +1501,58 @@ static zend_class_entry *register_class_ion_Timestamp_Precision(void)
        return class_entry;
 }
 
+static zend_class_entry *register_class_ion_Timestamp_Format(void)
+{
+       zend_class_entry *class_entry = zend_register_internal_enum("ion\\Timestamp\\Format", IS_STRING, class_ion_Timestamp_Format_methods);
+
+       zval enum_case_Year_value;
+       zend_string *enum_case_Year_value_str = zend_string_init("Y\\T", sizeof("Y\\T") - 1, 1);
+       ZVAL_STR(&enum_case_Year_value, enum_case_Year_value_str);
+       zend_enum_add_case_cstr(class_entry, "Year", &enum_case_Year_value);
+
+       zval enum_case_Month_value;
+       zend_string *enum_case_Month_value_str = zend_string_init("Y-m\\T", sizeof("Y-m\\T") - 1, 1);
+       ZVAL_STR(&enum_case_Month_value, enum_case_Month_value_str);
+       zend_enum_add_case_cstr(class_entry, "Month", &enum_case_Month_value);
+
+       zval enum_case_Day_value;
+       zend_string *enum_case_Day_value_str = zend_string_init("Y-m-d\\T", sizeof("Y-m-d\\T") - 1, 1);
+       ZVAL_STR(&enum_case_Day_value, enum_case_Day_value_str);
+       zend_enum_add_case_cstr(class_entry, "Day", &enum_case_Day_value);
+
+       zval enum_case_Min_value;
+       zend_string *enum_case_Min_value_str = zend_string_init("Y-m-d\\TH:i", sizeof("Y-m-d\\TH:i") - 1, 1);
+       ZVAL_STR(&enum_case_Min_value, enum_case_Min_value_str);
+       zend_enum_add_case_cstr(class_entry, "Min", &enum_case_Min_value);
+
+       zval enum_case_Sec_value;
+       zend_string *enum_case_Sec_value_str = zend_string_init("Y-m-d\\TH:i:s", sizeof("Y-m-d\\TH:i:s") - 1, 1);
+       ZVAL_STR(&enum_case_Sec_value, enum_case_Sec_value_str);
+       zend_enum_add_case_cstr(class_entry, "Sec", &enum_case_Sec_value);
+
+       zval enum_case_Frac_value;
+       zend_string *enum_case_Frac_value_str = zend_string_init("Y-m-d\\TH:i:s.v", sizeof("Y-m-d\\TH:i:s.v") - 1, 1);
+       ZVAL_STR(&enum_case_Frac_value, enum_case_Frac_value_str);
+       zend_enum_add_case_cstr(class_entry, "Frac", &enum_case_Frac_value);
+
+       zval enum_case_MinTZ_value;
+       zend_string *enum_case_MinTZ_value_str = zend_string_init("Y-m-d\\TH:iP", sizeof("Y-m-d\\TH:iP") - 1, 1);
+       ZVAL_STR(&enum_case_MinTZ_value, enum_case_MinTZ_value_str);
+       zend_enum_add_case_cstr(class_entry, "MinTZ", &enum_case_MinTZ_value);
+
+       zval enum_case_SecTZ_value;
+       zend_string *enum_case_SecTZ_value_str = zend_string_init("Y-m-d\\TH:i:sP", sizeof("Y-m-d\\TH:i:sP") - 1, 1);
+       ZVAL_STR(&enum_case_SecTZ_value, enum_case_SecTZ_value_str);
+       zend_enum_add_case_cstr(class_entry, "SecTZ", &enum_case_SecTZ_value);
+
+       zval enum_case_FracTZ_value;
+       zend_string *enum_case_FracTZ_value_str = zend_string_init("Y-m-d\\TH:i:s.vP", sizeof("Y-m-d\\TH:i:s.vP") - 1, 1);
+       ZVAL_STR(&enum_case_FracTZ_value, enum_case_FracTZ_value_str);
+       zend_enum_add_case_cstr(class_entry, "FracTZ", &enum_case_FracTZ_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 600f18beddcb77aaf8fdbf255aa5144895cb162c..acb2403cff905110818b2c6657c55664e5ab1316 100644 (file)
@@ -128,6 +128,26 @@ LOCAL int g_sym_init(void)
        return SUCCESS;
 }
 
+static struct {
+       zend_string *Year, *Month, *Day, *Min, *Sec, *Frac, *MinTZ, *SecTZ, *FracTZ;
+} g_intern_str;
+
+static void g_intern_str_init()
+{
+#define NEW_INTERN_STR(s) \
+       g_intern_str.s = zend_string_init_interned(#s, sizeof(#s)-1, 1)
+       NEW_INTERN_STR(Year);
+       NEW_INTERN_STR(Month);
+       NEW_INTERN_STR(Day);
+       NEW_INTERN_STR(Min);
+       NEW_INTERN_STR(Sec);
+       NEW_INTERN_STR(Frac);
+       NEW_INTERN_STR(MinTZ);
+       NEW_INTERN_STR(SecTZ);
+       NEW_INTERN_STR(FracTZ);
+#undef NEW_INTERN_STR
+}
+
 typedef struct php_ion_serializer {
        ION_WRITER *writer;
        ION_WRITER_OPTIONS *options;
@@ -223,6 +243,7 @@ static zend_class_entry
        *ce_Symbol_Table_Shared,
        *ce_Symbol_Table_System,
        *ce_Timestamp,
+       *ce_Timestamp_Format,
        *ce_Timestamp_Precision,
        *ce_Type,
        *ce_Unserializer,
@@ -860,23 +881,34 @@ LOCAL decQuad *ion_ts_frac_from_usec(decQuad *frac, int usec, decContext *ctx)
        return decQuadDivide(frac, decQuadFromInt32(&us, usec), decQuadFromInt32(&microsecs, 1000000), ctx);
 }
 
+LOCAL zend_string *php_ion_timestamp_format_fetch(zend_string *fmt_case)
+{
+       return Z_STR_P(zend_enum_fetch_case_value(zend_enum_get_case(ce_Timestamp_Format, fmt_case)));
+}
+
 LOCAL zend_string *php_dt_format_from_precision(uint8_t precision)
 {
-       switch (precision & 0x7f) {
+       switch (precision) {
+       case ION_TS_FRAC | 0x80:
+               return php_ion_timestamp_format_fetch(g_intern_str.FracTZ);
        case ION_TS_FRAC:
-               return zend_string_init(ZEND_STRL("c"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Frac);
+       case ION_TS_SEC | 0x80:
+               return php_ion_timestamp_format_fetch(g_intern_str.SecTZ);
        case ION_TS_SEC:
-               return zend_string_init(ZEND_STRL("Y-m-d\\TH:i:sP"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Sec);
+       case ION_TS_MIN | 0x80:
+               return php_ion_timestamp_format_fetch(g_intern_str.MinTZ);
        case ION_TS_MIN:
-               return zend_string_init(ZEND_STRL("Y-m-d\\TH:iP"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Min);
        case ION_TS_DAY:
-               return zend_string_init(ZEND_STRL("Y-m-d\\T"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Day);
        case ION_TS_MONTH:
-               return zend_string_init(ZEND_STRL("Y-m\\T"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Month);
        case ION_TS_YEAR:
-               return zend_string_init(ZEND_STRL("Y\\T"), 0);
+               return php_ion_timestamp_format_fetch(g_intern_str.Year);
        default:
-               return zend_string_init(ZEND_STRL("c"), 0);
+               return zend_one_char_string['c'];
        }
 }
 
@@ -884,9 +916,7 @@ LOCAL timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContext *ctx,
 {
        timelib_time *time = ecalloc(1, sizeof(*time));
 
-       int precision = ION_TS_FRAC;
-       ion_timestamp_get_precision(ts,  &precision);
-       switch (precision) {
+       switch (ts->precision & 0x7f) {
        case ION_TS_FRAC:
                time->us = php_usec_from_ion(&ts->fraction, ctx);
                /* fallthrough */
@@ -908,7 +938,7 @@ LOCAL timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContext *ctx,
                /* fallthrough */
        default:
                time->z = ts->tz_offset * 60;
-               if (time->z) {
+               if (time->z || ts->precision & 0x80) {
                        time->zone_type = TIMELIB_ZONETYPE_OFFSET;
                } else {
                        time->zone_type = TIMELIB_ZONETYPE_ID;
@@ -917,7 +947,7 @@ LOCAL timelib_time* php_time_from_ion(const ION_TIMESTAMP *ts, decContext *ctx,
        }
 
        if (fmt) {
-               *fmt = php_dt_format_from_precision(precision);
+               *fmt = php_dt_format_from_precision(ts->precision);
        }
        return time;
 }
index c0f0171ca3cb9d42634ebb6a49ad669e2457a5c6..e92905bbfd7229c5f47187cf7260359f82a96fd3 100644 (file)
@@ -18,8 +18,9 @@ $full = "2021-12-07T14:08:51.123456+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);
+$t->setTimezone(new DateTimeZone("Europe/Helsinki"));
+var_dump((string) $t);
 var_dump($t=new Timestamp(Timestamp\Precision::Day, "!Y-m", "2000-10"),(string)$t);
-
 var_dump(ion\unserialize(ion\serialize(clone new ion\Timestamp(ion\Timestamp\Precision::Sec, DateTime::RFC3339, "1971-02-03T04:05:06Z"))));
 ?>
 DONE
@@ -56,7 +57,7 @@ object(ion\Timestamp)#%d (5) {
   ["precision"]=>
   int(23)
   ["format"]=>
-  string(11) "Y-m-d\TH:iP"
+  string(10) "Y-m-d\TH:i"
   ["date"]=>
   string(26) "2020-10-01 00:00:00.000000"
   ["timezone_type"]=>
@@ -64,7 +65,8 @@ object(ion\Timestamp)#%d (5) {
   ["timezone"]=>
   string(3) "CET"
 }
-string(22) "2020-10-01T00:00+02:00"
+string(16) "2020-10-01T00:00"
+string(16) "2020-10-01T01:00"
 object(ion\Timestamp)#%d (5) {
   ["precision"]=>
   int(7)
@@ -78,11 +80,11 @@ object(ion\Timestamp)#%d (5) {
   string(3) "CET"
 }
 string(11) "2000-10-01T"
-object(ion\Timestamp)#8 (3) {
+object(ion\Timestamp)#%d (3) {
   ["precision"]=>
   int(55)
   ["format"]=>
-  string(13) "Y-m-d\TH:i:sP"
+  string(12) "Y-m-d\TH:i:s"
   ["date"]=>
   string(26) "1971-02-03 04:05:06.000000"
 }
index 21941af10094af95ed18f82ba4c8224f2e0d1782..396bbb5399dcfea7268294285d9be249dae88727 100644 (file)
@@ -6,7 +6,7 @@ ion
 TEST
 <?php
 $dt = ion\unserialize("1971-02-03T04:05:06.789Z");
-var_dump($dt);
+var_dump($dt, (string) $dt);
 $ts = ion\serialize($dt);
 var_dump($ts);
 
@@ -15,20 +15,21 @@ var_dump(ion\unserialize($ts));
 DONE
 --EXPECTF--
 TEST
-object(ion\Timestamp)#5 (3) {
+object(ion\Timestamp)#%d (3) {
   ["precision"]=>
   int(247)
   ["format"]=>
-  string(1) "c"
+  string(15) "Y-m-d\TH:i:s.vP"
   ["date"]=>
   string(26) "1971-02-03 04:05:06.789000"
 }
+string(29) "1971-02-03T04:05:06.789+00:00"
 string(24) "1971-02-03T04:05:06.789Z"
-object(ion\Timestamp)#6 (3) {
+object(ion\Timestamp)#%d (3) {
   ["precision"]=>
   int(247)
   ["format"]=>
-  string(1) "c"
+  string(15) "Y-m-d\TH:i:s.vP"
   ["date"]=>
   string(26) "1971-02-03 04:05:06.789000"
 }