kernel32/lcformat: Avoid back jumps on failure.
[wine/multimedia.git] / dlls / kernel32 / lcformat.c
blob83c190f3f6b6cc05246551ad8a4400ee6421ffdb
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)))
358 goto invalid_parameter;
360 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
362 if (lpFormat &&
363 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
364 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
366 goto invalid_flags;
369 if (dwFlags & DATE_DATEVARSONLY)
371 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
372 goto invalid_flags;
373 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
374 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
376 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
378 case 0:
379 break;
380 case DATE_SHORTDATE:
381 case DATE_LONGDATE:
382 case DATE_YEARMONTH:
383 if (lpFormat)
384 goto invalid_flags;
385 break;
386 default:
387 goto invalid_flags;
392 if (!lpFormat)
394 /* Use the appropriate default format */
395 if (dwFlags & DATE_DATEVARSONLY)
397 if (dwFlags & DATE_YEARMONTH)
398 lpFormat = GetYearMonth(node);
399 else if (dwFlags & DATE_LONGDATE)
400 lpFormat = GetLongDate(node);
401 else
402 lpFormat = GetShortDate(node);
404 else
405 lpFormat = GetTime(node);
408 if (!lpTime)
410 GetLocalTime(&st); /* Default to current time */
411 lpTime = &st;
413 else
415 if (dwFlags & DATE_DATEVARSONLY)
417 FILETIME ftTmp;
419 /* Verify the date and correct the D.O.W. if needed */
420 memset(&st, 0, sizeof(st));
421 st.wYear = lpTime->wYear;
422 st.wMonth = lpTime->wMonth;
423 st.wDay = lpTime->wDay;
425 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
426 goto invalid_parameter;
428 FileTimeToSystemTime(&ftTmp, &st);
429 lpTime = &st;
432 if (dwFlags & TIME_TIMEVARSONLY)
434 /* Verify the time */
435 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
436 goto invalid_parameter;
440 /* Format the output */
441 while (*lpFormat)
443 if (IsLiteralMarker(*lpFormat))
445 /* Start of a literal string */
446 lpFormat++;
448 /* Loop until the end of the literal marker or end of the string */
449 while (*lpFormat)
451 if (IsLiteralMarker(*lpFormat))
453 lpFormat++;
454 if (!IsLiteralMarker(*lpFormat))
455 break; /* Terminating literal marker */
458 if (!cchOut)
459 cchWritten++; /* Count size only */
460 else if (cchWritten >= cchOut)
461 goto overrun;
462 else if (!bSkipping)
464 lpStr[cchWritten] = *lpFormat;
465 cchWritten++;
467 lpFormat++;
470 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
471 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
473 char buffA[32];
474 WCHAR buff[32], fmtChar;
475 LPCWSTR szAdd = NULL;
476 DWORD dwVal = 0;
477 int count = 0, dwLen;
479 bSkipping = FALSE;
481 fmtChar = *lpFormat;
482 while (*lpFormat == fmtChar)
484 count++;
485 lpFormat++;
487 buff[0] = '\0';
489 switch(fmtChar)
491 case 'd':
492 if (count >= 4)
493 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
494 else if (count == 3)
495 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
496 else
498 dwVal = lpTime->wDay;
499 szAdd = buff;
501 break;
503 case 'M':
504 if (count >= 4)
505 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
506 else if (count == 3)
507 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
508 else
510 dwVal = lpTime->wMonth;
511 szAdd = buff;
513 break;
515 case 'y':
516 if (count >= 4)
518 count = 4;
519 dwVal = lpTime->wYear;
521 else
523 count = count > 2 ? 2 : count;
524 dwVal = lpTime->wYear % 100;
526 szAdd = buff;
527 break;
529 case 'g':
530 if (count == 2)
532 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
533 * When it is fixed, this string should be cached in 'node'.
535 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
536 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
538 else
540 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
542 szAdd = buff;
543 break;
545 case 'h':
546 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
548 count = count > 2 ? 2 : count;
549 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
550 szAdd = buff;
551 break;
553 /* .. fall through if we are forced to output in 24 hour format */
555 case 'H':
556 count = count > 2 ? 2 : count;
557 dwVal = lpTime->wHour;
558 szAdd = buff;
559 break;
561 case 'm':
562 if (dwFlags & TIME_NOMINUTESORSECONDS)
564 cchWritten = lastFormatPos; /* Skip */
565 bSkipping = TRUE;
567 else
569 count = count > 2 ? 2 : count;
570 dwVal = lpTime->wMinute;
571 szAdd = buff;
573 break;
575 case 's':
576 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
578 cchWritten = lastFormatPos; /* Skip */
579 bSkipping = TRUE;
581 else
583 count = count > 2 ? 2 : count;
584 dwVal = lpTime->wSecond;
585 szAdd = buff;
587 break;
589 case 't':
590 if (dwFlags & TIME_NOTIMEMARKER)
592 cchWritten = lastFormatPos; /* Skip */
593 bSkipping = TRUE;
595 else
597 if (count == 1)
598 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
599 else
600 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
602 break;
605 if (szAdd == buff && buff[0] == '\0')
607 /* We have a numeric value to add */
608 sprintf(buffA, "%.*d", count, dwVal);
609 MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
612 dwLen = szAdd ? strlenW(szAdd) : 0;
614 if (cchOut && dwLen)
616 if (cchWritten + dwLen < cchOut)
617 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
618 else
620 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
621 goto overrun;
624 cchWritten += dwLen;
625 lastFormatPos = cchWritten; /* Save position of last output format text */
627 else
629 /* Literal character */
630 if (!cchOut)
631 cchWritten++; /* Count size only */
632 else if (cchWritten >= cchOut)
633 goto overrun;
634 else if (!bSkipping || *lpFormat == ' ')
636 lpStr[cchWritten] = *lpFormat;
637 cchWritten++;
639 lpFormat++;
643 /* Final string terminator and sanity check */
644 if (cchOut)
646 if (cchWritten >= cchOut)
647 goto overrun;
648 else
649 lpStr[cchWritten] = '\0';
651 cchWritten++; /* Include terminating NUL */
653 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
654 return cchWritten;
656 overrun:
657 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
658 SetLastError(ERROR_INSUFFICIENT_BUFFER);
659 return 0;
661 invalid_parameter:
662 SetLastError(ERROR_INVALID_PARAMETER);
663 return 0;
665 invalid_flags:
666 SetLastError(ERROR_INVALID_FLAGS);
667 return 0;
670 /******************************************************************************
671 * NLS_GetDateTimeFormatA <internal>
673 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
675 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
676 const SYSTEMTIME* lpTime,
677 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
679 DWORD cp = CP_ACP;
680 WCHAR szFormat[128], szOut[128];
681 INT iRet;
683 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
684 debugstr_a(lpFormat), lpStr, cchOut);
686 if (NLS_IsUnicodeOnlyLcid(lcid))
688 GetDateTimeFormatA_InvalidParameter:
689 SetLastError(ERROR_INVALID_PARAMETER);
690 return 0;
693 if (!(dwFlags & LOCALE_USE_CP_ACP))
695 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
696 if (!node)
697 goto GetDateTimeFormatA_InvalidParameter;
698 cp = node->dwCodePage;
701 if (lpFormat)
702 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
704 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
705 cchOut = sizeof(szOut)/sizeof(WCHAR);
707 szOut[0] = '\0';
709 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
710 lpStr ? szOut : NULL, cchOut);
712 if (lpStr)
714 if (szOut[0])
715 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
716 else if (cchOut && iRet)
717 *lpStr = '\0';
719 return iRet;
722 /******************************************************************************
723 * GetDateFormatA [KERNEL32.@]
725 * Format a date for a given locale.
727 * PARAMS
728 * lcid [I] Locale to format for
729 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
730 * lpTime [I] Date to format
731 * lpFormat [I] Format string, or NULL to use the system defaults
732 * lpDateStr [O] Destination for formatted string
733 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
735 * NOTES
736 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
737 * details returned by GetLocaleInfoA() and modified by dwFlags.
738 * - lpFormat is a string of characters and formatting tokens. Any characters
739 * in the string are copied verbatim to lpDateStr, with tokens being replaced
740 * by the date values they represent.
741 * - The following tokens have special meanings in a date format string:
742 *| Token Meaning
743 *| ----- -------
744 *| d Single digit day of the month (no leading 0)
745 *| dd Double digit day of the month
746 *| ddd Short name for the day of the week
747 *| dddd Long name for the day of the week
748 *| M Single digit month of the year (no leading 0)
749 *| MM Double digit month of the year
750 *| MMM Short name for the month of the year
751 *| MMMM Long name for the month of the year
752 *| y Double digit year number (no leading 0)
753 *| yy Double digit year number
754 *| yyyy Four digit year number
755 *| gg Era string, for example 'AD'.
756 * - To output any literal character that could be misidentified as a token,
757 * enclose it in single quotes.
758 * - The Ascii version of this function fails if lcid is Unicode only.
760 * RETURNS
761 * Success: The number of character written to lpDateStr, or that would
762 * have been written, if cchOut is 0.
763 * Failure: 0. Use GetLastError() to determine the cause.
765 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
766 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
768 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
769 debugstr_a(lpFormat), lpDateStr, cchOut);
771 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
772 lpFormat, lpDateStr, cchOut);
776 /******************************************************************************
777 * GetDateFormatW [KERNEL32.@]
779 * See GetDateFormatA.
781 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
782 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
784 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
785 debugstr_w(lpFormat), lpDateStr, cchOut);
787 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
788 lpFormat, lpDateStr, cchOut);
791 /******************************************************************************
792 * GetTimeFormatA [KERNEL32.@]
794 * Format a time for a given locale.
796 * PARAMS
797 * lcid [I] Locale to format for
798 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
799 * lpTime [I] Time to format
800 * lpFormat [I] Formatting overrides
801 * lpTimeStr [O] Destination for formatted string
802 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
804 * NOTES
805 * - If lpFormat is NULL, lpszValue will be formatted according to the format
806 * details returned by GetLocaleInfoA() and modified by dwFlags.
807 * - lpFormat is a string of characters and formatting tokens. Any characters
808 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
809 * by the time values they represent.
810 * - The following tokens have special meanings in a time format string:
811 *| Token Meaning
812 *| ----- -------
813 *| h Hours with no leading zero (12-hour clock)
814 *| hh Hours with full two digits (12-hour clock)
815 *| H Hours with no leading zero (24-hour clock)
816 *| HH Hours with full two digits (24-hour clock)
817 *| m Minutes with no leading zero
818 *| mm Minutes with full two digits
819 *| s Seconds with no leading zero
820 *| ss Seconds with full two digits
821 *| t Short time marker (e.g. "A" or "P")
822 *| tt Long time marker (e.g. "AM", "PM")
823 * - To output any literal character that could be misidentified as a token,
824 * enclose it in single quotes.
825 * - The Ascii version of this function fails if lcid is Unicode only.
827 * RETURNS
828 * Success: The number of character written to lpTimeStr, or that would
829 * have been written, if cchOut is 0.
830 * Failure: 0. Use GetLastError() to determine the cause.
832 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
833 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
835 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
836 debugstr_a(lpFormat), lpTimeStr, cchOut);
838 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
839 lpFormat, lpTimeStr, cchOut);
842 /******************************************************************************
843 * GetTimeFormatW [KERNEL32.@]
845 * See GetTimeFormatA.
847 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
848 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
850 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
851 debugstr_w(lpFormat), lpTimeStr, cchOut);
853 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
854 lpFormat, lpTimeStr, cchOut);
857 /**************************************************************************
858 * GetNumberFormatA (KERNEL32.@)
860 * Format a number string for a given locale.
862 * PARAMS
863 * lcid [I] Locale to format for
864 * dwFlags [I] LOCALE_ flags from "winnls.h"
865 * lpszValue [I] String to format
866 * lpFormat [I] Formatting overrides
867 * lpNumberStr [O] Destination for formatted string
868 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
870 * NOTES
871 * - lpszValue can contain only '0' - '9', '-' and '.'.
872 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
873 * be formatted according to the format details returned by GetLocaleInfoA().
874 * - This function rounds the number string if the number of decimals exceeds the
875 * locales normal number of decimal places.
876 * - If cchOut is 0, this function does not write to lpNumberStr.
877 * - The Ascii version of this function fails if lcid is Unicode only.
879 * RETURNS
880 * Success: The number of character written to lpNumberStr, or that would
881 * have been written, if cchOut is 0.
882 * Failure: 0. Use GetLastError() to determine the cause.
884 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
885 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
886 LPSTR lpNumberStr, int cchOut)
888 DWORD cp = CP_ACP;
889 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
890 NUMBERFMTW fmt;
891 const NUMBERFMTW *pfmt = NULL;
892 INT iRet;
894 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
895 lpFormat, lpNumberStr, cchOut);
897 if (NLS_IsUnicodeOnlyLcid(lcid))
899 GetNumberFormatA_InvalidParameter:
900 SetLastError(ERROR_INVALID_PARAMETER);
901 return 0;
904 if (!(dwFlags & LOCALE_USE_CP_ACP))
906 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
907 if (!node)
908 goto GetNumberFormatA_InvalidParameter;
909 cp = node->dwCodePage;
912 if (lpFormat)
914 memcpy(&fmt, lpFormat, sizeof(fmt));
915 pfmt = &fmt;
916 if (lpFormat->lpDecimalSep)
918 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
919 fmt.lpDecimalSep = szDec;
921 if (lpFormat->lpThousandSep)
923 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
924 fmt.lpThousandSep = szGrp;
928 if (lpszValue)
929 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
931 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
932 cchOut = sizeof(szOut)/sizeof(WCHAR);
934 szOut[0] = '\0';
936 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
937 lpNumberStr ? szOut : NULL, cchOut);
939 if (szOut[0] && lpNumberStr)
940 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
941 return iRet;
944 /* Number parsing state flags */
945 #define NF_ISNEGATIVE 0x1 /* '-' found */
946 #define NF_ISREAL 0x2 /* '.' found */
947 #define NF_DIGITS 0x4 /* '0'-'9' found */
948 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
949 #define NF_ROUND 0x10 /* Number needs to be rounded */
951 /* Formatting options for Numbers */
952 #define NLS_NEG_PARENS 0 /* "(1.1)" */
953 #define NLS_NEG_LEFT 1 /* "-1.1" */
954 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
955 #define NLS_NEG_RIGHT 3 /* "1.1-" */
956 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
958 /**************************************************************************
959 * GetNumberFormatW (KERNEL32.@)
961 * See GetNumberFormatA.
963 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
964 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
965 LPWSTR lpNumberStr, int cchOut)
967 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
968 WCHAR szNegBuff[8];
969 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
970 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
971 INT iRet;
973 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
974 lpFormat, lpNumberStr, cchOut);
976 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
977 !IsValidLocale(lcid, 0) ||
978 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
980 GetNumberFormatW_Error:
981 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
982 return 0;
985 if (!lpFormat)
987 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
989 if (!node)
990 goto GetNumberFormatW_Error;
991 lpFormat = &node->fmt;
992 lpszNegStart = lpszNeg = GetNegative(node);
994 else
996 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
997 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
998 lpszNegStart = lpszNeg = szNegBuff;
1000 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1002 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1004 /* Format the number backwards into a temporary buffer */
1006 szSrc = lpszValue;
1007 *szOut-- = '\0';
1009 /* Check the number for validity */
1010 while (*szSrc)
1012 if (*szSrc >= '0' && *szSrc <= '9')
1014 dwState |= NF_DIGITS;
1015 if (dwState & NF_ISREAL)
1016 dwDecimals++;
1018 else if (*szSrc == '-')
1020 if (dwState)
1021 goto GetNumberFormatW_Error; /* '-' not first character */
1022 dwState |= NF_ISNEGATIVE;
1024 else if (*szSrc == '.')
1026 if (dwState & NF_ISREAL)
1027 goto GetNumberFormatW_Error; /* More than one '.' */
1028 dwState |= NF_ISREAL;
1030 else
1031 goto GetNumberFormatW_Error; /* Invalid char */
1032 szSrc++;
1034 szSrc--; /* Point to last character */
1036 if (!(dwState & NF_DIGITS))
1037 goto GetNumberFormatW_Error; /* No digits */
1039 /* Add any trailing negative sign */
1040 if (dwState & NF_ISNEGATIVE)
1042 switch (lpFormat->NegativeOrder)
1044 case NLS_NEG_PARENS:
1045 *szOut-- = ')';
1046 break;
1047 case NLS_NEG_RIGHT:
1048 case NLS_NEG_RIGHT_SPACE:
1049 while (lpszNeg >= lpszNegStart)
1050 *szOut-- = *lpszNeg--;
1051 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1052 *szOut-- = ' ';
1053 break;
1057 /* Copy all digits up to the decimal point */
1058 if (!lpFormat->NumDigits)
1060 if (dwState & NF_ISREAL)
1062 while (*szSrc != '.') /* Don't write any decimals or a separator */
1064 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1065 dwState |= NF_ROUND;
1066 else
1067 dwState &= ~NF_ROUND;
1068 szSrc--;
1070 szSrc--;
1073 else
1075 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1077 if (dwDecimals <= lpFormat->NumDigits)
1079 dwDecimals = lpFormat->NumDigits - dwDecimals;
1080 while (dwDecimals--)
1081 *szOut-- = '0'; /* Pad to correct number of dp */
1083 else
1085 dwDecimals -= lpFormat->NumDigits;
1086 /* Skip excess decimals, and determine if we have to round the number */
1087 while (dwDecimals--)
1089 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1090 dwState |= NF_ROUND;
1091 else
1092 dwState &= ~NF_ROUND;
1093 szSrc--;
1097 if (dwState & NF_ISREAL)
1099 while (*szSrc != '.')
1101 if (dwState & NF_ROUND)
1103 if (*szSrc == '9')
1104 *szOut-- = '0'; /* continue rounding */
1105 else
1107 dwState &= ~NF_ROUND;
1108 *szOut-- = (*szSrc)+1;
1110 szSrc--;
1112 else
1113 *szOut-- = *szSrc--; /* Write existing decimals */
1115 szSrc--; /* Skip '.' */
1118 while (lpszDec >= lpFormat->lpDecimalSep)
1119 *szOut-- = *lpszDec--; /* Write decimal separator */
1122 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1124 /* Write the remaining whole number digits, including grouping chars */
1125 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1127 if (dwState & NF_ROUND)
1129 if (*szSrc == '9')
1130 *szOut-- = '0'; /* continue rounding */
1131 else
1133 dwState &= ~NF_ROUND;
1134 *szOut-- = (*szSrc)+1;
1136 szSrc--;
1138 else
1139 *szOut-- = *szSrc--;
1141 dwState |= NF_DIGITS_OUT;
1142 dwCurrentGroupCount++;
1143 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1145 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1147 while (lpszGrp >= lpFormat->lpThousandSep)
1148 *szOut-- = *lpszGrp--; /* Write grouping char */
1150 dwCurrentGroupCount = 0;
1151 if (lpFormat->Grouping == 32)
1152 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1155 if (dwState & NF_ROUND)
1157 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1159 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1160 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1162 /* Add any leading negative sign */
1163 if (dwState & NF_ISNEGATIVE)
1165 switch (lpFormat->NegativeOrder)
1167 case NLS_NEG_PARENS:
1168 *szOut-- = '(';
1169 break;
1170 case NLS_NEG_LEFT_SPACE:
1171 *szOut-- = ' ';
1172 /* Fall through */
1173 case NLS_NEG_LEFT:
1174 while (lpszNeg >= lpszNegStart)
1175 *szOut-- = *lpszNeg--;
1176 break;
1179 szOut++;
1181 iRet = strlenW(szOut) + 1;
1182 if (cchOut)
1184 if (iRet <= cchOut)
1185 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1186 else
1188 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1189 lpNumberStr[cchOut - 1] = '\0';
1190 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1191 iRet = 0;
1194 return iRet;
1197 /**************************************************************************
1198 * GetCurrencyFormatA (KERNEL32.@)
1200 * Format a currency string for a given locale.
1202 * PARAMS
1203 * lcid [I] Locale to format for
1204 * dwFlags [I] LOCALE_ flags from "winnls.h"
1205 * lpszValue [I] String to format
1206 * lpFormat [I] Formatting overrides
1207 * lpCurrencyStr [O] Destination for formatted string
1208 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1210 * NOTES
1211 * - lpszValue can contain only '0' - '9', '-' and '.'.
1212 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1213 * be formatted according to the format details returned by GetLocaleInfoA().
1214 * - This function rounds the currency if the number of decimals exceeds the
1215 * locales number of currency decimal places.
1216 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1217 * - The Ascii version of this function fails if lcid is Unicode only.
1219 * RETURNS
1220 * Success: The number of character written to lpNumberStr, or that would
1221 * have been written, if cchOut is 0.
1222 * Failure: 0. Use GetLastError() to determine the cause.
1224 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1225 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1226 LPSTR lpCurrencyStr, int cchOut)
1228 DWORD cp = CP_ACP;
1229 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1230 CURRENCYFMTW fmt;
1231 const CURRENCYFMTW *pfmt = NULL;
1232 INT iRet;
1234 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1235 lpFormat, lpCurrencyStr, cchOut);
1237 if (NLS_IsUnicodeOnlyLcid(lcid))
1239 GetCurrencyFormatA_InvalidParameter:
1240 SetLastError(ERROR_INVALID_PARAMETER);
1241 return 0;
1244 if (!(dwFlags & LOCALE_USE_CP_ACP))
1246 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1247 if (!node)
1248 goto GetCurrencyFormatA_InvalidParameter;
1249 cp = node->dwCodePage;
1252 if (lpFormat)
1254 memcpy(&fmt, lpFormat, sizeof(fmt));
1255 pfmt = &fmt;
1256 if (lpFormat->lpDecimalSep)
1258 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1259 fmt.lpDecimalSep = szDec;
1261 if (lpFormat->lpThousandSep)
1263 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1264 fmt.lpThousandSep = szGrp;
1266 if (lpFormat->lpCurrencySymbol)
1268 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1269 fmt.lpCurrencySymbol = szCy;
1273 if (lpszValue)
1274 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1276 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1277 cchOut = sizeof(szOut)/sizeof(WCHAR);
1279 szOut[0] = '\0';
1281 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1282 lpCurrencyStr ? szOut : NULL, cchOut);
1284 if (szOut[0] && lpCurrencyStr)
1285 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1286 return iRet;
1289 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1290 #define CF_PARENS 0x1 /* Parentheses */
1291 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1292 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1293 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1294 #define CF_CY_LEFT 0x10 /* '$' to the left */
1295 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1296 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1298 /**************************************************************************
1299 * GetCurrencyFormatW (KERNEL32.@)
1301 * See GetCurrencyFormatA.
1303 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1304 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1305 LPWSTR lpCurrencyStr, int cchOut)
1307 static const BYTE NLS_NegCyFormats[16] =
1309 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1310 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1311 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1312 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1313 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1314 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1315 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1316 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1317 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1318 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1319 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1320 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1321 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1322 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1323 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1324 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1326 static const BYTE NLS_PosCyFormats[4] =
1328 CF_CY_LEFT, /* $1.1 */
1329 CF_CY_RIGHT, /* 1.1$ */
1330 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1331 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1333 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1334 WCHAR szNegBuff[8];
1335 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1336 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1337 INT iRet;
1339 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1340 lpFormat, lpCurrencyStr, cchOut);
1342 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1343 !IsValidLocale(lcid, 0) ||
1344 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1345 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1346 lpFormat->PositiveOrder > 3)))
1348 GetCurrencyFormatW_Error:
1349 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1350 return 0;
1353 if (!lpFormat)
1355 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1357 if (!node)
1358 goto GetCurrencyFormatW_Error;
1359 lpFormat = &node->cyfmt;
1360 lpszNegStart = lpszNeg = GetNegative(node);
1362 else
1364 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1365 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1366 lpszNegStart = lpszNeg = szNegBuff;
1368 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1370 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1371 lpszCyStart = lpFormat->lpCurrencySymbol;
1372 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1374 /* Format the currency backwards into a temporary buffer */
1376 szSrc = lpszValue;
1377 *szOut-- = '\0';
1379 /* Check the number for validity */
1380 while (*szSrc)
1382 if (*szSrc >= '0' && *szSrc <= '9')
1384 dwState |= NF_DIGITS;
1385 if (dwState & NF_ISREAL)
1386 dwDecimals++;
1388 else if (*szSrc == '-')
1390 if (dwState)
1391 goto GetCurrencyFormatW_Error; /* '-' not first character */
1392 dwState |= NF_ISNEGATIVE;
1394 else if (*szSrc == '.')
1396 if (dwState & NF_ISREAL)
1397 goto GetCurrencyFormatW_Error; /* More than one '.' */
1398 dwState |= NF_ISREAL;
1400 else
1401 goto GetCurrencyFormatW_Error; /* Invalid char */
1402 szSrc++;
1404 szSrc--; /* Point to last character */
1406 if (!(dwState & NF_DIGITS))
1407 goto GetCurrencyFormatW_Error; /* No digits */
1409 if (dwState & NF_ISNEGATIVE)
1410 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1411 else
1412 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1414 /* Add any trailing negative or currency signs */
1415 if (dwFmt & CF_PARENS)
1416 *szOut-- = ')';
1418 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1420 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1422 case CF_MINUS_RIGHT:
1423 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1424 while (lpszNeg >= lpszNegStart)
1425 *szOut-- = *lpszNeg--;
1426 dwFmt &= ~CF_MINUS_RIGHT;
1427 break;
1429 case CF_CY_RIGHT:
1430 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1431 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1432 while (lpszCy >= lpszCyStart)
1433 *szOut-- = *lpszCy--;
1434 if (dwFmt & CF_CY_SPACE)
1435 *szOut-- = ' ';
1436 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1437 break;
1441 /* Copy all digits up to the decimal point */
1442 if (!lpFormat->NumDigits)
1444 if (dwState & NF_ISREAL)
1446 while (*szSrc != '.') /* Don't write any decimals or a separator */
1448 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1449 dwState |= NF_ROUND;
1450 else
1451 dwState &= ~NF_ROUND;
1452 szSrc--;
1454 szSrc--;
1457 else
1459 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1461 if (dwDecimals <= lpFormat->NumDigits)
1463 dwDecimals = lpFormat->NumDigits - dwDecimals;
1464 while (dwDecimals--)
1465 *szOut-- = '0'; /* Pad to correct number of dp */
1467 else
1469 dwDecimals -= lpFormat->NumDigits;
1470 /* Skip excess decimals, and determine if we have to round the number */
1471 while (dwDecimals--)
1473 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1474 dwState |= NF_ROUND;
1475 else
1476 dwState &= ~NF_ROUND;
1477 szSrc--;
1481 if (dwState & NF_ISREAL)
1483 while (*szSrc != '.')
1485 if (dwState & NF_ROUND)
1487 if (*szSrc == '9')
1488 *szOut-- = '0'; /* continue rounding */
1489 else
1491 dwState &= ~NF_ROUND;
1492 *szOut-- = (*szSrc)+1;
1494 szSrc--;
1496 else
1497 *szOut-- = *szSrc--; /* Write existing decimals */
1499 szSrc--; /* Skip '.' */
1501 while (lpszDec >= lpFormat->lpDecimalSep)
1502 *szOut-- = *lpszDec--; /* Write decimal separator */
1505 dwGroupCount = lpFormat->Grouping;
1507 /* Write the remaining whole number digits, including grouping chars */
1508 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1510 if (dwState & NF_ROUND)
1512 if (*szSrc == '9')
1513 *szOut-- = '0'; /* continue rounding */
1514 else
1516 dwState &= ~NF_ROUND;
1517 *szOut-- = (*szSrc)+1;
1519 szSrc--;
1521 else
1522 *szOut-- = *szSrc--;
1524 dwState |= NF_DIGITS_OUT;
1525 dwCurrentGroupCount++;
1526 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1528 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1530 while (lpszGrp >= lpFormat->lpThousandSep)
1531 *szOut-- = *lpszGrp--; /* Write grouping char */
1533 dwCurrentGroupCount = 0;
1536 if (dwState & NF_ROUND)
1537 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1538 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1539 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1541 /* Add any leading negative or currency sign */
1542 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1544 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1546 case CF_MINUS_LEFT:
1547 case CF_MINUS_LEFT|CF_CY_LEFT:
1548 while (lpszNeg >= lpszNegStart)
1549 *szOut-- = *lpszNeg--;
1550 dwFmt &= ~CF_MINUS_LEFT;
1551 break;
1553 case CF_CY_LEFT:
1554 case CF_CY_LEFT|CF_MINUS_BEFORE:
1555 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1556 if (dwFmt & CF_CY_SPACE)
1557 *szOut-- = ' ';
1558 while (lpszCy >= lpszCyStart)
1559 *szOut-- = *lpszCy--;
1560 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1561 break;
1564 if (dwFmt & CF_PARENS)
1565 *szOut-- = '(';
1566 szOut++;
1568 iRet = strlenW(szOut) + 1;
1569 if (cchOut)
1571 if (iRet <= cchOut)
1572 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1573 else
1575 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1576 lpCurrencyStr[cchOut - 1] = '\0';
1577 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1578 iRet = 0;
1581 return iRet;
1584 /* FIXME: Everything below here needs to move somewhere else along with the
1585 * other EnumXXX functions, when a method for storing resources for
1586 * alternate calendars is determined.
1589 /**************************************************************************
1590 * EnumDateFormatsExA (KERNEL32.@)
1592 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1593 * LOCALE_NOUSEROVERRIDE here as well?
1595 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1597 CALID cal_id;
1598 char buf[256];
1600 if (!proc)
1602 SetLastError(ERROR_INVALID_PARAMETER);
1603 return FALSE;
1606 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1607 return FALSE;
1609 switch (flags & ~LOCALE_USE_CP_ACP)
1611 case 0:
1612 case DATE_SHORTDATE:
1613 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1614 proc(buf, cal_id);
1615 break;
1617 case DATE_LONGDATE:
1618 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1619 proc(buf, cal_id);
1620 break;
1622 case DATE_YEARMONTH:
1623 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1624 proc(buf, cal_id);
1625 break;
1627 default:
1628 FIXME("Unknown date format (%d)\n", flags);
1629 SetLastError(ERROR_INVALID_PARAMETER);
1630 return FALSE;
1632 return TRUE;
1635 /**************************************************************************
1636 * EnumDateFormatsExW (KERNEL32.@)
1638 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1640 CALID cal_id;
1641 WCHAR buf[256];
1643 if (!proc)
1645 SetLastError(ERROR_INVALID_PARAMETER);
1646 return FALSE;
1649 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1650 return FALSE;
1652 switch (flags & ~LOCALE_USE_CP_ACP)
1654 case 0:
1655 case DATE_SHORTDATE:
1656 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1657 proc(buf, cal_id);
1658 break;
1660 case DATE_LONGDATE:
1661 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1662 proc(buf, cal_id);
1663 break;
1665 case DATE_YEARMONTH:
1666 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1667 proc(buf, cal_id);
1668 break;
1670 default:
1671 FIXME("Unknown date format (%d)\n", flags);
1672 SetLastError(ERROR_INVALID_PARAMETER);
1673 return FALSE;
1675 return TRUE;
1678 /**************************************************************************
1679 * EnumDateFormatsA (KERNEL32.@)
1681 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1682 * LOCALE_NOUSEROVERRIDE here as well?
1684 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1686 char buf[256];
1688 if (!proc)
1690 SetLastError(ERROR_INVALID_PARAMETER);
1691 return FALSE;
1694 switch (flags & ~LOCALE_USE_CP_ACP)
1696 case 0:
1697 case DATE_SHORTDATE:
1698 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1699 proc(buf);
1700 break;
1702 case DATE_LONGDATE:
1703 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1704 proc(buf);
1705 break;
1707 case DATE_YEARMONTH:
1708 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1709 proc(buf);
1710 break;
1712 default:
1713 FIXME("Unknown date format (%d)\n", flags);
1714 SetLastError(ERROR_INVALID_PARAMETER);
1715 return FALSE;
1717 return TRUE;
1720 /**************************************************************************
1721 * EnumDateFormatsW (KERNEL32.@)
1723 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1725 WCHAR buf[256];
1727 if (!proc)
1729 SetLastError(ERROR_INVALID_PARAMETER);
1730 return FALSE;
1733 switch (flags & ~LOCALE_USE_CP_ACP)
1735 case 0:
1736 case DATE_SHORTDATE:
1737 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1738 proc(buf);
1739 break;
1741 case DATE_LONGDATE:
1742 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1743 proc(buf);
1744 break;
1746 case DATE_YEARMONTH:
1747 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1748 proc(buf);
1749 break;
1751 default:
1752 FIXME("Unknown date format (%d)\n", flags);
1753 SetLastError(ERROR_INVALID_PARAMETER);
1754 return FALSE;
1756 return TRUE;
1759 /**************************************************************************
1760 * EnumTimeFormatsA (KERNEL32.@)
1762 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1763 * LOCALE_NOUSEROVERRIDE here as well?
1765 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1767 char buf[256];
1769 if (!proc)
1771 SetLastError(ERROR_INVALID_PARAMETER);
1772 return FALSE;
1775 switch (flags & ~LOCALE_USE_CP_ACP)
1777 case 0:
1778 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1779 proc(buf);
1780 break;
1782 default:
1783 FIXME("Unknown time format (%d)\n", flags);
1784 SetLastError(ERROR_INVALID_PARAMETER);
1785 return FALSE;
1787 return TRUE;
1790 /**************************************************************************
1791 * EnumTimeFormatsW (KERNEL32.@)
1793 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1795 WCHAR buf[256];
1797 if (!proc)
1799 SetLastError(ERROR_INVALID_PARAMETER);
1800 return FALSE;
1803 switch (flags & ~LOCALE_USE_CP_ACP)
1805 case 0:
1806 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1807 proc(buf);
1808 break;
1810 default:
1811 FIXME("Unknown time format (%d)\n", flags);
1812 SetLastError(ERROR_INVALID_PARAMETER);
1813 return FALSE;
1815 return TRUE;
1818 /******************************************************************************
1819 * NLS_EnumCalendarInfoAW <internal>
1820 * Enumerates calendar information for a specified locale.
1822 * PARAMS
1823 * calinfoproc [I] Pointer to the callback
1824 * locale [I] The locale for which to retrieve calendar information.
1825 * This parameter can be a locale identifier created by the
1826 * MAKELCID macro, or one of the following values:
1827 * LOCALE_SYSTEM_DEFAULT
1828 * Use the default system locale.
1829 * LOCALE_USER_DEFAULT
1830 * Use the default user locale.
1831 * calendar [I] The calendar for which information is requested, or
1832 * ENUM_ALL_CALENDARS.
1833 * caltype [I] The type of calendar information to be returned. Note
1834 * that only one CALTYPE value can be specified per call
1835 * of this function, except where noted.
1836 * unicode [I] Specifies if the callback expects a unicode string.
1837 * ex [I] Specifies if the callback needs the calendar identifier.
1839 * RETURNS
1840 * Success: TRUE.
1841 * Failure: FALSE. Use GetLastError() to determine the cause.
1843 * NOTES
1844 * When the ANSI version of this function is used with a Unicode-only LCID,
1845 * the call can succeed because the system uses the system code page.
1846 * However, characters that are undefined in the system code page appear
1847 * in the string as a question mark (?).
1849 * TODO
1850 * The above note should be respected by GetCalendarInfoA.
1852 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1853 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1855 WCHAR *buf, *opt = NULL, *iter = NULL;
1856 BOOL ret = FALSE;
1857 int bufSz = 200; /* the size of the buffer */
1859 if (calinfoproc == NULL)
1861 SetLastError(ERROR_INVALID_PARAMETER);
1862 return FALSE;
1865 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1866 if (buf == NULL)
1868 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1869 return FALSE;
1872 if (calendar == ENUM_ALL_CALENDARS)
1874 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1875 if (optSz > 1)
1877 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1878 if (opt == NULL)
1880 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1881 goto NLS_EnumCalendarInfoAW_Cleanup;
1883 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1884 iter = opt;
1886 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1889 while (TRUE) /* loop through calendars */
1891 do /* loop until there's no error */
1893 if (unicode)
1894 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1895 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1897 if (!ret)
1899 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1900 { /* so resize it */
1901 int newSz;
1902 if (unicode)
1903 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1904 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1905 if (bufSz >= newSz)
1907 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1908 goto NLS_EnumCalendarInfoAW_Cleanup;
1910 bufSz = newSz;
1911 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1912 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1913 if (buf == NULL)
1914 goto NLS_EnumCalendarInfoAW_Cleanup;
1915 } else goto NLS_EnumCalendarInfoAW_Cleanup;
1917 } while (!ret);
1919 /* Here we are. We pass the buffer to the correct version of
1920 * the callback. Because it's not the same number of params,
1921 * we must check for Ex, but we don't care about Unicode
1922 * because the buffer is already in the correct format.
1924 if (ex) {
1925 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
1926 } else
1927 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
1929 if (!ret) { /* the callback told to stop */
1930 ret = TRUE;
1931 break;
1934 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
1935 break;
1937 calendar = 0;
1938 while ((*iter >= '0') && (*iter <= '9'))
1939 calendar = calendar * 10 + *iter++ - '0';
1941 if (*iter++ != 0)
1943 SetLastError(ERROR_BADDB);
1944 ret = FALSE;
1945 break;
1949 NLS_EnumCalendarInfoAW_Cleanup:
1950 HeapFree(GetProcessHeap(), 0, opt);
1951 HeapFree(GetProcessHeap(), 0, buf);
1952 return ret;
1955 /******************************************************************************
1956 * EnumCalendarInfoA [KERNEL32.@]
1958 * See EnumCalendarInfoAW.
1960 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
1961 CALID calendar,CALTYPE caltype )
1963 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1964 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
1967 /******************************************************************************
1968 * EnumCalendarInfoW [KERNEL32.@]
1970 * See EnumCalendarInfoAW.
1972 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
1973 CALID calendar,CALTYPE caltype )
1975 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1976 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
1979 /******************************************************************************
1980 * EnumCalendarInfoExA [KERNEL32.@]
1982 * See EnumCalendarInfoAW.
1984 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
1985 CALID calendar,CALTYPE caltype )
1987 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
1988 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
1991 /******************************************************************************
1992 * EnumCalendarInfoExW [KERNEL32.@]
1994 * See EnumCalendarInfoAW.
1996 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
1997 CALID calendar,CALTYPE caltype )
1999 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2000 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);