Fix a regression in IE where the Favourites menu didn't appear
[wine/testsucceed.git] / dlls / kernel / lcformat.c
blobdd3c3bb84e1c05f912433953baf0aa8e752e8681
1 /*
2 * Locale-dependent format handling
4 * Copyright 1995 Martin von Loewis
5 * Copyright 1998 David Lee Lambert
6 * Copyright 2000 Julio César Gázquez
7 * Copyright 2003 Jon Griffiths
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "config.h"
25 #include "wine/port.h"
27 #include <string.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
32 #include "windef.h"
33 #include "winbase.h"
34 #include "wine/unicode.h"
35 #include "wine/debug.h"
36 #include "winternl.h"
38 #include "kernel_private.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(nls);
42 #define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
43 #define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
45 /* Since calculating the formatting data for each locale is time-consuming,
46 * we get the format data for each locale only once and cache it in memory.
47 * We cache both the system default and user overridden data, after converting
48 * them into the formats that the functions here expect. Since these functions
49 * will typically be called with only a small number of the total locales
50 * installed, the memory overhead is minimal while the speedup is significant.
52 * Our cache takes the form of a singly linked list, whose node is below:
54 #define NLS_NUM_CACHED_STRINGS 45
56 typedef struct _NLS_FORMAT_NODE
58 LCID lcid; /* Locale Id */
59 DWORD dwFlags; /* 0 or LOCALE_NOUSEROVERRIDE */
60 DWORD dwCodePage; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
61 NUMBERFMTW fmt; /* Default format for numbers */
62 CURRENCYFMTW cyfmt; /* Default format for currencies */
63 LPWSTR lppszStrings[NLS_NUM_CACHED_STRINGS]; /* Default formats,day/month names */
64 WCHAR szShortAM[2]; /* Short 'AM' marker */
65 WCHAR szShortPM[2]; /* Short 'PM' marker */
66 struct _NLS_FORMAT_NODE *next;
67 } NLS_FORMAT_NODE;
69 /* Macros to get particular data strings from a format node */
70 #define GetNegative(fmt) fmt->lppszStrings[0]
71 #define GetLongDate(fmt) fmt->lppszStrings[1]
72 #define GetShortDate(fmt) fmt->lppszStrings[2]
73 #define GetTime(fmt) fmt->lppszStrings[3]
74 #define GetAM(fmt) fmt->lppszStrings[42]
75 #define GetPM(fmt) fmt->lppszStrings[43]
76 #define GetYearMonth(fmt) fmt->lppszStrings[44]
78 #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
79 #define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
80 #define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
81 #define GetShortMonth(fmt,mth) fmt->lppszStrings[30 + mth]
83 /* Write access to the cache is protected by this critical section */
84 static CRITICAL_SECTION NLS_FormatsCS;
85 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
87 0, 0, &NLS_FormatsCS,
88 { &NLS_FormatsCS_debug.ProcessLocksList,
89 &NLS_FormatsCS_debug.ProcessLocksList },
90 0, 0, { 0, (DWORD)(__FILE__ ": NLS_Formats") }
92 static CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
94 /**************************************************************************
95 * NLS_GetLocaleNumber <internal>
97 * Get a numeric locale format value.
99 static DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
101 WCHAR szBuff[80];
102 DWORD dwVal = 0;
104 szBuff[0] = '\0';
105 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
107 if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '0')
108 dwVal = (szBuff[0] - '0') * 10 + (szBuff[2] - '0');
109 else
111 const WCHAR* iter = szBuff;
112 dwVal = 0;
113 while(*iter >= '0' && *iter <= '9')
114 dwVal = dwVal * 10 + (*iter++ - '0');
116 return dwVal;
119 /**************************************************************************
120 * NLS_GetLocaleString <internal>
122 * Get a string locale format value.
124 static WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
126 WCHAR szBuff[80], *str;
127 DWORD dwLen;
129 szBuff[0] = '\0';
130 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
131 dwLen = strlenW(szBuff) + 1;
132 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
133 if (str)
134 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
135 return str;
138 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
139 TRACE( #type ": %ld (%08lx)\n", (DWORD)num, (DWORD)num)
141 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
142 TRACE( #type ": '%s'\n", debugstr_w(str))
144 /**************************************************************************
145 * NLS_GetFormats <internal>
147 * Calculate (and cache) the number formats for a locale.
149 static const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
151 /* GetLocaleInfo() identifiers for cached formatting strings */
152 static const USHORT NLS_LocaleIndices[] = {
153 LOCALE_SNEGATIVESIGN,
154 LOCALE_SLONGDATE, LOCALE_SSHORTDATE,
155 LOCALE_STIMEFORMAT,
156 LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
157 LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
158 LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
159 LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
160 LOCALE_SABBREVDAYNAME7,
161 LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
162 LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
163 LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
164 LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
165 LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
166 LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
167 LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
168 LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
169 LOCALE_S1159, LOCALE_S2359,
170 LOCALE_SYEARMONTH
172 static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
173 NLS_FORMAT_NODE *node = NLS_CachedFormats;
175 dwFlags &= LOCALE_NOUSEROVERRIDE;
177 TRACE("(0x%04lx,0x%08lx)\n", lcid, dwFlags);
179 /* See if we have already cached the locales number format */
180 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
181 node = node->next;
183 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
185 NLS_FORMAT_NODE *new_node;
186 DWORD i;
188 TRACE("Creating new cache entry\n");
190 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
191 return NULL;
193 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
195 /* Number Format */
196 new_node->lcid = lcid;
197 new_node->dwFlags = dwFlags;
198 new_node->next = NULL;
200 GET_LOCALE_NUMBER(new_node->fmt.NumDigits, LOCALE_IDIGITS);
201 GET_LOCALE_NUMBER(new_node->fmt.LeadingZero, LOCALE_ILZERO);
202 GET_LOCALE_NUMBER(new_node->fmt.NegativeOrder, LOCALE_INEGNUMBER);
204 GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
205 if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
207 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
208 new_node->fmt.Grouping);
209 new_node->fmt.Grouping = 0;
212 GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
213 GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
215 /* Currency Format */
216 new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
217 new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
219 GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
221 if (new_node->cyfmt.Grouping > 9)
223 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
224 new_node->cyfmt.Grouping);
225 new_node->cyfmt.Grouping = 0;
228 GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
229 if (new_node->cyfmt.NegativeOrder > 15)
231 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
232 new_node->cyfmt.NegativeOrder);
233 new_node->cyfmt.NegativeOrder = 0;
235 GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
236 if (new_node->cyfmt.PositiveOrder > 3)
238 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
239 new_node->cyfmt.PositiveOrder);
240 new_node->cyfmt.PositiveOrder = 0;
242 GET_LOCALE_STRING(new_node->cyfmt.lpDecimalSep, LOCALE_SMONDECIMALSEP);
243 GET_LOCALE_STRING(new_node->cyfmt.lpThousandSep, LOCALE_SMONTHOUSANDSEP);
244 GET_LOCALE_STRING(new_node->cyfmt.lpCurrencySymbol, LOCALE_SCURRENCY);
246 /* Date/Time Format info, negative character, etc */
247 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
249 GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
251 new_node->szShortAM[0] = GetAM(new_node)[0]; new_node->szShortAM[1] = '\0';
252 new_node->szShortPM[0] = GetPM(new_node)[0]; new_node->szShortPM[1] = '\0';
254 /* Now add the computed format to the cache */
255 RtlEnterCriticalSection(&NLS_FormatsCS);
257 /* Search again: We may have raced to add the node */
258 node = NLS_CachedFormats;
259 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
260 node = node->next;
262 if (!node)
264 node = NLS_CachedFormats = new_node; /* Empty list */
265 new_node = NULL;
267 else if (node->lcid != lcid || node->dwFlags != dwFlags)
269 node->next = new_node; /* Not in the list, add to end */
270 node = new_node;
271 new_node = NULL;
274 RtlLeaveCriticalSection(&NLS_FormatsCS);
276 if (new_node)
278 /* We raced and lost: The node was already added by another thread.
279 * node points to the currently cached node, so free new_node.
281 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
282 HeapFree(GetProcessHeap(), 0, new_node->lppszStrings[i]);
283 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpDecimalSep);
284 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpThousandSep);
285 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpDecimalSep);
286 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpThousandSep);
287 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpCurrencySymbol);
288 HeapFree(GetProcessHeap(), 0, new_node);
291 return node;
294 /**************************************************************************
295 * NLS_IsUnicodeOnlyLcid <internal>
297 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
299 BOOL NLS_IsUnicodeOnlyLcid(LCID lcid)
301 switch (PRIMARYLANGID(lcid))
303 case LANG_ARMENIAN:
304 case LANG_DIVEHI:
305 case LANG_GEORGIAN:
306 case LANG_GUJARATI:
307 case LANG_HINDI:
308 case LANG_KANNADA:
309 case LANG_KONKANI:
310 case LANG_MARATHI:
311 case LANG_PUNJABI:
312 case LANG_SANSKRIT:
313 TRACE("lcid 0x%08lx: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
314 return TRUE;
315 default:
316 return FALSE;
321 * Formatting of dates, times, numbers and currencies.
324 #define IsLiteralMarker(p) (p == '\'')
325 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
326 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
328 /* Only the following flags can be given if a date/time format is specified */
329 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY|LOCALE_NOUSEROVERRIDE)
330 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
331 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
332 TIME_NOTIMEMARKER|LOCALE_NOUSEROVERRIDE)
334 /******************************************************************************
335 * NLS_GetDateTimeFormatW <internal>
337 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
339 * FIXME
340 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
341 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
343 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
344 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
345 LPWSTR lpStr, INT cchOut)
347 const NLS_FORMAT_NODE *node;
348 SYSTEMTIME st;
349 INT cchWritten = 0;
350 INT lastFormatPos = 0;
351 BOOL bSkipping = FALSE; /* Skipping text around marker? */
353 /* Verify our arguments */
354 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
356 NLS_GetDateTimeFormatW_InvalidParameter:
357 SetLastError(ERROR_INVALID_PARAMETER);
358 return 0;
361 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
363 if (lpFormat &&
364 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
365 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
367 NLS_GetDateTimeFormatW_InvalidFlags:
368 SetLastError(ERROR_INVALID_FLAGS);
369 return 0;
372 if (dwFlags & DATE_DATEVARSONLY)
374 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
375 goto NLS_GetDateTimeFormatW_InvalidFlags;
376 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
377 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
379 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
381 case 0:
382 break;
383 case DATE_SHORTDATE:
384 case DATE_LONGDATE:
385 case DATE_YEARMONTH:
386 if (lpFormat)
387 goto NLS_GetDateTimeFormatW_InvalidFlags;
388 break;
389 default:
390 goto NLS_GetDateTimeFormatW_InvalidFlags;
395 if (!lpFormat)
397 /* Use the appropriate default format */
398 if (dwFlags & DATE_DATEVARSONLY)
400 if (dwFlags & DATE_YEARMONTH)
401 lpFormat = GetYearMonth(node);
402 else if (dwFlags & DATE_LONGDATE)
403 lpFormat = GetLongDate(node);
404 else
405 lpFormat = GetShortDate(node);
407 else
408 lpFormat = GetTime(node);
411 if (!lpTime)
413 GetLocalTime(&st); /* Default to current time */
414 lpTime = &st;
416 else
418 if (dwFlags & DATE_DATEVARSONLY)
420 FILETIME ftTmp;
422 /* Verify the date and correct the D.O.W. if needed */
423 memset(&st, 0, sizeof(st));
424 st.wYear = lpTime->wYear;
425 st.wMonth = lpTime->wMonth;
426 st.wDay = lpTime->wDay;
428 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
429 goto NLS_GetDateTimeFormatW_InvalidParameter;
431 FileTimeToSystemTime(&ftTmp, &st);
432 lpTime = &st;
435 if (dwFlags & TIME_TIMEVARSONLY)
437 /* Verify the time */
438 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
439 goto NLS_GetDateTimeFormatW_InvalidParameter;
443 /* Format the output */
444 while (*lpFormat)
446 if (IsLiteralMarker(*lpFormat))
448 /* Start of a literal string */
449 lpFormat++;
451 /* Loop until the end of the literal marker or end of the string */
452 while (*lpFormat)
454 if (IsLiteralMarker(*lpFormat))
456 lpFormat++;
457 if (!IsLiteralMarker(*lpFormat))
458 break; /* Terminating literal marker */
461 if (!cchOut)
462 cchWritten++; /* Count size only */
463 else if (cchWritten >= cchOut)
464 goto NLS_GetDateTimeFormatW_Overrun;
465 else if (!bSkipping)
467 lpStr[cchWritten] = *lpFormat;
468 cchWritten++;
470 lpFormat++;
473 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
474 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
476 char buffA[32];
477 WCHAR buff[32], fmtChar;
478 LPCWSTR szAdd = NULL;
479 DWORD dwVal = 0;
480 int count = 0, dwLen;
482 bSkipping = FALSE;
484 fmtChar = *lpFormat;
485 while (*lpFormat == fmtChar)
487 count++;
488 lpFormat++;
490 buff[0] = '\0';
492 switch(fmtChar)
494 case 'd':
495 if (count >= 4)
496 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
497 else if (count == 3)
498 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
499 else
501 dwVal = lpTime->wDay;
502 szAdd = buff;
504 break;
506 case 'M':
507 if (count >= 4)
508 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
509 else if (count == 3)
510 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
511 else
513 dwVal = lpTime->wMonth;
514 szAdd = buff;
516 break;
518 case 'y':
519 if (count >= 4)
521 count = 4;
522 dwVal = lpTime->wYear;
524 else
526 count = count > 2 ? 2 : count;
527 dwVal = lpTime->wYear % 100;
529 szAdd = buff;
530 break;
532 case 'g':
533 if (count == 2)
535 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
536 * When it is fixed, this string should be cached in 'node'.
538 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
539 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
541 else
543 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
545 szAdd = buff;
546 break;
548 case 'h':
549 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
551 count = count > 2 ? 2 : count;
552 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
553 szAdd = buff;
554 break;
556 /* .. fall through if we are forced to output in 24 hour format */
558 case 'H':
559 count = count > 2 ? 2 : count;
560 dwVal = lpTime->wHour;
561 szAdd = buff;
562 break;
564 case 'm':
565 if (dwFlags & TIME_NOMINUTESORSECONDS)
567 cchWritten = lastFormatPos; /* Skip */
568 bSkipping = TRUE;
570 else
572 count = count > 2 ? 2 : count;
573 dwVal = lpTime->wMinute;
574 szAdd = buff;
576 break;
578 case 's':
579 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
581 cchWritten = lastFormatPos; /* Skip */
582 bSkipping = TRUE;
584 else
586 count = count > 2 ? 2 : count;
587 dwVal = lpTime->wSecond;
588 szAdd = buff;
590 break;
592 case 't':
593 if (dwFlags & TIME_NOTIMEMARKER)
595 cchWritten = lastFormatPos; /* Skip */
596 bSkipping = TRUE;
598 else
600 if (count == 1)
601 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
602 else
603 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
605 break;
608 if (szAdd == buff && buff[0] == '\0')
610 /* We have a numeric value to add */
611 sprintf(buffA, "%.*ld", count, dwVal);
612 MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
615 dwLen = szAdd ? strlenW(szAdd) : 0;
617 if (cchOut && dwLen)
619 if (cchWritten + dwLen < cchOut)
620 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
621 else
623 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
624 goto NLS_GetDateTimeFormatW_Overrun;
627 cchWritten += dwLen;
628 lastFormatPos = cchWritten; /* Save position of last output format text */
630 else
632 /* Literal character */
633 if (!cchOut)
634 cchWritten++; /* Count size only */
635 else if (cchWritten >= cchOut)
636 goto NLS_GetDateTimeFormatW_Overrun;
637 else if (!bSkipping || *lpFormat == ' ')
639 lpStr[cchWritten] = *lpFormat;
640 cchWritten++;
642 lpFormat++;
646 /* Final string terminator and sanity check */
647 if (cchOut)
649 if (cchWritten >= cchOut)
650 goto NLS_GetDateTimeFormatW_Overrun;
651 else
652 lpStr[cchWritten] = '\0';
654 cchWritten++; /* Include terminating NUL */
656 TRACE("returning length=%d, ouput='%s'\n", cchWritten, debugstr_w(lpStr));
657 return cchWritten;
659 NLS_GetDateTimeFormatW_Overrun:
660 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
661 SetLastError(ERROR_INSUFFICIENT_BUFFER);
662 return 0;
665 /******************************************************************************
666 * NLS_GetDateTimeFormatA <internal>
668 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
670 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
671 const SYSTEMTIME* lpTime,
672 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
674 DWORD cp = CP_ACP;
675 WCHAR szFormat[128], szOut[128];
676 INT iRet;
678 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
679 debugstr_a(lpFormat), lpStr, cchOut);
681 if (NLS_IsUnicodeOnlyLcid(lcid))
683 GetDateTimeFormatA_InvalidParameter:
684 SetLastError(ERROR_INVALID_PARAMETER);
685 return 0;
688 if (!(dwFlags & LOCALE_USE_CP_ACP))
690 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
691 if (!node)
692 goto GetDateTimeFormatA_InvalidParameter;
693 cp = node->dwCodePage;
696 if (lpFormat)
697 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
699 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
700 cchOut = sizeof(szOut)/sizeof(WCHAR);
702 szOut[0] = '\0';
704 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
705 lpStr ? szOut : NULL, cchOut);
707 if (lpStr)
709 if (szOut[0])
710 WideCharToMultiByte(cp, 0, szOut, -1, lpStr, cchOut, 0, 0);
711 else if (cchOut && iRet)
712 *lpStr = '\0';
714 return iRet;
717 /******************************************************************************
718 * GetDateFormatA [KERNEL32.@]
720 * Format a date for a given locale.
722 * PARAMS
723 * lcid [I] Locale to format for
724 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
725 * lpTime [I] Date to format
726 * lpFormat [I] Format string, or NULL to use the system defaults
727 * lpDateStr [O] Destination for formatted string
728 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
730 * NOTES
731 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
732 * details returned by GetLocaleInfoA() and modified by dwFlags.
733 * - lpFormat is a string of characters and formatting tokens. Any characters
734 * in the string are copied verbatim to lpDateStr, with tokens being replaced
735 * by the date values they represent.
736 * - The following tokens have special meanings in a date format string:
737 *| Token Meaning
738 *| ----- -------
739 *| d Single digit day of the month (no leading 0)
740 *| dd Double digit day of the month
741 *| ddd Short name for the day of the week
742 *| dddd Long name for the day of the week
743 *| M Single digit month of the year (no leading 0)
744 *| MM Double digit month of the year
745 *| MMM Short name for the month of the year
746 *| MMMM Long name for the month of the year
747 *| y Double digit year number (no leading 0)
748 *| yy Double digit year number
749 *| yyyy Four digit year number
750 *| gg Era string, for example 'AD'.
751 * - To output any literal character that could be misidentified as a token,
752 * enclose it in single quotes.
753 * - The Ascii version of this function fails if lcid is Unicode only.
755 * RETURNS
756 * Success: The number of character written to lpDateStr, or that would
757 * have been written, if cchOut is 0.
758 * Failure: 0. Use GetLastError() to determine the cause.
760 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
761 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
763 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
764 debugstr_a(lpFormat), lpDateStr, cchOut);
766 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
767 lpFormat, lpDateStr, cchOut);
771 /******************************************************************************
772 * GetDateFormatW [KERNEL32.@]
774 * See GetDateFormatA.
776 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
777 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
779 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
780 debugstr_w(lpFormat), lpDateStr, cchOut);
782 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
783 lpFormat, lpDateStr, cchOut);
786 /******************************************************************************
787 * GetTimeFormatA [KERNEL32.@]
789 * Format a time for a given locale.
791 * PARAMS
792 * lcid [I] Locale to format for
793 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
794 * lpTime [I] Time to format
795 * lpFormat [I] Formatting overrides
796 * lpTimeStr [O] Destination for formatted string
797 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
799 * NOTES
800 * - If lpFormat is NULL, lpszValue will be formatted according to the format
801 * details returned by GetLocaleInfoA() and modified by dwFlags.
802 * - lpFormat is a string of characters and formatting tokens. Any characters
803 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
804 * by the time values they represent.
805 * - The following tokens have special meanings in a time format string:
806 *| Token Meaning
807 *| ----- -------
808 *| h Hours with no leading zero (12-hour clock)
809 *| hh Hours with full two digits (12-hour clock)
810 *| H Hours with no leading zero (24-hour clock)
811 *| HH Hours with full two digits (24-hour clock)
812 *| m Minutes with no leading zero
813 *| mm Minutes with full two digits
814 *| s Seconds with no leading zero
815 *| ss Seconds with full two digits
816 *| t Short time marker (e.g. "A" or "P")
817 *| tt Long time marker (e.g. "AM", "PM")
818 * - To output any literal character that could be misidentified as a token,
819 * enclose it in single quotes.
820 * - The Ascii version of this function fails if lcid is Unicode only.
822 * RETURNS
823 * Success: The number of character written to lpTimeStr, or that would
824 * have been written, if cchOut is 0.
825 * Failure: 0. Use GetLastError() to determine the cause.
827 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
828 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
830 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
831 debugstr_a(lpFormat), lpTimeStr, cchOut);
833 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
834 lpFormat, lpTimeStr, cchOut);
837 /******************************************************************************
838 * GetTimeFormatW [KERNEL32.@]
840 * See GetTimeFormatA.
842 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
843 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
845 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
846 debugstr_w(lpFormat), lpTimeStr, cchOut);
848 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
849 lpFormat, lpTimeStr, cchOut);
852 /**************************************************************************
853 * GetNumberFormatA (KERNEL32.@)
855 * Format a number string for a given locale.
857 * PARAMS
858 * lcid [I] Locale to format for
859 * dwFlags [I] LOCALE_ flags from "winnls.h"
860 * lpszValue [I] String to format
861 * lpFormat [I] Formatting overrides
862 * lpNumberStr [O] Destination for formatted string
863 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
865 * NOTES
866 * - lpszValue can contain only '0' - '9', '-' and '.'.
867 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
868 * be formatted according to the format details returned by GetLocaleInfoA().
869 * - This function rounds the number string if the number of decimals exceeds the
870 * locales normal number of decimal places.
871 * - If cchOut is 0, this function does not write to lpNumberStr.
872 * - The Ascii version of this function fails if lcid is Unicode only.
874 * RETURNS
875 * Success: The number of character written to lpNumberStr, or that would
876 * have been written, if cchOut is 0.
877 * Failure: 0. Use GetLastError() to determine the cause.
879 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
880 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
881 LPSTR lpNumberStr, int cchOut)
883 DWORD cp = CP_ACP;
884 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
885 NUMBERFMTW fmt;
886 const NUMBERFMTW *pfmt = NULL;
887 INT iRet;
889 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
890 lpFormat, lpNumberStr, cchOut);
892 if (NLS_IsUnicodeOnlyLcid(lcid))
894 GetNumberFormatA_InvalidParameter:
895 SetLastError(ERROR_INVALID_PARAMETER);
896 return 0;
899 if (!(dwFlags & LOCALE_USE_CP_ACP))
901 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
902 if (!node)
903 goto GetNumberFormatA_InvalidParameter;
904 cp = node->dwCodePage;
907 if (lpFormat)
909 memcpy(&fmt, lpFormat, sizeof(fmt));
910 pfmt = &fmt;
911 if (lpFormat->lpDecimalSep)
913 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
914 fmt.lpDecimalSep = szDec;
916 if (lpFormat->lpThousandSep)
918 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
919 fmt.lpThousandSep = szGrp;
923 if (lpszValue)
924 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
926 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
927 cchOut = sizeof(szOut)/sizeof(WCHAR);
929 szOut[0] = '\0';
931 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
932 lpNumberStr ? szOut : NULL, cchOut);
934 if (szOut[0] && lpNumberStr)
935 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
936 return iRet;
939 /* Number parsing state flags */
940 #define NF_ISNEGATIVE 0x1 /* '-' found */
941 #define NF_ISREAL 0x2 /* '.' found */
942 #define NF_DIGITS 0x4 /* '0'-'9' found */
943 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
944 #define NF_ROUND 0x10 /* Number needs to be rounded */
946 /* Formatting options for Numbers */
947 #define NLS_NEG_PARENS 0 /* "(1.1)" */
948 #define NLS_NEG_LEFT 1 /* "-1.1" */
949 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
950 #define NLS_NEG_RIGHT 3 /* "1.1-" */
951 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
953 /**************************************************************************
954 * GetNumberFormatW (KERNEL32.@)
956 * See GetNumberFormatA.
958 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
959 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
960 LPWSTR lpNumberStr, int cchOut)
962 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
963 WCHAR szNegBuff[8];
964 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
965 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
966 INT iRet;
968 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
969 lpFormat, lpNumberStr, cchOut);
971 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
972 !IsValidLocale(lcid, 0) ||
973 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
975 GetNumberFormatW_Error:
976 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
977 return 0;
980 if (!lpFormat)
982 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
984 if (!node)
985 goto GetNumberFormatW_Error;
986 lpFormat = &node->fmt;
987 lpszNegStart = lpszNeg = GetNegative(node);
989 else
991 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
992 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
993 lpszNegStart = lpszNeg = szNegBuff;
995 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
997 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
999 /* Format the number backwards into a temporary buffer */
1001 szSrc = lpszValue;
1002 *szOut-- = '\0';
1004 /* Check the number for validity */
1005 while (*szSrc)
1007 if (*szSrc >= '0' && *szSrc <= '9')
1009 dwState |= NF_DIGITS;
1010 if (dwState & NF_ISREAL)
1011 dwDecimals++;
1013 else if (*szSrc == '-')
1015 if (dwState)
1016 goto GetNumberFormatW_Error; /* '-' not first character */
1017 dwState |= NF_ISNEGATIVE;
1019 else if (*szSrc == '.')
1021 if (dwState & NF_ISREAL)
1022 goto GetNumberFormatW_Error; /* More than one '.' */
1023 dwState |= NF_ISREAL;
1025 else
1026 goto GetNumberFormatW_Error; /* Invalid char */
1027 szSrc++;
1029 szSrc--; /* Point to last character */
1031 if (!(dwState & NF_DIGITS))
1032 goto GetNumberFormatW_Error; /* No digits */
1034 /* Add any trailing negative sign */
1035 if (dwState & NF_ISNEGATIVE)
1037 switch (lpFormat->NegativeOrder)
1039 case NLS_NEG_PARENS:
1040 *szOut-- = ')';
1041 break;
1042 case NLS_NEG_RIGHT:
1043 case NLS_NEG_RIGHT_SPACE:
1044 while (lpszNeg >= lpszNegStart)
1045 *szOut-- = *lpszNeg--;
1046 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1047 *szOut-- = ' ';
1048 break;
1052 /* Copy all digits up to the decimal point */
1053 if (!lpFormat->NumDigits)
1055 if (dwState & NF_ISREAL)
1057 while (*szSrc != '.') /* Don't write any decimals or a separator */
1059 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1060 dwState |= NF_ROUND;
1061 else
1062 dwState &= ~NF_ROUND;
1063 szSrc--;
1065 szSrc--;
1068 else
1070 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1072 if (dwDecimals <= lpFormat->NumDigits)
1074 dwDecimals = lpFormat->NumDigits - dwDecimals;
1075 while (dwDecimals--)
1076 *szOut-- = '0'; /* Pad to correct number of dp */
1078 else
1080 dwDecimals -= lpFormat->NumDigits;
1081 /* Skip excess decimals, and determine if we have to round the number */
1082 while (dwDecimals--)
1084 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1085 dwState |= NF_ROUND;
1086 else
1087 dwState &= ~NF_ROUND;
1088 szSrc--;
1092 if (dwState & NF_ISREAL)
1094 while (*szSrc != '.')
1096 if (dwState & NF_ROUND)
1098 if (*szSrc == '9')
1099 *szOut-- = '0'; /* continue rounding */
1100 else
1102 dwState &= ~NF_ROUND;
1103 *szOut-- = (*szSrc)+1;
1105 szSrc--;
1107 else
1108 *szOut-- = *szSrc--; /* Write existing decimals */
1110 szSrc--; /* Skip '.' */
1113 while (lpszDec >= lpFormat->lpDecimalSep)
1114 *szOut-- = *lpszDec--; /* Write decimal separator */
1117 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1119 /* Write the remaining whole number digits, including grouping chars */
1120 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1122 if (dwState & NF_ROUND)
1124 if (*szSrc == '9')
1125 *szOut-- = '0'; /* continue rounding */
1126 else
1128 dwState &= ~NF_ROUND;
1129 *szOut-- = (*szSrc)+1;
1131 szSrc--;
1133 else
1134 *szOut-- = *szSrc--;
1136 dwState |= NF_DIGITS_OUT;
1137 dwCurrentGroupCount++;
1138 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1140 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1142 while (lpszGrp >= lpFormat->lpThousandSep)
1143 *szOut-- = *lpszGrp--; /* Write grouping char */
1145 dwCurrentGroupCount = 0;
1146 if (lpFormat->Grouping == 32)
1147 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1150 if (dwState & NF_ROUND)
1152 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1154 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1155 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1157 /* Add any leading negative sign */
1158 if (dwState & NF_ISNEGATIVE)
1160 switch (lpFormat->NegativeOrder)
1162 case NLS_NEG_PARENS:
1163 *szOut-- = '(';
1164 break;
1165 case NLS_NEG_LEFT_SPACE:
1166 *szOut-- = ' ';
1167 /* Fall through */
1168 case NLS_NEG_LEFT:
1169 while (lpszNeg >= lpszNegStart)
1170 *szOut-- = *lpszNeg--;
1171 break;
1174 szOut++;
1176 iRet = strlenW(szOut) + 1;
1177 if (cchOut)
1179 if (iRet <= cchOut)
1180 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1181 else
1183 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1184 lpNumberStr[cchOut - 1] = '\0';
1185 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1186 iRet = 0;
1189 return iRet;
1192 /**************************************************************************
1193 * GetCurrencyFormatA (KERNEL32.@)
1195 * Format a currency string for a given locale.
1197 * PARAMS
1198 * lcid [I] Locale to format for
1199 * dwFlags [I] LOCALE_ flags from "winnls.h"
1200 * lpszValue [I] String to format
1201 * lpFormat [I] Formatting overrides
1202 * lpCurrencyStr [O] Destination for formatted string
1203 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1205 * NOTES
1206 * - lpszValue can contain only '0' - '9', '-' and '.'.
1207 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1208 * be formatted according to the format details returned by GetLocaleInfoA().
1209 * - This function rounds the currency if the number of decimals exceeds the
1210 * locales number of currency decimal places.
1211 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1212 * - The Ascii version of this function fails if lcid is Unicode only.
1214 * RETURNS
1215 * Success: The number of character written to lpNumberStr, or that would
1216 * have been written, if cchOut is 0.
1217 * Failure: 0. Use GetLastError() to determine the cause.
1219 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1220 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1221 LPSTR lpCurrencyStr, int cchOut)
1223 DWORD cp = CP_ACP;
1224 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1225 CURRENCYFMTW fmt;
1226 const CURRENCYFMTW *pfmt = NULL;
1227 INT iRet;
1229 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1230 lpFormat, lpCurrencyStr, cchOut);
1232 if (NLS_IsUnicodeOnlyLcid(lcid))
1234 GetCurrencyFormatA_InvalidParameter:
1235 SetLastError(ERROR_INVALID_PARAMETER);
1236 return 0;
1239 if (!(dwFlags & LOCALE_USE_CP_ACP))
1241 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1242 if (!node)
1243 goto GetCurrencyFormatA_InvalidParameter;
1244 cp = node->dwCodePage;
1247 if (lpFormat)
1249 memcpy(&fmt, lpFormat, sizeof(fmt));
1250 pfmt = &fmt;
1251 if (lpFormat->lpDecimalSep)
1253 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1254 fmt.lpDecimalSep = szDec;
1256 if (lpFormat->lpThousandSep)
1258 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1259 fmt.lpThousandSep = szGrp;
1261 if (lpFormat->lpCurrencySymbol)
1263 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1264 fmt.lpCurrencySymbol = szCy;
1268 if (lpszValue)
1269 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1271 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1272 cchOut = sizeof(szOut)/sizeof(WCHAR);
1274 szOut[0] = '\0';
1276 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1277 lpCurrencyStr ? szOut : NULL, cchOut);
1279 if (szOut[0] && lpCurrencyStr)
1280 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1281 return iRet;
1284 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1285 #define CF_PARENS 0x1 /* Parentheses */
1286 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1287 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1288 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1289 #define CF_CY_LEFT 0x10 /* '$' to the left */
1290 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1291 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1293 /**************************************************************************
1294 * GetCurrencyFormatW (KERNEL32.@)
1296 * See GetCurrencyFormatA.
1298 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1299 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1300 LPWSTR lpCurrencyStr, int cchOut)
1302 static const BYTE NLS_NegCyFormats[16] =
1304 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1305 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1306 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1307 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1308 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1309 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1310 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1311 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1312 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1313 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1314 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1315 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1316 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1317 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1318 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1319 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1321 static const BYTE NLS_PosCyFormats[4] =
1323 CF_CY_LEFT, /* $1.1 */
1324 CF_CY_RIGHT, /* 1.1$ */
1325 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1326 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1328 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1329 WCHAR szNegBuff[8];
1330 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1331 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1332 INT iRet;
1334 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1335 lpFormat, lpCurrencyStr, cchOut);
1337 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1338 !IsValidLocale(lcid, 0) ||
1339 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1340 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1341 lpFormat->PositiveOrder > 3)))
1343 GetCurrencyFormatW_Error:
1344 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1345 return 0;
1348 if (!lpFormat)
1350 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1352 if (!node)
1353 goto GetCurrencyFormatW_Error;
1354 lpFormat = &node->cyfmt;
1355 lpszNegStart = lpszNeg = GetNegative(node);
1357 else
1359 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1360 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1361 lpszNegStart = lpszNeg = szNegBuff;
1363 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1365 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1366 lpszCyStart = lpFormat->lpCurrencySymbol;
1367 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1369 /* Format the currency backwards into a temporary buffer */
1371 szSrc = lpszValue;
1372 *szOut-- = '\0';
1374 /* Check the number for validity */
1375 while (*szSrc)
1377 if (*szSrc >= '0' && *szSrc <= '9')
1379 dwState |= NF_DIGITS;
1380 if (dwState & NF_ISREAL)
1381 dwDecimals++;
1383 else if (*szSrc == '-')
1385 if (dwState)
1386 goto GetCurrencyFormatW_Error; /* '-' not first character */
1387 dwState |= NF_ISNEGATIVE;
1389 else if (*szSrc == '.')
1391 if (dwState & NF_ISREAL)
1392 goto GetCurrencyFormatW_Error; /* More than one '.' */
1393 dwState |= NF_ISREAL;
1395 else
1396 goto GetCurrencyFormatW_Error; /* Invalid char */
1397 szSrc++;
1399 szSrc--; /* Point to last character */
1401 if (!(dwState & NF_DIGITS))
1402 goto GetCurrencyFormatW_Error; /* No digits */
1404 if (dwState & NF_ISNEGATIVE)
1405 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1406 else
1407 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1409 /* Add any trailing negative or currency signs */
1410 if (dwFmt & CF_PARENS)
1411 *szOut-- = ')';
1413 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1415 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1417 case CF_MINUS_RIGHT:
1418 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1419 while (lpszNeg >= lpszNegStart)
1420 *szOut-- = *lpszNeg--;
1421 dwFmt &= ~CF_MINUS_RIGHT;
1422 break;
1424 case CF_CY_RIGHT:
1425 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1426 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1427 while (lpszCy >= lpszCyStart)
1428 *szOut-- = *lpszCy--;
1429 if (dwFmt & CF_CY_SPACE)
1430 *szOut-- = ' ';
1431 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1432 break;
1436 /* Copy all digits up to the decimal point */
1437 if (!lpFormat->NumDigits)
1439 if (dwState & NF_ISREAL)
1441 while (*szSrc != '.') /* Don't write any decimals or a separator */
1443 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1444 dwState |= NF_ROUND;
1445 else
1446 dwState &= ~NF_ROUND;
1447 szSrc--;
1449 szSrc--;
1452 else
1454 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1456 if (dwDecimals <= lpFormat->NumDigits)
1458 dwDecimals = lpFormat->NumDigits - dwDecimals;
1459 while (dwDecimals--)
1460 *szOut-- = '0'; /* Pad to correct number of dp */
1462 else
1464 dwDecimals -= lpFormat->NumDigits;
1465 /* Skip excess decimals, and determine if we have to round the number */
1466 while (dwDecimals--)
1468 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1469 dwState |= NF_ROUND;
1470 else
1471 dwState &= ~NF_ROUND;
1472 szSrc--;
1476 if (dwState & NF_ISREAL)
1478 while (*szSrc != '.')
1480 if (dwState & NF_ROUND)
1482 if (*szSrc == '9')
1483 *szOut-- = '0'; /* continue rounding */
1484 else
1486 dwState &= ~NF_ROUND;
1487 *szOut-- = (*szSrc)+1;
1489 szSrc--;
1491 else
1492 *szOut-- = *szSrc--; /* Write existing decimals */
1494 szSrc--; /* Skip '.' */
1496 while (lpszDec >= lpFormat->lpDecimalSep)
1497 *szOut-- = *lpszDec--; /* Write decimal separator */
1500 dwGroupCount = lpFormat->Grouping;
1502 /* Write the remaining whole number digits, including grouping chars */
1503 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1505 if (dwState & NF_ROUND)
1507 if (*szSrc == '9')
1508 *szOut-- = '0'; /* continue rounding */
1509 else
1511 dwState &= ~NF_ROUND;
1512 *szOut-- = (*szSrc)+1;
1514 szSrc--;
1516 else
1517 *szOut-- = *szSrc--;
1519 dwState |= NF_DIGITS_OUT;
1520 dwCurrentGroupCount++;
1521 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount)
1523 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1525 while (lpszGrp >= lpFormat->lpThousandSep)
1526 *szOut-- = *lpszGrp--; /* Write grouping char */
1528 dwCurrentGroupCount = 0;
1531 if (dwState & NF_ROUND)
1532 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1533 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1534 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1536 /* Add any leading negative or currency sign */
1537 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1539 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1541 case CF_MINUS_LEFT:
1542 case CF_MINUS_LEFT|CF_CY_LEFT:
1543 while (lpszNeg >= lpszNegStart)
1544 *szOut-- = *lpszNeg--;
1545 dwFmt &= ~CF_MINUS_LEFT;
1546 break;
1548 case CF_CY_LEFT:
1549 case CF_CY_LEFT|CF_MINUS_BEFORE:
1550 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1551 if (dwFmt & CF_CY_SPACE)
1552 *szOut-- = ' ';
1553 while (lpszCy >= lpszCyStart)
1554 *szOut-- = *lpszCy--;
1555 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1556 break;
1559 if (dwFmt & CF_PARENS)
1560 *szOut-- = '(';
1561 szOut++;
1563 iRet = strlenW(szOut) + 1;
1564 if (cchOut)
1566 if (iRet <= cchOut)
1567 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1568 else
1570 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1571 lpCurrencyStr[cchOut - 1] = '\0';
1572 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1573 iRet = 0;
1576 return iRet;
1579 /* FIXME: Everything below here needs to move somewhere else along with the
1580 * other EnumXXX functions, when a method for storing resources for
1581 * alternate calendars is determined.
1584 /**************************************************************************
1585 * EnumDateFormatsExA (KERNEL32.@)
1587 BOOL WINAPI EnumDateFormatsExA( DATEFMT_ENUMPROCEXA lpDateFmtEnumProc, LCID Locale, DWORD dwFlags )
1589 FIXME("(%p, %ld, %ld): stub\n", lpDateFmtEnumProc, Locale, dwFlags);
1590 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1591 return FALSE;
1594 /**************************************************************************
1595 * EnumDateFormatsExW (KERNEL32.@)
1597 BOOL WINAPI EnumDateFormatsExW( DATEFMT_ENUMPROCEXW lpDateFmtEnumProc, LCID Locale, DWORD dwFlags )
1599 FIXME("(%p, %ld, %ld): stub\n", lpDateFmtEnumProc, Locale, dwFlags);
1600 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1601 return FALSE;
1604 /**************************************************************************
1605 * EnumDateFormatsA (KERNEL32.@)
1607 BOOL WINAPI EnumDateFormatsA( DATEFMT_ENUMPROCA lpDateFmtEnumProc, LCID Locale, DWORD dwFlags)
1609 LCID Loc = GetUserDefaultLCID();
1610 if(!lpDateFmtEnumProc)
1612 SetLastError(ERROR_INVALID_PARAMETER);
1613 return FALSE;
1616 switch( Loc )
1619 case 0x00000407: /* (Loc,"de_DE") */
1621 switch(dwFlags)
1623 case DATE_SHORTDATE:
1624 if(!(*lpDateFmtEnumProc)("dd.MM.yy")) return TRUE;
1625 if(!(*lpDateFmtEnumProc)("d.M.yyyy")) return TRUE;
1626 if(!(*lpDateFmtEnumProc)("d.MM.yy")) return TRUE;
1627 if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE;
1628 return TRUE;
1629 case DATE_LONGDATE:
1630 if(!(*lpDateFmtEnumProc)("dddd,d. MMMM yyyy")) return TRUE;
1631 if(!(*lpDateFmtEnumProc)("d. MMMM yyyy")) return TRUE;
1632 if(!(*lpDateFmtEnumProc)("d. MMM yyyy")) return TRUE;
1633 return TRUE;
1634 default:
1635 FIXME("Unknown date format (%ld)\n", dwFlags);
1636 SetLastError(ERROR_INVALID_PARAMETER);
1637 return FALSE;
1641 case 0x0000040c: /* (Loc,"fr_FR") */
1643 switch(dwFlags)
1645 case DATE_SHORTDATE:
1646 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1647 if(!(*lpDateFmtEnumProc)("dd.MM.yy")) return TRUE;
1648 if(!(*lpDateFmtEnumProc)("dd-MM-yy")) return TRUE;
1649 if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE;
1650 return TRUE;
1651 case DATE_LONGDATE:
1652 if(!(*lpDateFmtEnumProc)("dddd d MMMM yyyy")) return TRUE;
1653 if(!(*lpDateFmtEnumProc)("d MMM yy")) return TRUE;
1654 if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE;
1655 return TRUE;
1656 default:
1657 FIXME("Unknown date format (%ld)\n", dwFlags);
1658 SetLastError(ERROR_INVALID_PARAMETER);
1659 return FALSE;
1663 case 0x00000c0c: /* (Loc,"fr_CA") */
1665 switch(dwFlags)
1667 case DATE_SHORTDATE:
1668 if(!(*lpDateFmtEnumProc)("yy-MM-dd")) return TRUE;
1669 if(!(*lpDateFmtEnumProc)("dd-MM-yy")) return TRUE;
1670 if(!(*lpDateFmtEnumProc)("yy MM dd")) return TRUE;
1671 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1672 return TRUE;
1673 case DATE_LONGDATE:
1674 if(!(*lpDateFmtEnumProc)("d MMMM, yyyy")) return TRUE;
1675 if(!(*lpDateFmtEnumProc)("d MMM yyyy")) return TRUE;
1676 return TRUE;
1677 default:
1678 FIXME("Unknown date format (%ld)\n", dwFlags);
1679 SetLastError(ERROR_INVALID_PARAMETER);
1680 return FALSE;
1684 case 0x00000809: /* (Loc,"en_UK") */
1686 switch(dwFlags)
1688 case DATE_SHORTDATE:
1689 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1690 if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE;
1691 if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE;
1692 if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE;
1693 return TRUE;
1694 case DATE_LONGDATE:
1695 if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE;
1696 if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE;
1697 return TRUE;
1698 default:
1699 FIXME("Unknown date format (%ld)\n", dwFlags);
1700 SetLastError(ERROR_INVALID_PARAMETER);
1701 return FALSE;
1705 case 0x00000c09: /* (Loc,"en_AU") */
1707 switch(dwFlags)
1709 case DATE_SHORTDATE:
1710 if(!(*lpDateFmtEnumProc)("d/MM/yy")) return TRUE;
1711 if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE;
1712 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1713 return TRUE;
1714 case DATE_LONGDATE:
1715 if(!(*lpDateFmtEnumProc)("dddd,d MMMM yyyy")) return TRUE;
1716 if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE;
1717 return TRUE;
1718 default:
1719 FIXME("Unknown date format (%ld)\n", dwFlags);
1720 SetLastError(ERROR_INVALID_PARAMETER);
1721 return FALSE;
1725 case 0x00001009: /* (Loc,"en_CA") */
1727 switch(dwFlags)
1729 case DATE_SHORTDATE:
1730 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1731 if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE;
1732 if(!(*lpDateFmtEnumProc)("yy-MM-dd")) return TRUE;
1733 if(!(*lpDateFmtEnumProc)("M/dd/yy")) return TRUE;
1734 return TRUE;
1735 case DATE_LONGDATE:
1736 if(!(*lpDateFmtEnumProc)("d-MMM-yy")) return TRUE;
1737 if(!(*lpDateFmtEnumProc)("MMMM d, yyyy")) return TRUE;
1738 return TRUE;
1739 default:
1740 FIXME("Unknown date format (%ld)\n", dwFlags);
1741 SetLastError(ERROR_INVALID_PARAMETER);
1742 return FALSE;
1746 case 0x00001409: /* (Loc,"en_NZ") */
1748 switch(dwFlags)
1750 case DATE_SHORTDATE:
1751 if(!(*lpDateFmtEnumProc)("d/MM/yy")) return TRUE;
1752 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1753 if(!(*lpDateFmtEnumProc)("d.MM.yy")) return TRUE;
1754 return TRUE;
1755 case DATE_LONGDATE:
1756 if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE;
1757 if(!(*lpDateFmtEnumProc)("dddd, d MMMM yyyy")) return TRUE;
1758 return TRUE;
1759 default:
1760 FIXME("Unknown date format (%ld)\n", dwFlags);
1761 SetLastError(ERROR_INVALID_PARAMETER);
1762 return FALSE;
1766 case 0x00001809: /* (Loc,"en_IE") */
1768 switch(dwFlags)
1770 case DATE_SHORTDATE:
1771 if(!(*lpDateFmtEnumProc)("dd/MM/yy")) return TRUE;
1772 if(!(*lpDateFmtEnumProc)("d/M/yy")) return TRUE;
1773 if(!(*lpDateFmtEnumProc)("d.M.yy")) return TRUE;
1774 return TRUE;
1775 case DATE_LONGDATE:
1776 if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE;
1777 if(!(*lpDateFmtEnumProc)("d MMMM yyyy")) return TRUE;
1778 return TRUE;
1779 default:
1780 FIXME("Unknown date format (%ld)\n", dwFlags);
1781 SetLastError(ERROR_INVALID_PARAMETER);
1782 return FALSE;
1786 case 0x00001c09: /* (Loc,"en_ZA") */
1788 switch(dwFlags)
1790 case DATE_SHORTDATE:
1791 if(!(*lpDateFmtEnumProc)("yy/MM/dd")) return TRUE;
1792 return TRUE;
1793 case DATE_LONGDATE:
1794 if(!(*lpDateFmtEnumProc)("dd MMMM yyyy")) return TRUE;
1795 return TRUE;
1796 default:
1797 FIXME("Unknown date format (%ld)\n", dwFlags);
1798 SetLastError(ERROR_INVALID_PARAMETER);
1799 return FALSE;
1803 case 0x00002009: /* (Loc,"en_JM") */
1805 switch(dwFlags)
1807 case DATE_SHORTDATE:
1808 if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE;
1809 return TRUE;
1810 case DATE_LONGDATE:
1811 if(!(*lpDateFmtEnumProc)("dddd,MMMM dd,yyyy")) return TRUE;
1812 if(!(*lpDateFmtEnumProc)("MMMM dd,yyyy")) return TRUE;
1813 if(!(*lpDateFmtEnumProc)("dddd,dd MMMM,yyyy")) return TRUE;
1814 if(!(*lpDateFmtEnumProc)("dd MMMM,yyyy")) return TRUE;
1815 return TRUE;
1816 default:
1817 FIXME("Unknown date format (%ld)\n", dwFlags);
1818 SetLastError(ERROR_INVALID_PARAMETER);
1819 return FALSE;
1823 case 0x00002809: /* (Loc,"en_BZ") */
1824 case 0x00002c09: /* (Loc,"en_TT") */
1826 switch(dwFlags)
1828 case DATE_SHORTDATE:
1829 if(!(*lpDateFmtEnumProc)("dd/MM/yyyy")) return TRUE;
1830 return TRUE;
1831 case DATE_LONGDATE:
1832 if(!(*lpDateFmtEnumProc)("dddd,dd MMMM yyyy")) return TRUE;
1833 return TRUE;
1834 default:
1835 FIXME("Unknown date format (%ld)\n", dwFlags);
1836 SetLastError(ERROR_INVALID_PARAMETER);
1837 return FALSE;
1841 default: /* default to US English "en_US" */
1843 switch(dwFlags)
1845 case DATE_SHORTDATE:
1846 if(!(*lpDateFmtEnumProc)("M/d/yy")) return TRUE;
1847 if(!(*lpDateFmtEnumProc)("M/d/yyyy")) return TRUE;
1848 if(!(*lpDateFmtEnumProc)("MM/dd/yy")) return TRUE;
1849 if(!(*lpDateFmtEnumProc)("MM/dd/yyyy")) return TRUE;
1850 if(!(*lpDateFmtEnumProc)("yy/MM/dd")) return TRUE;
1851 if(!(*lpDateFmtEnumProc)("dd-MMM-yy")) return TRUE;
1852 return TRUE;
1853 case DATE_LONGDATE:
1854 if(!(*lpDateFmtEnumProc)("dddd, MMMM dd, yyyy")) return TRUE;
1855 if(!(*lpDateFmtEnumProc)("MMMM dd, yyyy")) return TRUE;
1856 if(!(*lpDateFmtEnumProc)("dddd, dd MMMM, yyyy")) return TRUE;
1857 if(!(*lpDateFmtEnumProc)("dd MMMM, yyyy")) return TRUE;
1858 return TRUE;
1859 default:
1860 FIXME("Unknown date format (%ld)\n", dwFlags);
1861 SetLastError(ERROR_INVALID_PARAMETER);
1862 return FALSE;
1868 /**************************************************************************
1869 * EnumDateFormatsW (KERNEL32.@)
1871 BOOL WINAPI EnumDateFormatsW( DATEFMT_ENUMPROCW lpDateFmtEnumProc, LCID Locale, DWORD dwFlags )
1873 FIXME("(%p, %ld, %ld): stub\n", lpDateFmtEnumProc, Locale, dwFlags);
1874 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1875 return FALSE;
1878 /**************************************************************************
1879 * EnumTimeFormatsA (KERNEL32.@)
1881 BOOL WINAPI EnumTimeFormatsA( TIMEFMT_ENUMPROCA lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags )
1883 LCID Loc = GetUserDefaultLCID();
1884 if(!lpTimeFmtEnumProc)
1886 SetLastError(ERROR_INVALID_PARAMETER);
1887 return FALSE;
1889 if(dwFlags)
1891 FIXME("Unknown time format (%ld)\n", dwFlags);
1894 switch( Loc )
1896 case 0x00000407: /* (Loc,"de_DE") */
1898 if(!(*lpTimeFmtEnumProc)("HH.mm")) return TRUE;
1899 if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE;
1900 if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE;
1901 if(!(*lpTimeFmtEnumProc)("H.mm")) return TRUE;
1902 if(!(*lpTimeFmtEnumProc)("H.mm'Uhr'")) return TRUE;
1903 return TRUE;
1906 case 0x0000040c: /* (Loc,"fr_FR") */
1907 case 0x00000c0c: /* (Loc,"fr_CA") */
1909 if(!(*lpTimeFmtEnumProc)("H:mm")) return TRUE;
1910 if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE;
1911 if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE;
1912 if(!(*lpTimeFmtEnumProc)("HH.mm")) return TRUE;
1913 if(!(*lpTimeFmtEnumProc)("HH'h'mm")) return TRUE;
1914 return TRUE;
1917 case 0x00000809: /* (Loc,"en_UK") */
1918 case 0x00000c09: /* (Loc,"en_AU") */
1919 case 0x00001409: /* (Loc,"en_NZ") */
1920 case 0x00001809: /* (Loc,"en_IE") */
1922 if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE;
1923 if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE;
1924 if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE;
1925 return TRUE;
1928 case 0x00001c09: /* (Loc,"en_ZA") */
1929 case 0x00002809: /* (Loc,"en_BZ") */
1930 case 0x00002c09: /* (Loc,"en_TT") */
1932 if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE;
1933 if(!(*lpTimeFmtEnumProc)("hh:mm:ss tt")) return TRUE;
1934 return TRUE;
1937 default: /* default to US style "en_US" */
1939 if(!(*lpTimeFmtEnumProc)("h:mm:ss tt")) return TRUE;
1940 if(!(*lpTimeFmtEnumProc)("hh:mm:ss tt")) return TRUE;
1941 if(!(*lpTimeFmtEnumProc)("H:mm:ss")) return TRUE;
1942 if(!(*lpTimeFmtEnumProc)("HH:mm:ss")) return TRUE;
1943 return TRUE;
1948 /**************************************************************************
1949 * EnumTimeFormatsW (KERNEL32.@)
1951 BOOL WINAPI EnumTimeFormatsW( TIMEFMT_ENUMPROCW lpTimeFmtEnumProc, LCID Locale, DWORD dwFlags )
1953 FIXME("(%p,%ld,%ld): stub\n", lpTimeFmtEnumProc, Locale, dwFlags);
1954 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1955 return FALSE;
1958 /******************************************************************************
1959 * NLS_EnumCalendarInfoAW <internal>
1960 * Enumerates calendar information for a specified locale.
1962 * PARAMS
1963 * calinfoproc [I] Pointer to the callback
1964 * locale [I] The locale for which to retrieve calendar information.
1965 * This parameter can be a locale identifier created by the
1966 * MAKELCID macro, or one of the following values:
1967 * LOCALE_SYSTEM_DEFAULT
1968 * Use the default system locale.
1969 * LOCALE_USER_DEFAULT
1970 * Use the default user locale.
1971 * calendar [I] The calendar for which information is requested, or
1972 * ENUM_ALL_CALENDARS.
1973 * caltype [I] The type of calendar information to be returned. Note
1974 * that only one CALTYPE value can be specified per call
1975 * of this function, except where noted.
1976 * unicode [I] Specifies if the callback expects a unicode string.
1977 * ex [I] Specifies if the callback needs the calendar identifier.
1979 * RETURNS
1980 * Success: TRUE.
1981 * Failure: FALSE. Use GetLastError() to determine the cause.
1983 * NOTES
1984 * When the ANSI version of this function is used with a Unicode-only LCID,
1985 * the call can succeed because the system uses the system code page.
1986 * However, characters that are undefined in the system code page appear
1987 * in the string as a question mark (?).
1989 * TODO
1990 * The above note should be respected by GetCalendarInfoA.
1992 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1993 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1995 WCHAR *buf, *opt = NULL, *iter = NULL;
1996 BOOL ret = FALSE;
1997 int bufSz = 200; /* the size of the buffer */
1999 if (calinfoproc == NULL)
2001 SetLastError(ERROR_INVALID_PARAMETER);
2002 return FALSE;
2005 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
2006 if (buf == NULL)
2008 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2009 return FALSE;
2012 if (calendar == ENUM_ALL_CALENDARS)
2014 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
2015 if (optSz > 1)
2017 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
2018 if (opt == NULL)
2020 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2021 goto NLS_EnumCalendarInfoAW_Cleanup;
2023 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
2024 iter = opt;
2026 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
2029 while (TRUE) /* loop through calendars */
2031 do /* loop until there's no error */
2033 if (unicode)
2034 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
2035 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
2037 if (!ret)
2039 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
2040 { /* so resize it */
2041 int newSz;
2042 if (unicode)
2043 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
2044 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
2045 if (bufSz >= newSz)
2047 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
2048 goto NLS_EnumCalendarInfoAW_Cleanup;
2050 bufSz = newSz;
2051 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
2052 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
2053 if (buf == NULL)
2054 goto NLS_EnumCalendarInfoAW_Cleanup;
2055 } else goto NLS_EnumCalendarInfoAW_Cleanup;
2057 } while (!ret);
2059 /* Here we are. We pass the buffer to the correct version of
2060 * the callback. Because it's not the same number of params,
2061 * we must check for Ex, but we don't care about Unicode
2062 * because the buffer is already in the correct format.
2064 if (ex) {
2065 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
2066 } else
2067 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
2069 if (!ret) { /* the callback told to stop */
2070 ret = TRUE;
2071 break;
2074 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2075 break;
2077 calendar = 0;
2078 while ((*iter >= '0') && (*iter <= '9'))
2079 calendar = calendar * 10 + *iter++ - '0';
2081 if (*iter++ != 0)
2083 SetLastError(ERROR_BADDB);
2084 ret = FALSE;
2085 break;
2089 NLS_EnumCalendarInfoAW_Cleanup:
2090 HeapFree(GetProcessHeap(), 0, opt);
2091 HeapFree(GetProcessHeap(), 0, buf);
2092 return ret;
2095 /******************************************************************************
2096 * EnumCalendarInfoA [KERNEL32.@]
2098 * See EnumCalendarInfoAW.
2100 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2101 CALID calendar,CALTYPE caltype )
2103 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
2104 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
2107 /******************************************************************************
2108 * EnumCalendarInfoW [KERNEL32.@]
2110 * See EnumCalendarInfoAW.
2112 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2113 CALID calendar,CALTYPE caltype )
2115 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
2116 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
2119 /******************************************************************************
2120 * EnumCalendarInfoExA [KERNEL32.@]
2122 * See EnumCalendarInfoAW.
2124 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2125 CALID calendar,CALTYPE caltype )
2127 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
2128 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
2131 /******************************************************************************
2132 * EnumCalendarInfoExW [KERNEL32.@]
2134 * See EnumCalendarInfoAW.
2136 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2137 CALID calendar,CALTYPE caltype )
2139 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
2140 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);