Hi folks,

attached is a patch (with the respective test cases) that implements DateTime marshalling from and to xsd:dateTime in ext/soap as requested in http://bugs.php.net/44383

Right now, it is implemented for xsd:date, xsd:time and xsd:dateTime, but not for other types defined in W3C XML Schema such as gDayMonth; I don't really think it makes sense mapping from and to DateTimes in this case (from DateTime to gDayMonth would work, but the other way round would prove rather difficult).

Some notes about this patch:
- it conforms strictly to the XML Schema specification by only producing canonical representations of values when generating xsd:dateTime and xsd:time values. Specifically: - it will not generate trailing zeroes on microseconds (in other words, it simply generates a fractional second part as mandated by the specification), but it will accept such values - UTC is always used as the timezone (one of the tests in ext/date/ tests that mirrors SBR1-echoDate from http://www.w3.org/TR/2007/REC-soap12-testcollection-20070427/#SBR1-echoDate currently does this wrong), but it will accept any timezone
- xsd:time produces current date when generating a DateTime object
- xsd:date is relatively straightfoward as well:
 - produces "00:00:00" as the time when creating a DateTime object
 - accepts any time when parsing
 - also supports timezones
- as a side effect of the patch, microseconds are now supported in time values (for xsd:time and xsd:dateTime), hence the removed comment in to_xml_time

The tests have several permutations, but all but one is commented out each. The test_schema() function does some odd (but understandable) stunts with output buffering and global variables that make it impossible to test more than one case at a time. We didn't want to produce a million test files for the several variants; is there a nicer way to test this properly?

This feature is enabled by a SoapClient "feature" called SOAP_MARSHAL_DATETIME. I think this is a reasonable choice.

Greetings,

David

Attachment: schema089.phpt
Description: Binary data

? .DS_Store
? run-tests.php
? soap_marshal_datatype.diff.txt
? wsdlj2ZAlf
? tests/.DS_Store
? tests/bugs/.DS_Store
? tests/bugs/bug48557.phpt
? tests/bugs/bug48557.wsdl
? tests/schema/.DS_Store
? tests/schema/schema086.phpt
? tests/schema/schema087.phpt
? tests/schema/schema088.phpt
? tests/schema/schema089.phpt
? tests/schema/schema090.phpt
? tests/schema/schema091.phpt
Index: php_encoding.c
===================================================================
RCS file: /repository/php-src/ext/soap/php_encoding.c,v
retrieving revision 1.103.2.21.2.37.2.14
diff -u -r1.103.2.21.2.37.2.14 php_encoding.c
--- php_encoding.c      15 Jun 2009 17:31:02 -0000      1.103.2.21.2.37.2.14
+++ php_encoding.c      22 Jun 2009 12:42:22 -0000
@@ -27,6 +27,12 @@
 #include <libxml/parserInternals.h>
 #include "zend_strtod.h"
 #include "zend_interfaces.h"
+#include "ext/date/lib/timelib.h"
+#include "ext/date/php_date.h"
+
+#ifdef PHP_WIN32
+# include "win32/php_stdint.h"
+#endif
 
 /* zval type decode */
 static zval *to_zval_double(encodeTypePtr type, xmlNodePtr data);
@@ -39,6 +45,7 @@
 static zval *to_zval_null(encodeTypePtr type, xmlNodePtr data);
 static zval *to_zval_base64(encodeTypePtr type, xmlNodePtr data);
 static zval *to_zval_hexbin(encodeTypePtr type, xmlNodePtr data);
+static zval *to_zval_datetime(encodeTypePtr type, xmlNodePtr data);
 
 static xmlNodePtr to_xml_long(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent);
 static xmlNodePtr to_xml_double(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent);
@@ -60,7 +67,7 @@
 static xmlNodePtr to_xml_list1(encodeTypePtr enc, zval *data, int style, 
xmlNodePtr parent);
 
 /* Datetime encode/decode */
-static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char 
*format, int style, xmlNodePtr parent);
+static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, int 
style, xmlNodePtr parent, void (*format_func)(timelib_time *, smart_str *));
 static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent);
 static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent);
 static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent);
@@ -146,9 +153,9 @@
        {{XSD_FLOAT, XSD_FLOAT_STRING, XSD_NAMESPACE, NULL}, to_zval_double, 
to_xml_double},
        {{XSD_DOUBLE, XSD_DOUBLE_STRING, XSD_NAMESPACE, NULL}, to_zval_double, 
to_xml_double},
 
-       {{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL}, 
to_zval_stringc, to_xml_datetime},
-       {{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL}, to_zval_stringc, 
to_xml_time},
-       {{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL}, to_zval_stringc, 
to_xml_date},
+       {{XSD_DATETIME, XSD_DATETIME_STRING, XSD_NAMESPACE, NULL}, 
to_zval_datetime, to_xml_datetime},
+       {{XSD_TIME, XSD_TIME_STRING, XSD_NAMESPACE, NULL}, to_zval_datetime, 
to_xml_time},
+       {{XSD_DATE, XSD_DATE_STRING, XSD_NAMESPACE, NULL}, to_zval_datetime, 
to_xml_date},
        {{XSD_GYEARMONTH, XSD_GYEARMONTH_STRING, XSD_NAMESPACE, NULL}, 
to_zval_stringc, to_xml_gyearmonth},
        {{XSD_GYEAR, XSD_GYEAR_STRING, XSD_NAMESPACE, NULL}, to_zval_stringc, 
to_xml_gyear},
        {{XSD_GMONTHDAY, XSD_GMONTHDAY_STRING, XSD_NAMESPACE, NULL}, 
to_zval_stringc, to_xml_gmonthday},
@@ -837,6 +844,31 @@
        return ret;
 }
 
+static zval *to_zval_datetime(encodeTypePtr type, xmlNodePtr data)
+{
+       zval *ret = to_zval_stringc(type, data);
+       if (SOAP_GLOBAL(features) & SOAP_MARSHAL_DATETIME) {
+       
+               zval *object;
+               zend_class_entry *ce = php_date_get_date_ce();
+
+               ALLOC_ZVAL(object);
+               Z_TYPE_P(object) = IS_OBJECT;
+               object_init_ex(object, ce);
+               Z_SET_REFCOUNT_P(object, 1);
+               Z_SET_ISREF_P(object);
+
+               zend_call_method_with_1_params(&object, ce, &ce->constructor, 
"__construct", NULL, ret);
+               zval_ptr_dtor(&ret);
+               
+               return object;
+       }
+       else {
+               return ret;
+       }
+}
+
+
 static xmlNodePtr to_xml_string(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
        xmlNodePtr ret, text;
@@ -2959,15 +2991,8 @@
 }
 
 /* Time encode/decode */
-static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char 
*format, int style, xmlNodePtr parent)
+static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, int 
style, xmlNodePtr parent, void (*format_func)(timelib_time *, smart_str *))
 {
-       /* logic hacked from ext/standard/datetime.c */
-       struct tm *ta, tmbuf;
-       time_t timestamp;
-       int max_reallocs = 5;
-       size_t buf_len=64, real_len;
-       char *buf;
-       char tzbuf[8];
 
        xmlNodePtr xmlParam;
 
@@ -2975,48 +3000,59 @@
        xmlAddChild(parent, xmlParam);
        FIND_ZVAL_NULL(data, xmlParam, style);
 
-       if (Z_TYPE_P(data) == IS_LONG) {
-               timestamp = Z_LVAL_P(data);
-               ta = php_localtime_r(&timestamp, &tmbuf);
-               /*ta = php_gmtime_r(&timestamp, &tmbuf);*/
-
-               buf = (char *) emalloc(buf_len);
-               while ((real_len = strftime(buf, buf_len, format, ta)) == 
buf_len || real_len == 0) {
-                       buf_len *= 2;
-                       buf = (char *) erealloc(buf, buf_len);
-                       if (!--max_reallocs) break;
-               }
-
-               /* Time zone support */
-#ifdef HAVE_TM_GMTOFF
-               snprintf(tzbuf, sizeof(tzbuf), "%c%02d:%02d", (ta->tm_gmtoff < 
0) ? '-' : '+', abs(ta->tm_gmtoff / 3600), abs( (ta->tm_gmtoff % 3600) / 60 ));
-#else
-# if defined(__CYGWIN__) || defined(NETWARE)
-               snprintf(tzbuf, sizeof(tzbuf), "%c%02d:%02d", ((ta->tm_isdst ? 
_timezone - 3600:_timezone)>0)?'-':'+', abs((ta->tm_isdst ? _timezone - 3600 : 
_timezone) / 3600), abs(((ta->tm_isdst ? _timezone - 3600 : _timezone) % 3600) 
/ 60));
-# else
-               snprintf(tzbuf, sizeof(tzbuf), "%c%02d:%02d", ((ta->tm_isdst ? 
timezone - 3600:timezone)>0)?'-':'+', abs((ta->tm_isdst ? timezone - 3600 : 
timezone) / 3600), abs(((ta->tm_isdst ? timezone - 3600 : timezone) % 3600) / 
60));
-# endif
-#endif
-               if (strcmp(tzbuf,"+00:00") == 0) {
-                 strcpy(tzbuf,"Z");
-                 real_len++;
+       if (Z_TYPE_P(data) == IS_STRING) {
+               xmlNodeSetContentLen(xmlParam, BAD_CAST(Z_STRVAL_P(data)), 
Z_STRLEN_P(data));
+       } else {
+               timelib_time *time = timelib_time_ctor();
+               if (Z_TYPE_P(data) == IS_LONG) {
+                       timelib_unixtime2local(time, (timelib_sll) 
Z_LVAL_P(data));
+                       timelib_update_ts(time, get_timezone_info(TSRMLS_C));
+               } else if ((SOAP_GLOBAL(features) & SOAP_MARSHAL_DATETIME) && 
Z_TYPE_P(data) == IS_OBJECT && instanceof_function(Z_OBJCE_P(data), 
php_date_get_date_ce() TSRMLS_CC)) {
+                       /* XXX: when the developers decide to follow their 
advice, this should be
+                        * updated to reference `timelib_time_clone`. All we 
need to do is clone
+                        * the referent's time into our time data. */
+                       php_date_obj *dateobj = (php_date_obj *) 
zend_object_store_get_object(data TSRMLS_CC);
+               
+                       *time = *dateobj->time;
+                       if (dateobj->time->tz_abbr) {
+                               time->tz_abbr = strdup(dateobj->time->tz_abbr);
+                       }
+                       if (dateobj->time->tz_info) {
+                               time->tz_info = dateobj->time->tz_info;
+                       }
                } else {
-                       real_len += 6;
+                       goto out;
                }
-               if (real_len >= buf_len) {
-                       buf = (char *) erealloc(buf, real_len+1);
+               
+               if (time->tz_info) {
+                       /* If we have timezone information, switch us to UTC. */
+                       timelib_apply_localtime(time, 0);
                }
-               strcat(buf, tzbuf);
 
-               xmlNodeSetContent(xmlParam, BAD_CAST(buf));
-               efree(buf);
-       } else if (Z_TYPE_P(data) == IS_STRING) {
-               xmlNodeSetContentLen(xmlParam, BAD_CAST(Z_STRVAL_P(data)), 
Z_STRLEN_P(data));
+               {
+                       /* Now format it. We could use `php_format_date`, but 
unrolling it here
+                        * seems to make enough sense when we want to generate 
a canonical
+                        * dateTime. */
+                       smart_str buffer = {0};
+
+                       /* We pass in the ptr to smart_str to the formatting 
function so that they
+                        * can be chained or something. Flexible, easy, etc. */
+                       format_func(time, &buffer);
+
+                       /* And we can finalize it sanely here which is always 
good. */
+                       smart_str_0(&buffer);
+                       xmlNodeSetContentLen(xmlParam, BAD_CAST(buffer.c), 
buffer.len);
+                       smart_str_free(&buffer);
+               }
+
+       out:
+               timelib_time_dtor(time);
        }
 
        if (style == SOAP_ENCODED) {
                set_ns_and_type(xmlParam, type);
        }
+       
        return xmlParam;
 }
 
@@ -3026,45 +3062,174 @@
        return to_xml_string(type, data, style, parent);
 }
 
+static void to_xml_datetime_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+       int time_frac;
+
+       /* Why are these members declared as signed long longs? I don't know 
about
+        * you, but I'm not worried about there ever being more than 12 months 
in
+        * one year, and certainly no more than INT_MAX. My poor brain can't 
count
+        * that high. */
+       /* seconds are appended further down */
+       length = slprintf(temp, 32, "%04d-%02d-%02dT%02d:%02d:%02d",
+                         (int) time->y, /* Year */
+                         (int) time->m, /* Month */
+                         (int) time->d, /* Day */
+                         (int) time->h, /* Hour */
+                         (int) time->i, /* Minute */
+                         (int) time->s /* Second */);
+       smart_str_appendl(buffer, temp, length);
+
+       time_frac = (int) floor(time->f * 1000000);
+       if (time_frac > 0) {
+               while (time_frac % 10 == 0) {
+                       time_frac /= 10;
+               }
+               
+               length = slprintf(temp, 32, ".%d", time_frac);
+               smart_str_appendl(buffer, temp, length);
+       }
+       
+       if (time->tz_info) {
+               /* Add a 'Z' to indicate that this is a "timezoned" value. */
+               smart_str_appendc(buffer, 'Z');
+       }
+}
+
 static xmlNodePtr to_xml_datetime(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "%Y-%m-%dT%H:%M:%S", style, 
parent);
+       return to_xml_datetime_ex(type, data, style, parent, 
to_xml_datetime_format /* Y-m-dTH:i:s */);
+}
+
+static void to_xml_time_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+       int time_frac;
+
+       length = slprintf(temp, 32, "%02d:%02d:%02d",
+                         (int) time->h, /* Hour */
+                         (int) time->i, /* Minute */
+                         (int) time->s /* Second */);
+       smart_str_appendl(buffer, temp, length);
+
+       time_frac = (int) floor(time->f * 1000000);
+       if (time_frac > 0) {
+               while (time_frac % 10 == 0) {
+                       time_frac /= 10;
+               }
+               
+               length = slprintf(temp, 32, ".%d", time_frac);
+               smart_str_appendl(buffer, temp, length);
+       }
+       
+       if (time->tz_info) {
+               /* Add a 'Z' to indicate that this is a "timezoned" value. */
+               smart_str_appendc(buffer, 'Z');
+       }
 }
 
 static xmlNodePtr to_xml_time(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       /* TODO: microsecconds */
-       return to_xml_datetime_ex(type, data, "%H:%M:%S", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, to_xml_time_format 
/* H:i:s(.sss) */);
+}
+
+static void to_xml_date_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "%04d-%02d-%02d",
+                         (int) time->y, /* Year */
+                         (int) time->m, /* Month */
+                         (int) time->d /* Day */);
+       smart_str_appendl(buffer, temp, length);
+       
+       if (time->tz_info) {
+               /* Add a 'Z' to indicate that this is a "timezoned" value. */
+               smart_str_appendc(buffer, 'Z');
+       }
 }
 
 static xmlNodePtr to_xml_date(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "%Y-%m-%d", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, to_xml_date_format 
/* Y-m-d */);
+}
+
+static void to_xml_gyearmonth_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "%04d-%02d",
+                         (int) time->y, /* Year */
+                         (int) time->m /* Month */);
+       smart_str_appendl(buffer, temp, length);
 }
 
 static xmlNodePtr to_xml_gyearmonth(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "%Y-%m", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, 
to_xml_gyearmonth_format /* Y-m */);
+}
+
+static void to_xml_gyear_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "%04d", (int) time->y /* Year */);
+       smart_str_appendl(buffer, temp, length);
 }
 
 static xmlNodePtr to_xml_gyear(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "%Y", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, 
to_xml_gyear_format /* Y */);
+}
+
+static void to_xml_gmonthday_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "--%02d-%02d",
+                         (int) time->m, /* Month */
+                         (int) time->d /* Day */);
+       smart_str_appendl(buffer, temp, length);
 }
 
 static xmlNodePtr to_xml_gmonthday(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "--%m-%d", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, 
to_xml_gmonthday_format /* --m-d */);
+}
+
+static void to_xml_gday_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "---%02d", (int) time->d /* Day */);
+       smart_str_appendl(buffer, temp, length);
 }
 
 static xmlNodePtr to_xml_gday(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "---%d", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, to_xml_gday_format 
/* ---d */);
+}
+
+static void to_xml_gmonth_format(timelib_time *time, smart_str *buffer)
+{
+       char temp[33];
+       int length;
+
+       length = slprintf(temp, 32, "--%02d", (int) time->m /* Month */);
+       smart_str_appendl(buffer, temp, length);
 }
 
 static xmlNodePtr to_xml_gmonth(encodeTypePtr type, zval *data, int style, 
xmlNodePtr parent)
 {
-       return to_xml_datetime_ex(type, data, "--%m--", style, parent);
+       return to_xml_datetime_ex(type, data, style, parent, 
to_xml_gmonth_format /* --m */);
 }
 
 static zval* to_zval_list(encodeTypePtr enc, xmlNodePtr data) {
Index: php_soap.h
===================================================================
RCS file: /repository/php-src/ext/soap/php_soap.h,v
retrieving revision 1.38.2.6.2.6.2.4
diff -u -r1.38.2.6.2.6.2.4 php_soap.h
--- php_soap.h  31 Dec 2008 11:15:43 -0000      1.38.2.6.2.6.2.4
+++ php_soap.h  22 Jun 2009 12:42:22 -0000
@@ -143,6 +143,7 @@
 #define SOAP_SINGLE_ELEMENT_ARRAYS  (1<<0)
 #define SOAP_WAIT_ONE_WAY_CALLS     (1<<1)
 #define SOAP_USE_XSI_ARRAY_TYPE     (1<<2)
+#define SOAP_MARSHAL_DATETIME        (1<<3)
 
 #define WSDL_CACHE_NONE     0x0
 #define WSDL_CACHE_DISK     0x1
Index: soap.c
===================================================================
RCS file: /repository/php-src/ext/soap/soap.c,v
retrieving revision 1.156.2.28.2.30.2.32
diff -u -r1.156.2.28.2.30.2.32 soap.c
--- soap.c      18 Feb 2009 13:25:48 -0000      1.156.2.28.2.30.2.32
+++ soap.c      22 Jun 2009 12:42:23 -0000
@@ -828,6 +828,7 @@
        REGISTER_LONG_CONSTANT("SOAP_SINGLE_ELEMENT_ARRAYS", 
SOAP_SINGLE_ELEMENT_ARRAYS, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("SOAP_WAIT_ONE_WAY_CALLS", 
SOAP_WAIT_ONE_WAY_CALLS, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("SOAP_USE_XSI_ARRAY_TYPE", 
SOAP_USE_XSI_ARRAY_TYPE, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("SOAP_MARSHAL_DATETIME", SOAP_MARSHAL_DATETIME, 
CONST_CS | CONST_PERSISTENT);
 
        REGISTER_LONG_CONSTANT("WSDL_CACHE_NONE",   WSDL_CACHE_NONE,   CONST_CS 
| CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("WSDL_CACHE_DISK",   WSDL_CACHE_DISK,   CONST_CS 
| CONST_PERSISTENT);

Attachment: schema087.phpt
Description: Binary data

Attachment: schema088.phpt
Description: Binary data

Attachment: schema086.phpt
Description: Binary data

Attachment: schema090.phpt
Description: Binary data

Attachment: schema091.phpt
Description: Binary data

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to