/* * Locale-dependent format handling * * Copyright 1995 Martin von Loewis * Copyright 1998 David Lee Lambert * Copyright 2000 Julio César Gázquez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "wine/port.h" #include #include #include #include "windef.h" #include "winbase.h" #include "wine/unicode.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(nls); /****************************************************************************** * get_date_time_formatW [Internal] * * dateformat is set TRUE if being called for a date, false for a time * * This function implements stuff for GetDateFormat() and * GetTimeFormat(). * * d single-digit (no leading zero) day (of month) * dd two-digit day (of month) * ddd short day-of-week name * dddd long day-of-week name * M single-digit month * MM two-digit month * MMM short month name * MMMM full month name * y two-digit year, no leading 0 * yy two-digit year * yyyy four-digit year * gg era string * h hours with no leading zero (12-hour) * hh hours with full two digits * H hours with no leading zero (24-hour) * HH hours with full two digits * m minutes with no leading zero * mm minutes with full two digits * s seconds with no leading zero * ss seconds with full two digits * t time marker (A or P) * tt time marker (AM, PM) * '' 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 get_date_time_formatW(LCID locale, DWORD flags, DWORD tflags, const SYSTEMTIME* xtime, LPCWSTR format, 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), " "%p with max len %d\n", locale, flags, tflags, xtime->wDay, xtime->wHour, xtime->wMinute, xtime->wSecond, debugstr_w(format), format, output, outlen); /* initialize state variables */ outpos = 0; lastFormatPos = 0; while (*format) { /* Literal string: Maybe terminated early by a \0 */ if (*format == (WCHAR) '\'') { format++; /* 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 != '\'') { 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) { outpos++; /* We are counting */; } else if (outpos >= outlen) { goto too_short; } else { /* even drop literal strings */ if(!dropUntilNextFormattingChar) { output[outpos] = *format; outpos++; } } format++; } } else if ( (dateformat && (*format=='d' || *format=='M' || *format=='y' || *format=='g') ) || (!dateformat && (*format=='H' || *format=='h' || *format=='m' || *format=='s' || *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': if (count >= 4) { GetLocaleInfoW(locale, LOCALE_SDAYNAME1 + (xtime->wDayOfWeek +6)%7, buf, sizeof(buf)/sizeof(WCHAR) ); } else if (count == 3) { GetLocaleInfoW(locale, LOCALE_SABBREVDAYNAME1 + (xtime->wDayOfWeek +6)%7, buf, sizeof(buf)/sizeof(WCHAR) ); } else { sprintf( tmp, "%.*d", count, xtime->wDay ); MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); } break; case 'M': if (count >= 4) { GetLocaleInfoW(locale, LOCALE_SMONTHNAME1 + xtime->wMonth -1, buf, sizeof(buf)/sizeof(WCHAR) ); } else if (count == 3) { GetLocaleInfoW(locale, LOCALE_SABBREVMONTHNAME1 + xtime->wMonth -1, buf, sizeof(buf)/sizeof(WCHAR) ); } else { sprintf( tmp, "%.*d", count, xtime->wMonth ); MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); } break; case 'y': if (count >= 4) { sprintf( tmp, "%d", xtime->wYear ); } else { sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wYear % 100 ); } MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); break; case 'g': if (count == 2) { FIXME("LOCALE_ICALENDARTYPE unimplemented\n"); strcpy( tmp, "AD" ); } else { /* Win API sez we copy it verbatim */ strcpy( tmp, "g" ); } MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); break; case 'h': /* 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': /* 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': /* 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': if(!(tflags & TIME_NOTIMEMARKER)) { GetLocaleInfoW(locale, (xtime->wHour < 12) ? LOCALE_S1159 : LOCALE_S2359, buf, sizeof(buf) ); 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) { strcpyW( output + outpos, buf ); } else { lstrcpynW( output + outpos, buf, outlen - outpos ); /* Is this an undocumented feature we are supporting? */ goto too_short; } outpos += buflen; 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) { outpos++; /* We are counting */; } else if (outpos >= outlen) { goto too_short; } else /* just copy the character into the output buffer */ { /* unless we are dropping characters */ if(!dropUntilNextFormattingChar) { output[outpos] = *format; outpos++; } } format++; } } /* final string terminator and sanity check */ if (!outlen) /* We are counting */; else if (outpos >= outlen) goto too_short; else output[outpos] = '\0'; outpos++; /* add one for the terminating null character */ TRACE(" returning %d %s\n", outpos, debugstr_w(output)); return outpos; too_short: SetLastError(ERROR_INSUFFICIENT_BUFFER); WARN(" buffer overflow\n"); return 0; } /****************************************************************************** * GetDateFormatA [KERNEL32.@] * Makes an ASCII string of the date * * Acts the same as GetDateFormatW(), except that it's ASCII. */ INT WINAPI GetDateFormatA( LCID locale, DWORD flags, const SYSTEMTIME* xtime, LPCSTR format, LPSTR date, INT datelen ) { INT ret; LPWSTR wformat = NULL; LPWSTR wdate = NULL; if (format) { wformat = HeapAlloc(GetProcessHeap(), 0, (strlen(format) + 1) * sizeof(wchar_t)); MultiByteToWideChar(CP_ACP, 0, format, -1, wformat, strlen(format) + 1); } if (date && datelen) { wdate = HeapAlloc(GetProcessHeap(), 0, (datelen + 1) * sizeof(wchar_t)); } ret = GetDateFormatW(locale, flags, xtime, wformat, wdate, datelen); if (wdate) { WideCharToMultiByte(CP_ACP, 0, wdate, ret, date, datelen, NULL, NULL); HeapFree(GetProcessHeap(), 0, wdate); } if (wformat) { HeapFree(GetProcessHeap(), 0, wformat); } return ret; } /****************************************************************************** * GetDateFormatW [KERNEL32.@] * Makes a Unicode string of the date * * This function uses format to format the date, or, if format * is NULL, uses the default for the locale. format is a string * of literal fields and characters as follows: * * - d single-digit (no leading zero) day (of month) * - dd two-digit day (of month) * - ddd short day-of-week name * - dddd long day-of-week name * - M single-digit month * - MM two-digit month * - MMM short month name * - MMMM full month name * - y two-digit year, no leading 0 * - yy two-digit year * - yyyy four-digit year * - gg era string * * Accepts & returns sizes as counts of Unicode characters. */ INT WINAPI GetDateFormatW( LCID locale, DWORD flags, const SYSTEMTIME* xtime, LPCWSTR format, LPWSTR date, INT datelen) { WCHAR format_buf[40]; LPCWSTR thisformat; SYSTEMTIME t; LCID thislocale; INT ret; FILETIME ft; BOOL res; TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", locale,flags,xtime,debugstr_w(format),date,datelen); /* Tests */ if (flags && format) /* if lpFormat is non-null then flags must be zero */ { SetLastError (ERROR_INVALID_FLAGS); return 0; } if (datelen && !date) { SetLastError (ERROR_INVALID_PARAMETER); return 0; } if (!locale) { locale = LOCALE_SYSTEM_DEFAULT; } if (locale == LOCALE_SYSTEM_DEFAULT) { thislocale = GetSystemDefaultLCID(); } else if (locale == LOCALE_USER_DEFAULT) { thislocale = GetUserDefaultLCID(); } else { thislocale = locale; } /* 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 { /* 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->wMonth > 12)) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } /* For all we know the day of week and the time may be absolute * rubbish. Therefore if we are going to use conversion through * FileTime we had better use a clean time (and hopefully we won't * fall over any timezone complications). * If we go with an alternative method of correcting the day of week * (e.g. Zeller's congruence) then we won't need to, but we will need * to check the date. */ memset (&t, 0, sizeof(t)); t.wYear = xtime->wYear; t.wMonth = xtime->wMonth; t.wDay = xtime->wDay; /* Silently correct wDayOfWeek by transforming to FileTime and back again */ res=SystemTimeToFileTime(&t,&ft); /* Check year(?)/month and date for range and set ERROR_INVALID_PARAMETER on error */ if(!res) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } FileTimeToSystemTime(&ft,&t); } if (format == NULL) { GetLocaleInfoW(thislocale, ((flags&DATE_LONGDATE) ? LOCALE_SLONGDATE : LOCALE_SSHORTDATE), format_buf, sizeof(format_buf)/sizeof(*format_buf)); thisformat = format_buf; } else { thisformat = format; } ret = get_date_time_formatW(thislocale, flags, 0, &t, thisformat, date, datelen, 1); TRACE("GetDateFormatW() returning %d, with data=%s\n", ret, debugstr_w(date)); return ret; } /************************************************************************** * EnumDateFormatsA (KERNEL32.@) */ BOOL WINAPI EnumDateFormatsA( DATEFMT_ENUMPROCA lpDateFmtEnumProc, LCID Locale, DWORD dwFlags) { LCID Loc = GetUserDefaultLCID(); if(!lpDateFmtEnumProc) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } switch( Loc ) { case 0x00000407: /* (Loc,"de_DE") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd.MM.yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.M.yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.MM.yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd,d. MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d. MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d. MMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x0000040c: /* (Loc,"fr_FR") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd.MM.yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd-MM-yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd d MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMM yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00000c0c: /* (Loc,"fr_CA") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("yy-MM-dd")) return TRUE; if(!(*lpDateFmtEnumProc)("dd-MM-yy")) return TRUE; if(!(*lpDateFmtEnumProc)("yy MM dd")) return TRUE; if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("d MMMM, yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00000809: /* (Loc,"en_UK") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00000c09: /* (Loc,"en_AU") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("d/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd,d MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00001009: /* (Loc,"en_CA") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("yy-MM-dd")) return TRUE; if(!(*lpDateFmtEnumProc)("M/dd/yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("d-MMM-yy")) return TRUE; if(!(*lpDateFmtEnumProc)("MMMM d, yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00001409: /* (Loc,"en_NZ") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("d/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.MM.yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("dddd, d MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00001809: /* (Loc,"en_IE") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00001c09: /* (Loc,"en_ZA") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("yy/MM/dd")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00002009: /* (Loc,"en_JM") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd,MMMM dd,yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("MMMM dd,yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("dddd,dd MMMM,yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd MMMM,yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } case 0x00002809: /* (Loc,"en_BZ") */ case 0x00002c09: /* (Loc,"en_TT") */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd,dd MMMM yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } default: /* default to US English "en_US" */ { switch(dwFlags) { case DATE_SHORTDATE: if(!(*lpDateFmtEnumProc)("M/d/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("M/d/yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("MM/dd/yy")) return TRUE; if(!(*lpDateFmtEnumProc)("MM/dd/yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("yy/MM/dd")) return TRUE; if(!(*lpDateFmtEnumProc)("dd-MMM-yy")) return TRUE; return TRUE; case DATE_LONGDATE: if(!(*lpDateFmtEnumProc)("dddd, MMMM dd, yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("MMMM dd, yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("dddd, dd MMMM, yyyy")) return TRUE; if(!(*lpDateFmtEnumProc)("dd MMMM, yyyy")) return TRUE; return TRUE; default: FIXME("Unknown date format (%ld)\n", dwFlags); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } } } /************************************************************************** * EnumDateFormatsW (KERNEL32.@) */ BOOL WINAPI EnumDateFormatsW( DATEFMT_ENUMPROCW lpDateFmtEnumProc, LCID Locale, DWORD dwFlags ) { FIXME("(%p, %ld, %ld): stub\n", lpDateFmtEnumProc, Locale, dwFlags); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } /************************************************************************** * EnumTimeFormatsA (KERNEL32.@) */ BOOL WINAPI EnumTimeFormatsA( TIMEFMT_ENUMPROCA lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags ) { LCID Loc = GetUserDefaultLCID(); if(!lpTimeFmtEnumProc) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if(dwFlags) { FIXME("Unknown time format (%ld)\n", dwFlags); } switch( Loc ) { case 0x00000407: /* (Loc,"de_DE") */ { if(!(*lpTimeFmtEnumProc)("HH.mm")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("H.mm")) return TRUE; if(!(*lpTimeFmtEnumProc)("H.mm'Uhr'")) return TRUE; return TRUE; } case 0x0000040c: /* (Loc,"fr_FR") */ case 0x00000c0c: /* (Loc,"fr_CA") */ { if(!(*lpTimeFmtEnumProc)("H:mm")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH.mm")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH'h'mm")) return TRUE; return TRUE; } case 0x00000809: /* (Loc,"en_UK") */ case 0x00000c09: /* (Loc,"en_AU") */ case 0x00001409: /* (Loc,"en_NZ") */ case 0x00001809: /* (Loc,"en_IE") */ { if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE; return TRUE; } case 0x00001c09: /* (Loc,"en_ZA") */ case 0x00002809: /* (Loc,"en_BZ") */ case 0x00002c09: /* (Loc,"en_TT") */ { if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE; if(!(*lpTimeFmtEnumProc)("hh:mm:ss tt")) return TRUE; return TRUE; } default: /* default to US style "en_US" */ { if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE; if(!(*lpTimeFmtEnumProc)("hh:mm:ss tt")) return TRUE; if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE; if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE; return TRUE; } } } /************************************************************************** * EnumTimeFormatsW (KERNEL32.@) */ BOOL WINAPI EnumTimeFormatsW( TIMEFMT_ENUMPROCW lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags ) { FIXME("(%p,%ld,%ld): stub\n", lpTimeFmtEnumProc, Locale, dwFlags); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } /************************************************************************** * This function is used just locally ! * Description: Inverts a string. */ static void invert_string(char* string) { int i, j; for (i = 0, j = strlen(string) - 1; i < j; i++, j--) { char ch = string[i]; string[i] = string[j]; string[j] = ch; } } /*************************************************************************************** * This function is used just locally ! * Description: Test if the given string (psNumber) is valid or not. * The valid characters are the following: * - Characters '0' through '9'. * - One decimal point (dot) if the number is a floating-point value. * - A minus sign in the first character position if the number is * a negative value. * If the function succeeds, psBefore/psAfter will point to the string * on the right/left of the decimal symbol. pbNegative indicates if the * number is negative. */ static INT get_number_components(char* pInput, char* psBefore, char* psAfter, BOOL* pbNegative) { char sNumberSet[] = "0123456789"; BOOL bInDecimal = FALSE; /* Test if we do have a minus sign */ if ( *pInput == '-' ) { *pbNegative = TRUE; pInput++; /* Jump to the next character. */ } while(*pInput != '\0') { /* Do we have a valid numeric character */ if ( strchr(sNumberSet, *pInput) != NULL ) { if (bInDecimal == TRUE) *psAfter++ = *pInput; else *psBefore++ = *pInput; } else { /* Is this a decimal point (dot) */ if ( *pInput == '.' ) { /* Is it the first time we find it */ if ((bInDecimal == FALSE)) bInDecimal = TRUE; else return -1; /* ERROR: Invalid parameter */ } else { /* It's neither a numeric character, nor a decimal point. * Thus, return an error. */ return -1; } } pInput++; } /* Add an End of Line character to the output buffers */ *psBefore = '\0'; *psAfter = '\0'; return 0; } /************************************************************************** * This function is used just locally ! * Description: A number could be formatted using different numbers * of "digits in group" (example: 4;3;2;0). * The first parameter of this function is an array * containing the rule to be used. Its format is the following: * |NDG|DG1|DG2|...|0| * where NDG is the number of used "digits in group" and DG1, DG2, * are the corresponding "digits in group". * Thus, this function returns the grouping value in the array * pointed by the second parameter. */ static INT get_grouping(char* sRule, INT index) { char sData[2], sRuleSize[2]; INT nData, nRuleSize; memcpy(sRuleSize, sRule, 1); memcpy(sRuleSize+1, "\0", 1); nRuleSize = atoi(sRuleSize); if (index > 0 && index < nRuleSize) { memcpy(sData, sRule+index, 1); memcpy(sData+1, "\0", 1); nData = atoi(sData); } else { memcpy(sData, sRule+nRuleSize-1, 1); memcpy(sData+1, "\0", 1); nData = atoi(sData); } return nData; } /************************************************************************** * GetNumberFormatA (KERNEL32.@) */ INT WINAPI GetNumberFormatA(LCID locale, DWORD dwflags, LPCSTR lpvalue, const NUMBERFMTA * lpFormat, LPSTR lpNumberStr, int cchNumber) { char sNumberDigits[3], sDecimalSymbol[5], sDigitsInGroup[11], sDigitGroupSymbol[5], sILZero[2]; INT nNumberDigits, nNumberDecimal, i, j, nCounter, nStep, nRuleIndex, nGrouping, nDigits, retVal, nLZ; char sNumber[128], sDestination[128], sDigitsAfterDecimal[10], sDigitsBeforeDecimal[128]; char sRule[10], sSemiColumn[]=";", sBuffer[5], sNegNumber[2]; char *pStr = NULL, *pTmpStr = NULL; LCID systemDefaultLCID; BOOL bNegative = FALSE; enum Operations { USE_PARAMETER, USE_LOCALEINFO, USE_SYSTEMDEFAULT, RETURN_ERROR } used_operation; strncpy(sNumber, lpvalue, 128); sNumber[127] = '\0'; /* Make sure we have a valid input string, get the number * of digits before and after the decimal symbol, and check * if this is a negative number. */ if (get_number_components(sNumber, sDigitsBeforeDecimal, sDigitsAfterDecimal, &bNegative) != -1) { nNumberDecimal = strlen(sDigitsBeforeDecimal); nDigits = strlen(sDigitsAfterDecimal); } else { SetLastError(ERROR_INVALID_PARAMETER); return 0; } /* Which source will we use to format the string */ used_operation = RETURN_ERROR; if (lpFormat != NULL) { if (dwflags == 0) used_operation = USE_PARAMETER; } else { if (dwflags & LOCALE_NOUSEROVERRIDE) used_operation = USE_SYSTEMDEFAULT; else used_operation = USE_LOCALEINFO; } /* Load the fields we need */ switch(used_operation) { case USE_LOCALEINFO: GetLocaleInfoA(locale, LOCALE_IDIGITS, sNumberDigits, sizeof(sNumberDigits)); GetLocaleInfoA(locale, LOCALE_SDECIMAL, sDecimalSymbol, sizeof(sDecimalSymbol)); GetLocaleInfoA(locale, LOCALE_SGROUPING, sDigitsInGroup, sizeof(sDigitsInGroup)); GetLocaleInfoA(locale, LOCALE_STHOUSAND, sDigitGroupSymbol, sizeof(sDigitGroupSymbol)); GetLocaleInfoA(locale, LOCALE_ILZERO, sILZero, sizeof(sILZero)); GetLocaleInfoA(locale, LOCALE_INEGNUMBER, sNegNumber, sizeof(sNegNumber)); break; case USE_PARAMETER: sprintf(sNumberDigits, "%d",lpFormat->NumDigits); strcpy(sDecimalSymbol, lpFormat->lpDecimalSep); sprintf(sDigitsInGroup, "%d;0",lpFormat->Grouping); /* Win95-WinME only allow 0-9 for grouping, no matter what MSDN says. */ strcpy(sDigitGroupSymbol, lpFormat->lpThousandSep); sprintf(sILZero, "%d",lpFormat->LeadingZero); sprintf(sNegNumber, "%d",lpFormat->NegativeOrder); break; case USE_SYSTEMDEFAULT: systemDefaultLCID = GetSystemDefaultLCID(); GetLocaleInfoA(systemDefaultLCID, LOCALE_IDIGITS, sNumberDigits, sizeof(sNumberDigits)); GetLocaleInfoA(systemDefaultLCID, LOCALE_SDECIMAL, sDecimalSymbol, sizeof(sDecimalSymbol)); GetLocaleInfoA(systemDefaultLCID, LOCALE_SGROUPING, sDigitsInGroup, sizeof(sDigitsInGroup)); GetLocaleInfoA(systemDefaultLCID, LOCALE_STHOUSAND, sDigitGroupSymbol, sizeof(sDigitGroupSymbol)); GetLocaleInfoA(systemDefaultLCID, LOCALE_ILZERO, sILZero, sizeof(sILZero)); GetLocaleInfoA(systemDefaultLCID, LOCALE_INEGNUMBER, sNegNumber, sizeof(sNegNumber)); break; default: SetLastError(ERROR_INVALID_PARAMETER); return 0; } nNumberDigits = atoi(sNumberDigits); /* Remove the ";" */ i=0; j = 1; for (nCounter=0; nCounter0) && (atoi(sDigitsBeforeDecimal) != 0)) { /* Working on an inverted string is easier ! */ invert_string(sDigitsBeforeDecimal); nStep = nCounter = i = j = 0; nRuleIndex = 1; nGrouping = get_grouping(sRule, nRuleIndex); if (nGrouping == 0) /* If the first grouping is zero */ nGrouping = nNumberDecimal; /* Don't do grouping */ /* Here, we will loop until we reach the end of the string. * An internal counter (j) is used in order to know when to * insert the "digit group symbol". */ while (nNumberDecimal > 0) { i = nCounter + nStep; memcpy(sDestination + i, sDigitsBeforeDecimal + nCounter, 1); nCounter++; j++; if (j >= nGrouping) { j = 0; if (nRuleIndex < sRule[0]) nRuleIndex++; nGrouping = get_grouping(sRule, nRuleIndex); memcpy(sDestination + i+1, sDigitGroupSymbol, strlen(sDigitGroupSymbol)); nStep+= strlen(sDigitGroupSymbol); } nNumberDecimal--; } memcpy(sDestination + i+1, "\0", 1); /* Get the string in the right order ! */ invert_string(sDestination); } else { nLZ = atoi(sILZero); if (nLZ != 0) { /* Use 0.xxx instead of .xxx */ memcpy(sDestination, "0", 1); memcpy(sDestination+1, "\0", 1); } else memcpy(sDestination, "\0", 1); } /* Second, format the digits after the decimal. */ j = 0; nCounter = nNumberDigits; if ( (nDigits>0) && (pStr = strstr (sNumber, ".")) ) { i = strlen(sNumber) - strlen(pStr) + 1; strncpy ( sDigitsAfterDecimal, sNumber + i, nNumberDigits); j = strlen(sDigitsAfterDecimal); if (j < nNumberDigits) nCounter = nNumberDigits-j; } for (i=0;iNegativeOrder; nPosOrder = lpFormat->PositiveOrder; strcpy(sCurrencySymbol, lpFormat->lpCurrencySymbol); break; case USE_SYSTEMDEFAULT: systemDefaultLCID = GetSystemDefaultLCID(); /* Specific to CURRENCYFMTA */ GetLocaleInfoA(systemDefaultLCID, LOCALE_INEGCURR, sNegOrder, sizeof(sNegOrder)); GetLocaleInfoA(systemDefaultLCID, LOCALE_ICURRENCY, sPosOrder, sizeof(sPosOrder)); GetLocaleInfoA(systemDefaultLCID, LOCALE_SCURRENCY, sCurrencySymbol, sizeof(sCurrencySymbol)); nPosOrder = atoi(sPosOrder); nNegOrder = atoi(sNegOrder); break; default: SetLastError(ERROR_INVALID_PARAMETER); return 0; } /* Construct a temporary number format structure */ if (lpFormat != NULL) { numberFmt.NumDigits = lpFormat->NumDigits; numberFmt.LeadingZero = lpFormat->LeadingZero; numberFmt.Grouping = lpFormat->Grouping; numberFmt.NegativeOrder = 0; numberFmt.lpDecimalSep = lpFormat->lpDecimalSep; numberFmt.lpThousandSep = lpFormat->lpThousandSep; bValidFormat = TRUE; } /* Make a call to GetNumberFormatA() */ if (*lpvalue == '-') { bIsPositive = FALSE; retVal = GetNumberFormatA(locale,0,lpvalue+1,(bValidFormat)?&numberFmt:NULL,pNumberFormated,128); } else { bIsPositive = TRUE; retVal = GetNumberFormatA(locale,0,lpvalue,(bValidFormat)?&numberFmt:NULL,pNumberFormated,128); } if (retVal == 0) return 0; /* construct the formatted string */ if (bIsPositive) { switch (nPosOrder) { case 0: /* Prefix, no separation */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, pNumberFormated); break; case 1: /* Suffix, no separation */ strcpy (pDestination, pNumberFormated); strcat (pDestination, sCurrencySymbol); break; case 2: /* Prefix, 1 char separation */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, " "); strcat (pDestination, pNumberFormated); break; case 3: /* Suffix, 1 char separation */ strcpy (pDestination, pNumberFormated); strcat (pDestination, " "); strcat (pDestination, sCurrencySymbol); break; default: SetLastError(ERROR_INVALID_PARAMETER); return 0; } } else /* negative number */ { switch (nNegOrder) { case 0: /* format: ($1.1) */ strcpy (pDestination, "("); strcat (pDestination, sCurrencySymbol); strcat (pDestination, pNumberFormated); strcat (pDestination, ")"); break; case 1: /* format: -$1.1 */ strcpy (pDestination, "-"); strcat (pDestination, sCurrencySymbol); strcat (pDestination, pNumberFormated); break; case 2: /* format: $-1.1 */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, "-"); strcat (pDestination, pNumberFormated); break; case 3: /* format: $1.1- */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, pNumberFormated); strcat (pDestination, "-"); break; case 4: /* format: (1.1$) */ strcpy (pDestination, "("); strcat (pDestination, pNumberFormated); strcat (pDestination, sCurrencySymbol); strcat (pDestination, ")"); break; case 5: /* format: -1.1$ */ strcpy (pDestination, "-"); strcat (pDestination, pNumberFormated); strcat (pDestination, sCurrencySymbol); break; case 6: /* format: 1.1-$ */ strcpy (pDestination, pNumberFormated); strcat (pDestination, "-"); strcat (pDestination, sCurrencySymbol); break; case 7: /* format: 1.1$- */ strcpy (pDestination, pNumberFormated); strcat (pDestination, sCurrencySymbol); strcat (pDestination, "-"); break; case 8: /* format: -1.1 $ */ strcpy (pDestination, "-"); strcat (pDestination, pNumberFormated); strcat (pDestination, " "); strcat (pDestination, sCurrencySymbol); break; case 9: /* format: -$ 1.1 */ strcpy (pDestination, "-"); strcat (pDestination, sCurrencySymbol); strcat (pDestination, " "); strcat (pDestination, pNumberFormated); break; case 10: /* format: 1.1 $- */ strcpy (pDestination, pNumberFormated); strcat (pDestination, " "); strcat (pDestination, sCurrencySymbol); strcat (pDestination, "-"); break; case 11: /* format: $ 1.1- */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, " "); strcat (pDestination, pNumberFormated); strcat (pDestination, "-"); break; case 12: /* format: $ -1.1 */ strcpy (pDestination, sCurrencySymbol); strcat (pDestination, " "); strcat (pDestination, "-"); strcat (pDestination, pNumberFormated); break; case 13: /* format: 1.1- $ */ strcpy (pDestination, pNumberFormated); strcat (pDestination, "-"); strcat (pDestination, " "); strcat (pDestination, sCurrencySymbol); break; case 14: /* format: ($ 1.1) */ strcpy (pDestination, "("); strcat (pDestination, sCurrencySymbol); strcat (pDestination, " "); strcat (pDestination, pNumberFormated); strcat (pDestination, ")"); break; case 15: /* format: (1.1 $) */ strcpy (pDestination, "("); strcat (pDestination, pNumberFormated); strcat (pDestination, " "); strcat (pDestination, sCurrencySymbol); strcat (pDestination, ")"); break; default: SetLastError(ERROR_INVALID_PARAMETER); return 0; } } retVal = strlen(pDestination) + 1; if (cchCurrency) { memcpy( lpCurrencyStr, pDestination, min(cchCurrency, retVal) ); if (cchCurrency < retVal) { retVal = 0; SetLastError(ERROR_INSUFFICIENT_BUFFER); } } return retVal; } /************************************************************************** * GetCurrencyFormatW (KERNEL32.@) */ INT WINAPI GetCurrencyFormatW(LCID locale, DWORD dwflags, LPCWSTR lpvalue, const CURRENCYFMTW * lpFormat, LPWSTR lpCurrencyStr, int cchCurrency) { FIXME("This API function is NOT implemented !\n"); return 0; } /****************************************************************************** * GetTimeFormatA [KERNEL32.@] * Makes an ASCII string of the time * * Formats date according to format, or locale default if format is * NULL. The format consists of literal characters and fields as follows: * * h hours with no leading zero (12-hour) * hh hours with full two digits * H hours with no leading zero (24-hour) * HH hours with full two digits * m minutes with no leading zero * mm minutes with full two digits * s seconds with no leading zero * ss seconds with full two digits * t time marker (A or P) * tt time marker (AM, PM) * */ INT WINAPI GetTimeFormatA(LCID locale, /* [in] */ DWORD flags, /* [in] */ const SYSTEMTIME* xtime, /* [in] */ LPCSTR format, /* [in] */ LPSTR timestr, /* [out] */ INT timelen /* [in] */) { INT ret; LPWSTR wformat = NULL; LPWSTR wtime = NULL; if (format) { wformat = HeapAlloc(GetProcessHeap(), 0, (strlen(format) + 1) * sizeof(wchar_t)); MultiByteToWideChar(CP_ACP, 0, format, -1, wformat, strlen(format) + 1); } if (timestr && timelen) { wtime = HeapAlloc(GetProcessHeap(), 0, (timelen + 1) * sizeof(wchar_t)); } ret = GetTimeFormatW(locale, flags, xtime, wformat, wtime, timelen); if (wtime) { WideCharToMultiByte(CP_ACP, 0, wtime, ret, timestr, timelen, NULL, NULL); HeapFree(GetProcessHeap(), 0, wtime); } if (wformat) { HeapFree(GetProcessHeap(), 0, wformat); } return ret; } /****************************************************************************** * GetTimeFormatW [KERNEL32.@] * Makes a Unicode string of the time * * NOTE: See get_date_time_formatW() for further documentation */ INT WINAPI GetTimeFormatW(LCID locale, /* [in] */ DWORD flags, /* [in] */ const SYSTEMTIME* xtime, /* [in] */ LPCWSTR format, /* [in] */ LPWSTR timestr, /* [out] */ INT timelen /* [in] */) { WCHAR format_buf[40]; LPCWSTR thisformat; SYSTEMTIME t; const SYSTEMTIME* thistime; DWORD thisflags=LOCALE_STIMEFORMAT; /* standard timeformat */ INT ret; TRACE("GetTimeFormat(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",locale,flags, xtime,debugstr_w(format),timestr,timelen); if (!locale) locale = LOCALE_SYSTEM_DEFAULT; locale = ConvertDefaultLocale( 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 */ { locale = GetSystemDefaultLCID(); } GetLocaleInfoW(locale, thisflags, format_buf, 40); thisformat = format_buf; } else { /* 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 */ { GetLocalTime(&t); thistime = &t; } else { /* check time values */ if((xtime->wHour > 24) || (xtime->wMinute >= 60) || (xtime->wSecond >= 60)) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } thistime = xtime; } ret = get_date_time_formatW(locale, thisflags, flags, thistime, thisformat, timestr, timelen, 0); return ret; } /****************************************************************************** * EnumCalendarInfoA [KERNEL32.@] */ BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale, CALID calendar,CALTYPE caltype ) { FIXME("(%p,0x%04lx,0x%08lx,0x%08lx),stub!\n",calinfoproc,locale,calendar,caltype); return FALSE; }