* ole/ole2nls.c, include/winnls.h: Chris Morgan <cmorgan@alum.wpi.edu> GetDateFormat() and GetTimeFormat() should check the validity of the time/date structure it is processing. Add comments to describe flags behavior. Add support for TIME_FORCE24HOURFORMAT, TIME_NOMINUTESORSECONDS, TIME_NOSECONDS and TIME_NOTIMEMARKER. Add check in GetDateFormatW() for invalid flag combinations. Added some missing DATE_* defines to winnls.h. Behavior verified against NT4.0. * dlls/kernel/tests/locale.c: Chris Morgan <cmorgan@alum.wpi.edu> Add additional tests for GetTimeFormat() and GetDateFormat(). Uncomment wine_todo tests that now succeed with fixes to GetTime/DateFormat().
Index: ole/ole2nls.c =================================================================== RCS file: /home/wine/wine/ole/ole2nls.c,v retrieving revision 1.109 diff -u -r1.109 ole2nls.c --- ole/ole2nls.c 12 Dec 2002 03:55:45 -0000 1.109 +++ ole/ole2nls.c 18 Dec 2002 05:03:48 -0000 @@ -1357,7 +1357,22 @@ '' used to quote literal characters '' (within a quoted string) indicates a literal ' + If TIME_NOMINUTESORSECONDS or TIME_NOSECONDS is specified, the function + removes the separator(s) preceding the minutes and/or seconds element(s). + + If TIME_NOTIMEMARKER is specified, the function removes the separator(s) + preceding and following the time marker. + + If TIME_FORCE24HOURFORMAT is specified, the function displays any existing + time marker, unless the TIME_NOTIMEMARKER flag is also set. + These functions REQUIRE valid locale, date, and format. + + If the time or date is invalid, return 0 and set ERROR_INVALID_PARAMETER + + Return value is the number of characters written, or if outlen is zero + it is the number of characters required for the output including + the terminating null. */ static INT OLE_GetFormatW(LCID locale, DWORD flags, DWORD tflags, const SYSTEMTIME* xtime, @@ -1365,6 +1380,12 @@ LPWSTR output, INT outlen, int dateformat) { INT outpos; + INT lastFormatPos; /* the position in the output buffer of */ + /* the end of the output from the last formatting */ + /* character */ + BOOL dropUntilNextFormattingChar = FALSE; /* TIME_NOTIMEMARKER drops + all of the text around the dropped marker, + eg. "h@!t@!m" becomes "hm" */ /* make a debug report */ TRACE("args: 0x%lx, 0x%lx, 0x%lx, time(d=%d,h=%d,m=%d,s=%d), fmt:%s (at %p), " @@ -1375,25 +1396,49 @@ /* initialize state variables */ outpos = 0; + lastFormatPos = 0; while (*format) { /* Literal string: Maybe terminated early by a \0 */ - if (*format == (WCHAR) '\'') { + if (*format == (WCHAR) '\'') + { format++; - while (*format) { - if (*format == (WCHAR) '\'') { + + /* We loop while we haven't reached the end of the format string */ + /* and until we haven't found another "'" character */ + while (*format) + { + /* we found what might be the close single quote mark */ + /* we need to make sure there isn't another single quote */ + /* after it, if there is we need to skip over this quote mark */ + /* as the user is trying to put a single quote mark in their output */ + if (*format == (WCHAR) '\'') + { format++; - if (*format != '\'') { + if (*format != '\'') + { break; /* It was a terminating quote */ } } + + /* if outlen is zero then we are couting the number of */ + /* characters of space we need to output this text, don't */ + /* modify the output buffer */ if (!outlen) - /* We are counting */; - else if (outpos >= outlen) + { + outpos++; /* We are counting */; + } else if (outpos >= outlen) + { goto too_short; - else - output[outpos] = *format; - outpos++; + } else + { + /* even drop literal strings */ + if(!dropUntilNextFormattingChar) + { + output[outpos] = *format; + outpos++; + } + } format++; } } else if ( (dateformat && (*format=='d' || @@ -1404,15 +1449,28 @@ *format=='h' || *format=='m' || *format=='s' || - *format=='t') ) ) { + *format=='t') ) ) + /* if processing a date and we have a date formatting character, OR */ + /* if we are processing a time and we have a time formatting character */ + { int type, count; char tmp[16]; WCHAR buf[40]; int buflen=0; type = *format; format++; + + /* clear out the drop text flag if we are in here */ + dropUntilNextFormattingChar = FALSE; + + /* count up the number of the same letter values in a row that */ + /* we get, this lets us distinguish between "s" and "ss" and it */ + /* eliminates the duplicate to simplify the below case statement */ for (count = 1; *format == type; format++) count++; + + buf[0] = 0; /* always null terminate the buffer */ + switch(type) { case 'd': @@ -1466,38 +1524,72 @@ break; case 'h': - /* hours 1:00-12:00 --- is this right? */ - sprintf( tmp, "%.*d", count > 2 ? 2 : count, (xtime->wHour-1)%12 +1); - MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); - break; - + /* fallthrough if we are forced to output in 24 hour format */ + if(!(tflags & TIME_FORCE24HOURFORMAT)) + { + /* hours 1:00-12:00 --- is this right? */ + /* NOTE: 0000 hours is also 12 midnight */ + sprintf( tmp, "%.*d", count > 2 ? 2 : count, + xtime->wHour == 0 ? 12 : (xtime->wHour-1)%12 +1); + MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); + break; + } case 'H': sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wHour ); MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); break; case 'm': - sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wMinute ); - MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); + /* if TIME_NOMINUTESORSECONDS don't display minutes */ + if(!(tflags & TIME_NOMINUTESORSECONDS)) + { + sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wMinute ); + MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); + } else + { + outpos = lastFormatPos; + } break; case 's': - sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wSecond ); - MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); + /* if we have a TIME_NOSECONDS or TIME_NOMINUTESORSECONDS + flag then don't display seconds */ + if(!(tflags & TIME_NOSECONDS) && !(tflags & + TIME_NOMINUTESORSECONDS)) + { + sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wSecond ); + MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); + } else + { + outpos = lastFormatPos; + } break; case 't': - GetLocaleInfoW(locale, (xtime->wHour < 12) ? + if(!(tflags & TIME_NOTIMEMARKER)) + { + GetLocaleInfoW(locale, (xtime->wHour < 12) ? LOCALE_S1159 : LOCALE_S2359, buf, sizeof(buf) ); - if (count == 1) { - buf[1] = 0; + if (count == 1) + { + buf[1] = 0; + } + } else + { + outpos = lastFormatPos; /* remove any prior text up until + the output due to formatting characters */ + dropUntilNextFormattingChar = TRUE; /* drop everything + until we hit the next formatting character */ } break; } /* cat buf onto the output */ buflen = strlenW(buf); + + /* we are counting how many characters we need for output */ + /* don't modify the output buffer... */ if (!outlen) /* We are counting */; else if (outpos + buflen < outlen) { @@ -1508,15 +1600,27 @@ goto too_short; } outpos += buflen; - } else { + lastFormatPos = outpos; /* record the end of the formatting text we just output */ + } else /* we are processing a NON formatting character */ + { /* a literal character */ if (!outlen) - /* We are counting */; + { + outpos++; /* We are counting */; + } else if (outpos >= outlen) + { goto too_short; - else - output[outpos] = *format; - outpos++; + } + else /* just copy the character into the output buffer */ + { + /* unless we are dropping characters */ + if(!dropUntilNextFormattingChar) + { + output[outpos] = *format; + outpos++; + } + } format++; } } @@ -1528,7 +1632,8 @@ goto too_short; else output[outpos] = '\0'; - outpos++; + + outpos++; /* add one for the terminating null character */ TRACE(" returning %d %s\n", outpos, debugstr_w(output)); return outpos; @@ -1624,9 +1729,9 @@ TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", locale,flags,xtime,debugstr_w(format),date,datelen); - /* Tests (could be left until OLE_GetFormatW) */ - if (flags && format) - { + /* Tests */ + if (flags && format) /* if lpFormat is non-null then flags must be zero */ + { SetLastError (ERROR_INVALID_FLAGS); return 0; } @@ -1635,26 +1740,62 @@ SetLastError (ERROR_INVALID_PARAMETER); return 0; } - if (!locale) { + if (!locale) + { locale = LOCALE_SYSTEM_DEFAULT; }; - if (locale == LOCALE_SYSTEM_DEFAULT) { + if (locale == LOCALE_SYSTEM_DEFAULT) + { thislocale = GetSystemDefaultLCID(); - } else if (locale == LOCALE_USER_DEFAULT) { + } else if (locale == LOCALE_USER_DEFAULT) + { thislocale = GetUserDefaultLCID(); - } else { + } else + { thislocale = locale; }; - - if (xtime == NULL) { + + /* check for invalid flag combinations */ + if((flags & DATE_LTRREADING) && (flags & DATE_RTLREADING)) + { + SetLastError (ERROR_INVALID_FLAGS); + return 0; + } + + /* DATE_SHORTDATE, DATE_LONGDATE and DATE_YEARMONTH are mutually */ + /* exclusive */ + if((flags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH)) + && !((flags & DATE_SHORTDATE) ^ (flags & + DATE_LONGDATE) ^ (flags & DATE_YEARMONTH))) + { + SetLastError (ERROR_INVALID_FLAGS); + return 0; + } + + /* if the user didn't pass in a pointer to the current time we read it */ + /* here */ + if (xtime == NULL) + { GetSystemTime(&t); - } else { + } else + { + /* NOTE: check here before we perform the SystemTimeToFileTime conversion */ + /* because this conversion will fix invalid time values */ + /* check to see if the time/date is valid */ + /* set ERROR_INVALID_PARAMETER and return 0 if invalid */ + if((xtime->wDay > 31) || (xtime->wDayOfWeek > 6) || (xtime->wMonth > 12)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + /* Silently correct wDayOfWeek by transforming to FileTime and back again */ res=SystemTimeToFileTime(xtime,&ft); + /* Check year(?)/month and date for range and set ERROR_INVALID_PARAMETER on error */ - /*FIXME: SystemTimeToFileTime doesn't yet do that check */ - if(!res) { + if(!res) + { SetLastError(ERROR_INVALID_PARAMETER); return 0; } @@ -1663,13 +1804,15 @@ }; thistime = &t; - if (format == NULL) { + if (format == NULL) + { GetLocaleInfoW(thislocale, ((flags&DATE_LONGDATE) ? LOCALE_SLONGDATE : LOCALE_SSHORTDATE), format_buf, sizeof(format_buf)/sizeof(*format_buf)); thisformat = format_buf; - } else { + } else + { thisformat = format; }; @@ -2758,6 +2901,8 @@ /****************************************************************************** * GetTimeFormatW [KERNEL32.@] * Makes a Unicode string of the time + * + * NOTE: See OLE_GetFormatW() for further documentation */ INT WINAPI GetTimeFormatW(LCID locale, /* [in] */ @@ -2779,15 +2924,29 @@ thislocale = OLE2NLS_CheckLocale ( locale ); + /* if the user didn't specify a format we use the default */ + /* format for this locale */ if (format == NULL) - { if (flags & LOCALE_NOUSEROVERRIDE) /* use system default */ - { thislocale = GetSystemDefaultLCID(); + { + if (flags & LOCALE_NOUSEROVERRIDE) /* use system default */ + { + thislocale = GetSystemDefaultLCID(); } GetLocaleInfoW(thislocale, thisflags, format_buf, 40); thisformat = format_buf; } else - { thisformat = format; + { + /* if non-null format and LOCALE_NOUSEROVERRIDE then fail */ + /* NOTE: this could be either invalid flags or invalid parameter */ + /* windows sets it to invalid flags */ + if (flags & LOCALE_NOUSEROVERRIDE) + { + SetLastError(ERROR_INVALID_FLAGS); + return 0; + } + + thisformat = format; } if (xtime == NULL) /* NULL means use the current local time */ @@ -2795,7 +2954,15 @@ thistime = &t; } else - { thistime = xtime; + { + /* check time values */ + if((xtime->wHour > 24) || (xtime->wMinute >= 60) || (xtime->wSecond >= 60)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + thistime = xtime; } ret = OLE_GetFormatW(thislocale, thisflags, flags, thistime, thisformat, Index: include/winnls.h =================================================================== RCS file: /home/wine/wine/include/winnls.h,v retrieving revision 1.37 diff -u -r1.37 winnls.h --- include/winnls.h 4 Nov 2002 22:39:45 -0000 1.37 +++ include/winnls.h 18 Dec 2002 05:03:48 -0000 @@ -196,6 +196,10 @@ #define DATE_USE_ALT_CALENDAR 0x00000004 /* use alternate calendar */ /* alt. calendar support is broken anyway */ +#define DATE_YEARMONTH 0x00000008 /* use year/month */ +#define DATE_LTRREADING 0x00000010 /* left to right reading order */ +#define DATE_RTLREADING 0x00000020 /* right to left reading order */ + #define TIME_FORCE24HOURFORMAT 0x00000008 /* force 24 hour format*/ #define TIME_NOTIMEMARKER 0x00000004 /* show no AM/PM */ #define TIME_NOSECONDS 0x00000002 /* show no seconds */
Index: dlls/kernel/tests/locale.c =================================================================== RCS file: /home/wine/wine/dlls/kernel/tests/locale.c,v retrieving revision 1.9 diff -u -r1.9 locale.c --- dlls/kernel/tests/locale.c 12 Dec 2002 22:25:23 -0000 1.9 +++ dlls/kernel/tests/locale.c 18 Dec 2002 05:01:51 -0000 @@ -27,7 +27,7 @@ #define eq(received, expected, label, type) \ ok((received) == (expected), "%s: got " type " instead of " type, (label),(received),(expected)) -#define BUFFER_SIZE 50 +#define BUFFER_SIZE 128 /* Buffer used by callback function */ char GlobalBuffer[BUFFER_SIZE]; #define COUNTOF(x) (sizeof(x)/sizeof(x)[0]) @@ -43,17 +43,17 @@ void TestGetLocaleInfoA() { -int ret, cmp; -LCID lcid; -char buffer[BUFFER_SIZE], Expected[BUFFER_SIZE]; + int ret, cmp; + LCID lcid; + char buffer[BUFFER_SIZE], Expected[BUFFER_SIZE]; strcpy(Expected, "Monday"); lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT ); ok (lcid == 0x409, "wrong LCID calculated"); - /* HTMLKit and "Font xplorer lite" expect GetLocaleInfoA to - * partially fill the buffer even if it is too short. See bug 637. - */ + /* HTMLKit and "Font xplorer lite" expect GetLocaleInfoA to + * partially fill the buffer even if it is too short. See bug 637. + */ strcpy(Expected, "xxxxx"); memset( buffer, 'x', sizeof(buffer) ); ret = GetLocaleInfoA(lcid, LOCALE_SDAYNAME1, buffer, 0); @@ -90,103 +90,425 @@ void TestGetTimeFormatA() { -int ret, error, cmp; -SYSTEMTIME curtime; -char buffer[BUFFER_SIZE], format[BUFFER_SIZE], Expected[BUFFER_SIZE]; -LCID lcid; + int ret, error, cmp; + SYSTEMTIME curtime; + char buffer[BUFFER_SIZE], format[BUFFER_SIZE], Expected[BUFFER_SIZE]; + LCID lcid; + + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT ); + strcpy(format, "tt HH':'mm'@'ss"); + + /* fill curtime with dummy data */ + memset(&curtime, 2, sizeof(SYSTEMTIME)); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, COUNTOF(buffer)); + error = GetLastError (); + ok (ret == 0, "GetTimeFormat should fail on dummy data"); + eq (error, ERROR_INVALID_PARAMETER, "GetTimeFormat GetLastError()", "%d"); + SetLastError(NO_ERROR); /* clear out the last error */ + + /* test that we can correctly produce the expected output, not a very */ + /* demanding test ;-) */ + strcpy(Expected, "AM 08:56@13"); + curtime.wHour = 8; curtime.wMinute = 56; + curtime.wSecond = 13; curtime.wMilliseconds = 22; + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, COUNTOF(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetTimeFormat got %s instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* check that the size reported by the above call is accuate */ + memset(buffer, 'x', sizeof(buffer)); + ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, ret); + ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, + "GetTimeFormat(right size): ret=%d error=%ld\n",ret,GetLastError()); + ok(buffer[0] != 'x',"GetTimeFormat(right size): buffer=[%s]\n",buffer); - lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT ); - strcpy(format, "tt HH':'mm'@'ss"); + /* test failure due to insufficent buffer */ + ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, 2); + ok(ret==0 && GetLastError()==ERROR_INSUFFICIENT_BUFFER, + "GetTimeFormat(len=2): ret=%d error=%ld", ret, GetLastError()); - todo_wine { - /* fill curtime with dummy data */ - memset(&curtime, 2, sizeof(SYSTEMTIME)); - ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, COUNTOF(buffer)); - error = GetLastError (); - ok (ret == 0, "GetTimeFormat should fail on dummy data"); - eq (error, ERROR_INVALID_PARAMETER, "GetTimeFormat GetLastError()", "%d"); - } - - strcpy(Expected, "AM 08:56@13"); - curtime.wHour = 8; curtime.wMinute = 56; - curtime.wSecond = 13; curtime.wMilliseconds = 22; - ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, COUNTOF(buffer)); - cmp = strncmp (Expected, buffer, strlen(Expected)+1); - ok (cmp == 0, "GetTimeFormat got %s instead of %s", buffer, Expected); - eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); - - /* test with too small buffers */ - SetLastError(0); - ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, NULL, 0); - ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, + /* test with too small buffers */ + SetLastError(0); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, NULL, 0); + ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, "GetTimeFormat(len=0): ret=%d error=%ld\n",ret,GetLastError()); - memset(buffer, 'x', sizeof(buffer)); - ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, ret); - ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, - "GetTimeFormat(right size): ret=%d error=%ld\n",ret,GetLastError()); - ok(buffer[0]!='x',"GetTimeFormat(right size): buffer=[%s]\n",buffer); - - ret = GetTimeFormatA(lcid, TIME_FORCE24HOURFORMAT, &curtime, format, buffer, 2); - ok(ret==0 && GetLastError()==ERROR_INSUFFICIENT_BUFFER, - "GetTimeFormat(len=2): ret=%d error=%ld", ret, GetLastError()); + /************************************/ + /* test out TIME_NOMINUTESORSECONDS */ + strcpy(Expected, "8 AM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOMINUTESORSECONDS, &curtime, NULL, buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test out TIME_NOMINUTESORSECONDS with complex format strings */ + strcpy(Expected, "4"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOMINUTESORSECONDS, &curtime, "m1s2m3s4", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + + /************************************/ + /* test out TIME_NOSECONDS */ + strcpy(Expected, "8:56 AM"); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &curtime, NULL, buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test out TIME_NOSECONDS with a format string of "h:m:s tt" */ + strcpy(Expected, "8:56 AM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &curtime, "h:m:s tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test out TIME_NOSECONDS a strange format string of multiple delimiters "h@:m@:s tt" */ + /* expected behavior is to turn "hD1D2...mD3D4...sD5D6...tt" and turn this into */ + /* "hD1D2...mD3D4...tt" */ + strcpy(Expected, "8.@:56.@:AM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &curtime, "h.@:m.@:s.@:tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test out TIME_NOSECONDS with an string of "1s2s3s4" */ + /* expect to see only "3" */ + strcpy(Expected, "3"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &curtime, "s1s2s3", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /************************/ + /* Test out time marker */ + /* test out time marker(AM/PM) behavior */ + strcpy(Expected, "A/AM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "t/tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* time marker testing part 2 */ + curtime.wHour = 13; + strcpy(Expected, "P/PM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "t/tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /******************************/ + /* test out TIME_NOTIMEMARKER */ + /* NOTE: TIME_NOTIMEMARKER elminates all text around any time marker */ + /* formatting character until the previous or next formatting character */ + strcpy(Expected, "156"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOTIMEMARKER, &curtime, "h1t2tt3m", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /***********************************/ + /* test out TIME_FORCE24HOURFORMAT */ + strcpy(Expected, "13:56:13 PM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, &curtime, "h:m:s tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* check to confirm that unlike what msdn documentation suggests, the time marker */ + /* is not added under TIME_FORCE24HOURFORMAT */ + strcpy(Expected, "13:56:13"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, &curtime, "h:m:s", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of %s", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + + /*********************************************/ + /* test advanced formatting of GetTimeFormat */ + + /* test for 24 hour conversion and for leading zero */ + /* NOTE: we do not test the "hh or HH" case since hours is two digits */ + /* "h hh H HH m mm s ss t tt" */ + curtime.wHour = 14; /* change this to 14 or 2pm */ + curtime.wMinute = 5; + curtime.wSecond = 3; + strcpy(Expected, "2 02 14 14 5 05 3 03 P PM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h hh H HH m mm s ss t tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* complete testing on the advanced formatting by testing "hh" and "HH" */ + /* 0 hour is 12 o'clock or 00 hundred hours */ + curtime.wHour = 0; + strcpy(Expected, "12/0/12/00"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h/H/hh/HH", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test for LOCALE_NOUSEROVERRIDE set, lpFormat must be NULL */ + strcpy(Expected, "0:5:3 AM"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, LOCALE_NOUSEROVERRIDE, &curtime, "h:m:s tt", buffer, sizeof(buffer)); + /* NOTE: we expect this to FAIL */ + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (ret == 0, "GetTimeFormat succeeded instead of failing for LOCALE_NOUSEROVERRIDE and a non-null lpFormat\n"); + + /* try to convert formatting strings with more than two letters */ + /* "h:hh:hhh:H:HH:HHH:m:mm:mmm:M:MM:MMM:s:ss:sss:S:SS:SSS" */ + /* NOTE: we expect any letter for which there is an upper case value */ + /* we should expect to see a replacement. For letters that DO NOT */ + /* have upper case values we expect to see NO REPLACEMENT */ + curtime.wHour = 8; curtime.wMinute = 56; + curtime.wSecond = 13; curtime.wMilliseconds = 22; + strcpy(Expected, "8:08:08 8:08:08 56:56:56 M:MM:MMM 13:13:13 S:SS:SSS"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h:hh:hhh H:HH:HHH m:mm:mmm M:MM:MMM s:ss:sss S:SS:SSS", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test that if the size of the buffer is zero that the buffer is not modified */ + /* and that the number of necessary characters is returned */ + /* NOTE: The count includes the terminating null. */ + strcpy(buffer, "THIS SHOULD NOT BE MODIFIED"); + strcpy(Expected, "THIS SHOULD NOT BE MODIFIED"); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h", buffer, 0); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, 2, "GetTimeFormat", "%d"); /* we expect to require two characters of space from "h" */ + + /* test that characters in single quotation marks are ignored and left in */ + /* the same location in the output string */ + strcpy(Expected, "8 h 8 H 08 HH 56 m 13 s A t AM tt"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h 'h' H 'H' HH 'HH' m 'm' s 's' t 't' tt 'tt'", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test the printing of the single quotation marks when */ + /* we use an invalid formatting string of "'''" instead of "''''" */ + strcpy(Expected, "'"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "'''", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test that msdn suggested single quotation usage works as expected */ + strcpy(Expected, "'"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "''''", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test for more normal use of single quotation mark */ + strcpy(Expected, "08"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "''HHHHHH", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* and test for normal use of the single quotation mark */ + strcpy(Expected, "'HHHHHH"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "'''HHHHHH'", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test for more odd use of the single quotation mark */ + strcpy(Expected, "'HHHHHH"); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "'''HHHHHH", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test that with TIME_NOTIMEMARKER that even if something is defined */ + /* as a literal we drop it before and after the markers until the next */ + /* formatting character */ + strcpy(Expected, ""); + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, TIME_NOTIMEMARKER, &curtime, "'123'tt", buffer, sizeof(buffer)); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok (cmp == 0, "GetTimeFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetTimeFormat", "%d"); + + /* test for expected return and error value when we have a */ + /* non-null format and LOCALE_NOUSEROVERRIDE for flags */ + SetLastError(NO_ERROR); /* reset last error value */ + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, LOCALE_NOUSEROVERRIDE, &curtime, "'123'tt", buffer, sizeof(buffer)); + error = GetLastError(); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok ((ret == 0) && (error == ERROR_INVALID_FLAGS), "GetTimeFormat got ret of '%d' and error of '%d'", ret, error); + + /* test that invalid time values result in ERROR_INVALID_PARAMETER */ + /* and a return value of 0 */ + curtime.wHour = 25; + SetLastError(NO_ERROR); /* reset last error value */ + memset(buffer, '0', sizeof(buffer)); + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "'123'tt", buffer, sizeof(buffer)); + error = GetLastError(); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok ((ret == 0) && (error == ERROR_INVALID_PARAMETER), "GetTimeFormat got ret of '%d' and error of '%d'", ret, error); + + /* test that invalid information in the date section of the current time */ + /* doesn't result in an error since GetTimeFormat() should ignore this information */ + curtime.wHour = 12; /* valid wHour */ + curtime.wMonth = 60; /* very invalid wMonth */ + strcpy(Expected, "12:56:13"); + SetLastError(NO_ERROR); /* reset last error value */ + ret = GetTimeFormatA(LOCALE_SYSTEM_DEFAULT, 0, &curtime, "h:m:s", buffer, sizeof(buffer)); + error = GetLastError(); + cmp = strncmp(buffer, Expected, BUFFER_SIZE); + ok ((ret == lstrlenA(Expected)+1) && (error == NO_ERROR), "GetTimeFormat got ret of '%d' and error of '%d' and a buffer of '%s'", ret, error, buffer); } void TestGetDateFormatA() { -int ret, error, cmp; -SYSTEMTIME curtime; -char buffer[BUFFER_SIZE], format[BUFFER_SIZE], Expected[BUFFER_SIZE]; -LCID lcid; - - lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT ); - strcpy(format, "ddd',' MMM dd yy"); - - todo_wine { - /* fill curtime with dummy data */ - memset(&curtime, 2, sizeof(SYSTEMTIME)); - memset(buffer, 'x', sizeof(buffer)); - ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); - error = GetLastError (); - ok (ret== 0, "GetDateFormat should fail on dummy data"); - eq (error, ERROR_INVALID_PARAMETER, "GetDateFormat", "%d"); - } - - strcpy(Expected, "Sat, May 04 02"); - memset(buffer, 'x', sizeof(buffer)); - curtime.wYear = 2002; - curtime.wMonth = 5; - curtime.wDay = 4; - curtime.wDayOfWeek = 3; - ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); - cmp = strncmp (Expected, buffer, strlen(Expected)+1); - todo_wine { ok (cmp == 0, "GetDateFormat got %s instead of %s", buffer, Expected); } - eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); - - /* test format with "'" */ - memset(buffer, 'x', sizeof(buffer)); - strcpy(format, "ddd',' MMM dd ''''yy"); - strcpy(Expected, "Sat, May 04 '02"); - ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); - cmp = strncmp (Expected, buffer, strlen(Expected)+1); - todo_wine { ok (cmp == 0, "GetDateFormat got %s instead of %s", buffer, Expected); } - eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); - - /* test with too small buffers */ - SetLastError(0); - ret = GetDateFormatA(lcid, 0, &curtime, format, NULL, 0); - ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, - "GetDateFormat(len=0): ret=%d error=%ld\n",ret,GetLastError()); - - memset(buffer, 'x', sizeof(buffer)); - ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, ret); - ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, - "GetDateFormat(right size): ret=%d error=%ld\n",ret,GetLastError()); - ok(buffer[0]!='x',"GetTimeFormat(right size): buffer=[%s]\n",buffer); - - ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, 2); - ok(ret==0 && GetLastError()==ERROR_INSUFFICIENT_BUFFER, + int ret, error, cmp; + SYSTEMTIME curtime; + char buffer[BUFFER_SIZE], format[BUFFER_SIZE], Expected[BUFFER_SIZE]; + LCID lcid; + + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT ); + strcpy(format, "ddd',' MMM dd yy"); + + /* test for failure on dummy data */ + memset(&curtime, 2, sizeof(SYSTEMTIME)); + memset(buffer, '0', sizeof(buffer)); + SetLastError(NO_ERROR); + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); + error = GetLastError (); + ok(ret == 0, "GetDateFormat should fail on dummy data"); + eq(error, ERROR_INVALID_PARAMETER, "GetDateFormat", "%d"); + + /* test for a simple case of date conversion */ + todo_wine { + strcpy(Expected, "Sat, May 04 02"); + curtime.wYear = 2002; + curtime.wMonth = 5; + curtime.wDay = 4; + curtime.wDayOfWeek = 3; + memset(buffer, 0, sizeof(buffer)); + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetDateFormat got %s instead of %s", buffer, Expected); +/* Uncomment the below when todo_wine is removed */ +/* eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); */ + } + + /* test format with "'" */ + todo_wine { + strcpy(format, "ddd',' MMM dd ''''yy"); + strcpy(Expected, "Sat, May 04 '02"); + memset(buffer, 0, sizeof(buffer)); + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetDateFormat got %s instead of %s", buffer, Expected); +/* Uncomment the below when todo_wine is removed */ +/* eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); */ + } + + /* test for success with dummy time data */ + todo_wine { + curtime.wHour = 36; + memset(buffer, 0, sizeof(buffer)); + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, COUNTOF(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetDateFormat got %s instead of %s", buffer, Expected); +/* Uncomment the below when the todo_wine is removed */ +/* eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); */ + } + + /* test that we retrieve the expected size for the necessary output of this string */ + SetLastError(NO_ERROR); + memset(buffer, 0, sizeof(buffer)); + ret = GetDateFormatA(lcid, 0, &curtime, format, NULL, 0); + ok(ret==lstrlenA(Expected)+1 && GetLastError() == 0, + "GetDateFormat(len=0): ret=%d error=%ld buffer='%s', expected NO_ERROR(0)\n",ret,GetLastError(), buffer); + + /* test that the expected size matches the actual required size by passing */ + /* in the expected size */ + memset(buffer, '0', sizeof(buffer)); + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, ret); + ok(ret==lstrlenA(Expected)+1 && GetLastError()==0, + "GetDateFormat(right size): ret=%d error=%ld, buffer = '%s'\n",ret,GetLastError(), buffer); + ok(buffer[0]!='x',"GetDateFormat(right size): buffer=[%s]\n",buffer); + + /* test that a buffer shorter than the necessary size results in ERROR_INSUFFICIENT_BUFFER */ + ret = GetDateFormatA(lcid, 0, &curtime, format, buffer, 2); + ok(ret==0 && GetLastError()==ERROR_INSUFFICIENT_BUFFER, "GetDateFormat(len=2): ret=%d error=%ld", ret, GetLastError()); + + /* test for default behavior being DATE_SHORTDATE */ + todo_wine { + strcpy(Expected, "5/4/02"); + memset(buffer, '0', sizeof(buffer)); + ret = GetDateFormat(lcid, 0, &curtime, NULL, buffer, sizeof(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetDateFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); + } + + + todo_wine { + /* test for expected DATE_LONGDATE behavior with null format */ + strcpy(Expected, "Saturday, May 04, 2002"); + memset(buffer, '0', sizeof(buffer)); + ret = GetDateFormat(lcid, DATE_LONGDATE, &curtime, NULL, buffer, sizeof(buffer)); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (cmp == 0, "GetDateFormat got '%s' instead of '%s'", buffer, Expected); + eq (ret, lstrlenA(Expected)+1, "GetDateFormat", "%d"); + } + + /* test for expected DATE_YEARMONTH behavior with null format */ + /* NT4 returns ERROR_INVALID_FLAGS for DATE_YEARMONTH */ + todo_wine { + strcpy(Expected, ""); + buffer[0] = 0; + SetLastError(NO_ERROR); + memset(buffer, '0', sizeof(buffer)); + ret = GetDateFormat(lcid, DATE_YEARMONTH, &curtime, NULL, buffer, sizeof(buffer)); + error = GetLastError(); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok (ret == 0 && (error == ERROR_INVALID_FLAGS), "GetDateFormat check DATE_YEARMONTH with null format expected ERROR_INVALID_FLAGS got return of '%d' and error of '%d'", ret, error); + } + + /* Test that using invalid DATE_* flags results in the correct error */ + /* and return values */ + strcpy(format, "m/d/y"); + strcpy(Expected, "Saturday May 2002"); + SetLastError(NO_ERROR); + memset(buffer, '0', sizeof(buffer)); + ret = GetDateFormat(lcid, DATE_YEARMONTH | DATE_SHORTDATE | DATE_LONGDATE, &curtime, format, buffer, sizeof(buffer)); + error = GetLastError(); + cmp = strncmp (Expected, buffer, strlen(Expected)+1); + ok ((ret == 0) && (error == ERROR_INVALID_FLAGS), "GetDateFormat checking for mutually exclusive flags got '%s' instead of '%s', got error of %d, expected ERROR_INVALID_FLAGS", buffer, Expected, error); } void TestGetDateFormatW() @@ -231,16 +553,6 @@ MultiByteToWideChar (CP_ACP, 0, "Wednesday 23 October 2002", -1, Expected, COUNTOF(Expected)); cmp = ret ? lstrcmpW (buffer, Expected) : 2; ok (ret == lstrlenW(Expected)+1 && error == 0 && cmp == 0, "Day of week correction failed\n"); - - /* 1d Invalid year, month or day results in error */ - - /* 1e Insufficient space results in error */ - - /* 2. Standard behaviour */ - /* 1c is a reasonable test */ - - /* 3. Replicated undocumented behaviour */ - /* e.g. unexepected characters are retained. */ }