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
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(×tamp, &tmbuf); - /*ta = php_gmtime_r(×tamp, &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);
schema087.phpt
Description: Binary data
schema088.phpt
Description: Binary data
schema086.phpt
Description: Binary data
schema090.phpt
Description: Binary data
schema091.phpt
Description: Binary data
smime.p7s
Description: S/MIME cryptographic signature