//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. // //********************************************************* // Functions to convert dates back and forth to the ISO8601 format // http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html #include "pch.h" #include "ISO8601.h" #define ISO8601_MAX_USED_CCH 26 // Max amount of characters when generating ISO 8601 strings: YYYY-MM-DDThh:mm:ss.ssssZ + terminating zero // This table defines different "types" of characters for use as the columns // of the state table: // 0 - invalid character // 1 - number // 2 - '-' // 3 - date-time separator ('T', 't' and ' ') // 4 - ':' // 5 - UTC zone ('Z' and 'z') // 6 - '+' // 7 - second-fraction separator ('.' and ',') static const unsigned char iso8601chartable[256] = { // 0 1 2 3 4 5 6 7 8 9 a b c d e f 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 (00 - 0f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 (10 - 1f) 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 2, 7, 0, // 2 (20 - 2f) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, // 3 (30 - 3f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 (40 - 4f) 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, // 5 (50 - 5f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 (60 - 6f) 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, // 7 (70 - 7f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 (80 - 8f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 (90 - 9f) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a (a0 - af) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // b (b0 - bf) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c (c0 - cf) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // d (d0 - df) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e (e0 - ef) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // f (f0 - ff) }; // Parsing state table is made up of WORD values with this meaning: // // | action | | next state | // |_|_|_|_|_|_|_|_| |_|_|_|_|_|_|_|_| // HIBYTE LOBYTE #define STENTRY WORD #define S(a,b) MAKEWORD(b,a) #define ACTION(a) HIBYTE(a) #define STATE(a) LOBYTE(a) // Actions #define _OK_ 0x00 // valid character #define _NXT 0x01 // end of a segment, move to next #define _TZM 0x02 // starting '-' time zone offset #define _TZP 0x03 // starting '+' time zone offset #define _MSC 0x04 // millisecond digit #define _TZU 0x05 // time zone UTC #define _ERR 0x80 // error, invalid character #define ___ERROR____ MAKEWORD(0x00, _ERR) // State table #define STATE_TABLE_DIM 8 static const STENTRY iso8601StateTable[][STATE_TABLE_DIM] = { // unknown number '-' 'T' ':' 'Z' '+' '.' ___ERROR____, S(_OK_,0x01), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x00 year ___ERROR____, S(_OK_,0x02), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x01 ___ERROR____, S(_OK_,0x03), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x02 ___ERROR____, S(_NXT,0x04), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x03 ___ERROR____, S(_OK_,0x06), S(_OK_,0x05), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x04 month ___ERROR____, S(_OK_,0x06), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x05 ___ERROR____, S(_NXT,0x07), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x06 ___ERROR____, S(_OK_,0x09), S(_OK_,0x08), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x07 day ___ERROR____, S(_OK_,0x09), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x08 ___ERROR____, S(_NXT,0x0a), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x09 ___ERROR____, S(_OK_,0x0c), ___ERROR____, S(_OK_,0x0b), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x0a hour ___ERROR____, S(_OK_,0x0c), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x0b ___ERROR____, S(_NXT,0x0d), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x0c ___ERROR____, S(_OK_,0x0f), S(_TZM,0x15), ___ERROR____, S(_OK_,0x0e), S(_TZU,0x1b), S(_TZP,0x15), ___ERROR____, //0x0d min ___ERROR____, S(_OK_,0x0f), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x0e ___ERROR____, S(_NXT,0x10), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x0f ___ERROR____, S(_OK_,0x12), S(_TZM,0x15), ___ERROR____, S(_OK_,0x11), S(_TZU,0x1b), S(_TZP,0x15), ___ERROR____, //0x10 sec ___ERROR____, S(_OK_,0x12), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x11 ___ERROR____, S(_NXT,0x13), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x12 ___ERROR____, ___ERROR____, S(_TZM,0x15), ___ERROR____, ___ERROR____, S(_TZU,0x1b), S(_TZP,0x15), S(_OK_,0x14), //0x13 '.' or 'Z' or '+/-' ___ERROR____, S(_MSC,0x14), S(_TZM,0x15), ___ERROR____, ___ERROR____, S(_TZU,0x1b), S(_TZP,0x15), ___ERROR____, //0x14 fragment of a second ___ERROR____, S(_OK_,0x16), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x15 TZ offset - hour ___ERROR____, S(_NXT,0x17), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x16 ___ERROR____, S(_OK_,0x19), ___ERROR____, ___ERROR____, S(_OK_,0x18), ___ERROR____, ___ERROR____, ___ERROR____, //0x17 TZ offset - minute ___ERROR____, S(_OK_,0x19), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x18 ___ERROR____, S(_NXT,0x1a), ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x19 ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x1a offset done - parsing done ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, ___ERROR____, //0x1b UTC zone done - parsing done }; #define IsLeapYear(YEARS) ( \ (((YEARS) % 400 == 0) || \ ((YEARS) % 100 != 0) && ((YEARS) % 4 == 0)) ? \ TRUE \ : \ FALSE \ ) static HRESULT _GetNumDaysForYearMonth( WORD wYear, WORD wMonth, __out WORD *pwDays) { HRESULT hr = S_OK; if (NULL == pwDays || wMonth < 1 || wMonth > 12 || wYear < 1601 || wYear > 9999) { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { static const WORD s_wNumDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; *pwDays = s_wNumDays[wMonth - 1]; // Check for leap year if ((wMonth == 2) && IsLeapYear(wYear)) { (*pwDays)++; } } return hr; } #define ONE_SECOND 10000000ui64 // number of 100-nanosecond intervals in a second #define ONE_MINUTE (ONE_SECOND * 60) // number of 100-nanosecond intervals in a minute #define ONE_HOUR (ONE_MINUTE * 60) // number of 100-nanosecond intervals in an hour typedef union tagTU { FILETIME ft; ULARGE_INTEGER ui; } TU; static HRESULT _AddTZOffset( Iso8601ParsingStage ips, WORD wValue, int iTZDirection, __inout SYSTEMTIME *pSysTime) { HRESULT hr = S_OK; _ASSERTE((ips == IPS_TZHOUR && wValue < 24) || (ips == IPS_TZMINUTE && wValue < 60)); _ASSERTE(iTZDirection == 1 || iTZDirection == -1); // Convert SYSTEMTIME to FILETIME to perform date-time arithmetic TU ftTime = {0}; if (SystemTimeToFileTime(pSysTime, &ftTime.ft)) { ULARGE_INTEGER ulOffset; ulOffset.QuadPart = (ips == IPS_TZHOUR) ? ONE_HOUR * wValue : ONE_MINUTE * wValue; if (iTZDirection > 0) { ftTime.ui.QuadPart += ulOffset.QuadPart; } else { ftTime.ui.QuadPart -= ulOffset.QuadPart; } if (!FileTimeToSystemTime(&ftTime.ft, pSysTime)) { hr = HRESULT_FROM_WIN32( GetLastError() ); } } else { hr = HRESULT_FROM_WIN32( GetLastError() ); } return hr; } static HRESULT _CheckValueAndAddToSysTime( Iso8601ParsingStage ips, WORD wValue, WORD wMSDigits, int iTZDirection, __inout SYSTEMTIME *pSysTime) { HRESULT hr = S_OK; WORD wHiLimit = 0; WORD wLoLimit = 0; WORD *pDateWord = NULL; _ASSERTE((iTZDirection == 0 && ips != IPS_TZHOUR && ips != IPS_TZMINUTE) || (iTZDirection != 0 && (ips == IPS_TZHOUR || ips == IPS_TZMINUTE))); _ASSERTE((wMSDigits == 0 && ips != IPS_MILLISECOND) || (wMSDigits > 0 && wMSDigits < 4 && ips == IPS_MILLISECOND)); switch (ips) { case IPS_YEAR: wLoLimit = 1601; wHiLimit = 9999; pDateWord = (WORD *) &(pSysTime->wYear); break; case IPS_MONTH: wLoLimit = 1; wHiLimit = 12; pDateWord = (WORD *) &(pSysTime->wMonth); break; case IPS_DAY: wLoLimit = 1; hr = _GetNumDaysForYearMonth(pSysTime->wYear, pSysTime->wMonth, &wHiLimit); _ASSERTE(SUCCEEDED(hr)); // internal call, should never fail pDateWord = (WORD *) &(pSysTime->wDay); break; case IPS_HOUR: wLoLimit = 0; wHiLimit = 23; pDateWord = (WORD *) &(pSysTime->wHour); break; case IPS_MINUTE: wLoLimit = 0; wHiLimit = 59; pDateWord = (WORD *) &(pSysTime->wMinute); break; case IPS_SECOND: wLoLimit = 0; wHiLimit = 59; pDateWord = (WORD *) &(pSysTime->wSecond); break; case IPS_MILLISECOND: wLoLimit = 0; wHiLimit = 999; while (wMSDigits++ < 3) { wValue *= 10; } pDateWord = (WORD *) &(pSysTime->wMilliseconds); break; case IPS_TZHOUR: wLoLimit = 0; wHiLimit = 23; // valid offsets appear to be -12 to +14, but we'll allow any valid hour value pDateWord = (WORD *) &(pSysTime->wHour); break; case IPS_TZMINUTE: wLoLimit = 0; wHiLimit = 59; pDateWord = (WORD *) &(pSysTime->wMinute); break; default: hr = E_UNEXPECTED; break; } if (SUCCEEDED(hr)) { _ASSERTE(NULL != pDateWord); if (wValue < wLoLimit || wValue > wHiLimit) { hr = E_ABORT; } else { _ASSERTE(*pDateWord == 0 || ips >= IPS_MILLISECOND); if (ips != IPS_TZHOUR && ips != IPS_TZMINUTE) { *pDateWord = wValue; } else { _ASSERTE(iTZDirection == 1 || iTZDirection == -1); // handle offset rollover - can affect entire date hr = _AddTZOffset(ips, wValue, iTZDirection, pSysTime); } } } return hr; } static HRESULT _iso8601ToSysTime( const char *pszisoDate, __out SYSTEMTIME *pSysTime, __out Iso8601ParsingStage *pips) { HRESULT hr = S_OK; if (NULL == pszisoDate || NULL == pSysTime || NULL == pips || *pszisoDate == '\0') { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { ZeroMemory(pSysTime, sizeof(SYSTEMTIME)); *pips = IPS_INVALID; BYTE action = 0; BYTE state = 0; WORD DateWord = 0; Iso8601ParsingStage ips = IPS_YEAR; int iTZDirection = 0; WORD wMSDigits = 0; // Main state machine loop while(*pszisoDate && SUCCEEDED(hr)) { unsigned char code = iso8601chartable[*pszisoDate]; // Prevent overflows - sanity check if ((code >= STATE_TABLE_DIM) || (state >= ARRAYSIZE(iso8601StateTable))) { hr = E_UNEXPECTED; break; } STENTRY stValue = iso8601StateTable[state][code]; state = STATE(stValue); action = ACTION(stValue); switch(action) { case _OK_: // input OK, valid character case _NXT: // finish piece and advance to next stage, valid character { if (code == 1) { DateWord = (DateWord * 10) + (*pszisoDate - '0'); } if (action == _NXT) { hr = _CheckValueAndAddToSysTime(ips, DateWord, 0 /*wMSDigits*/, iTZDirection, pSysTime); DateWord = 0; if (SUCCEEDED(hr)) { *pips = ips; ips = Iso8601ParsingStage(int(ips) + 1); } } } break; case _TZM: // start '-' timezone offset iTZDirection = 1; DateWord = 0; *pips = ips; ips = IPS_TZHOUR; break; case _TZP: // start '+' timezone offset iTZDirection = -1; DateWord = 0; *pips = ips; ips = IPS_TZHOUR; break; case _MSC: // process millisecond digit _ASSERTE(code == 1 && ips == IPS_MILLISECOND); wMSDigits++; if (wMSDigits < 4) { DateWord = (DateWord * 10) + (*pszisoDate - '0'); hr = _CheckValueAndAddToSysTime(ips, DateWord, wMSDigits, iTZDirection, pSysTime); if (SUCCEEDED(hr)) { *pips = IPS_MILLISECOND; } } break; case _TZU: // 'Z' for UTC zone *pips = IPS_TZUTC; ips = IPS_TZUTC; break; case _ERR: // error, invalid character _ASSERTE(state == 0x00); hr = E_ABORT; break; default: hr = E_UNEXPECTED; break; } pszisoDate++; } if (SUCCEEDED(hr) && *pszisoDate == '\0') { if (action != _NXT && action != _MSC && action != _TZU) { hr = E_ABORT; } } if (hr != S_OK && hr != E_ABORT) { *pips = IPS_INVALID; } } _ASSERTE(hr != E_UNEXPECTED); return hr; } static HRESULT _iso8601ToFileTime( const char *pszisoDate, __out FILETIME *pftTime, __out Iso8601ParsingStage *pips) { HRESULT hr = S_OK; SYSTEMTIME stTime = {0}; hr = _iso8601ToSysTime(pszisoDate, &stTime, pips); if (SUCCEEDED(hr)) { if (!SystemTimeToFileTime(&stTime, pftTime)) { hr = E_FAIL; } } return hr; } static HRESULT _SysTimeToiso8601( __in SYSTEMTIME *pstTime, __in BOOL fGeneratePartial, __out_ecount(cch) char *pszBuf, __in ULONG cch, __in BOOL fUseShortTimeFormat = FALSE ) { HRESULT hr = S_OK; if (NULL == pstTime || NULL == pszBuf || cch < ISO8601_MAX_USED_CCH) { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { if (pstTime->wYear < 1601 || // FILETIME cannot handle less pstTime->wYear > 9999 || // ISO8601 has four digits for a year pstTime->wMonth > 12 || pstTime->wDay > 31 || pstTime->wHour > 24 || pstTime->wMinute > 59 || pstTime->wSecond > 59 || pstTime->wMilliseconds > 999) { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { WORD wDays = 0; hr = _GetNumDaysForYearMonth(pstTime->wYear, pstTime->wMonth, &wDays); if (SUCCEEDED(hr) && pstTime->wDay > wDays) { hr = E_INVALIDARG; } } } if (SUCCEEDED(hr)) { pszBuf[0] = static_cast((pstTime->wYear / 1000) + '0'); pszBuf[1] = static_cast(((pstTime->wYear / 100) % 10) + '0'); pszBuf[2] = static_cast(((pstTime->wYear / 10) % 10) + '0'); pszBuf[3] = static_cast(((pstTime->wYear) % 10) + '0'); if (pstTime->wMonth > 0 || !fGeneratePartial) { pszBuf[4] = '-'; pszBuf[5] = static_cast((pstTime->wMonth / 10) + '0'); pszBuf[6] = static_cast((pstTime->wMonth % 10) + '0'); if (pstTime->wDay > 0 || !fGeneratePartial) { pszBuf[7] = '-'; pszBuf[8] = static_cast((pstTime->wDay / 10) + '0'); pszBuf[9] = static_cast((pstTime->wDay % 10) + '0'); if (pstTime->wHour != 0 || pstTime->wMinute != 0 || pstTime->wSecond != 0 || pstTime->wMilliseconds != 0 || !fGeneratePartial) { pszBuf[10] = 'T'; pszBuf[11] = static_cast(pstTime->wHour / 10 + '0'); pszBuf[12] = static_cast((pstTime->wHour % 10) + '0'); pszBuf[13] = ':'; pszBuf[14] = static_cast(pstTime->wMinute / 10 + '0'); pszBuf[15] = static_cast((pstTime->wMinute % 10) + '0'); pszBuf[16] = ':'; pszBuf[17] = static_cast(pstTime->wSecond / 10 + '0'); pszBuf[18] = static_cast((pstTime->wSecond % 10) + '0'); if ( !fUseShortTimeFormat && ( pstTime->wMilliseconds != 0 ) ) { // YYYY-MM-DDThh:mm:ss.ssssZ pszBuf[19] = '.'; pszBuf[20] = static_cast(pstTime->wMilliseconds / 100 + '0'); pszBuf[21] = static_cast(((pstTime->wMilliseconds / 10) % 10) + '0'); pszBuf[22] = static_cast((pstTime->wMilliseconds % 10) + '0'); // pad the last digit of millisecond with 0 pszBuf[23] = '0'; pszBuf[24] = 'Z'; pszBuf[25] = 0; } else { // YYYY-MM-DDThh:mm:ssZ pszBuf[19] = 'Z'; pszBuf[20] = 0; } } else { // YYYY-MM-DD pszBuf[10] = 0; } } else { // YYYY-MM pszBuf[7] = 0; } } else { // YYYY pszBuf[4] = 0; } } return hr; } static HRESULT _FileTimeToiso8601( const FILETIME *pftTime, BOOL fGeneratePartial, __out_ecount(cch) char *pszBuf, ULONG cch, BOOL fUseShortTimeFormat ) { HRESULT hr = S_OK; SYSTEMTIME stTime = {0}; if (NULL == pftTime) { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { if (FileTimeToSystemTime( pftTime, &stTime)) { hr = _SysTimeToiso8601( &stTime, fGeneratePartial, pszBuf, cch, fUseShortTimeFormat ); } else { hr = E_FAIL; } } return hr; } HRESULT FILETIMEToISO8601W( __in const FILETIME* pft, __in BOOL fGeneratePartial, __out_ecount(cchISO8601) PWSTR pszISO8601, __in size_t cchISO8601, __in BOOL fUseShortTimeFormat ) { HRESULT hr = S_OK; CHAR aszISO8601[ISO8601_MAX_USED_CCH]; hr = _FileTimeToiso8601( pft, fGeneratePartial, aszISO8601, ARRAYSIZE(aszISO8601), fUseShortTimeFormat ); if (SUCCEEDED(hr)) { int cchSHAnsiToUnicode; hr = SizeTToInt(cchISO8601, &cchSHAnsiToUnicode); if (SUCCEEDED(hr)) { hr = ( ISO8601_MAX_USED_CCH >= ::MultiByteToWideChar( CP_ACP, 0, aszISO8601, -1, pszISO8601, cchSHAnsiToUnicode) ) ? S_OK : E_UNEXPECTED; _ASSERTE(SUCCEEDED(hr)); } } return hr; } HRESULT ISO8601ToFILETIMEW( __in PCWSTR pszISO8601, __out FILETIME* pft, __out Iso8601ParsingStage *pips) { HRESULT hr = S_OK; CHAR aszISO8601[ISO8601_MAX_CCH]; if( !::WideCharToMultiByte( CP_ACP, 0, pszISO8601, -1, aszISO8601, ISO8601_MAX_CCH, NULL, NULL ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); } if (SUCCEEDED(hr)) { hr = _iso8601ToFileTime(aszISO8601, pft, pips); } return hr; } HRESULT SYSTEMTIMEToISO8601ExW( __in const SYSTEMTIME* pst, __in BOOL fGeneratePartial, __out_ecount(cchISO8601) PWSTR pszISO8601, __in size_t cchISO8601) { HRESULT hr = S_OK; SYSTEMTIME st = *pst; CHAR aszISO8601[ISO8601_MAX_USED_CCH]; hr = _SysTimeToiso8601(&st, fGeneratePartial, aszISO8601, ARRAYSIZE(aszISO8601)); if (SUCCEEDED(hr)) { int cchSHAnsiToUnicode; hr = SizeTToInt(cchISO8601, &cchSHAnsiToUnicode); if (SUCCEEDED(hr)) { hr = ( ISO8601_MAX_USED_CCH >= ::MultiByteToWideChar( CP_ACP, 0, aszISO8601, -1, pszISO8601, cchSHAnsiToUnicode ) ) ? S_OK : E_UNEXPECTED; _ASSERTE(SUCCEEDED(hr)); } } return hr; } HRESULT SYSTEMTIMEToISO8601W( __in const SYSTEMTIME* pst, __out_ecount(cchISO8601) PWSTR pszISO8601, __in size_t cchISO8601) { return SYSTEMTIMEToISO8601ExW(pst, FALSE /*fGeneratePartial*/, pszISO8601, cchISO8601); } HRESULT ISO8601ToSYSTEMTIMEExW( __in PCWSTR pszISO8601, __out SYSTEMTIME* pst, __out Iso8601ParsingStage *pips) { HRESULT hr = S_OK; CHAR aszISO8601[ISO8601_MAX_CCH]; if( !::WideCharToMultiByte( CP_ACP, 0, pszISO8601, -1, aszISO8601, ISO8601_MAX_CCH, NULL, NULL ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); } if (SUCCEEDED(hr)) { hr = _iso8601ToSysTime(aszISO8601, pst, pips); } return hr; } HRESULT ISO8601ToSYSTEMTIMEW( __in PCWSTR pszISO8601, __out SYSTEMTIME* pst) { HRESULT hr = S_OK; Iso8601ParsingStage ips = IPS_INVALID; hr = ISO8601ToSYSTEMTIMEExW(pszISO8601, pst, &ips); // Fix up less-than-full-date if (SUCCEEDED(hr) && ips < IPS_DAY) { if (ips < IPS_DAY) { pst->wDay = 1; } if (ips < IPS_MONTH) { pst->wMonth = 1; } hr = S_OK; } return hr; }