dplayx/tests: Fix compilation on systems that don't support nameless unions.
[wine.git] / dlls / kernel32 / lcformat.c
blob0445a9bf15138dcd4fac53b24635e704d36346e5
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
8 * Copyright 2005 Dmitry Timoshkov
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 #include "config.h"
26 #include "wine/port.h"
28 #include <string.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wine/unicode.h"
36 #include "wine/debug.h"
37 #include "winternl.h"
39 #include "kernel_private.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(nls);
43 #define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
44 #define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
46 /* Since calculating the formatting data for each locale is time-consuming,
47 * we get the format data for each locale only once and cache it in memory.
48 * We cache both the system default and user overridden data, after converting
49 * them into the formats that the functions here expect. Since these functions
50 * will typically be called with only a small number of the total locales
51 * installed, the memory overhead is minimal while the speedup is significant.
53 * Our cache takes the form of a singly linked list, whose node is below:
55 #define NLS_NUM_CACHED_STRINGS 45
57 typedef struct _NLS_FORMAT_NODE
59 LCID lcid; /* Locale Id */
60 DWORD dwFlags; /* 0 or LOCALE_NOUSEROVERRIDE */
61 DWORD dwCodePage; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
62 NUMBERFMTW fmt; /* Default format for numbers */
63 CURRENCYFMTW cyfmt; /* Default format for currencies */
64 LPWSTR lppszStrings[NLS_NUM_CACHED_STRINGS]; /* Default formats,day/month names */
65 WCHAR szShortAM[2]; /* Short 'AM' marker */
66 WCHAR szShortPM[2]; /* Short 'PM' marker */
67 struct _NLS_FORMAT_NODE *next;
68 } NLS_FORMAT_NODE;
70 /* Macros to get particular data strings from a format node */
71 #define GetNegative(fmt) fmt->lppszStrings[0]
72 #define GetLongDate(fmt) fmt->lppszStrings[1]
73 #define GetShortDate(fmt) fmt->lppszStrings[2]
74 #define GetTime(fmt) fmt->lppszStrings[3]
75 #define GetAM(fmt) fmt->lppszStrings[42]
76 #define GetPM(fmt) fmt->lppszStrings[43]
77 #define GetYearMonth(fmt) fmt->lppszStrings[44]
79 #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
80 #define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
81 #define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
82 #define GetShortMonth(fmt,mth) fmt->lppszStrings[30 + mth]
84 /* Write access to the cache is protected by this critical section */
85 static CRITICAL_SECTION NLS_FormatsCS;
86 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
88 0, 0, &NLS_FormatsCS,
89 { &NLS_FormatsCS_debug.ProcessLocksList,
90 &NLS_FormatsCS_debug.ProcessLocksList },
91 0, 0, { (DWORD_PTR)(__FILE__ ": NLS_Formats") }
93 static CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
95 /**************************************************************************
96 * NLS_GetLocaleNumber <internal>
98 * Get a numeric locale format value.
100 static DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
102 WCHAR szBuff[80];
103 DWORD dwVal = 0;
105 szBuff[0] = '\0';
106 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
108 if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '0')
109 dwVal = (szBuff[0] - '0') * 10 + (szBuff[2] - '0');
110 else
112 const WCHAR* iter = szBuff;
113 dwVal = 0;
114 while(*iter >= '0' && *iter <= '9')
115 dwVal = dwVal * 10 + (*iter++ - '0');
117 return dwVal;
120 /**************************************************************************
121 * NLS_GetLocaleString <internal>
123 * Get a string locale format value.
125 static WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
127 WCHAR szBuff[80], *str;
128 DWORD dwLen;
130 szBuff[0] = '\0';
131 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
132 dwLen = strlenW(szBuff) + 1;
133 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
134 if (str)
135 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
136 return str;
139 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
140 TRACE( #type ": %d (%08x)\n", (DWORD)num, (DWORD)num)
142 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
143 TRACE( #type ": %s\n", debugstr_w(str))
145 /**************************************************************************
146 * NLS_GetFormats <internal>
148 * Calculate (and cache) the number formats for a locale.
150 static const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
152 /* GetLocaleInfo() identifiers for cached formatting strings */
153 static const USHORT NLS_LocaleIndices[] = {
154 LOCALE_SNEGATIVESIGN,
155 LOCALE_SLONGDATE, LOCALE_SSHORTDATE,
156 LOCALE_STIMEFORMAT,
157 LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
158 LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
159 LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
160 LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
161 LOCALE_SABBREVDAYNAME7,
162 LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
163 LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
164 LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
165 LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
166 LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
167 LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
168 LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
169 LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
170 LOCALE_S1159, LOCALE_S2359,
171 LOCALE_SYEARMONTH
173 static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
174 NLS_FORMAT_NODE *node = NLS_CachedFormats;
176 dwFlags &= LOCALE_NOUSEROVERRIDE;
178 TRACE("(0x%04x,0x%08x)\n", lcid, dwFlags);
180 /* See if we have already cached the locales number format */
181 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
182 node = node->next;
184 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
186 NLS_FORMAT_NODE *new_node;
187 DWORD i;
189 TRACE("Creating new cache entry\n");
191 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
192 return NULL;
194 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
196 /* Number Format */
197 new_node->lcid = lcid;
198 new_node->dwFlags = dwFlags;
199 new_node->next = NULL;
201 GET_LOCALE_NUMBER(new_node->fmt.NumDigits, LOCALE_IDIGITS);
202 GET_LOCALE_NUMBER(new_node->fmt.LeadingZero, LOCALE_ILZERO);
203 GET_LOCALE_NUMBER(new_node->fmt.NegativeOrder, LOCALE_INEGNUMBER);
205 GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
206 if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
208 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
209 new_node->fmt.Grouping);
210 new_node->fmt.Grouping = 0;
213 GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
214 GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
216 /* Currency Format */
217 new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
218 new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
220 GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
222 if (new_node->cyfmt.Grouping > 9)
224 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
225 new_node->cyfmt.Grouping);
226 new_node->cyfmt.Grouping = 0;
229 GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
230 if (new_node->cyfmt.NegativeOrder > 15)
232 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
233 new_node->cyfmt.NegativeOrder);
234 new_node->cyfmt.NegativeOrder = 0;
236 GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
237 if (new_node->cyfmt.PositiveOrder > 3)
239 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
240 new_node->cyfmt.PositiveOrder);
241 new_node->cyfmt.PositiveOrder = 0;
243 GET_LOCALE_STRING(new_node->cyfmt.lpDecimalSep, LOCALE_SMONDECIMALSEP);
244 GET_LOCALE_STRING(new_node->cyfmt.lpThousandSep, LOCALE_SMONTHOUSANDSEP);
245 GET_LOCALE_STRING(new_node->cyfmt.lpCurrencySymbol, LOCALE_SCURRENCY);
247 /* Date/Time Format info, negative character, etc */
248 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
250 GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
252 new_node->szShortAM[0] = GetAM(new_node)[0]; new_node->szShortAM[1] = '\0';
253 new_node->szShortPM[0] = GetPM(new_node)[0]; new_node->szShortPM[1] = '\0';
255 /* Now add the computed format to the cache */
256 RtlEnterCriticalSection(&NLS_FormatsCS);
258 /* Search again: We may have raced to add the node */
259 node = NLS_CachedFormats;
260 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
261 node = node->next;
263 if (!node)
265 node = NLS_CachedFormats = new_node; /* Empty list */
266 new_node = NULL;
268 else if (node->lcid != lcid || node->dwFlags != dwFlags)
270 node->next = new_node; /* Not in the list, add to end */
271 node = new_node;
272 new_node = NULL;
275 RtlLeaveCriticalSection(&NLS_FormatsCS);
277 if (new_node)
279 /* We raced and lost: The node was already added by another thread.
280 * node points to the currently cached node, so free new_node.
282 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
283 HeapFree(GetProcessHeap(), 0, new_node->lppszStrings[i]);
284 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpDecimalSep);
285 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpThousandSep);
286 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpDecimalSep);
287 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpThousandSep);
288 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpCurrencySymbol);
289 HeapFree(GetProcessHeap(), 0, new_node);
292 return node;
295 /**************************************************************************
296 * NLS_IsUnicodeOnlyLcid <internal>
298 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
300 BOOL NLS_IsUnicodeOnlyLcid(LCID lcid)
302 lcid = ConvertDefaultLocale(lcid);
304 switch (PRIMARYLANGID(lcid))
306 case LANG_ARMENIAN:
307 case LANG_DIVEHI:
308 case LANG_GEORGIAN:
309 case LANG_GUJARATI:
310 case LANG_HINDI:
311 case LANG_KANNADA:
312 case LANG_KONKANI:
313 case LANG_MARATHI:
314 case LANG_PUNJABI:
315 case LANG_SANSKRIT:
316 TRACE("lcid 0x%08x: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
317 return TRUE;
318 default:
319 return FALSE;
324 * Formatting of dates, times, numbers and currencies.
327 #define IsLiteralMarker(p) (p == '\'')
328 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
329 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
331 /* Only the following flags can be given if a date/time format is specified */
332 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY)
333 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
334 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
335 TIME_NOTIMEMARKER)
337 /******************************************************************************
338 * NLS_GetDateTimeFormatW <internal>
340 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
342 * FIXME
343 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
344 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
346 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
347 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
348 LPWSTR lpStr, INT cchOut)
350 const NLS_FORMAT_NODE *node;
351 SYSTEMTIME st;
352 INT cchWritten = 0;
353 INT lastFormatPos = 0;
354 BOOL bSkipping = FALSE; /* Skipping text around marker? */
356 /* Verify our arguments */
357 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
359 NLS_GetDateTimeFormatW_InvalidParameter:
360 SetLastError(ERROR_INVALID_PARAMETER);
361 return 0;
364 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
366 if (lpFormat &&
367 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
368 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
370 NLS_GetDateTimeFormatW_InvalidFlags:
371 SetLastError(ERROR_INVALID_FLAGS);
372 return 0;
375 if (dwFlags & DATE_DATEVARSONLY)
377 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
378 goto NLS_GetDateTimeFormatW_InvalidFlags;
379 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
380 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
382 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
384 case 0:
385 break;
386 case DATE_SHORTDATE:
387 case DATE_LONGDATE:
388 case DATE_YEARMONTH:
389 if (lpFormat)
390 goto NLS_GetDateTimeFormatW_InvalidFlags;
391 break;
392 default:
393 goto NLS_GetDateTimeFormatW_InvalidFlags;
398 if (!lpFormat)
400 /* Use the appropriate default format */
401 if (dwFlags & DATE_DATEVARSONLY)
403 if (dwFlags & DATE_YEARMONTH)
404 lpFormat = GetYearMonth(node);
405 else if (dwFlags & DATE_LONGDATE)
406 lpFormat = GetLongDate(node);
407 else
408 lpFormat = GetShortDate(node);
410 else
411 lpFormat = GetTime(node);
414 if (!lpTime)
416 GetLocalTime(&st); /* Default to current time */
417 lpTime = &st;
419 else
421 if (dwFlags & DATE_DATEVARSONLY)
423 FILETIME ftTmp;
425 /* Verify the date and correct the D.O.W. if needed */
426 memset(&st, 0, sizeof(st));
427 st.wYear = lpTime->wYear;
428 st.wMonth = lpTime->wMonth;
429 st.wDay = lpTime->wDay;
431 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
432 goto NLS_GetDateTimeFormatW_InvalidParameter;
434 FileTimeToSystemTime(&ftTmp, &st);
435 lpTime = &st;
438 if (dwFlags & TIME_TIMEVARSONLY)
440 /* Verify the time */
441 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
442 goto NLS_GetDateTimeFormatW_InvalidParameter;
446 /* Format the output */
447 while (*lpFormat)
449 if (IsLiteralMarker(*lpFormat))
451 /* Start of a literal string */
452 lpFormat++;
454 /* Loop until the end of the literal marker or end of the string */
455 while (*lpFormat)
457 if (IsLiteralMarker(*lpFormat))
459 lpFormat++;
460 if (!IsLiteralMarker(*lpFormat))
461 break; /* Terminating literal marker */
464 if (!cchOut)
465 cchWritten++; /* Count size only */
466 else if (cchWritten >= cchOut)
467 goto NLS_GetDateTimeFormatW_Overrun;
468 else if (!bSkipping)
470 lpStr[cchWritten] = *lpFormat;
471 cchWritten++;
473 lpFormat++;
476 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
477 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
479 char buffA[32];
480 WCHAR buff[32], fmtChar;
481 LPCWSTR szAdd = NULL;
482 DWORD dwVal = 0;
483 int count = 0, dwLen;
485 bSkipping = FALSE;
487 fmtChar = *lpFormat;
488 while (*lpFormat == fmtChar)
490 count++;
491 lpFormat++;
493 buff[0] = '\0';
495 switch(fmtChar)
497 case 'd':
498 if (count >= 4)
499 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
500 else if (count == 3)
501 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
502 else
504 dwVal = lpTime->wDay;
505 szAdd = buff;
507 break;
509 case 'M':
510 if (count >= 4)
511 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
512 else if (count == 3)
513 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
514 else
516 dwVal = lpTime->wMonth;
517 szAdd = buff;
519 break;
521 case 'y':
522 if (count >= 4)
524 count = 4;
525 dwVal = lpTime->wYear;
527 else
529 count = count > 2 ? 2 : count;
530 dwVal = lpTime->wYear % 100;
532 szAdd = buff;
533 break;
535 case 'g':
536 if (count == 2)
538 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
539 * When it is fixed, this string should be cached in 'node'.
541 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
542 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
544 else
546 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
548 szAdd = buff;
549 break;
551 case 'h':
552 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
554 count = count > 2 ? 2 : count;
555 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
556 szAdd = buff;
557 break;
559 /* .. fall through if we are forced to output in 24 hour format */
561 case 'H':
562 count = count > 2 ? 2 : count;
563 dwVal = lpTime->wHour;
564 szAdd = buff;
565 break;
567 case 'm':
568 if (dwFlags & TIME_NOMINUTESORSECONDS)
570 cchWritten = lastFormatPos; /* Skip */
571 bSkipping = TRUE;
573 else
575 count = count > 2 ? 2 : count;
576 dwVal = lpTime->wMinute;
577 szAdd = buff;
579 break;
581 case 's':
582 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
584 cchWritten = lastFormatPos; /* Skip */
585 bSkipping = TRUE;
587 else
589 count = count > 2 ? 2 : count;
590 dwVal = lpTime->wSecond;
591 szAdd = buff;
593 break;
595 case 't':
596 if (dwFlags & TIME_NOTIMEMARKER)
598 cchWritten = lastFormatPos; /* Skip */
599 bSkipping = TRUE;
601 else
603 if (count == 1)
604 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
605 else
606 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
608 break;
611 if (szAdd == buff && buff[0] == '\0')
613 /* We have a numeric value to add */
614 sprintf(buffA, "%.*d", count, dwVal);
615 MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
618 dwLen = szAdd ? strlenW(szAdd) : 0;
620 if (cchOut && dwLen)
622 if (cchWritten + dwLen < cchOut)
623 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
624 else
626 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
627 goto NLS_GetDateTimeFormatW_Overrun;
630 cchWritten += dwLen;
631 lastFormatPos = cchWritten; /* Save position of last output format text */
633 else
635 /* Literal character */
636 if (!cchOut)
637 cchWritten++; /* Count size only */
638 else if (cchWritten >= cchOut)
639 goto NLS_GetDateTimeFormatW_Overrun;
640 else if (!bSkipping || *lpFormat == ' ')
642 lpStr[cchWritten] = *lpFormat;
643 cchWritten++;
645 lpFormat++;
649 /* Final string terminator and sanity check */
650 if (cchOut)
652 if (cchWritten >= cchOut)
653 goto NLS_GetDateTimeFormatW_Overrun;
654 else
655 lpStr[cchWritten] = '\0';
657 cchWritten++; /* Include terminating NUL */
659 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
660 return cchWritten;
662 NLS_GetDateTimeFormatW_Overrun:
663 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
664 SetLastError(ERROR_INSUFFICIENT_BUFFER);
665 return 0;
668 /******************************************************************************
669 * NLS_GetDateTimeFormatA <internal>
671 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
673 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
674 const SYSTEMTIME* lpTime,
675 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
677 DWORD cp = CP_ACP;
678 WCHAR szFormat[128], szOut[128];
679 INT iRet;
681 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
682 debugstr_a(lpFormat), lpStr, cchOut);
684 if (NLS_IsUnicodeOnlyLcid(lcid))
686 GetDateTimeFormatA_InvalidParameter:
687 SetLastError(ERROR_INVALID_PARAMETER);
688 return 0;
691 if (!(dwFlags & LOCALE_USE_CP_ACP))
693 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
694 if (!node)
695 goto GetDateTimeFormatA_InvalidParameter;
696 cp = node->dwCodePage;
699 if (lpFormat)
700 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
702 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
703 cchOut = sizeof(szOut)/sizeof(WCHAR);
705 szOut[0] = '\0';
707 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
708 lpStr ? szOut : NULL, cchOut);
710 if (lpStr)
712 if (szOut[0])
713 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
714 else if (cchOut && iRet)
715 *lpStr = '\0';
717 return iRet;
720 /******************************************************************************
721 * GetDateFormatA [KERNEL32.@]
723 * Format a date for a given locale.
725 * PARAMS
726 * lcid [I] Locale to format for
727 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
728 * lpTime [I] Date to format
729 * lpFormat [I] Format string, or NULL to use the system defaults
730 * lpDateStr [O] Destination for formatted string
731 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
733 * NOTES
734 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
735 * details returned by GetLocaleInfoA() and modified by dwFlags.
736 * - lpFormat is a string of characters and formatting tokens. Any characters
737 * in the string are copied verbatim to lpDateStr, with tokens being replaced
738 * by the date values they represent.
739 * - The following tokens have special meanings in a date format string:
740 *| Token Meaning
741 *| ----- -------
742 *| d Single digit day of the month (no leading 0)
743 *| dd Double digit day of the month
744 *| ddd Short name for the day of the week
745 *| dddd Long name for the day of the week
746 *| M Single digit month of the year (no leading 0)
747 *| MM Double digit month of the year
748 *| MMM Short name for the month of the year
749 *| MMMM Long name for the month of the year
750 *| y Double digit year number (no leading 0)
751 *| yy Double digit year number
752 *| yyyy Four digit year number
753 *| gg Era string, for example 'AD'.
754 * - To output any literal character that could be misidentified as a token,
755 * enclose it in single quotes.
756 * - The Ascii version of this function fails if lcid is Unicode only.
758 * RETURNS
759 * Success: The number of character written to lpDateStr, or that would
760 * have been written, if cchOut is 0.
761 * Failure: 0. Use GetLastError() to determine the cause.
763 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
764 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
766 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
767 debugstr_a(lpFormat), lpDateStr, cchOut);
769 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
770 lpFormat, lpDateStr, cchOut);
774 /******************************************************************************
775 * GetDateFormatW [KERNEL32.@]
777 * See GetDateFormatA.
779 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
780 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
782 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
783 debugstr_w(lpFormat), lpDateStr, cchOut);
785 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
786 lpFormat, lpDateStr, cchOut);
789 /******************************************************************************
790 * GetTimeFormatA [KERNEL32.@]
792 * Format a time for a given locale.
794 * PARAMS
795 * lcid [I] Locale to format for
796 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
797 * lpTime [I] Time to format
798 * lpFormat [I] Formatting overrides
799 * lpTimeStr [O] Destination for formatted string
800 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
802 * NOTES
803 * - If lpFormat is NULL, lpszValue will be formatted according to the format
804 * details returned by GetLocaleInfoA() and modified by dwFlags.
805 * - lpFormat is a string of characters and formatting tokens. Any characters
806 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
807 * by the time values they represent.
808 * - The following tokens have special meanings in a time format string:
809 *| Token Meaning
810 *| ----- -------
811 *| h Hours with no leading zero (12-hour clock)
812 *| hh Hours with full two digits (12-hour clock)
813 *| H Hours with no leading zero (24-hour clock)
814 *| HH Hours with full two digits (24-hour clock)
815 *| m Minutes with no leading zero
816 *| mm Minutes with full two digits
817 *| s Seconds with no leading zero
818 *| ss Seconds with full two digits
819 *| t Short time marker (e.g. "A" or "P")
820 *| tt Long time marker (e.g. "AM", "PM")
821 * - To output any literal character that could be misidentified as a token,
822 * enclose it in single quotes.
823 * - The Ascii version of this function fails if lcid is Unicode only.
825 * RETURNS
826 * Success: The number of character written to lpTimeStr, or that would
827 * have been written, if cchOut is 0.
828 * Failure: 0. Use GetLastError() to determine the cause.
830 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
831 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
833 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
834 debugstr_a(lpFormat), lpTimeStr, cchOut);
836 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
837 lpFormat, lpTimeStr, cchOut);
840 /******************************************************************************
841 * GetTimeFormatW [KERNEL32.@]
843 * See GetTimeFormatA.
845 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
846 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
848 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
849 debugstr_w(lpFormat), lpTimeStr, cchOut);
851 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
852 lpFormat, lpTimeStr, cchOut);
855 /**************************************************************************
856 * GetNumberFormatA (KERNEL32.@)
858 * Format a number string for a given locale.
860 * PARAMS
861 * lcid [I] Locale to format for
862 * dwFlags [I] LOCALE_ flags from "winnls.h"
863 * lpszValue [I] String to format
864 * lpFormat [I] Formatting overrides
865 * lpNumberStr [O] Destination for formatted string
866 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
868 * NOTES
869 * - lpszValue can contain only '0' - '9', '-' and '.'.
870 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
871 * be formatted according to the format details returned by GetLocaleInfoA().
872 * - This function rounds the number string if the number of decimals exceeds the
873 * locales normal number of decimal places.
874 * - If cchOut is 0, this function does not write to lpNumberStr.
875 * - The Ascii version of this function fails if lcid is Unicode only.
877 * RETURNS
878 * Success: The number of character written to lpNumberStr, or that would
879 * have been written, if cchOut is 0.
880 * Failure: 0. Use GetLastError() to determine the cause.
882 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
883 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
884 LPSTR lpNumberStr, int cchOut)
886 DWORD cp = CP_ACP;
887 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
888 NUMBERFMTW fmt;
889 const NUMBERFMTW *pfmt = NULL;
890 INT iRet;
892 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
893 lpFormat, lpNumberStr, cchOut);
895 if (NLS_IsUnicodeOnlyLcid(lcid))
897 GetNumberFormatA_InvalidParameter:
898 SetLastError(ERROR_INVALID_PARAMETER);
899 return 0;
902 if (!(dwFlags & LOCALE_USE_CP_ACP))
904 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
905 if (!node)
906 goto GetNumberFormatA_InvalidParameter;
907 cp = node->dwCodePage;
910 if (lpFormat)
912 memcpy(&fmt, lpFormat, sizeof(fmt));
913 pfmt = &fmt;
914 if (lpFormat->lpDecimalSep)
916 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
917 fmt.lpDecimalSep = szDec;
919 if (lpFormat->lpThousandSep)
921 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
922 fmt.lpThousandSep = szGrp;
926 if (lpszValue)
927 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
929 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
930 cchOut = sizeof(szOut)/sizeof(WCHAR);
932 szOut[0] = '\0';
934 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
935 lpNumberStr ? szOut : NULL, cchOut);
937 if (szOut[0] && lpNumberStr)
938 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
939 return iRet;
942 /* Number parsing state flags */
943 #define NF_ISNEGATIVE 0x1 /* '-' found */
944 #define NF_ISREAL 0x2 /* '.' found */
945 #define NF_DIGITS 0x4 /* '0'-'9' found */
946 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
947 #define NF_ROUND 0x10 /* Number needs to be rounded */
949 /* Formatting options for Numbers */
950 #define NLS_NEG_PARENS 0 /* "(1.1)" */
951 #define NLS_NEG_LEFT 1 /* "-1.1" */
952 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
953 #define NLS_NEG_RIGHT 3 /* "1.1-" */
954 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
956 /**************************************************************************
957 * GetNumberFormatW (KERNEL32.@)
959 * See GetNumberFormatA.
961 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
962 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
963 LPWSTR lpNumberStr, int cchOut)
965 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
966 WCHAR szNegBuff[8];
967 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
968 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
969 INT iRet;
971 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
972 lpFormat, lpNumberStr, cchOut);
974 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
975 !IsValidLocale(lcid, 0) ||
976 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
978 GetNumberFormatW_Error:
979 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
980 return 0;
983 if (!lpFormat)
985 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
987 if (!node)
988 goto GetNumberFormatW_Error;
989 lpFormat = &node->fmt;
990 lpszNegStart = lpszNeg = GetNegative(node);
992 else
994 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
995 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
996 lpszNegStart = lpszNeg = szNegBuff;
998 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1000 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1002 /* Format the number backwards into a temporary buffer */
1004 szSrc = lpszValue;
1005 *szOut-- = '\0';
1007 /* Check the number for validity */
1008 while (*szSrc)
1010 if (*szSrc >= '0' && *szSrc <= '9')
1012 dwState |= NF_DIGITS;
1013 if (dwState & NF_ISREAL)
1014 dwDecimals++;
1016 else if (*szSrc == '-')
1018 if (dwState)
1019 goto GetNumberFormatW_Error; /* '-' not first character */
1020 dwState |= NF_ISNEGATIVE;
1022 else if (*szSrc == '.')
1024 if (dwState & NF_ISREAL)
1025 goto GetNumberFormatW_Error; /* More than one '.' */
1026 dwState |= NF_ISREAL;
1028 else
1029 goto GetNumberFormatW_Error; /* Invalid char */
1030 szSrc++;
1032 szSrc--; /* Point to last character */
1034 if (!(dwState & NF_DIGITS))
1035 goto GetNumberFormatW_Error; /* No digits */
1037 /* Add any trailing negative sign */
1038 if (dwState & NF_ISNEGATIVE)
1040 switch (lpFormat->NegativeOrder)
1042 case NLS_NEG_PARENS:
1043 *szOut-- = ')';
1044 break;
1045 case NLS_NEG_RIGHT:
1046 case NLS_NEG_RIGHT_SPACE:
1047 while (lpszNeg >= lpszNegStart)
1048 *szOut-- = *lpszNeg--;
1049 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1050 *szOut-- = ' ';
1051 break;
1055 /* Copy all digits up to the decimal point */
1056 if (!lpFormat->NumDigits)
1058 if (dwState & NF_ISREAL)
1060 while (*szSrc != '.') /* Don't write any decimals or a separator */
1062 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1063 dwState |= NF_ROUND;
1064 else
1065 dwState &= ~NF_ROUND;
1066 szSrc--;
1068 szSrc--;
1071 else
1073 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1075 if (dwDecimals <= lpFormat->NumDigits)
1077 dwDecimals = lpFormat->NumDigits - dwDecimals;
1078 while (dwDecimals--)
1079 *szOut-- = '0'; /* Pad to correct number of dp */
1081 else
1083 dwDecimals -= lpFormat->NumDigits;
1084 /* Skip excess decimals, and determine if we have to round the number */
1085 while (dwDecimals--)
1087 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1088 dwState |= NF_ROUND;
1089 else
1090 dwState &= ~NF_ROUND;
1091 szSrc--;
1095 if (dwState & NF_ISREAL)
1097 while (*szSrc != '.')
1099 if (dwState & NF_ROUND)
1101 if (*szSrc == '9')
1102 *szOut-- = '0'; /* continue rounding */
1103 else
1105 dwState &= ~NF_ROUND;
1106 *szOut-- = (*szSrc)+1;
1108 szSrc--;
1110 else
1111 *szOut-- = *szSrc--; /* Write existing decimals */
1113 szSrc--; /* Skip '.' */
1116 while (lpszDec >= lpFormat->lpDecimalSep)
1117 *szOut-- = *lpszDec--; /* Write decimal separator */
1120 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1122 /* Write the remaining whole number digits, including grouping chars */
1123 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1125 if (dwState & NF_ROUND)
1127 if (*szSrc == '9')
1128 *szOut-- = '0'; /* continue rounding */
1129 else
1131 dwState &= ~NF_ROUND;
1132 *szOut-- = (*szSrc)+1;
1134 szSrc--;
1136 else
1137 *szOut-- = *szSrc--;
1139 dwState |= NF_DIGITS_OUT;
1140 dwCurrentGroupCount++;
1141 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1143 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1145 while (lpszGrp >= lpFormat->lpThousandSep)
1146 *szOut-- = *lpszGrp--; /* Write grouping char */
1148 dwCurrentGroupCount = 0;
1149 if (lpFormat->Grouping == 32)
1150 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1153 if (dwState & NF_ROUND)
1155 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1157 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1158 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1160 /* Add any leading negative sign */
1161 if (dwState & NF_ISNEGATIVE)
1163 switch (lpFormat->NegativeOrder)
1165 case NLS_NEG_PARENS:
1166 *szOut-- = '(';
1167 break;
1168 case NLS_NEG_LEFT_SPACE:
1169 *szOut-- = ' ';
1170 /* Fall through */
1171 case NLS_NEG_LEFT:
1172 while (lpszNeg >= lpszNegStart)
1173 *szOut-- = *lpszNeg--;
1174 break;
1177 szOut++;
1179 iRet = strlenW(szOut) + 1;
1180 if (cchOut)
1182 if (iRet <= cchOut)
1183 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1184 else
1186 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1187 lpNumberStr[cchOut - 1] = '\0';
1188 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1189 iRet = 0;
1192 return iRet;
1195 /**************************************************************************
1196 * GetCurrencyFormatA (KERNEL32.@)
1198 * Format a currency string for a given locale.
1200 * PARAMS
1201 * lcid [I] Locale to format for
1202 * dwFlags [I] LOCALE_ flags from "winnls.h"
1203 * lpszValue [I] String to format
1204 * lpFormat [I] Formatting overrides
1205 * lpCurrencyStr [O] Destination for formatted string
1206 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1208 * NOTES
1209 * - lpszValue can contain only '0' - '9', '-' and '.'.
1210 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1211 * be formatted according to the format details returned by GetLocaleInfoA().
1212 * - This function rounds the currency if the number of decimals exceeds the
1213 * locales number of currency decimal places.
1214 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1215 * - The Ascii version of this function fails if lcid is Unicode only.
1217 * RETURNS
1218 * Success: The number of character written to lpNumberStr, or that would
1219 * have been written, if cchOut is 0.
1220 * Failure: 0. Use GetLastError() to determine the cause.
1222 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1223 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1224 LPSTR lpCurrencyStr, int cchOut)
1226 DWORD cp = CP_ACP;
1227 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1228 CURRENCYFMTW fmt;
1229 const CURRENCYFMTW *pfmt = NULL;
1230 INT iRet;
1232 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1233 lpFormat, lpCurrencyStr, cchOut);
1235 if (NLS_IsUnicodeOnlyLcid(lcid))
1237 GetCurrencyFormatA_InvalidParameter:
1238 SetLastError(ERROR_INVALID_PARAMETER);
1239 return 0;
1242 if (!(dwFlags & LOCALE_USE_CP_ACP))
1244 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1245 if (!node)
1246 goto GetCurrencyFormatA_InvalidParameter;
1247 cp = node->dwCodePage;
1250 if (lpFormat)
1252 memcpy(&fmt, lpFormat, sizeof(fmt));
1253 pfmt = &fmt;
1254 if (lpFormat->lpDecimalSep)
1256 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1257 fmt.lpDecimalSep = szDec;
1259 if (lpFormat->lpThousandSep)
1261 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1262 fmt.lpThousandSep = szGrp;
1264 if (lpFormat->lpCurrencySymbol)
1266 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1267 fmt.lpCurrencySymbol = szCy;
1271 if (lpszValue)
1272 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1274 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1275 cchOut = sizeof(szOut)/sizeof(WCHAR);
1277 szOut[0] = '\0';
1279 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1280 lpCurrencyStr ? szOut : NULL, cchOut);
1282 if (szOut[0] && lpCurrencyStr)
1283 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1284 return iRet;
1287 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1288 #define CF_PARENS 0x1 /* Parentheses */
1289 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1290 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1291 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1292 #define CF_CY_LEFT 0x10 /* '$' to the left */
1293 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1294 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1296 /**************************************************************************
1297 * GetCurrencyFormatW (KERNEL32.@)
1299 * See GetCurrencyFormatA.
1301 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1302 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1303 LPWSTR lpCurrencyStr, int cchOut)
1305 static const BYTE NLS_NegCyFormats[16] =
1307 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1308 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1309 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1310 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1311 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1312 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1313 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1314 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1315 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1316 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1317 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1318 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1319 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1320 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1321 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1322 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1324 static const BYTE NLS_PosCyFormats[4] =
1326 CF_CY_LEFT, /* $1.1 */
1327 CF_CY_RIGHT, /* 1.1$ */
1328 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1329 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1331 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1332 WCHAR szNegBuff[8];
1333 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1334 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1335 INT iRet;
1337 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1338 lpFormat, lpCurrencyStr, cchOut);
1340 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1341 !IsValidLocale(lcid, 0) ||
1342 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1343 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1344 lpFormat->PositiveOrder > 3)))
1346 GetCurrencyFormatW_Error:
1347 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1348 return 0;
1351 if (!lpFormat)
1353 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1355 if (!node)
1356 goto GetCurrencyFormatW_Error;
1357 lpFormat = &node->cyfmt;
1358 lpszNegStart = lpszNeg = GetNegative(node);
1360 else
1362 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1363 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1364 lpszNegStart = lpszNeg = szNegBuff;
1366 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1368 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1369 lpszCyStart = lpFormat->lpCurrencySymbol;
1370 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1372 /* Format the currency backwards into a temporary buffer */
1374 szSrc = lpszValue;
1375 *szOut-- = '\0';
1377 /* Check the number for validity */
1378 while (*szSrc)
1380 if (*szSrc >= '0' && *szSrc <= '9')
1382 dwState |= NF_DIGITS;
1383 if (dwState & NF_ISREAL)
1384 dwDecimals++;
1386 else if (*szSrc == '-')
1388 if (dwState)
1389 goto GetCurrencyFormatW_Error; /* '-' not first character */
1390 dwState |= NF_ISNEGATIVE;
1392 else if (*szSrc == '.')
1394 if (dwState & NF_ISREAL)
1395 goto GetCurrencyFormatW_Error; /* More than one '.' */
1396 dwState |= NF_ISREAL;
1398 else
1399 goto GetCurrencyFormatW_Error; /* Invalid char */
1400 szSrc++;
1402 szSrc--; /* Point to last character */
1404 if (!(dwState & NF_DIGITS))
1405 goto GetCurrencyFormatW_Error; /* No digits */
1407 if (dwState & NF_ISNEGATIVE)
1408 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1409 else
1410 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1412 /* Add any trailing negative or currency signs */
1413 if (dwFmt & CF_PARENS)
1414 *szOut-- = ')';
1416 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1418 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1420 case CF_MINUS_RIGHT:
1421 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1422 while (lpszNeg >= lpszNegStart)
1423 *szOut-- = *lpszNeg--;
1424 dwFmt &= ~CF_MINUS_RIGHT;
1425 break;
1427 case CF_CY_RIGHT:
1428 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1429 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1430 while (lpszCy >= lpszCyStart)
1431 *szOut-- = *lpszCy--;
1432 if (dwFmt & CF_CY_SPACE)
1433 *szOut-- = ' ';
1434 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1435 break;
1439 /* Copy all digits up to the decimal point */
1440 if (!lpFormat->NumDigits)
1442 if (dwState & NF_ISREAL)
1444 while (*szSrc != '.') /* Don't write any decimals or a separator */
1446 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1447 dwState |= NF_ROUND;
1448 else
1449 dwState &= ~NF_ROUND;
1450 szSrc--;
1452 szSrc--;
1455 else
1457 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1459 if (dwDecimals <= lpFormat->NumDigits)
1461 dwDecimals = lpFormat->NumDigits - dwDecimals;
1462 while (dwDecimals--)
1463 *szOut-- = '0'; /* Pad to correct number of dp */
1465 else
1467 dwDecimals -= lpFormat->NumDigits;
1468 /* Skip excess decimals, and determine if we have to round the number */
1469 while (dwDecimals--)
1471 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1472 dwState |= NF_ROUND;
1473 else
1474 dwState &= ~NF_ROUND;
1475 szSrc--;
1479 if (dwState & NF_ISREAL)
1481 while (*szSrc != '.')
1483 if (dwState & NF_ROUND)
1485 if (*szSrc == '9')
1486 *szOut-- = '0'; /* continue rounding */
1487 else
1489 dwState &= ~NF_ROUND;
1490 *szOut-- = (*szSrc)+1;
1492 szSrc--;
1494 else
1495 *szOut-- = *szSrc--; /* Write existing decimals */
1497 szSrc--; /* Skip '.' */
1499 while (lpszDec >= lpFormat->lpDecimalSep)
1500 *szOut-- = *lpszDec--; /* Write decimal separator */
1503 dwGroupCount = lpFormat->Grouping;
1505 /* Write the remaining whole number digits, including grouping chars */
1506 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1508 if (dwState & NF_ROUND)
1510 if (*szSrc == '9')
1511 *szOut-- = '0'; /* continue rounding */
1512 else
1514 dwState &= ~NF_ROUND;
1515 *szOut-- = (*szSrc)+1;
1517 szSrc--;
1519 else
1520 *szOut-- = *szSrc--;
1522 dwState |= NF_DIGITS_OUT;
1523 dwCurrentGroupCount++;
1524 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount)
1526 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1528 while (lpszGrp >= lpFormat->lpThousandSep)
1529 *szOut-- = *lpszGrp--; /* Write grouping char */
1531 dwCurrentGroupCount = 0;
1534 if (dwState & NF_ROUND)
1535 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1536 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1537 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1539 /* Add any leading negative or currency sign */
1540 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1542 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1544 case CF_MINUS_LEFT:
1545 case CF_MINUS_LEFT|CF_CY_LEFT:
1546 while (lpszNeg >= lpszNegStart)
1547 *szOut-- = *lpszNeg--;
1548 dwFmt &= ~CF_MINUS_LEFT;
1549 break;
1551 case CF_CY_LEFT:
1552 case CF_CY_LEFT|CF_MINUS_BEFORE:
1553 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1554 if (dwFmt & CF_CY_SPACE)
1555 *szOut-- = ' ';
1556 while (lpszCy >= lpszCyStart)
1557 *szOut-- = *lpszCy--;
1558 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1559 break;
1562 if (dwFmt & CF_PARENS)
1563 *szOut-- = '(';
1564 szOut++;
1566 iRet = strlenW(szOut) + 1;
1567 if (cchOut)
1569 if (iRet <= cchOut)
1570 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1571 else
1573 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1574 lpCurrencyStr[cchOut - 1] = '\0';
1575 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1576 iRet = 0;
1579 return iRet;
1582 /* FIXME: Everything below here needs to move somewhere else along with the
1583 * other EnumXXX functions, when a method for storing resources for
1584 * alternate calendars is determined.
1587 /**************************************************************************
1588 * EnumDateFormatsExA (KERNEL32.@)
1590 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1591 * LOCALE_NOUSEROVERRIDE here as well?
1593 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1595 CALID cal_id;
1596 char buf[256];
1598 if (!proc)
1600 SetLastError(ERROR_INVALID_PARAMETER);
1601 return FALSE;
1604 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1605 return FALSE;
1607 switch (flags & ~LOCALE_USE_CP_ACP)
1609 case 0:
1610 case DATE_SHORTDATE:
1611 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1612 proc(buf, cal_id);
1613 break;
1615 case DATE_LONGDATE:
1616 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1617 proc(buf, cal_id);
1618 break;
1620 case DATE_YEARMONTH:
1621 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1622 proc(buf, cal_id);
1623 break;
1625 default:
1626 FIXME("Unknown date format (%d)\n", flags);
1627 SetLastError(ERROR_INVALID_PARAMETER);
1628 return FALSE;
1630 return TRUE;
1633 /**************************************************************************
1634 * EnumDateFormatsExW (KERNEL32.@)
1636 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1638 CALID cal_id;
1639 WCHAR buf[256];
1641 if (!proc)
1643 SetLastError(ERROR_INVALID_PARAMETER);
1644 return FALSE;
1647 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1648 return FALSE;
1650 switch (flags & ~LOCALE_USE_CP_ACP)
1652 case 0:
1653 case DATE_SHORTDATE:
1654 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1655 proc(buf, cal_id);
1656 break;
1658 case DATE_LONGDATE:
1659 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1660 proc(buf, cal_id);
1661 break;
1663 case DATE_YEARMONTH:
1664 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1665 proc(buf, cal_id);
1666 break;
1668 default:
1669 FIXME("Unknown date format (%d)\n", flags);
1670 SetLastError(ERROR_INVALID_PARAMETER);
1671 return FALSE;
1673 return TRUE;
1676 /**************************************************************************
1677 * EnumDateFormatsA (KERNEL32.@)
1679 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1680 * LOCALE_NOUSEROVERRIDE here as well?
1682 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1684 char buf[256];
1686 if (!proc)
1688 SetLastError(ERROR_INVALID_PARAMETER);
1689 return FALSE;
1692 switch (flags & ~LOCALE_USE_CP_ACP)
1694 case 0:
1695 case DATE_SHORTDATE:
1696 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1697 proc(buf);
1698 break;
1700 case DATE_LONGDATE:
1701 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1702 proc(buf);
1703 break;
1705 case DATE_YEARMONTH:
1706 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1707 proc(buf);
1708 break;
1710 default:
1711 FIXME("Unknown date format (%d)\n", flags);
1712 SetLastError(ERROR_INVALID_PARAMETER);
1713 return FALSE;
1715 return TRUE;
1718 /**************************************************************************
1719 * EnumDateFormatsW (KERNEL32.@)
1721 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1723 WCHAR buf[256];
1725 if (!proc)
1727 SetLastError(ERROR_INVALID_PARAMETER);
1728 return FALSE;
1731 switch (flags & ~LOCALE_USE_CP_ACP)
1733 case 0:
1734 case DATE_SHORTDATE:
1735 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1736 proc(buf);
1737 break;
1739 case DATE_LONGDATE:
1740 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1741 proc(buf);
1742 break;
1744 case DATE_YEARMONTH:
1745 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1746 proc(buf);
1747 break;
1749 default:
1750 FIXME("Unknown date format (%d)\n", flags);
1751 SetLastError(ERROR_INVALID_PARAMETER);
1752 return FALSE;
1754 return TRUE;
1757 /**************************************************************************
1758 * EnumTimeFormatsA (KERNEL32.@)
1760 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1761 * LOCALE_NOUSEROVERRIDE here as well?
1763 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1765 char buf[256];
1767 if (!proc)
1769 SetLastError(ERROR_INVALID_PARAMETER);
1770 return FALSE;
1773 switch (flags & ~LOCALE_USE_CP_ACP)
1775 case 0:
1776 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1777 proc(buf);
1778 break;
1780 default:
1781 FIXME("Unknown time format (%d)\n", flags);
1782 SetLastError(ERROR_INVALID_PARAMETER);
1783 return FALSE;
1785 return TRUE;
1788 /**************************************************************************
1789 * EnumTimeFormatsW (KERNEL32.@)
1791 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1793 WCHAR buf[256];
1795 if (!proc)
1797 SetLastError(ERROR_INVALID_PARAMETER);
1798 return FALSE;
1801 switch (flags & ~LOCALE_USE_CP_ACP)
1803 case 0:
1804 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1805 proc(buf);
1806 break;
1808 default:
1809 FIXME("Unknown time format (%d)\n", flags);
1810 SetLastError(ERROR_INVALID_PARAMETER);
1811 return FALSE;
1813 return TRUE;
1816 /******************************************************************************
1817 * NLS_EnumCalendarInfoAW <internal>
1818 * Enumerates calendar information for a specified locale.
1820 * PARAMS
1821 * calinfoproc [I] Pointer to the callback
1822 * locale [I] The locale for which to retrieve calendar information.
1823 * This parameter can be a locale identifier created by the
1824 * MAKELCID macro, or one of the following values:
1825 * LOCALE_SYSTEM_DEFAULT
1826 * Use the default system locale.
1827 * LOCALE_USER_DEFAULT
1828 * Use the default user locale.
1829 * calendar [I] The calendar for which information is requested, or
1830 * ENUM_ALL_CALENDARS.
1831 * caltype [I] The type of calendar information to be returned. Note
1832 * that only one CALTYPE value can be specified per call
1833 * of this function, except where noted.
1834 * unicode [I] Specifies if the callback expects a unicode string.
1835 * ex [I] Specifies if the callback needs the calendar identifier.
1837 * RETURNS
1838 * Success: TRUE.
1839 * Failure: FALSE. Use GetLastError() to determine the cause.
1841 * NOTES
1842 * When the ANSI version of this function is used with a Unicode-only LCID,
1843 * the call can succeed because the system uses the system code page.
1844 * However, characters that are undefined in the system code page appear
1845 * in the string as a question mark (?).
1847 * TODO
1848 * The above note should be respected by GetCalendarInfoA.
1850 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1851 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1853 WCHAR *buf, *opt = NULL, *iter = NULL;
1854 BOOL ret = FALSE;
1855 int bufSz = 200; /* the size of the buffer */
1857 if (calinfoproc == NULL)
1859 SetLastError(ERROR_INVALID_PARAMETER);
1860 return FALSE;
1863 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1864 if (buf == NULL)
1866 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1867 return FALSE;
1870 if (calendar == ENUM_ALL_CALENDARS)
1872 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1873 if (optSz > 1)
1875 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1876 if (opt == NULL)
1878 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1879 goto NLS_EnumCalendarInfoAW_Cleanup;
1881 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1882 iter = opt;
1884 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1887 while (TRUE) /* loop through calendars */
1889 do /* loop until there's no error */
1891 if (unicode)
1892 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1893 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1895 if (!ret)
1897 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1898 { /* so resize it */
1899 int newSz;
1900 if (unicode)
1901 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1902 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1903 if (bufSz >= newSz)
1905 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1906 goto NLS_EnumCalendarInfoAW_Cleanup;
1908 bufSz = newSz;
1909 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1910 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1911 if (buf == NULL)
1912 goto NLS_EnumCalendarInfoAW_Cleanup;
1913 } else goto NLS_EnumCalendarInfoAW_Cleanup;
1915 } while (!ret);
1917 /* Here we are. We pass the buffer to the correct version of
1918 * the callback. Because it's not the same number of params,
1919 * we must check for Ex, but we don't care about Unicode
1920 * because the buffer is already in the correct format.
1922 if (ex) {
1923 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
1924 } else
1925 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
1927 if (!ret) { /* the callback told to stop */
1928 ret = TRUE;
1929 break;
1932 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
1933 break;
1935 calendar = 0;
1936 while ((*iter >= '0') && (*iter <= '9'))
1937 calendar = calendar * 10 + *iter++ - '0';
1939 if (*iter++ != 0)
1941 SetLastError(ERROR_BADDB);
1942 ret = FALSE;
1943 break;
1947 NLS_EnumCalendarInfoAW_Cleanup:
1948 HeapFree(GetProcessHeap(), 0, opt);
1949 HeapFree(GetProcessHeap(), 0, buf);
1950 return ret;
1953 /******************************************************************************
1954 * EnumCalendarInfoA [KERNEL32.@]
1956 * See EnumCalendarInfoAW.
1958 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
1959 CALID calendar,CALTYPE caltype )
1961 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1962 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
1965 /******************************************************************************
1966 * EnumCalendarInfoW [KERNEL32.@]
1968 * See EnumCalendarInfoAW.
1970 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
1971 CALID calendar,CALTYPE caltype )
1973 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1974 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
1977 /******************************************************************************
1978 * EnumCalendarInfoExA [KERNEL32.@]
1980 * See EnumCalendarInfoAW.
1982 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
1983 CALID calendar,CALTYPE caltype )
1985 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1986 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
1989 /******************************************************************************
1990 * EnumCalendarInfoExW [KERNEL32.@]
1992 * See EnumCalendarInfoAW.
1994 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
1995 CALID calendar,CALTYPE caltype )
1997 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1998 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);