diff --git a/dlls/webservices/reader.c b/dlls/webservices/reader.c
index e30864d520d..d4d4dccbec6 100644
--- a/dlls/webservices/reader.c
+++ b/dlls/webservices/reader.c
@@ -1881,6 +1881,148 @@ static HRESULT str_to_uint64( const unsigned char *str, ULONG len, UINT64 max, U
return S_OK;
}
+#define TICKS_PER_SEC 10000000
+#define TICKS_PER_MIN (60 * (ULONGLONG)TICKS_PER_SEC)
+#define TICKS_PER_HOUR (3600 * (ULONGLONG)TICKS_PER_SEC)
+#define TICKS_PER_DAY (86400 * (ULONGLONG)TICKS_PER_SEC)
+#define TICKS_MAX 3155378975999999999
+
+static const int month_offsets[2][12] =
+{
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
+};
+
+static const int month_days[2][12] =
+{
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
+
+static inline int is_leap_year( int year )
+{
+ return !(year % 4) && (year % 100 || !(year % 400));
+}
+
+static inline int valid_day( int year, int month, int day )
+{
+ return day > 0 && day <= month_days[is_leap_year( year )][month - 1];
+}
+
+static inline int leap_days_before( int year )
+{
+ return (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400;
+}
+
+static HRESULT str_to_datetime( const unsigned char *bytes, ULONG len, WS_DATETIME *ret )
+{
+ const unsigned char *p = bytes, *q;
+ int year, month, day, hour, min, sec, sec_frac = 0, tz_hour, tz_min, tz_neg;
+
+ while (len && read_isspace( *p )) { p++; len--; }
+ while (len && read_isspace( p[len - 1] )) { len--; }
+
+ q = p;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 4 || !len || *q != '-') return WS_E_INVALID_FORMAT;
+ year = (p[0] - '0') * 1000 + (p[1] - '0') * 100 + (p[2] - '0') * 10 + p[3] - '0';
+ if (year < 1) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len || *q != '-') return WS_E_INVALID_FORMAT;
+ month = (p[0] - '0') * 10 + p[1] - '0';
+ if (month < 1 || month > 12) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len || *q != 'T') return WS_E_INVALID_FORMAT;
+ day = (p[0] - '0') * 10 + p[1] - '0';
+ if (!valid_day( year, month, day )) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT;
+ hour = (p[0] - '0') * 10 + p[1] - '0';
+ if (hour > 24) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT;
+ min = (p[0] - '0') * 10 + p[1] - '0';
+ if (min > 59 || (min > 0 && hour == 24)) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len) return WS_E_INVALID_FORMAT;
+ sec = (p[0] - '0') * 10 + p[1] - '0';
+ if (sec > 59 || (sec > 0 && hour == 24)) return WS_E_INVALID_FORMAT;
+
+ if (*q == '.')
+ {
+ unsigned int i, nb_digits, mul = TICKS_PER_SEC / 10;
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ nb_digits = q - p;
+ if (nb_digits < 1 || nb_digits > 7) return WS_E_INVALID_FORMAT;
+ for (i = 0; i < nb_digits; i++)
+ {
+ sec_frac += (p[i] - '0') * mul;
+ mul /= 10;
+ }
+ }
+ if (*q == 'Z')
+ {
+ if (--len) return WS_E_INVALID_FORMAT;
+ tz_hour = tz_min = tz_neg = 0;
+ ret->format = WS_DATETIME_FORMAT_UTC;
+ }
+ else if (*q == '+' || *q == '-')
+ {
+ tz_neg = (*q == '-') ? 1 : 0;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT;
+ tz_hour = (p[0] - '0') * 10 + p[1] - '0';
+ if (tz_hour > 14) return WS_E_INVALID_FORMAT;
+
+ p = ++q; len--;
+ while (len && isdigit( *q )) { q++; len--; };
+ if (q - p != 2 || len) return WS_E_INVALID_FORMAT;
+ tz_min = (p[0] - '0') * 10 + p[1] - '0';
+ if (tz_min > 59 || (tz_min > 0 && tz_hour == 14)) return WS_E_INVALID_FORMAT;
+
+ ret->format = WS_DATETIME_FORMAT_LOCAL;
+ }
+ else return WS_E_INVALID_FORMAT;
+
+ ret->ticks = ((year - 1) * 365 + leap_days_before( year )) * TICKS_PER_DAY;
+ ret->ticks += month_offsets[is_leap_year( year )][month - 1] * TICKS_PER_DAY;
+ ret->ticks += (day - 1) * TICKS_PER_DAY;
+ ret->ticks += hour * TICKS_PER_HOUR;
+ ret->ticks += min * TICKS_PER_MIN;
+ ret->ticks += sec * TICKS_PER_SEC;
+ ret->ticks += sec_frac;
+
+ if (tz_neg)
+ {
+ if (tz_hour * TICKS_PER_HOUR + tz_min * TICKS_PER_MIN + ret->ticks > TICKS_MAX)
+ return WS_E_INVALID_FORMAT;
+ ret->ticks += tz_hour * TICKS_PER_HOUR;
+ ret->ticks += tz_min * TICKS_PER_MIN;
+ }
+ else
+ {
+ if (tz_hour * TICKS_PER_HOUR + tz_min * TICKS_PER_MIN > ret->ticks)
+ return WS_E_INVALID_FORMAT;
+ ret->ticks -= tz_hour * TICKS_PER_HOUR;
+ ret->ticks -= tz_min * TICKS_PER_MIN;
+ }
+
+ return S_OK;
+}
+
static HRESULT read_get_node_text( struct reader *reader, WS_XML_UTF8_TEXT **ret )
{
WS_XML_TEXT_NODE *text;
@@ -2570,6 +2712,53 @@ static HRESULT read_type_enum( struct reader *reader, WS_TYPE_MAPPING mapping,
return S_OK;
}
+static HRESULT read_type_datetime( struct reader *reader, WS_TYPE_MAPPING mapping,
+ const WS_XML_STRING *localname, const WS_XML_STRING *ns,
+ const WS_DATETIME_DESCRIPTION *desc, WS_READ_OPTION option,
+ WS_HEAP *heap, void *ret, ULONG size )
+{
+ WS_XML_UTF8_TEXT *utf8;
+ HRESULT hr;
+ WS_DATETIME val = {0, WS_DATETIME_FORMAT_UTC};
+ BOOL found;
+
+ if (desc) FIXME( "ignoring description\n" );
+
+ if ((hr = read_get_text( reader, mapping, localname, ns, &utf8, &found )) != S_OK) return hr;
+ if (found && (hr = str_to_datetime( utf8->value.bytes, utf8->value.length, &val )) != S_OK) return hr;
+
+ switch (option)
+ {
+ case WS_READ_REQUIRED_VALUE:
+ if (!found) return WS_E_INVALID_FORMAT;
+ if (size != sizeof(WS_DATETIME)) return E_INVALIDARG;
+ *(WS_DATETIME *)ret = val;
+ break;
+
+ case WS_READ_REQUIRED_POINTER:
+ if (!found) return WS_E_INVALID_FORMAT;
+ /* fall through */
+
+ case WS_READ_OPTIONAL_POINTER:
+ {
+ WS_DATETIME *heap_val = NULL;
+ if (size != sizeof(heap_val)) return E_INVALIDARG;
+ if (found)
+ {
+ if (!(heap_val = ws_alloc( heap, sizeof(*heap_val) ))) return WS_E_QUOTA_EXCEEDED;
+ *heap_val = val;
+ }
+ *(WS_DATETIME **)ret = heap_val;
+ break;
+ }
+ default:
+ FIXME( "read option %u not supported\n", option );
+ return E_NOTIMPL;
+ }
+
+ return S_OK;
+}
+
static BOOL is_empty_text_node( const struct node *node )
{
const WS_XML_TEXT_NODE *text = (const WS_XML_TEXT_NODE *)node;
@@ -2643,6 +2832,9 @@ static ULONG get_type_size( WS_TYPE type, const WS_STRUCT_DESCRIPTION *desc )
case WS_UINT64_TYPE:
return sizeof(INT64);
+ case WS_DATETIME_TYPE:
+ return sizeof(WS_DATETIME);
+
case WS_WSZ_TYPE:
return sizeof(WCHAR *);
@@ -2746,6 +2938,7 @@ static WS_READ_OPTION map_field_options( WS_TYPE type, ULONG options )
case WS_UINT32_TYPE:
case WS_UINT64_TYPE:
case WS_ENUM_TYPE:
+ case WS_DATETIME_TYPE:
return WS_READ_REQUIRED_VALUE;
case WS_WSZ_TYPE:
@@ -2979,6 +3172,11 @@ static HRESULT read_type( struct reader *reader, WS_TYPE_MAPPING mapping, WS_TYP
return hr;
break;
+ case WS_DATETIME_TYPE:
+ if ((hr = read_type_datetime( reader, mapping, localname, ns, desc, option, heap, value, size )) != S_OK)
+ return hr;
+ break;
+
default:
FIXME( "type %u not supported\n", type );
return E_NOTIMPL;
diff --git a/dlls/webservices/tests/reader.c b/dlls/webservices/tests/reader.c
index 5c94fef77f6..ced8a0fddd9 100644
--- a/dlls/webservices/tests/reader.c
+++ b/dlls/webservices/tests/reader.c
@@ -2924,6 +2924,91 @@ static void test_WsResetHeap(void)
WsFreeHeap( heap );
}
+static void test_datetime(void)
+{
+ static const struct
+ {
+ const char *str;
+ HRESULT hr;
+ __int64 ticks;
+ WS_DATETIME_FORMAT format;
+ }
+ tests[] =
+ {
+ {"0000-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00Z", S_OK, 0, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00.0Z", S_OK, 0, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.1Z", S_OK, 0x0000f4240, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.01Z", S_OK, 0x0000186a0, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.0000001Z", S_OK, 1, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.9999999Z", S_OK, 0x00098967f, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.0000000Z", S_OK, 0, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T00:00:00.00000001Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00Z-", WS_E_INVALID_FORMAT, 0},
+ {"-0001-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-00-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-13-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-12-01T00:00:00Z", S_OK, 0x1067555f88000, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-00T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"2001-01-32T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"2001-01-31T00:00:00Z", S_OK, 0x8c2592fe3794000, WS_DATETIME_FORMAT_UTC},
+ {"1900-02-29T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"2000-02-29T00:00:00Z", S_OK, 0x8c1505f0e438000, 0},
+ {"2001-02-29T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"2001-02-28T00:00:00Z", S_OK, 0x8c26f30870a4000, WS_DATETIME_FORMAT_UTC},
+ {"0001-00-01U00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T24:00:00Z", S_OK, 0xc92a69c000, WS_DATETIME_FORMAT_UTC},
+ {"0001-01-01T24:00:01Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:60:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:60Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00Y", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00+00:01", WS_E_INVALID_FORMAT, 0, 0},
+ {"0001-01-01T00:00:00-00:01", S_OK, 0x023c34600, WS_DATETIME_FORMAT_LOCAL},
+ {"9999-12-31T24:00:00+00:01", S_OK, 0x2bca2875d073fa00, WS_DATETIME_FORMAT_LOCAL},
+ {"9999-12-31T24:00:00-00:01", WS_E_INVALID_FORMAT, 0, 0},
+ {"0002-01-01T00:00:00+14:01", WS_E_INVALID_FORMAT, 0, 0},
+ {"0002-01-01T00:00:00+15:00", WS_E_INVALID_FORMAT, 0, 0},
+ {"0002-01-01T00:00:00+13:60", WS_E_INVALID_FORMAT, 0, 0},
+ {"0002-01-01T00:00:00+13:59", S_OK, 0x11e5c43cc5600, WS_DATETIME_FORMAT_LOCAL},
+ {"0002-01-01T00:00:00+01:00", S_OK, 0x11ec917025800, WS_DATETIME_FORMAT_LOCAL},
+ {"2016-01-01T00:00:00-01:00", S_OK, 0x8d31246dfbba800, WS_DATETIME_FORMAT_LOCAL},
+ {"2016-01-01T00:00:00Z", S_OK, 0x8d3123e7df74000, WS_DATETIME_FORMAT_UTC},
+ {" 2016-01-02T03:04:05Z ", S_OK, 0x8d313215fb64080, WS_DATETIME_FORMAT_UTC},
+ {"+2016-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"", WS_E_INVALID_FORMAT, 0, 0},
+ {"01-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0},
+ {"1601-01-01T00:00:00Z", S_OK, 0x701ce1722770000, WS_DATETIME_FORMAT_UTC},
+ };
+ HRESULT hr;
+ WS_XML_READER *reader;
+ WS_HEAP *heap;
+ WS_DATETIME date;
+ ULONG i;
+
+ hr = WsCreateHeap( 1 << 16, 0, NULL, 0, &heap, NULL );
+ ok( hr == S_OK, "got %08x\n", hr );
+
+ hr = WsCreateReader( NULL, 0, &reader, NULL ) ;
+ ok( hr == S_OK, "got %08x\n", hr );
+ for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ {
+ memset( &date, 0, sizeof(date) );
+ prepare_type_test( reader, tests[i].str, strlen(tests[i].str) );
+ hr = WsReadType( reader, WS_ELEMENT_CONTENT_TYPE_MAPPING, WS_DATETIME_TYPE, NULL,
+ WS_READ_REQUIRED_VALUE, heap, &date, sizeof(date), NULL );
+ ok( hr == tests[i].hr, "%u: got %08x\n", i, hr );
+ if (hr == S_OK)
+ {
+ ok( date.ticks == tests[i].ticks, "%u: got %x%08x\n", i, (ULONG)(date.ticks >> 32), (ULONG)date.ticks );
+ ok( date.format == tests[i].format, "%u: got %u\n", i, date.format );
+ }
+ }
+
+ WsFreeReader( reader );
+ WsFreeHeap( heap );
+}
+
START_TEST(reader)
{
test_WsCreateError();
@@ -2949,4 +3034,5 @@ START_TEST(reader)
test_complex_struct_type();
test_repeating_element();
test_WsResetHeap();
+ test_datetime();
}
diff --git a/include/webservices.h b/include/webservices.h
index 6e30f501b1e..a55072fb5d8 100644
--- a/include/webservices.h
+++ b/include/webservices.h
@@ -57,6 +57,8 @@ typedef struct _WS_PARAMETER_DESCRIPTION WS_PARAMETER_DESCRIPTION;
typedef struct _WS_OPERATION_CONTEXT WS_OPERATION_CONTEXT;
typedef struct _WS_CALL_PROPERTY WS_CALL_PROPERTY;
typedef struct _WS_DOUBLE_DESCRIPTION WS_DOUBLE_DESCRIPTION;
+typedef struct _WS_DATETIME WS_DATETIME;
+typedef struct _WS_DATETIME_DESCRIPTION WS_DATETIME_DESCRIPTION;
struct _WS_STRUCT_DESCRIPTION;
struct _WS_XML_STRING;
@@ -989,6 +991,22 @@ struct _WS_CALL_PROPERTY {
ULONG valueSize;
};
+typedef enum {
+ WS_DATETIME_FORMAT_UTC,
+ WS_DATETIME_FORMAT_LOCAL,
+ WS_DATETIME_FORMAT_NONE
+} WS_DATETIME_FORMAT;
+
+struct _WS_DATETIME {
+ unsigned __int64 ticks;
+ WS_DATETIME_FORMAT format;
+};
+
+struct _WS_DATETIME_DESCRIPTION {
+ WS_DATETIME minValue;
+ WS_DATETIME maxValue;
+};
+
HRESULT WINAPI WsAlloc(WS_HEAP*, SIZE_T, void**, WS_ERROR*);
HRESULT WINAPI WsCall(WS_SERVICE_PROXY*, const WS_OPERATION_DESCRIPTION*, const void**,
WS_HEAP*, const WS_CALL_PROPERTY*, const ULONG, const WS_ASYNC_CONTEXT*,
@@ -1015,6 +1033,8 @@ HRESULT WINAPI WsCreateServiceProxyFromTemplate(WS_CHANNEL_TYPE, const WS_PROXY_
HRESULT WINAPI WsCreateWriter(const WS_XML_WRITER_PROPERTY*, ULONG, WS_XML_WRITER**, WS_ERROR*);
HRESULT WINAPI WsCreateXmlBuffer(WS_HEAP*, const WS_XML_BUFFER_PROPERTY*, ULONG, WS_XML_BUFFER**,
WS_ERROR*);
+HRESULT WINAPI WsDateTimeToFileTime(const WS_DATETIME*, FILETIME*, WS_ERROR*);
+HRESULT WINAPI WsFileTimeToDateTime(const FILETIME*, WS_DATETIME*, WS_ERROR*);
HRESULT WINAPI WsFillReader(WS_XML_READER*, ULONG, const WS_ASYNC_CONTEXT*, WS_ERROR*);
HRESULT WINAPI WsFindAttribute(WS_XML_READER*, const WS_XML_STRING*, const WS_XML_STRING*, BOOL,
ULONG*, WS_ERROR*);