wined3d: Remove constant type field in stateblock.
[wine/multimedia.git] / dlls / kernel / lcformat.c
blob3132a190bf6e98825c51e3e1390cf7faf62569eb
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 ": %ld (%08lx)\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%04lx,0x%08lx)\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 switch (PRIMARYLANGID(lcid))
304 case LANG_ARMENIAN:
305 case LANG_DIVEHI:
306 case LANG_GEORGIAN:
307 case LANG_GUJARATI:
308 case LANG_HINDI:
309 case LANG_KANNADA:
310 case LANG_KONKANI:
311 case LANG_MARATHI:
312 case LANG_PUNJABI:
313 case LANG_SANSKRIT:
314 TRACE("lcid 0x%08lx: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
315 return TRUE;
316 default:
317 return FALSE;
322 * Formatting of dates, times, numbers and currencies.
325 #define IsLiteralMarker(p) (p == '\'')
326 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
327 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
329 /* Only the following flags can be given if a date/time format is specified */
330 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY|LOCALE_NOUSEROVERRIDE)
331 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
332 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
333 TIME_NOTIMEMARKER|LOCALE_NOUSEROVERRIDE)
335 /******************************************************************************
336 * NLS_GetDateTimeFormatW <internal>
338 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
340 * FIXME
341 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
342 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
344 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
345 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
346 LPWSTR lpStr, INT cchOut)
348 const NLS_FORMAT_NODE *node;
349 SYSTEMTIME st;
350 INT cchWritten = 0;
351 INT lastFormatPos = 0;
352 BOOL bSkipping = FALSE; /* Skipping text around marker? */
354 /* Verify our arguments */
355 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
357 NLS_GetDateTimeFormatW_InvalidParameter:
358 SetLastError(ERROR_INVALID_PARAMETER);
359 return 0;
362 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
364 if (lpFormat &&
365 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
366 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
368 NLS_GetDateTimeFormatW_InvalidFlags:
369 SetLastError(ERROR_INVALID_FLAGS);
370 return 0;
373 if (dwFlags & DATE_DATEVARSONLY)
375 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
376 goto NLS_GetDateTimeFormatW_InvalidFlags;
377 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
378 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
380 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
382 case 0:
383 break;
384 case DATE_SHORTDATE:
385 case DATE_LONGDATE:
386 case DATE_YEARMONTH:
387 if (lpFormat)
388 goto NLS_GetDateTimeFormatW_InvalidFlags;
389 break;
390 default:
391 goto NLS_GetDateTimeFormatW_InvalidFlags;
396 if (!lpFormat)
398 /* Use the appropriate default format */
399 if (dwFlags & DATE_DATEVARSONLY)
401 if (dwFlags & DATE_YEARMONTH)
402 lpFormat = GetYearMonth(node);
403 else if (dwFlags & DATE_LONGDATE)
404 lpFormat = GetLongDate(node);
405 else
406 lpFormat = GetShortDate(node);
408 else
409 lpFormat = GetTime(node);
412 if (!lpTime)
414 GetLocalTime(&st); /* Default to current time */
415 lpTime = &st;
417 else
419 if (dwFlags & DATE_DATEVARSONLY)
421 FILETIME ftTmp;
423 /* Verify the date and correct the D.O.W. if needed */
424 memset(&st, 0, sizeof(st));
425 st.wYear = lpTime->wYear;
426 st.wMonth = lpTime->wMonth;
427 st.wDay = lpTime->wDay;
429 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
430 goto NLS_GetDateTimeFormatW_InvalidParameter;
432 FileTimeToSystemTime(&ftTmp, &st);
433 lpTime = &st;
436 if (dwFlags & TIME_TIMEVARSONLY)
438 /* Verify the time */
439 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
440 goto NLS_GetDateTimeFormatW_InvalidParameter;
444 /* Format the output */
445 while (*lpFormat)
447 if (IsLiteralMarker(*lpFormat))
449 /* Start of a literal string */
450 lpFormat++;
452 /* Loop until the end of the literal marker or end of the string */
453 while (*lpFormat)
455 if (IsLiteralMarker(*lpFormat))
457 lpFormat++;
458 if (!IsLiteralMarker(*lpFormat))
459 break; /* Terminating literal marker */
462 if (!cchOut)
463 cchWritten++; /* Count size only */
464 else if (cchWritten >= cchOut)
465 goto NLS_GetDateTimeFormatW_Overrun;
466 else if (!bSkipping)
468 lpStr[cchWritten] = *lpFormat;
469 cchWritten++;
471 lpFormat++;
474 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
475 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
477 char buffA[32];
478 WCHAR buff[32], fmtChar;
479 LPCWSTR szAdd = NULL;
480 DWORD dwVal = 0;
481 int count = 0, dwLen;
483 bSkipping = FALSE;
485 fmtChar = *lpFormat;
486 while (*lpFormat == fmtChar)
488 count++;
489 lpFormat++;
491 buff[0] = '\0';
493 switch(fmtChar)
495 case 'd':
496 if (count >= 4)
497 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
498 else if (count == 3)
499 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
500 else
502 dwVal = lpTime->wDay;
503 szAdd = buff;
505 break;
507 case 'M':
508 if (count >= 4)
509 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
510 else if (count == 3)
511 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
512 else
514 dwVal = lpTime->wMonth;
515 szAdd = buff;
517 break;
519 case 'y':
520 if (count >= 4)
522 count = 4;
523 dwVal = lpTime->wYear;
525 else
527 count = count > 2 ? 2 : count;
528 dwVal = lpTime->wYear % 100;
530 szAdd = buff;
531 break;
533 case 'g':
534 if (count == 2)
536 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
537 * When it is fixed, this string should be cached in 'node'.
539 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
540 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
542 else
544 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
546 szAdd = buff;
547 break;
549 case 'h':
550 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
552 count = count > 2 ? 2 : count;
553 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
554 szAdd = buff;
555 break;
557 /* .. fall through if we are forced to output in 24 hour format */
559 case 'H':
560 count = count > 2 ? 2 : count;
561 dwVal = lpTime->wHour;
562 szAdd = buff;
563 break;
565 case 'm':
566 if (dwFlags & TIME_NOMINUTESORSECONDS)
568 cchWritten = lastFormatPos; /* Skip */
569 bSkipping = TRUE;
571 else
573 count = count > 2 ? 2 : count;
574 dwVal = lpTime->wMinute;
575 szAdd = buff;
577 break;
579 case 's':
580 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
582 cchWritten = lastFormatPos; /* Skip */
583 bSkipping = TRUE;
585 else
587 count = count > 2 ? 2 : count;
588 dwVal = lpTime->wSecond;
589 szAdd = buff;
591 break;
593 case 't':
594 if (dwFlags & TIME_NOTIMEMARKER)
596 cchWritten = lastFormatPos; /* Skip */
597 bSkipping = TRUE;
599 else
601 if (count == 1)
602 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
603 else
604 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
606 break;
609 if (szAdd == buff && buff[0] == '\0')
611 /* We have a numeric value to add */
612 sprintf(buffA, "%.*ld", count, dwVal);
613 MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
616 dwLen = szAdd ? strlenW(szAdd) : 0;
618 if (cchOut && dwLen)
620 if (cchWritten + dwLen < cchOut)
621 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
622 else
624 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
625 goto NLS_GetDateTimeFormatW_Overrun;
628 cchWritten += dwLen;
629 lastFormatPos = cchWritten; /* Save position of last output format text */
631 else
633 /* Literal character */
634 if (!cchOut)
635 cchWritten++; /* Count size only */
636 else if (cchWritten >= cchOut)
637 goto NLS_GetDateTimeFormatW_Overrun;
638 else if (!bSkipping || *lpFormat == ' ')
640 lpStr[cchWritten] = *lpFormat;
641 cchWritten++;
643 lpFormat++;
647 /* Final string terminator and sanity check */
648 if (cchOut)
650 if (cchWritten >= cchOut)
651 goto NLS_GetDateTimeFormatW_Overrun;
652 else
653 lpStr[cchWritten] = '\0';
655 cchWritten++; /* Include terminating NUL */
657 TRACE("returning length=%d, ouput='%s'\n", cchWritten, debugstr_w(lpStr));
658 return cchWritten;
660 NLS_GetDateTimeFormatW_Overrun:
661 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
662 SetLastError(ERROR_INSUFFICIENT_BUFFER);
663 return 0;
666 /******************************************************************************
667 * NLS_GetDateTimeFormatA <internal>
669 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
671 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
672 const SYSTEMTIME* lpTime,
673 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
675 DWORD cp = CP_ACP;
676 WCHAR szFormat[128], szOut[128];
677 INT iRet;
679 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
680 debugstr_a(lpFormat), lpStr, cchOut);
682 if (NLS_IsUnicodeOnlyLcid(lcid))
684 GetDateTimeFormatA_InvalidParameter:
685 SetLastError(ERROR_INVALID_PARAMETER);
686 return 0;
689 if (!(dwFlags & LOCALE_USE_CP_ACP))
691 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
692 if (!node)
693 goto GetDateTimeFormatA_InvalidParameter;
694 cp = node->dwCodePage;
697 if (lpFormat)
698 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
700 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
701 cchOut = sizeof(szOut)/sizeof(WCHAR);
703 szOut[0] = '\0';
705 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
706 lpStr ? szOut : NULL, cchOut);
708 if (lpStr)
710 if (szOut[0])
711 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
712 else if (cchOut && iRet)
713 *lpStr = '\0';
715 return iRet;
718 /******************************************************************************
719 * GetDateFormatA [KERNEL32.@]
721 * Format a date for a given locale.
723 * PARAMS
724 * lcid [I] Locale to format for
725 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
726 * lpTime [I] Date to format
727 * lpFormat [I] Format string, or NULL to use the system defaults
728 * lpDateStr [O] Destination for formatted string
729 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
731 * NOTES
732 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
733 * details returned by GetLocaleInfoA() and modified by dwFlags.
734 * - lpFormat is a string of characters and formatting tokens. Any characters
735 * in the string are copied verbatim to lpDateStr, with tokens being replaced
736 * by the date values they represent.
737 * - The following tokens have special meanings in a date format string:
738 *| Token Meaning
739 *| ----- -------
740 *| d Single digit day of the month (no leading 0)
741 *| dd Double digit day of the month
742 *| ddd Short name for the day of the week
743 *| dddd Long name for the day of the week
744 *| M Single digit month of the year (no leading 0)
745 *| MM Double digit month of the year
746 *| MMM Short name for the month of the year
747 *| MMMM Long name for the month of the year
748 *| y Double digit year number (no leading 0)
749 *| yy Double digit year number
750 *| yyyy Four digit year number
751 *| gg Era string, for example 'AD'.
752 * - To output any literal character that could be misidentified as a token,
753 * enclose it in single quotes.
754 * - The Ascii version of this function fails if lcid is Unicode only.
756 * RETURNS
757 * Success: The number of character written to lpDateStr, or that would
758 * have been written, if cchOut is 0.
759 * Failure: 0. Use GetLastError() to determine the cause.
761 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
762 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
764 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
765 debugstr_a(lpFormat), lpDateStr, cchOut);
767 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
768 lpFormat, lpDateStr, cchOut);
772 /******************************************************************************
773 * GetDateFormatW [KERNEL32.@]
775 * See GetDateFormatA.
777 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
778 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
780 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
781 debugstr_w(lpFormat), lpDateStr, cchOut);
783 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
784 lpFormat, lpDateStr, cchOut);
787 /******************************************************************************
788 * GetTimeFormatA [KERNEL32.@]
790 * Format a time for a given locale.
792 * PARAMS
793 * lcid [I] Locale to format for
794 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
795 * lpTime [I] Time to format
796 * lpFormat [I] Formatting overrides
797 * lpTimeStr [O] Destination for formatted string
798 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
800 * NOTES
801 * - If lpFormat is NULL, lpszValue will be formatted according to the format
802 * details returned by GetLocaleInfoA() and modified by dwFlags.
803 * - lpFormat is a string of characters and formatting tokens. Any characters
804 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
805 * by the time values they represent.
806 * - The following tokens have special meanings in a time format string:
807 *| Token Meaning
808 *| ----- -------
809 *| h Hours with no leading zero (12-hour clock)
810 *| hh Hours with full two digits (12-hour clock)
811 *| H Hours with no leading zero (24-hour clock)
812 *| HH Hours with full two digits (24-hour clock)
813 *| m Minutes with no leading zero
814 *| mm Minutes with full two digits
815 *| s Seconds with no leading zero
816 *| ss Seconds with full two digits
817 *| t Short time marker (e.g. "A" or "P")
818 *| tt Long time marker (e.g. "AM", "PM")
819 * - To output any literal character that could be misidentified as a token,
820 * enclose it in single quotes.
821 * - The Ascii version of this function fails if lcid is Unicode only.
823 * RETURNS
824 * Success: The number of character written to lpTimeStr, or that would
825 * have been written, if cchOut is 0.
826 * Failure: 0. Use GetLastError() to determine the cause.
828 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
829 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
831 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
832 debugstr_a(lpFormat), lpTimeStr, cchOut);
834 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
835 lpFormat, lpTimeStr, cchOut);
838 /******************************************************************************
839 * GetTimeFormatW [KERNEL32.@]
841 * See GetTimeFormatA.
843 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
844 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
846 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
847 debugstr_w(lpFormat), lpTimeStr, cchOut);
849 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
850 lpFormat, lpTimeStr, cchOut);
853 /**************************************************************************
854 * GetNumberFormatA (KERNEL32.@)
856 * Format a number string for a given locale.
858 * PARAMS
859 * lcid [I] Locale to format for
860 * dwFlags [I] LOCALE_ flags from "winnls.h"
861 * lpszValue [I] String to format
862 * lpFormat [I] Formatting overrides
863 * lpNumberStr [O] Destination for formatted string
864 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
866 * NOTES
867 * - lpszValue can contain only '0' - '9', '-' and '.'.
868 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
869 * be formatted according to the format details returned by GetLocaleInfoA().
870 * - This function rounds the number string if the number of decimals exceeds the
871 * locales normal number of decimal places.
872 * - If cchOut is 0, this function does not write to lpNumberStr.
873 * - The Ascii version of this function fails if lcid is Unicode only.
875 * RETURNS
876 * Success: The number of character written to lpNumberStr, or that would
877 * have been written, if cchOut is 0.
878 * Failure: 0. Use GetLastError() to determine the cause.
880 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
881 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
882 LPSTR lpNumberStr, int cchOut)
884 DWORD cp = CP_ACP;
885 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
886 NUMBERFMTW fmt;
887 const NUMBERFMTW *pfmt = NULL;
888 INT iRet;
890 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
891 lpFormat, lpNumberStr, cchOut);
893 if (NLS_IsUnicodeOnlyLcid(lcid))
895 GetNumberFormatA_InvalidParameter:
896 SetLastError(ERROR_INVALID_PARAMETER);
897 return 0;
900 if (!(dwFlags & LOCALE_USE_CP_ACP))
902 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
903 if (!node)
904 goto GetNumberFormatA_InvalidParameter;
905 cp = node->dwCodePage;
908 if (lpFormat)
910 memcpy(&fmt, lpFormat, sizeof(fmt));
911 pfmt = &fmt;
912 if (lpFormat->lpDecimalSep)
914 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
915 fmt.lpDecimalSep = szDec;
917 if (lpFormat->lpThousandSep)
919 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
920 fmt.lpThousandSep = szGrp;
924 if (lpszValue)
925 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
927 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
928 cchOut = sizeof(szOut)/sizeof(WCHAR);
930 szOut[0] = '\0';
932 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
933 lpNumberStr ? szOut : NULL, cchOut);
935 if (szOut[0] && lpNumberStr)
936 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
937 return iRet;
940 /* Number parsing state flags */
941 #define NF_ISNEGATIVE 0x1 /* '-' found */
942 #define NF_ISREAL 0x2 /* '.' found */
943 #define NF_DIGITS 0x4 /* '0'-'9' found */
944 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
945 #define NF_ROUND 0x10 /* Number needs to be rounded */
947 /* Formatting options for Numbers */
948 #define NLS_NEG_PARENS 0 /* "(1.1)" */
949 #define NLS_NEG_LEFT 1 /* "-1.1" */
950 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
951 #define NLS_NEG_RIGHT 3 /* "1.1-" */
952 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
954 /**************************************************************************
955 * GetNumberFormatW (KERNEL32.@)
957 * See GetNumberFormatA.
959 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
960 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
961 LPWSTR lpNumberStr, int cchOut)
963 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
964 WCHAR szNegBuff[8];
965 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
966 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
967 INT iRet;
969 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
970 lpFormat, lpNumberStr, cchOut);
972 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
973 !IsValidLocale(lcid, 0) ||
974 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
976 GetNumberFormatW_Error:
977 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
978 return 0;
981 if (!lpFormat)
983 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
985 if (!node)
986 goto GetNumberFormatW_Error;
987 lpFormat = &node->fmt;
988 lpszNegStart = lpszNeg = GetNegative(node);
990 else
992 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
993 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
994 lpszNegStart = lpszNeg = szNegBuff;
996 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
998 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1000 /* Format the number backwards into a temporary buffer */
1002 szSrc = lpszValue;
1003 *szOut-- = '\0';
1005 /* Check the number for validity */
1006 while (*szSrc)
1008 if (*szSrc >= '0' && *szSrc <= '9')
1010 dwState |= NF_DIGITS;
1011 if (dwState & NF_ISREAL)
1012 dwDecimals++;
1014 else if (*szSrc == '-')
1016 if (dwState)
1017 goto GetNumberFormatW_Error; /* '-' not first character */
1018 dwState |= NF_ISNEGATIVE;
1020 else if (*szSrc == '.')
1022 if (dwState & NF_ISREAL)
1023 goto GetNumberFormatW_Error; /* More than one '.' */
1024 dwState |= NF_ISREAL;
1026 else
1027 goto GetNumberFormatW_Error; /* Invalid char */
1028 szSrc++;
1030 szSrc--; /* Point to last character */
1032 if (!(dwState & NF_DIGITS))
1033 goto GetNumberFormatW_Error; /* No digits */
1035 /* Add any trailing negative sign */
1036 if (dwState & NF_ISNEGATIVE)
1038 switch (lpFormat->NegativeOrder)
1040 case NLS_NEG_PARENS:
1041 *szOut-- = ')';
1042 break;
1043 case NLS_NEG_RIGHT:
1044 case NLS_NEG_RIGHT_SPACE:
1045 while (lpszNeg >= lpszNegStart)
1046 *szOut-- = *lpszNeg--;
1047 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1048 *szOut-- = ' ';
1049 break;
1053 /* Copy all digits up to the decimal point */
1054 if (!lpFormat->NumDigits)
1056 if (dwState & NF_ISREAL)
1058 while (*szSrc != '.') /* Don't write any decimals or a separator */
1060 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1061 dwState |= NF_ROUND;
1062 else
1063 dwState &= ~NF_ROUND;
1064 szSrc--;
1066 szSrc--;
1069 else
1071 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1073 if (dwDecimals <= lpFormat->NumDigits)
1075 dwDecimals = lpFormat->NumDigits - dwDecimals;
1076 while (dwDecimals--)
1077 *szOut-- = '0'; /* Pad to correct number of dp */
1079 else
1081 dwDecimals -= lpFormat->NumDigits;
1082 /* Skip excess decimals, and determine if we have to round the number */
1083 while (dwDecimals--)
1085 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1086 dwState |= NF_ROUND;
1087 else
1088 dwState &= ~NF_ROUND;
1089 szSrc--;
1093 if (dwState & NF_ISREAL)
1095 while (*szSrc != '.')
1097 if (dwState & NF_ROUND)
1099 if (*szSrc == '9')
1100 *szOut-- = '0'; /* continue rounding */
1101 else
1103 dwState &= ~NF_ROUND;
1104 *szOut-- = (*szSrc)+1;
1106 szSrc--;
1108 else
1109 *szOut-- = *szSrc--; /* Write existing decimals */
1111 szSrc--; /* Skip '.' */
1114 while (lpszDec >= lpFormat->lpDecimalSep)
1115 *szOut-- = *lpszDec--; /* Write decimal separator */
1118 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1120 /* Write the remaining whole number digits, including grouping chars */
1121 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1123 if (dwState & NF_ROUND)
1125 if (*szSrc == '9')
1126 *szOut-- = '0'; /* continue rounding */
1127 else
1129 dwState &= ~NF_ROUND;
1130 *szOut-- = (*szSrc)+1;
1132 szSrc--;
1134 else
1135 *szOut-- = *szSrc--;
1137 dwState |= NF_DIGITS_OUT;
1138 dwCurrentGroupCount++;
1139 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1141 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1143 while (lpszGrp >= lpFormat->lpThousandSep)
1144 *szOut-- = *lpszGrp--; /* Write grouping char */
1146 dwCurrentGroupCount = 0;
1147 if (lpFormat->Grouping == 32)
1148 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1151 if (dwState & NF_ROUND)
1153 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1155 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1156 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1158 /* Add any leading negative sign */
1159 if (dwState & NF_ISNEGATIVE)
1161 switch (lpFormat->NegativeOrder)
1163 case NLS_NEG_PARENS:
1164 *szOut-- = '(';
1165 break;
1166 case NLS_NEG_LEFT_SPACE:
1167 *szOut-- = ' ';
1168 /* Fall through */
1169 case NLS_NEG_LEFT:
1170 while (lpszNeg >= lpszNegStart)
1171 *szOut-- = *lpszNeg--;
1172 break;
1175 szOut++;
1177 iRet = strlenW(szOut) + 1;
1178 if (cchOut)
1180 if (iRet <= cchOut)
1181 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1182 else
1184 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1185 lpNumberStr[cchOut - 1] = '\0';
1186 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1187 iRet = 0;
1190 return iRet;
1193 /**************************************************************************
1194 * GetCurrencyFormatA (KERNEL32.@)
1196 * Format a currency string for a given locale.
1198 * PARAMS
1199 * lcid [I] Locale to format for
1200 * dwFlags [I] LOCALE_ flags from "winnls.h"
1201 * lpszValue [I] String to format
1202 * lpFormat [I] Formatting overrides
1203 * lpCurrencyStr [O] Destination for formatted string
1204 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1206 * NOTES
1207 * - lpszValue can contain only '0' - '9', '-' and '.'.
1208 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1209 * be formatted according to the format details returned by GetLocaleInfoA().
1210 * - This function rounds the currency if the number of decimals exceeds the
1211 * locales number of currency decimal places.
1212 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1213 * - The Ascii version of this function fails if lcid is Unicode only.
1215 * RETURNS
1216 * Success: The number of character written to lpNumberStr, or that would
1217 * have been written, if cchOut is 0.
1218 * Failure: 0. Use GetLastError() to determine the cause.
1220 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1221 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1222 LPSTR lpCurrencyStr, int cchOut)
1224 DWORD cp = CP_ACP;
1225 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1226 CURRENCYFMTW fmt;
1227 const CURRENCYFMTW *pfmt = NULL;
1228 INT iRet;
1230 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1231 lpFormat, lpCurrencyStr, cchOut);
1233 if (NLS_IsUnicodeOnlyLcid(lcid))
1235 GetCurrencyFormatA_InvalidParameter:
1236 SetLastError(ERROR_INVALID_PARAMETER);
1237 return 0;
1240 if (!(dwFlags & LOCALE_USE_CP_ACP))
1242 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1243 if (!node)
1244 goto GetCurrencyFormatA_InvalidParameter;
1245 cp = node->dwCodePage;
1248 if (lpFormat)
1250 memcpy(&fmt, lpFormat, sizeof(fmt));
1251 pfmt = &fmt;
1252 if (lpFormat->lpDecimalSep)
1254 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1255 fmt.lpDecimalSep = szDec;
1257 if (lpFormat->lpThousandSep)
1259 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1260 fmt.lpThousandSep = szGrp;
1262 if (lpFormat->lpCurrencySymbol)
1264 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1265 fmt.lpCurrencySymbol = szCy;
1269 if (lpszValue)
1270 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1272 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1273 cchOut = sizeof(szOut)/sizeof(WCHAR);
1275 szOut[0] = '\0';
1277 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1278 lpCurrencyStr ? szOut : NULL, cchOut);
1280 if (szOut[0] && lpCurrencyStr)
1281 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1282 return iRet;
1285 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1286 #define CF_PARENS 0x1 /* Parentheses */
1287 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1288 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1289 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1290 #define CF_CY_LEFT 0x10 /* '$' to the left */
1291 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1292 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1294 /**************************************************************************
1295 * GetCurrencyFormatW (KERNEL32.@)
1297 * See GetCurrencyFormatA.
1299 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1300 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1301 LPWSTR lpCurrencyStr, int cchOut)
1303 static const BYTE NLS_NegCyFormats[16] =
1305 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1306 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1307 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1308 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1309 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1310 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1311 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1312 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1313 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1314 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1315 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1316 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1317 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1318 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1319 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1320 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1322 static const BYTE NLS_PosCyFormats[4] =
1324 CF_CY_LEFT, /* $1.1 */
1325 CF_CY_RIGHT, /* 1.1$ */
1326 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1327 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1329 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1330 WCHAR szNegBuff[8];
1331 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1332 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1333 INT iRet;
1335 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1336 lpFormat, lpCurrencyStr, cchOut);
1338 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1339 !IsValidLocale(lcid, 0) ||
1340 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1341 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1342 lpFormat->PositiveOrder > 3)))
1344 GetCurrencyFormatW_Error:
1345 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1346 return 0;
1349 if (!lpFormat)
1351 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1353 if (!node)
1354 goto GetCurrencyFormatW_Error;
1355 lpFormat = &node->cyfmt;
1356 lpszNegStart = lpszNeg = GetNegative(node);
1358 else
1360 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1361 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1362 lpszNegStart = lpszNeg = szNegBuff;
1364 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1366 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1367 lpszCyStart = lpFormat->lpCurrencySymbol;
1368 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1370 /* Format the currency backwards into a temporary buffer */
1372 szSrc = lpszValue;
1373 *szOut-- = '\0';
1375 /* Check the number for validity */
1376 while (*szSrc)
1378 if (*szSrc >= '0' && *szSrc <= '9')
1380 dwState |= NF_DIGITS;
1381 if (dwState & NF_ISREAL)
1382 dwDecimals++;
1384 else if (*szSrc == '-')
1386 if (dwState)
1387 goto GetCurrencyFormatW_Error; /* '-' not first character */
1388 dwState |= NF_ISNEGATIVE;
1390 else if (*szSrc == '.')
1392 if (dwState & NF_ISREAL)
1393 goto GetCurrencyFormatW_Error; /* More than one '.' */
1394 dwState |= NF_ISREAL;
1396 else
1397 goto GetCurrencyFormatW_Error; /* Invalid char */
1398 szSrc++;
1400 szSrc--; /* Point to last character */
1402 if (!(dwState & NF_DIGITS))
1403 goto GetCurrencyFormatW_Error; /* No digits */
1405 if (dwState & NF_ISNEGATIVE)
1406 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1407 else
1408 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1410 /* Add any trailing negative or currency signs */
1411 if (dwFmt & CF_PARENS)
1412 *szOut-- = ')';
1414 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1416 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1418 case CF_MINUS_RIGHT:
1419 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1420 while (lpszNeg >= lpszNegStart)
1421 *szOut-- = *lpszNeg--;
1422 dwFmt &= ~CF_MINUS_RIGHT;
1423 break;
1425 case CF_CY_RIGHT:
1426 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1427 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1428 while (lpszCy >= lpszCyStart)
1429 *szOut-- = *lpszCy--;
1430 if (dwFmt & CF_CY_SPACE)
1431 *szOut-- = ' ';
1432 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1433 break;
1437 /* Copy all digits up to the decimal point */
1438 if (!lpFormat->NumDigits)
1440 if (dwState & NF_ISREAL)
1442 while (*szSrc != '.') /* Don't write any decimals or a separator */
1444 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1445 dwState |= NF_ROUND;
1446 else
1447 dwState &= ~NF_ROUND;
1448 szSrc--;
1450 szSrc--;
1453 else
1455 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1457 if (dwDecimals <= lpFormat->NumDigits)
1459 dwDecimals = lpFormat->NumDigits - dwDecimals;
1460 while (dwDecimals--)
1461 *szOut-- = '0'; /* Pad to correct number of dp */
1463 else
1465 dwDecimals -= lpFormat->NumDigits;
1466 /* Skip excess decimals, and determine if we have to round the number */
1467 while (dwDecimals--)
1469 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1470 dwState |= NF_ROUND;
1471 else
1472 dwState &= ~NF_ROUND;
1473 szSrc--;
1477 if (dwState & NF_ISREAL)
1479 while (*szSrc != '.')
1481 if (dwState & NF_ROUND)
1483 if (*szSrc == '9')
1484 *szOut-- = '0'; /* continue rounding */
1485 else
1487 dwState &= ~NF_ROUND;
1488 *szOut-- = (*szSrc)+1;
1490 szSrc--;
1492 else
1493 *szOut-- = *szSrc--; /* Write existing decimals */
1495 szSrc--; /* Skip '.' */
1497 while (lpszDec >= lpFormat->lpDecimalSep)
1498 *szOut-- = *lpszDec--; /* Write decimal separator */
1501 dwGroupCount = lpFormat->Grouping;
1503 /* Write the remaining whole number digits, including grouping chars */
1504 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1506 if (dwState & NF_ROUND)
1508 if (*szSrc == '9')
1509 *szOut-- = '0'; /* continue rounding */
1510 else
1512 dwState &= ~NF_ROUND;
1513 *szOut-- = (*szSrc)+1;
1515 szSrc--;
1517 else
1518 *szOut-- = *szSrc--;
1520 dwState |= NF_DIGITS_OUT;
1521 dwCurrentGroupCount++;
1522 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount)
1524 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1526 while (lpszGrp >= lpFormat->lpThousandSep)
1527 *szOut-- = *lpszGrp--; /* Write grouping char */
1529 dwCurrentGroupCount = 0;
1532 if (dwState & NF_ROUND)
1533 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1534 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1535 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1537 /* Add any leading negative or currency sign */
1538 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1540 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1542 case CF_MINUS_LEFT:
1543 case CF_MINUS_LEFT|CF_CY_LEFT:
1544 while (lpszNeg >= lpszNegStart)
1545 *szOut-- = *lpszNeg--;
1546 dwFmt &= ~CF_MINUS_LEFT;
1547 break;
1549 case CF_CY_LEFT:
1550 case CF_CY_LEFT|CF_MINUS_BEFORE:
1551 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1552 if (dwFmt & CF_CY_SPACE)
1553 *szOut-- = ' ';
1554 while (lpszCy >= lpszCyStart)
1555 *szOut-- = *lpszCy--;
1556 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1557 break;
1560 if (dwFmt & CF_PARENS)
1561 *szOut-- = '(';
1562 szOut++;
1564 iRet = strlenW(szOut) + 1;
1565 if (cchOut)
1567 if (iRet <= cchOut)
1568 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1569 else
1571 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1572 lpCurrencyStr[cchOut - 1] = '\0';
1573 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1574 iRet = 0;
1577 return iRet;
1580 /* FIXME: Everything below here needs to move somewhere else along with the
1581 * other EnumXXX functions, when a method for storing resources for
1582 * alternate calendars is determined.
1585 /**************************************************************************
1586 * EnumDateFormatsExA (KERNEL32.@)
1588 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1589 * LOCALE_NOUSEROVERRIDE here as well?
1591 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1593 CALID cal_id;
1594 char buf[256];
1596 if (!proc)
1598 SetLastError(ERROR_INVALID_PARAMETER);
1599 return FALSE;
1602 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1603 return FALSE;
1605 switch (flags & ~LOCALE_USE_CP_ACP)
1607 case 0:
1608 case DATE_SHORTDATE:
1609 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1610 proc(buf, cal_id);
1611 break;
1613 case DATE_LONGDATE:
1614 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1615 proc(buf, cal_id);
1616 break;
1618 case DATE_YEARMONTH:
1619 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1620 proc(buf, cal_id);
1621 break;
1623 default:
1624 FIXME("Unknown date format (%ld)\n", flags);
1625 SetLastError(ERROR_INVALID_PARAMETER);
1626 return FALSE;
1628 return TRUE;
1631 /**************************************************************************
1632 * EnumDateFormatsExW (KERNEL32.@)
1634 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1636 CALID cal_id;
1637 WCHAR buf[256];
1639 if (!proc)
1641 SetLastError(ERROR_INVALID_PARAMETER);
1642 return FALSE;
1645 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1646 return FALSE;
1648 switch (flags & ~LOCALE_USE_CP_ACP)
1650 case 0:
1651 case DATE_SHORTDATE:
1652 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1653 proc(buf, cal_id);
1654 break;
1656 case DATE_LONGDATE:
1657 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1658 proc(buf, cal_id);
1659 break;
1661 case DATE_YEARMONTH:
1662 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1663 proc(buf, cal_id);
1664 break;
1666 default:
1667 FIXME("Unknown date format (%ld)\n", flags);
1668 SetLastError(ERROR_INVALID_PARAMETER);
1669 return FALSE;
1671 return TRUE;
1674 /**************************************************************************
1675 * EnumDateFormatsA (KERNEL32.@)
1677 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1678 * LOCALE_NOUSEROVERRIDE here as well?
1680 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1682 char buf[256];
1684 if (!proc)
1686 SetLastError(ERROR_INVALID_PARAMETER);
1687 return FALSE;
1690 switch (flags & ~LOCALE_USE_CP_ACP)
1692 case 0:
1693 case DATE_SHORTDATE:
1694 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1695 proc(buf);
1696 break;
1698 case DATE_LONGDATE:
1699 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1700 proc(buf);
1701 break;
1703 case DATE_YEARMONTH:
1704 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1705 proc(buf);
1706 break;
1708 default:
1709 FIXME("Unknown date format (%ld)\n", flags);
1710 SetLastError(ERROR_INVALID_PARAMETER);
1711 return FALSE;
1713 return TRUE;
1716 /**************************************************************************
1717 * EnumDateFormatsW (KERNEL32.@)
1719 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1721 WCHAR buf[256];
1723 if (!proc)
1725 SetLastError(ERROR_INVALID_PARAMETER);
1726 return FALSE;
1729 switch (flags & ~LOCALE_USE_CP_ACP)
1731 case 0:
1732 case DATE_SHORTDATE:
1733 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1734 proc(buf);
1735 break;
1737 case DATE_LONGDATE:
1738 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1739 proc(buf);
1740 break;
1742 case DATE_YEARMONTH:
1743 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1744 proc(buf);
1745 break;
1747 default:
1748 FIXME("Unknown date format (%ld)\n", flags);
1749 SetLastError(ERROR_INVALID_PARAMETER);
1750 return FALSE;
1752 return TRUE;
1755 /**************************************************************************
1756 * EnumTimeFormatsA (KERNEL32.@)
1758 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1759 * LOCALE_NOUSEROVERRIDE here as well?
1761 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1763 char buf[256];
1765 if (!proc)
1767 SetLastError(ERROR_INVALID_PARAMETER);
1768 return FALSE;
1771 switch (flags & ~LOCALE_USE_CP_ACP)
1773 case 0:
1774 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1775 proc(buf);
1776 break;
1778 default:
1779 FIXME("Unknown time format (%ld)\n", flags);
1780 SetLastError(ERROR_INVALID_PARAMETER);
1781 return FALSE;
1783 return TRUE;
1786 /**************************************************************************
1787 * EnumTimeFormatsW (KERNEL32.@)
1789 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1791 WCHAR buf[256];
1793 if (!proc)
1795 SetLastError(ERROR_INVALID_PARAMETER);
1796 return FALSE;
1799 switch (flags & ~LOCALE_USE_CP_ACP)
1801 case 0:
1802 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1803 proc(buf);
1804 break;
1806 default:
1807 FIXME("Unknown time format (%ld)\n", flags);
1808 SetLastError(ERROR_INVALID_PARAMETER);
1809 return FALSE;
1811 return TRUE;
1814 /******************************************************************************
1815 * NLS_EnumCalendarInfoAW <internal>
1816 * Enumerates calendar information for a specified locale.
1818 * PARAMS
1819 * calinfoproc [I] Pointer to the callback
1820 * locale [I] The locale for which to retrieve calendar information.
1821 * This parameter can be a locale identifier created by the
1822 * MAKELCID macro, or one of the following values:
1823 * LOCALE_SYSTEM_DEFAULT
1824 * Use the default system locale.
1825 * LOCALE_USER_DEFAULT
1826 * Use the default user locale.
1827 * calendar [I] The calendar for which information is requested, or
1828 * ENUM_ALL_CALENDARS.
1829 * caltype [I] The type of calendar information to be returned. Note
1830 * that only one CALTYPE value can be specified per call
1831 * of this function, except where noted.
1832 * unicode [I] Specifies if the callback expects a unicode string.
1833 * ex [I] Specifies if the callback needs the calendar identifier.
1835 * RETURNS
1836 * Success: TRUE.
1837 * Failure: FALSE. Use GetLastError() to determine the cause.
1839 * NOTES
1840 * When the ANSI version of this function is used with a Unicode-only LCID,
1841 * the call can succeed because the system uses the system code page.
1842 * However, characters that are undefined in the system code page appear
1843 * in the string as a question mark (?).
1845 * TODO
1846 * The above note should be respected by GetCalendarInfoA.
1848 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1849 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1851 WCHAR *buf, *opt = NULL, *iter = NULL;
1852 BOOL ret = FALSE;
1853 int bufSz = 200; /* the size of the buffer */
1855 if (calinfoproc == NULL)
1857 SetLastError(ERROR_INVALID_PARAMETER);
1858 return FALSE;
1861 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1862 if (buf == NULL)
1864 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1865 return FALSE;
1868 if (calendar == ENUM_ALL_CALENDARS)
1870 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1871 if (optSz > 1)
1873 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1874 if (opt == NULL)
1876 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1877 goto NLS_EnumCalendarInfoAW_Cleanup;
1879 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1880 iter = opt;
1882 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1885 while (TRUE) /* loop through calendars */
1887 do /* loop until there's no error */
1889 if (unicode)
1890 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1891 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1893 if (!ret)
1895 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1896 { /* so resize it */
1897 int newSz;
1898 if (unicode)
1899 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1900 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1901 if (bufSz >= newSz)
1903 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1904 goto NLS_EnumCalendarInfoAW_Cleanup;
1906 bufSz = newSz;
1907 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1908 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1909 if (buf == NULL)
1910 goto NLS_EnumCalendarInfoAW_Cleanup;
1911 } else goto NLS_EnumCalendarInfoAW_Cleanup;
1913 } while (!ret);
1915 /* Here we are. We pass the buffer to the correct version of
1916 * the callback. Because it's not the same number of params,
1917 * we must check for Ex, but we don't care about Unicode
1918 * because the buffer is already in the correct format.
1920 if (ex) {
1921 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
1922 } else
1923 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
1925 if (!ret) { /* the callback told to stop */
1926 ret = TRUE;
1927 break;
1930 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
1931 break;
1933 calendar = 0;
1934 while ((*iter >= '0') && (*iter <= '9'))
1935 calendar = calendar * 10 + *iter++ - '0';
1937 if (*iter++ != 0)
1939 SetLastError(ERROR_BADDB);
1940 ret = FALSE;
1941 break;
1945 NLS_EnumCalendarInfoAW_Cleanup:
1946 HeapFree(GetProcessHeap(), 0, opt);
1947 HeapFree(GetProcessHeap(), 0, buf);
1948 return ret;
1951 /******************************************************************************
1952 * EnumCalendarInfoA [KERNEL32.@]
1954 * See EnumCalendarInfoAW.
1956 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
1957 CALID calendar,CALTYPE caltype )
1959 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
1960 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
1963 /******************************************************************************
1964 * EnumCalendarInfoW [KERNEL32.@]
1966 * See EnumCalendarInfoAW.
1968 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
1969 CALID calendar,CALTYPE caltype )
1971 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
1972 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
1975 /******************************************************************************
1976 * EnumCalendarInfoExA [KERNEL32.@]
1978 * See EnumCalendarInfoAW.
1980 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
1981 CALID calendar,CALTYPE caltype )
1983 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
1984 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
1987 /******************************************************************************
1988 * EnumCalendarInfoExW [KERNEL32.@]
1990 * See EnumCalendarInfoAW.
1992 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
1993 CALID calendar,CALTYPE caltype )
1995 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc, locale, calendar, caltype);
1996 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);