wineconsole: Use the explicit W types/functions in dialog.c.
[wine.git] / dlls / kernel32 / lcformat.c
blobb6432f801590187dd19db69ee27aa70bb16e67d7
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 57
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[54]
76 #define GetPM(fmt) fmt->lppszStrings[55]
77 #define GetYearMonth(fmt) fmt->lppszStrings[56]
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 GetGenitiveMonth(fmt,mth) fmt->lppszStrings[30 + mth]
83 #define GetShortMonth(fmt,mth) fmt->lppszStrings[42 + mth]
85 /* Write access to the cache is protected by this critical section */
86 static CRITICAL_SECTION NLS_FormatsCS;
87 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
89 0, 0, &NLS_FormatsCS,
90 { &NLS_FormatsCS_debug.ProcessLocksList,
91 &NLS_FormatsCS_debug.ProcessLocksList },
92 0, 0, { (DWORD_PTR)(__FILE__ ": NLS_Formats") }
94 static CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
96 /**************************************************************************
97 * NLS_GetLocaleNumber <internal>
99 * Get a numeric locale format value.
101 static DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
103 WCHAR szBuff[80];
104 DWORD dwVal = 0;
106 szBuff[0] = '\0';
107 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
109 if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '0')
110 dwVal = (szBuff[0] - '0') * 10 + (szBuff[2] - '0');
111 else
113 const WCHAR* iter = szBuff;
114 dwVal = 0;
115 while(*iter >= '0' && *iter <= '9')
116 dwVal = dwVal * 10 + (*iter++ - '0');
118 return dwVal;
121 /**************************************************************************
122 * NLS_GetLocaleString <internal>
124 * Get a string locale format value.
126 static WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
128 WCHAR szBuff[80], *str;
129 DWORD dwLen;
131 szBuff[0] = '\0';
132 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
133 dwLen = strlenW(szBuff) + 1;
134 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
135 if (str)
136 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
137 return str;
140 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
141 TRACE( #type ": %d (%08x)\n", (DWORD)num, (DWORD)num)
143 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
144 TRACE( #type ": %s\n", debugstr_w(str))
146 /**************************************************************************
147 * NLS_GetFormats <internal>
149 * Calculate (and cache) the number formats for a locale.
151 static const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
153 /* GetLocaleInfo() identifiers for cached formatting strings */
154 static const LCTYPE NLS_LocaleIndices[] = {
155 LOCALE_SNEGATIVESIGN,
156 LOCALE_SLONGDATE, LOCALE_SSHORTDATE,
157 LOCALE_STIMEFORMAT,
158 LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
159 LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
160 LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
161 LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
162 LOCALE_SABBREVDAYNAME7,
163 LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
164 LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
165 LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
166 LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
167 LOCALE_SMONTHNAME1 | LOCALE_RETURN_GENITIVE_NAMES,
168 LOCALE_SMONTHNAME2 | LOCALE_RETURN_GENITIVE_NAMES,
169 LOCALE_SMONTHNAME3 | LOCALE_RETURN_GENITIVE_NAMES,
170 LOCALE_SMONTHNAME4 | LOCALE_RETURN_GENITIVE_NAMES,
171 LOCALE_SMONTHNAME5 | LOCALE_RETURN_GENITIVE_NAMES,
172 LOCALE_SMONTHNAME6 | LOCALE_RETURN_GENITIVE_NAMES,
173 LOCALE_SMONTHNAME7 | LOCALE_RETURN_GENITIVE_NAMES,
174 LOCALE_SMONTHNAME8 | LOCALE_RETURN_GENITIVE_NAMES,
175 LOCALE_SMONTHNAME9 | LOCALE_RETURN_GENITIVE_NAMES,
176 LOCALE_SMONTHNAME10 | LOCALE_RETURN_GENITIVE_NAMES,
177 LOCALE_SMONTHNAME11 | LOCALE_RETURN_GENITIVE_NAMES,
178 LOCALE_SMONTHNAME12 | LOCALE_RETURN_GENITIVE_NAMES,
179 LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
180 LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
181 LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
182 LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
183 LOCALE_S1159, LOCALE_S2359,
184 LOCALE_SYEARMONTH
186 static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
187 NLS_FORMAT_NODE *node = NLS_CachedFormats;
189 dwFlags &= LOCALE_NOUSEROVERRIDE;
191 TRACE("(0x%04x,0x%08x)\n", lcid, dwFlags);
193 /* See if we have already cached the locales number format */
194 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
195 node = node->next;
197 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
199 NLS_FORMAT_NODE *new_node;
200 DWORD i;
202 TRACE("Creating new cache entry\n");
204 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
205 return NULL;
207 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
209 /* Number Format */
210 new_node->lcid = lcid;
211 new_node->dwFlags = dwFlags;
212 new_node->next = NULL;
214 GET_LOCALE_NUMBER(new_node->fmt.NumDigits, LOCALE_IDIGITS);
215 GET_LOCALE_NUMBER(new_node->fmt.LeadingZero, LOCALE_ILZERO);
216 GET_LOCALE_NUMBER(new_node->fmt.NegativeOrder, LOCALE_INEGNUMBER);
218 GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
219 if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
221 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
222 new_node->fmt.Grouping);
223 new_node->fmt.Grouping = 0;
226 GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
227 GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
229 /* Currency Format */
230 new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
231 new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
233 GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
235 if (new_node->cyfmt.Grouping > 9)
237 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
238 new_node->cyfmt.Grouping);
239 new_node->cyfmt.Grouping = 0;
242 GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
243 if (new_node->cyfmt.NegativeOrder > 15)
245 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
246 new_node->cyfmt.NegativeOrder);
247 new_node->cyfmt.NegativeOrder = 0;
249 GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
250 if (new_node->cyfmt.PositiveOrder > 3)
252 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
253 new_node->cyfmt.PositiveOrder);
254 new_node->cyfmt.PositiveOrder = 0;
256 GET_LOCALE_STRING(new_node->cyfmt.lpDecimalSep, LOCALE_SMONDECIMALSEP);
257 GET_LOCALE_STRING(new_node->cyfmt.lpThousandSep, LOCALE_SMONTHOUSANDSEP);
258 GET_LOCALE_STRING(new_node->cyfmt.lpCurrencySymbol, LOCALE_SCURRENCY);
260 /* Date/Time Format info, negative character, etc */
261 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
263 GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
265 /* Save some memory if month genitive name is the same or not present */
266 for (i = 0; i < 12; i++)
268 if (strcmpW(GetLongMonth(new_node, i), GetGenitiveMonth(new_node, i)) == 0)
270 HeapFree(GetProcessHeap(), 0, GetGenitiveMonth(new_node, i));
271 GetGenitiveMonth(new_node, i) = NULL;
275 new_node->szShortAM[0] = GetAM(new_node)[0]; new_node->szShortAM[1] = '\0';
276 new_node->szShortPM[0] = GetPM(new_node)[0]; new_node->szShortPM[1] = '\0';
278 /* Now add the computed format to the cache */
279 RtlEnterCriticalSection(&NLS_FormatsCS);
281 /* Search again: We may have raced to add the node */
282 node = NLS_CachedFormats;
283 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
284 node = node->next;
286 if (!node)
288 node = NLS_CachedFormats = new_node; /* Empty list */
289 new_node = NULL;
291 else if (node->lcid != lcid || node->dwFlags != dwFlags)
293 node->next = new_node; /* Not in the list, add to end */
294 node = new_node;
295 new_node = NULL;
298 RtlLeaveCriticalSection(&NLS_FormatsCS);
300 if (new_node)
302 /* We raced and lost: The node was already added by another thread.
303 * node points to the currently cached node, so free new_node.
305 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
306 HeapFree(GetProcessHeap(), 0, new_node->lppszStrings[i]);
307 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpDecimalSep);
308 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpThousandSep);
309 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpDecimalSep);
310 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpThousandSep);
311 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpCurrencySymbol);
312 HeapFree(GetProcessHeap(), 0, new_node);
315 return node;
318 /**************************************************************************
319 * NLS_IsUnicodeOnlyLcid <internal>
321 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
323 BOOL NLS_IsUnicodeOnlyLcid(LCID lcid)
325 lcid = ConvertDefaultLocale(lcid);
327 switch (PRIMARYLANGID(lcid))
329 case LANG_ARMENIAN:
330 case LANG_DIVEHI:
331 case LANG_GEORGIAN:
332 case LANG_GUJARATI:
333 case LANG_HINDI:
334 case LANG_KANNADA:
335 case LANG_KONKANI:
336 case LANG_MARATHI:
337 case LANG_PUNJABI:
338 case LANG_SANSKRIT:
339 TRACE("lcid 0x%08x: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
340 return TRUE;
341 default:
342 return FALSE;
347 * Formatting of dates, times, numbers and currencies.
350 #define IsLiteralMarker(p) (p == '\'')
351 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
352 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
354 /* Only the following flags can be given if a date/time format is specified */
355 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY)
356 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
357 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
358 TIME_NOTIMEMARKER)
360 /******************************************************************************
361 * NLS_GetDateTimeFormatW <internal>
363 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
365 * FIXME
366 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
367 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
369 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
370 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
371 LPWSTR lpStr, INT cchOut)
373 const NLS_FORMAT_NODE *node;
374 SYSTEMTIME st;
375 INT cchWritten = 0;
376 INT lastFormatPos = 0;
377 BOOL bSkipping = FALSE; /* Skipping text around marker? */
378 BOOL d_dd_formatted = FALSE; /* previous formatted part was for d or dd */
380 /* Verify our arguments */
381 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
382 goto invalid_parameter;
384 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
386 if (lpFormat &&
387 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
388 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
390 goto invalid_flags;
393 if (dwFlags & DATE_DATEVARSONLY)
395 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
396 goto invalid_flags;
397 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
398 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
400 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
402 case 0:
403 break;
404 case DATE_SHORTDATE:
405 case DATE_LONGDATE:
406 case DATE_YEARMONTH:
407 if (lpFormat)
408 goto invalid_flags;
409 break;
410 default:
411 goto invalid_flags;
416 if (!lpFormat)
418 /* Use the appropriate default format */
419 if (dwFlags & DATE_DATEVARSONLY)
421 if (dwFlags & DATE_YEARMONTH)
422 lpFormat = GetYearMonth(node);
423 else if (dwFlags & DATE_LONGDATE)
424 lpFormat = GetLongDate(node);
425 else
426 lpFormat = GetShortDate(node);
428 else
429 lpFormat = GetTime(node);
432 if (!lpTime)
434 GetLocalTime(&st); /* Default to current time */
435 lpTime = &st;
437 else
439 if (dwFlags & DATE_DATEVARSONLY)
441 FILETIME ftTmp;
443 /* Verify the date and correct the D.O.W. if needed */
444 memset(&st, 0, sizeof(st));
445 st.wYear = lpTime->wYear;
446 st.wMonth = lpTime->wMonth;
447 st.wDay = lpTime->wDay;
449 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
450 goto invalid_parameter;
452 FileTimeToSystemTime(&ftTmp, &st);
453 lpTime = &st;
456 if (dwFlags & TIME_TIMEVARSONLY)
458 /* Verify the time */
459 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
460 goto invalid_parameter;
464 /* Format the output */
465 while (*lpFormat)
467 if (IsLiteralMarker(*lpFormat))
469 /* Start of a literal string */
470 lpFormat++;
472 /* Loop until the end of the literal marker or end of the string */
473 while (*lpFormat)
475 if (IsLiteralMarker(*lpFormat))
477 lpFormat++;
478 if (!IsLiteralMarker(*lpFormat))
479 break; /* Terminating literal marker */
482 if (!cchOut)
483 cchWritten++; /* Count size only */
484 else if (cchWritten >= cchOut)
485 goto overrun;
486 else if (!bSkipping)
488 lpStr[cchWritten] = *lpFormat;
489 cchWritten++;
491 lpFormat++;
494 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
495 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
497 WCHAR buff[32], fmtChar;
498 LPCWSTR szAdd = NULL;
499 DWORD dwVal = 0;
500 int count = 0, dwLen;
502 bSkipping = FALSE;
504 fmtChar = *lpFormat;
505 while (*lpFormat == fmtChar)
507 count++;
508 lpFormat++;
510 buff[0] = '\0';
512 if (fmtChar != 'M') d_dd_formatted = FALSE;
513 switch(fmtChar)
515 case 'd':
516 if (count >= 4)
517 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
518 else if (count == 3)
519 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
520 else
522 dwVal = lpTime->wDay;
523 szAdd = buff;
524 d_dd_formatted = TRUE;
526 break;
528 case 'M':
529 if (count >= 4)
531 LPCWSTR genitive = GetGenitiveMonth(node, lpTime->wMonth - 1);
532 if (genitive)
534 if (d_dd_formatted)
536 szAdd = genitive;
537 break;
539 else
541 LPCWSTR format = lpFormat;
542 /* Look forward now, if next format pattern is for day genitive
543 name should be used */
544 while (*format)
546 /* Skip parts within markers */
547 if (IsLiteralMarker(*format))
549 ++format;
550 while (*format)
552 if (IsLiteralMarker(*format))
554 ++format;
555 if (!IsLiteralMarker(*format)) break;
559 if (*format != ' ') break;
560 ++format;
562 /* Only numeric day form matters */
563 if (*format && *format == 'd')
565 INT dcount = 1;
566 while (*++format == 'd') dcount++;
567 if (dcount < 3)
569 szAdd = genitive;
570 break;
575 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
577 else if (count == 3)
578 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
579 else
581 dwVal = lpTime->wMonth;
582 szAdd = buff;
584 break;
586 case 'y':
587 if (count >= 4)
589 count = 4;
590 dwVal = lpTime->wYear;
592 else
594 count = count > 2 ? 2 : count;
595 dwVal = lpTime->wYear % 100;
597 szAdd = buff;
598 break;
600 case 'g':
601 if (count == 2)
603 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
604 * When it is fixed, this string should be cached in 'node'.
606 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
607 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
609 else
611 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
613 szAdd = buff;
614 break;
616 case 'h':
617 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
619 count = count > 2 ? 2 : count;
620 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
621 szAdd = buff;
622 break;
624 /* .. fall through if we are forced to output in 24 hour format */
626 case 'H':
627 count = count > 2 ? 2 : count;
628 dwVal = lpTime->wHour;
629 szAdd = buff;
630 break;
632 case 'm':
633 if (dwFlags & TIME_NOMINUTESORSECONDS)
635 cchWritten = lastFormatPos; /* Skip */
636 bSkipping = TRUE;
638 else
640 count = count > 2 ? 2 : count;
641 dwVal = lpTime->wMinute;
642 szAdd = buff;
644 break;
646 case 's':
647 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
649 cchWritten = lastFormatPos; /* Skip */
650 bSkipping = TRUE;
652 else
654 count = count > 2 ? 2 : count;
655 dwVal = lpTime->wSecond;
656 szAdd = buff;
658 break;
660 case 't':
661 if (dwFlags & TIME_NOTIMEMARKER)
663 cchWritten = lastFormatPos; /* Skip */
664 bSkipping = TRUE;
666 else
668 if (count == 1)
669 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
670 else
671 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
673 break;
676 if (szAdd == buff && buff[0] == '\0')
678 static const WCHAR fmtW[] = {'%','.','*','d',0};
679 /* We have a numeric value to add */
680 snprintfW(buff, sizeof(buff)/sizeof(WCHAR), fmtW, count, dwVal);
683 dwLen = szAdd ? strlenW(szAdd) : 0;
685 if (cchOut && dwLen)
687 if (cchWritten + dwLen < cchOut)
688 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
689 else
691 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
692 goto overrun;
695 cchWritten += dwLen;
696 lastFormatPos = cchWritten; /* Save position of last output format text */
698 else
700 /* Literal character */
701 if (!cchOut)
702 cchWritten++; /* Count size only */
703 else if (cchWritten >= cchOut)
704 goto overrun;
705 else if (!bSkipping || *lpFormat == ' ')
707 lpStr[cchWritten] = *lpFormat;
708 cchWritten++;
710 lpFormat++;
714 /* Final string terminator and sanity check */
715 if (cchOut)
717 if (cchWritten >= cchOut)
718 goto overrun;
719 else
720 lpStr[cchWritten] = '\0';
722 cchWritten++; /* Include terminating NUL */
724 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
725 return cchWritten;
727 overrun:
728 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
729 SetLastError(ERROR_INSUFFICIENT_BUFFER);
730 return 0;
732 invalid_parameter:
733 SetLastError(ERROR_INVALID_PARAMETER);
734 return 0;
736 invalid_flags:
737 SetLastError(ERROR_INVALID_FLAGS);
738 return 0;
741 /******************************************************************************
742 * NLS_GetDateTimeFormatA <internal>
744 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
746 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
747 const SYSTEMTIME* lpTime,
748 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
750 DWORD cp = CP_ACP;
751 WCHAR szFormat[128], szOut[128];
752 INT iRet;
754 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
755 debugstr_a(lpFormat), lpStr, cchOut);
757 if (NLS_IsUnicodeOnlyLcid(lcid))
759 SetLastError(ERROR_INVALID_PARAMETER);
760 return 0;
763 if (!(dwFlags & LOCALE_USE_CP_ACP))
765 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
766 if (!node)
768 SetLastError(ERROR_INVALID_PARAMETER);
769 return 0;
772 cp = node->dwCodePage;
775 if (lpFormat)
776 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
778 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
779 cchOut = sizeof(szOut)/sizeof(WCHAR);
781 szOut[0] = '\0';
783 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
784 lpStr ? szOut : NULL, cchOut);
786 if (lpStr)
788 if (szOut[0])
789 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
790 else if (cchOut && iRet)
791 *lpStr = '\0';
793 return iRet;
796 /******************************************************************************
797 * GetDateFormatA [KERNEL32.@]
799 * Format a date for a given locale.
801 * PARAMS
802 * lcid [I] Locale to format for
803 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
804 * lpTime [I] Date to format
805 * lpFormat [I] Format string, or NULL to use the system defaults
806 * lpDateStr [O] Destination for formatted string
807 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
809 * NOTES
810 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
811 * details returned by GetLocaleInfoA() and modified by dwFlags.
812 * - lpFormat is a string of characters and formatting tokens. Any characters
813 * in the string are copied verbatim to lpDateStr, with tokens being replaced
814 * by the date values they represent.
815 * - The following tokens have special meanings in a date format string:
816 *| Token Meaning
817 *| ----- -------
818 *| d Single digit day of the month (no leading 0)
819 *| dd Double digit day of the month
820 *| ddd Short name for the day of the week
821 *| dddd Long name for the day of the week
822 *| M Single digit month of the year (no leading 0)
823 *| MM Double digit month of the year
824 *| MMM Short name for the month of the year
825 *| MMMM Long name for the month of the year
826 *| y Double digit year number (no leading 0)
827 *| yy Double digit year number
828 *| yyyy Four digit year number
829 *| gg Era string, for example 'AD'.
830 * - To output any literal character that could be misidentified as a token,
831 * enclose it in single quotes.
832 * - The Ascii version of this function fails if lcid is Unicode only.
834 * RETURNS
835 * Success: The number of character written to lpDateStr, or that would
836 * have been written, if cchOut is 0.
837 * Failure: 0. Use GetLastError() to determine the cause.
839 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
840 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
842 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
843 debugstr_a(lpFormat), lpDateStr, cchOut);
845 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
846 lpFormat, lpDateStr, cchOut);
850 /******************************************************************************
851 * GetDateFormatW [KERNEL32.@]
853 * See GetDateFormatA.
855 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
856 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
858 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
859 debugstr_w(lpFormat), lpDateStr, cchOut);
861 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
862 lpFormat, lpDateStr, cchOut);
865 /******************************************************************************
866 * GetTimeFormatA [KERNEL32.@]
868 * Format a time for a given locale.
870 * PARAMS
871 * lcid [I] Locale to format for
872 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
873 * lpTime [I] Time to format
874 * lpFormat [I] Formatting overrides
875 * lpTimeStr [O] Destination for formatted string
876 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
878 * NOTES
879 * - If lpFormat is NULL, lpszValue will be formatted according to the format
880 * details returned by GetLocaleInfoA() and modified by dwFlags.
881 * - lpFormat is a string of characters and formatting tokens. Any characters
882 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
883 * by the time values they represent.
884 * - The following tokens have special meanings in a time format string:
885 *| Token Meaning
886 *| ----- -------
887 *| h Hours with no leading zero (12-hour clock)
888 *| hh Hours with full two digits (12-hour clock)
889 *| H Hours with no leading zero (24-hour clock)
890 *| HH Hours with full two digits (24-hour clock)
891 *| m Minutes with no leading zero
892 *| mm Minutes with full two digits
893 *| s Seconds with no leading zero
894 *| ss Seconds with full two digits
895 *| t Short time marker (e.g. "A" or "P")
896 *| tt Long time marker (e.g. "AM", "PM")
897 * - To output any literal character that could be misidentified as a token,
898 * enclose it in single quotes.
899 * - The Ascii version of this function fails if lcid is Unicode only.
901 * RETURNS
902 * Success: The number of character written to lpTimeStr, or that would
903 * have been written, if cchOut is 0.
904 * Failure: 0. Use GetLastError() to determine the cause.
906 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
907 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
909 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
910 debugstr_a(lpFormat), lpTimeStr, cchOut);
912 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
913 lpFormat, lpTimeStr, cchOut);
916 /******************************************************************************
917 * GetTimeFormatW [KERNEL32.@]
919 * See GetTimeFormatA.
921 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
922 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
924 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
925 debugstr_w(lpFormat), lpTimeStr, cchOut);
927 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
928 lpFormat, lpTimeStr, cchOut);
931 /**************************************************************************
932 * GetNumberFormatA (KERNEL32.@)
934 * Format a number string for a given locale.
936 * PARAMS
937 * lcid [I] Locale to format for
938 * dwFlags [I] LOCALE_ flags from "winnls.h"
939 * lpszValue [I] String to format
940 * lpFormat [I] Formatting overrides
941 * lpNumberStr [O] Destination for formatted string
942 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
944 * NOTES
945 * - lpszValue can contain only '0' - '9', '-' and '.'.
946 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
947 * be formatted according to the format details returned by GetLocaleInfoA().
948 * - This function rounds the number string if the number of decimals exceeds the
949 * locales normal number of decimal places.
950 * - If cchOut is 0, this function does not write to lpNumberStr.
951 * - The Ascii version of this function fails if lcid is Unicode only.
953 * RETURNS
954 * Success: The number of character written to lpNumberStr, or that would
955 * have been written, if cchOut is 0.
956 * Failure: 0. Use GetLastError() to determine the cause.
958 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
959 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
960 LPSTR lpNumberStr, int cchOut)
962 DWORD cp = CP_ACP;
963 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
964 NUMBERFMTW fmt;
965 const NUMBERFMTW *pfmt = NULL;
966 INT iRet;
968 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
969 lpFormat, lpNumberStr, cchOut);
971 if (NLS_IsUnicodeOnlyLcid(lcid))
973 SetLastError(ERROR_INVALID_PARAMETER);
974 return 0;
977 if (!(dwFlags & LOCALE_USE_CP_ACP))
979 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
980 if (!node)
982 SetLastError(ERROR_INVALID_PARAMETER);
983 return 0;
986 cp = node->dwCodePage;
989 if (lpFormat)
991 memcpy(&fmt, lpFormat, sizeof(fmt));
992 pfmt = &fmt;
993 if (lpFormat->lpDecimalSep)
995 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
996 fmt.lpDecimalSep = szDec;
998 if (lpFormat->lpThousandSep)
1000 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1001 fmt.lpThousandSep = szGrp;
1005 if (lpszValue)
1006 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1008 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1009 cchOut = sizeof(szOut)/sizeof(WCHAR);
1011 szOut[0] = '\0';
1013 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1014 lpNumberStr ? szOut : NULL, cchOut);
1016 if (szOut[0] && lpNumberStr)
1017 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
1018 return iRet;
1021 /* Number parsing state flags */
1022 #define NF_ISNEGATIVE 0x1 /* '-' found */
1023 #define NF_ISREAL 0x2 /* '.' found */
1024 #define NF_DIGITS 0x4 /* '0'-'9' found */
1025 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1026 #define NF_ROUND 0x10 /* Number needs to be rounded */
1028 /* Formatting options for Numbers */
1029 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1030 #define NLS_NEG_LEFT 1 /* "-1.1" */
1031 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1032 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1033 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1035 /**************************************************************************
1036 * GetNumberFormatW (KERNEL32.@)
1038 * See GetNumberFormatA.
1040 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
1041 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
1042 LPWSTR lpNumberStr, int cchOut)
1044 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1045 WCHAR szNegBuff[8];
1046 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1047 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1048 INT iRet;
1050 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1051 lpFormat, lpNumberStr, cchOut);
1053 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
1054 !IsValidLocale(lcid, 0) ||
1055 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
1057 goto error;
1060 if (!lpFormat)
1062 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1064 if (!node)
1065 goto error;
1066 lpFormat = &node->fmt;
1067 lpszNegStart = lpszNeg = GetNegative(node);
1069 else
1071 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1072 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1073 lpszNegStart = lpszNeg = szNegBuff;
1075 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1077 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1079 /* Format the number backwards into a temporary buffer */
1081 szSrc = lpszValue;
1082 *szOut-- = '\0';
1084 /* Check the number for validity */
1085 while (*szSrc)
1087 if (*szSrc >= '0' && *szSrc <= '9')
1089 dwState |= NF_DIGITS;
1090 if (dwState & NF_ISREAL)
1091 dwDecimals++;
1093 else if (*szSrc == '-')
1095 if (dwState)
1096 goto error; /* '-' not first character */
1097 dwState |= NF_ISNEGATIVE;
1099 else if (*szSrc == '.')
1101 if (dwState & NF_ISREAL)
1102 goto error; /* More than one '.' */
1103 dwState |= NF_ISREAL;
1105 else
1106 goto error; /* Invalid char */
1107 szSrc++;
1109 szSrc--; /* Point to last character */
1111 if (!(dwState & NF_DIGITS))
1112 goto error; /* No digits */
1114 /* Add any trailing negative sign */
1115 if (dwState & NF_ISNEGATIVE)
1117 switch (lpFormat->NegativeOrder)
1119 case NLS_NEG_PARENS:
1120 *szOut-- = ')';
1121 break;
1122 case NLS_NEG_RIGHT:
1123 case NLS_NEG_RIGHT_SPACE:
1124 while (lpszNeg >= lpszNegStart)
1125 *szOut-- = *lpszNeg--;
1126 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1127 *szOut-- = ' ';
1128 break;
1132 /* Copy all digits up to the decimal point */
1133 if (!lpFormat->NumDigits)
1135 if (dwState & NF_ISREAL)
1137 while (*szSrc != '.') /* Don't write any decimals or a separator */
1139 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1140 dwState |= NF_ROUND;
1141 else
1142 dwState &= ~NF_ROUND;
1143 szSrc--;
1145 szSrc--;
1148 else
1150 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1152 if (dwDecimals <= lpFormat->NumDigits)
1154 dwDecimals = lpFormat->NumDigits - dwDecimals;
1155 while (dwDecimals--)
1156 *szOut-- = '0'; /* Pad to correct number of dp */
1158 else
1160 dwDecimals -= lpFormat->NumDigits;
1161 /* Skip excess decimals, and determine if we have to round the number */
1162 while (dwDecimals--)
1164 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1165 dwState |= NF_ROUND;
1166 else
1167 dwState &= ~NF_ROUND;
1168 szSrc--;
1172 if (dwState & NF_ISREAL)
1174 while (*szSrc != '.')
1176 if (dwState & NF_ROUND)
1178 if (*szSrc == '9')
1179 *szOut-- = '0'; /* continue rounding */
1180 else
1182 dwState &= ~NF_ROUND;
1183 *szOut-- = (*szSrc)+1;
1185 szSrc--;
1187 else
1188 *szOut-- = *szSrc--; /* Write existing decimals */
1190 szSrc--; /* Skip '.' */
1193 while (lpszDec >= lpFormat->lpDecimalSep)
1194 *szOut-- = *lpszDec--; /* Write decimal separator */
1197 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1199 /* Write the remaining whole number digits, including grouping chars */
1200 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1202 if (dwState & NF_ROUND)
1204 if (*szSrc == '9')
1205 *szOut-- = '0'; /* continue rounding */
1206 else
1208 dwState &= ~NF_ROUND;
1209 *szOut-- = (*szSrc)+1;
1211 szSrc--;
1213 else
1214 *szOut-- = *szSrc--;
1216 dwState |= NF_DIGITS_OUT;
1217 dwCurrentGroupCount++;
1218 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1220 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1222 while (lpszGrp >= lpFormat->lpThousandSep)
1223 *szOut-- = *lpszGrp--; /* Write grouping char */
1225 dwCurrentGroupCount = 0;
1226 if (lpFormat->Grouping == 32)
1227 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1230 if (dwState & NF_ROUND)
1232 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1234 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1235 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1237 /* Add any leading negative sign */
1238 if (dwState & NF_ISNEGATIVE)
1240 switch (lpFormat->NegativeOrder)
1242 case NLS_NEG_PARENS:
1243 *szOut-- = '(';
1244 break;
1245 case NLS_NEG_LEFT_SPACE:
1246 *szOut-- = ' ';
1247 /* Fall through */
1248 case NLS_NEG_LEFT:
1249 while (lpszNeg >= lpszNegStart)
1250 *szOut-- = *lpszNeg--;
1251 break;
1254 szOut++;
1256 iRet = strlenW(szOut) + 1;
1257 if (cchOut)
1259 if (iRet <= cchOut)
1260 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1261 else
1263 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1264 lpNumberStr[cchOut - 1] = '\0';
1265 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1266 iRet = 0;
1269 return iRet;
1271 error:
1272 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1273 return 0;
1276 /**************************************************************************
1277 * GetCurrencyFormatA (KERNEL32.@)
1279 * Format a currency string for a given locale.
1281 * PARAMS
1282 * lcid [I] Locale to format for
1283 * dwFlags [I] LOCALE_ flags from "winnls.h"
1284 * lpszValue [I] String to format
1285 * lpFormat [I] Formatting overrides
1286 * lpCurrencyStr [O] Destination for formatted string
1287 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1289 * NOTES
1290 * - lpszValue can contain only '0' - '9', '-' and '.'.
1291 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1292 * be formatted according to the format details returned by GetLocaleInfoA().
1293 * - This function rounds the currency if the number of decimals exceeds the
1294 * locales number of currency decimal places.
1295 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1296 * - The Ascii version of this function fails if lcid is Unicode only.
1298 * RETURNS
1299 * Success: The number of character written to lpNumberStr, or that would
1300 * have been written, if cchOut is 0.
1301 * Failure: 0. Use GetLastError() to determine the cause.
1303 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1304 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1305 LPSTR lpCurrencyStr, int cchOut)
1307 DWORD cp = CP_ACP;
1308 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1309 CURRENCYFMTW fmt;
1310 const CURRENCYFMTW *pfmt = NULL;
1311 INT iRet;
1313 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1314 lpFormat, lpCurrencyStr, cchOut);
1316 if (NLS_IsUnicodeOnlyLcid(lcid))
1318 SetLastError(ERROR_INVALID_PARAMETER);
1319 return 0;
1322 if (!(dwFlags & LOCALE_USE_CP_ACP))
1324 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1325 if (!node)
1327 SetLastError(ERROR_INVALID_PARAMETER);
1328 return 0;
1331 cp = node->dwCodePage;
1334 if (lpFormat)
1336 memcpy(&fmt, lpFormat, sizeof(fmt));
1337 pfmt = &fmt;
1338 if (lpFormat->lpDecimalSep)
1340 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1341 fmt.lpDecimalSep = szDec;
1343 if (lpFormat->lpThousandSep)
1345 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1346 fmt.lpThousandSep = szGrp;
1348 if (lpFormat->lpCurrencySymbol)
1350 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1351 fmt.lpCurrencySymbol = szCy;
1355 if (lpszValue)
1356 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1358 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1359 cchOut = sizeof(szOut)/sizeof(WCHAR);
1361 szOut[0] = '\0';
1363 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1364 lpCurrencyStr ? szOut : NULL, cchOut);
1366 if (szOut[0] && lpCurrencyStr)
1367 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1368 return iRet;
1371 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1372 #define CF_PARENS 0x1 /* Parentheses */
1373 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1374 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1375 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1376 #define CF_CY_LEFT 0x10 /* '$' to the left */
1377 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1378 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1380 /**************************************************************************
1381 * GetCurrencyFormatW (KERNEL32.@)
1383 * See GetCurrencyFormatA.
1385 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1386 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1387 LPWSTR lpCurrencyStr, int cchOut)
1389 static const BYTE NLS_NegCyFormats[16] =
1391 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1392 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1393 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1394 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1395 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1396 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1397 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1398 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1399 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1400 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1401 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1402 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1403 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1404 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1405 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1406 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1408 static const BYTE NLS_PosCyFormats[4] =
1410 CF_CY_LEFT, /* $1.1 */
1411 CF_CY_RIGHT, /* 1.1$ */
1412 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1413 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1415 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1416 WCHAR szNegBuff[8];
1417 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1418 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1419 INT iRet;
1421 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1422 lpFormat, lpCurrencyStr, cchOut);
1424 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1425 !IsValidLocale(lcid, 0) ||
1426 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1427 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1428 lpFormat->PositiveOrder > 3)))
1430 goto error;
1433 if (!lpFormat)
1435 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1437 if (!node)
1438 goto error;
1440 lpFormat = &node->cyfmt;
1441 lpszNegStart = lpszNeg = GetNegative(node);
1443 else
1445 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1446 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1447 lpszNegStart = lpszNeg = szNegBuff;
1449 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1451 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1452 lpszCyStart = lpFormat->lpCurrencySymbol;
1453 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1455 /* Format the currency backwards into a temporary buffer */
1457 szSrc = lpszValue;
1458 *szOut-- = '\0';
1460 /* Check the number for validity */
1461 while (*szSrc)
1463 if (*szSrc >= '0' && *szSrc <= '9')
1465 dwState |= NF_DIGITS;
1466 if (dwState & NF_ISREAL)
1467 dwDecimals++;
1469 else if (*szSrc == '-')
1471 if (dwState)
1472 goto error; /* '-' not first character */
1473 dwState |= NF_ISNEGATIVE;
1475 else if (*szSrc == '.')
1477 if (dwState & NF_ISREAL)
1478 goto error; /* More than one '.' */
1479 dwState |= NF_ISREAL;
1481 else
1482 goto error; /* Invalid char */
1483 szSrc++;
1485 szSrc--; /* Point to last character */
1487 if (!(dwState & NF_DIGITS))
1488 goto error; /* No digits */
1490 if (dwState & NF_ISNEGATIVE)
1491 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1492 else
1493 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1495 /* Add any trailing negative or currency signs */
1496 if (dwFmt & CF_PARENS)
1497 *szOut-- = ')';
1499 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1501 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1503 case CF_MINUS_RIGHT:
1504 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1505 while (lpszNeg >= lpszNegStart)
1506 *szOut-- = *lpszNeg--;
1507 dwFmt &= ~CF_MINUS_RIGHT;
1508 break;
1510 case CF_CY_RIGHT:
1511 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1512 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1513 while (lpszCy >= lpszCyStart)
1514 *szOut-- = *lpszCy--;
1515 if (dwFmt & CF_CY_SPACE)
1516 *szOut-- = ' ';
1517 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1518 break;
1522 /* Copy all digits up to the decimal point */
1523 if (!lpFormat->NumDigits)
1525 if (dwState & NF_ISREAL)
1527 while (*szSrc != '.') /* Don't write any decimals or a separator */
1529 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1530 dwState |= NF_ROUND;
1531 else
1532 dwState &= ~NF_ROUND;
1533 szSrc--;
1535 szSrc--;
1538 else
1540 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1542 if (dwDecimals <= lpFormat->NumDigits)
1544 dwDecimals = lpFormat->NumDigits - dwDecimals;
1545 while (dwDecimals--)
1546 *szOut-- = '0'; /* Pad to correct number of dp */
1548 else
1550 dwDecimals -= lpFormat->NumDigits;
1551 /* Skip excess decimals, and determine if we have to round the number */
1552 while (dwDecimals--)
1554 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1555 dwState |= NF_ROUND;
1556 else
1557 dwState &= ~NF_ROUND;
1558 szSrc--;
1562 if (dwState & NF_ISREAL)
1564 while (*szSrc != '.')
1566 if (dwState & NF_ROUND)
1568 if (*szSrc == '9')
1569 *szOut-- = '0'; /* continue rounding */
1570 else
1572 dwState &= ~NF_ROUND;
1573 *szOut-- = (*szSrc)+1;
1575 szSrc--;
1577 else
1578 *szOut-- = *szSrc--; /* Write existing decimals */
1580 szSrc--; /* Skip '.' */
1582 while (lpszDec >= lpFormat->lpDecimalSep)
1583 *szOut-- = *lpszDec--; /* Write decimal separator */
1586 dwGroupCount = lpFormat->Grouping;
1588 /* Write the remaining whole number digits, including grouping chars */
1589 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1591 if (dwState & NF_ROUND)
1593 if (*szSrc == '9')
1594 *szOut-- = '0'; /* continue rounding */
1595 else
1597 dwState &= ~NF_ROUND;
1598 *szOut-- = (*szSrc)+1;
1600 szSrc--;
1602 else
1603 *szOut-- = *szSrc--;
1605 dwState |= NF_DIGITS_OUT;
1606 dwCurrentGroupCount++;
1607 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1609 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1611 while (lpszGrp >= lpFormat->lpThousandSep)
1612 *szOut-- = *lpszGrp--; /* Write grouping char */
1614 dwCurrentGroupCount = 0;
1617 if (dwState & NF_ROUND)
1618 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1619 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1620 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1622 /* Add any leading negative or currency sign */
1623 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1625 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1627 case CF_MINUS_LEFT:
1628 case CF_MINUS_LEFT|CF_CY_LEFT:
1629 while (lpszNeg >= lpszNegStart)
1630 *szOut-- = *lpszNeg--;
1631 dwFmt &= ~CF_MINUS_LEFT;
1632 break;
1634 case CF_CY_LEFT:
1635 case CF_CY_LEFT|CF_MINUS_BEFORE:
1636 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1637 if (dwFmt & CF_CY_SPACE)
1638 *szOut-- = ' ';
1639 while (lpszCy >= lpszCyStart)
1640 *szOut-- = *lpszCy--;
1641 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1642 break;
1645 if (dwFmt & CF_PARENS)
1646 *szOut-- = '(';
1647 szOut++;
1649 iRet = strlenW(szOut) + 1;
1650 if (cchOut)
1652 if (iRet <= cchOut)
1653 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1654 else
1656 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1657 lpCurrencyStr[cchOut - 1] = '\0';
1658 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1659 iRet = 0;
1662 return iRet;
1664 error:
1665 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1666 return 0;
1669 /* FIXME: Everything below here needs to move somewhere else along with the
1670 * other EnumXXX functions, when a method for storing resources for
1671 * alternate calendars is determined.
1674 /**************************************************************************
1675 * EnumDateFormatsExA (KERNEL32.@)
1677 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1678 * LOCALE_NOUSEROVERRIDE here as well?
1680 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1682 CALID cal_id;
1683 char buf[256];
1685 if (!proc)
1687 SetLastError(ERROR_INVALID_PARAMETER);
1688 return FALSE;
1691 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1692 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, cal_id);
1700 break;
1702 case DATE_LONGDATE:
1703 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1704 proc(buf, cal_id);
1705 break;
1707 case DATE_YEARMONTH:
1708 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1709 proc(buf, cal_id);
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 * EnumDateFormatsExW (KERNEL32.@)
1723 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1725 CALID cal_id;
1726 WCHAR buf[256];
1728 if (!proc)
1730 SetLastError(ERROR_INVALID_PARAMETER);
1731 return FALSE;
1734 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1735 return FALSE;
1737 switch (flags & ~LOCALE_USE_CP_ACP)
1739 case 0:
1740 case DATE_SHORTDATE:
1741 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1742 proc(buf, cal_id);
1743 break;
1745 case DATE_LONGDATE:
1746 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1747 proc(buf, cal_id);
1748 break;
1750 case DATE_YEARMONTH:
1751 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1752 proc(buf, cal_id);
1753 break;
1755 default:
1756 FIXME("Unknown date format (%d)\n", flags);
1757 SetLastError(ERROR_INVALID_PARAMETER);
1758 return FALSE;
1760 return TRUE;
1763 /**************************************************************************
1764 * EnumDateFormatsA (KERNEL32.@)
1766 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1767 * LOCALE_NOUSEROVERRIDE here as well?
1769 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1771 char buf[256];
1773 if (!proc)
1775 SetLastError(ERROR_INVALID_PARAMETER);
1776 return FALSE;
1779 switch (flags & ~LOCALE_USE_CP_ACP)
1781 case 0:
1782 case DATE_SHORTDATE:
1783 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1784 proc(buf);
1785 break;
1787 case DATE_LONGDATE:
1788 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1789 proc(buf);
1790 break;
1792 case DATE_YEARMONTH:
1793 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1794 proc(buf);
1795 break;
1797 default:
1798 FIXME("Unknown date format (%d)\n", flags);
1799 SetLastError(ERROR_INVALID_PARAMETER);
1800 return FALSE;
1802 return TRUE;
1805 /**************************************************************************
1806 * EnumDateFormatsW (KERNEL32.@)
1808 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1810 WCHAR buf[256];
1812 if (!proc)
1814 SetLastError(ERROR_INVALID_PARAMETER);
1815 return FALSE;
1818 switch (flags & ~LOCALE_USE_CP_ACP)
1820 case 0:
1821 case DATE_SHORTDATE:
1822 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1823 proc(buf);
1824 break;
1826 case DATE_LONGDATE:
1827 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1828 proc(buf);
1829 break;
1831 case DATE_YEARMONTH:
1832 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1833 proc(buf);
1834 break;
1836 default:
1837 FIXME("Unknown date format (%d)\n", flags);
1838 SetLastError(ERROR_INVALID_PARAMETER);
1839 return FALSE;
1841 return TRUE;
1844 /**************************************************************************
1845 * EnumTimeFormatsA (KERNEL32.@)
1847 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1848 * LOCALE_NOUSEROVERRIDE here as well?
1850 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1852 char buf[256];
1854 if (!proc)
1856 SetLastError(ERROR_INVALID_PARAMETER);
1857 return FALSE;
1860 switch (flags & ~LOCALE_USE_CP_ACP)
1862 case 0:
1863 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1864 proc(buf);
1865 break;
1867 default:
1868 FIXME("Unknown time format (%d)\n", flags);
1869 SetLastError(ERROR_INVALID_PARAMETER);
1870 return FALSE;
1872 return TRUE;
1875 /**************************************************************************
1876 * EnumTimeFormatsW (KERNEL32.@)
1878 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1880 WCHAR buf[256];
1882 if (!proc)
1884 SetLastError(ERROR_INVALID_PARAMETER);
1885 return FALSE;
1888 switch (flags & ~LOCALE_USE_CP_ACP)
1890 case 0:
1891 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1892 proc(buf);
1893 break;
1895 default:
1896 FIXME("Unknown time format (%d)\n", flags);
1897 SetLastError(ERROR_INVALID_PARAMETER);
1898 return FALSE;
1900 return TRUE;
1903 /******************************************************************************
1904 * NLS_EnumCalendarInfoAW <internal>
1905 * Enumerates calendar information for a specified locale.
1907 * PARAMS
1908 * calinfoproc [I] Pointer to the callback
1909 * locale [I] The locale for which to retrieve calendar information.
1910 * This parameter can be a locale identifier created by the
1911 * MAKELCID macro, or one of the following values:
1912 * LOCALE_SYSTEM_DEFAULT
1913 * Use the default system locale.
1914 * LOCALE_USER_DEFAULT
1915 * Use the default user locale.
1916 * calendar [I] The calendar for which information is requested, or
1917 * ENUM_ALL_CALENDARS.
1918 * caltype [I] The type of calendar information to be returned. Note
1919 * that only one CALTYPE value can be specified per call
1920 * of this function, except where noted.
1921 * unicode [I] Specifies if the callback expects a unicode string.
1922 * ex [I] Specifies if the callback needs the calendar identifier.
1924 * RETURNS
1925 * Success: TRUE.
1926 * Failure: FALSE. Use GetLastError() to determine the cause.
1928 * NOTES
1929 * When the ANSI version of this function is used with a Unicode-only LCID,
1930 * the call can succeed because the system uses the system code page.
1931 * However, characters that are undefined in the system code page appear
1932 * in the string as a question mark (?).
1934 * TODO
1935 * The above note should be respected by GetCalendarInfoA.
1937 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1938 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1940 WCHAR *buf, *opt = NULL, *iter = NULL;
1941 BOOL ret = FALSE;
1942 int bufSz = 200; /* the size of the buffer */
1944 if (calinfoproc == NULL)
1946 SetLastError(ERROR_INVALID_PARAMETER);
1947 return FALSE;
1950 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1951 if (buf == NULL)
1953 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1954 return FALSE;
1957 if (calendar == ENUM_ALL_CALENDARS)
1959 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1960 if (optSz > 1)
1962 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1963 if (opt == NULL)
1965 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1966 goto cleanup;
1968 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1969 iter = opt;
1971 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1974 while (TRUE) /* loop through calendars */
1976 do /* loop until there's no error */
1978 if (unicode)
1979 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1980 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1982 if (!ret)
1984 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1985 { /* so resize it */
1986 int newSz;
1987 if (unicode)
1988 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1989 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1990 if (bufSz >= newSz)
1992 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1993 goto cleanup;
1995 bufSz = newSz;
1996 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1997 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1998 if (buf == NULL)
1999 goto cleanup;
2000 } else goto cleanup;
2002 } while (!ret);
2004 /* Here we are. We pass the buffer to the correct version of
2005 * the callback. Because it's not the same number of params,
2006 * we must check for Ex, but we don't care about Unicode
2007 * because the buffer is already in the correct format.
2009 if (ex) {
2010 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
2011 } else
2012 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
2014 if (!ret) { /* the callback told to stop */
2015 ret = TRUE;
2016 break;
2019 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2020 break;
2022 calendar = 0;
2023 while ((*iter >= '0') && (*iter <= '9'))
2024 calendar = calendar * 10 + *iter++ - '0';
2026 if (*iter++ != 0)
2028 SetLastError(ERROR_BADDB);
2029 ret = FALSE;
2030 break;
2034 cleanup:
2035 HeapFree(GetProcessHeap(), 0, opt);
2036 HeapFree(GetProcessHeap(), 0, buf);
2037 return ret;
2040 /******************************************************************************
2041 * EnumCalendarInfoA [KERNEL32.@]
2043 * See EnumCalendarInfoAW.
2045 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2046 CALID calendar,CALTYPE caltype )
2048 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2049 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
2052 /******************************************************************************
2053 * EnumCalendarInfoW [KERNEL32.@]
2055 * See EnumCalendarInfoAW.
2057 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2058 CALID calendar,CALTYPE caltype )
2060 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2061 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
2064 /******************************************************************************
2065 * EnumCalendarInfoExA [KERNEL32.@]
2067 * See EnumCalendarInfoAW.
2069 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2070 CALID calendar,CALTYPE caltype )
2072 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2073 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
2076 /******************************************************************************
2077 * EnumCalendarInfoExW [KERNEL32.@]
2079 * See EnumCalendarInfoAW.
2081 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2082 CALID calendar,CALTYPE caltype )
2084 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2085 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);