push 149f0a5527ac85057a8ef03858d34d91c36f97e8
[wine/hacks.git] / dlls / kernel32 / lcformat.c
blob99d1c466533f9adbfed9486f30075b07c8a7296e
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 GetDateTimeFormatA_InvalidParameter:
760 SetLastError(ERROR_INVALID_PARAMETER);
761 return 0;
764 if (!(dwFlags & LOCALE_USE_CP_ACP))
766 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
767 if (!node)
768 goto GetDateTimeFormatA_InvalidParameter;
769 cp = node->dwCodePage;
772 if (lpFormat)
773 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
775 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
776 cchOut = sizeof(szOut)/sizeof(WCHAR);
778 szOut[0] = '\0';
780 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
781 lpStr ? szOut : NULL, cchOut);
783 if (lpStr)
785 if (szOut[0])
786 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
787 else if (cchOut && iRet)
788 *lpStr = '\0';
790 return iRet;
793 /******************************************************************************
794 * GetDateFormatA [KERNEL32.@]
796 * Format a date for a given locale.
798 * PARAMS
799 * lcid [I] Locale to format for
800 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
801 * lpTime [I] Date to format
802 * lpFormat [I] Format string, or NULL to use the system defaults
803 * lpDateStr [O] Destination for formatted string
804 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
806 * NOTES
807 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
808 * details returned by GetLocaleInfoA() and modified by dwFlags.
809 * - lpFormat is a string of characters and formatting tokens. Any characters
810 * in the string are copied verbatim to lpDateStr, with tokens being replaced
811 * by the date values they represent.
812 * - The following tokens have special meanings in a date format string:
813 *| Token Meaning
814 *| ----- -------
815 *| d Single digit day of the month (no leading 0)
816 *| dd Double digit day of the month
817 *| ddd Short name for the day of the week
818 *| dddd Long name for the day of the week
819 *| M Single digit month of the year (no leading 0)
820 *| MM Double digit month of the year
821 *| MMM Short name for the month of the year
822 *| MMMM Long name for the month of the year
823 *| y Double digit year number (no leading 0)
824 *| yy Double digit year number
825 *| yyyy Four digit year number
826 *| gg Era string, for example 'AD'.
827 * - To output any literal character that could be misidentified as a token,
828 * enclose it in single quotes.
829 * - The Ascii version of this function fails if lcid is Unicode only.
831 * RETURNS
832 * Success: The number of character written to lpDateStr, or that would
833 * have been written, if cchOut is 0.
834 * Failure: 0. Use GetLastError() to determine the cause.
836 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
837 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
839 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
840 debugstr_a(lpFormat), lpDateStr, cchOut);
842 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
843 lpFormat, lpDateStr, cchOut);
847 /******************************************************************************
848 * GetDateFormatW [KERNEL32.@]
850 * See GetDateFormatA.
852 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
853 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
855 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
856 debugstr_w(lpFormat), lpDateStr, cchOut);
858 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
859 lpFormat, lpDateStr, cchOut);
862 /******************************************************************************
863 * GetTimeFormatA [KERNEL32.@]
865 * Format a time for a given locale.
867 * PARAMS
868 * lcid [I] Locale to format for
869 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
870 * lpTime [I] Time to format
871 * lpFormat [I] Formatting overrides
872 * lpTimeStr [O] Destination for formatted string
873 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
875 * NOTES
876 * - If lpFormat is NULL, lpszValue will be formatted according to the format
877 * details returned by GetLocaleInfoA() and modified by dwFlags.
878 * - lpFormat is a string of characters and formatting tokens. Any characters
879 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
880 * by the time values they represent.
881 * - The following tokens have special meanings in a time format string:
882 *| Token Meaning
883 *| ----- -------
884 *| h Hours with no leading zero (12-hour clock)
885 *| hh Hours with full two digits (12-hour clock)
886 *| H Hours with no leading zero (24-hour clock)
887 *| HH Hours with full two digits (24-hour clock)
888 *| m Minutes with no leading zero
889 *| mm Minutes with full two digits
890 *| s Seconds with no leading zero
891 *| ss Seconds with full two digits
892 *| t Short time marker (e.g. "A" or "P")
893 *| tt Long time marker (e.g. "AM", "PM")
894 * - To output any literal character that could be misidentified as a token,
895 * enclose it in single quotes.
896 * - The Ascii version of this function fails if lcid is Unicode only.
898 * RETURNS
899 * Success: The number of character written to lpTimeStr, or that would
900 * have been written, if cchOut is 0.
901 * Failure: 0. Use GetLastError() to determine the cause.
903 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
904 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
906 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
907 debugstr_a(lpFormat), lpTimeStr, cchOut);
909 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
910 lpFormat, lpTimeStr, cchOut);
913 /******************************************************************************
914 * GetTimeFormatW [KERNEL32.@]
916 * See GetTimeFormatA.
918 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
919 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
921 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
922 debugstr_w(lpFormat), lpTimeStr, cchOut);
924 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
925 lpFormat, lpTimeStr, cchOut);
928 /**************************************************************************
929 * GetNumberFormatA (KERNEL32.@)
931 * Format a number string for a given locale.
933 * PARAMS
934 * lcid [I] Locale to format for
935 * dwFlags [I] LOCALE_ flags from "winnls.h"
936 * lpszValue [I] String to format
937 * lpFormat [I] Formatting overrides
938 * lpNumberStr [O] Destination for formatted string
939 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
941 * NOTES
942 * - lpszValue can contain only '0' - '9', '-' and '.'.
943 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
944 * be formatted according to the format details returned by GetLocaleInfoA().
945 * - This function rounds the number string if the number of decimals exceeds the
946 * locales normal number of decimal places.
947 * - If cchOut is 0, this function does not write to lpNumberStr.
948 * - The Ascii version of this function fails if lcid is Unicode only.
950 * RETURNS
951 * Success: The number of character written to lpNumberStr, or that would
952 * have been written, if cchOut is 0.
953 * Failure: 0. Use GetLastError() to determine the cause.
955 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
956 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
957 LPSTR lpNumberStr, int cchOut)
959 DWORD cp = CP_ACP;
960 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
961 NUMBERFMTW fmt;
962 const NUMBERFMTW *pfmt = NULL;
963 INT iRet;
965 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
966 lpFormat, lpNumberStr, cchOut);
968 if (NLS_IsUnicodeOnlyLcid(lcid))
970 GetNumberFormatA_InvalidParameter:
971 SetLastError(ERROR_INVALID_PARAMETER);
972 return 0;
975 if (!(dwFlags & LOCALE_USE_CP_ACP))
977 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
978 if (!node)
979 goto GetNumberFormatA_InvalidParameter;
980 cp = node->dwCodePage;
983 if (lpFormat)
985 memcpy(&fmt, lpFormat, sizeof(fmt));
986 pfmt = &fmt;
987 if (lpFormat->lpDecimalSep)
989 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
990 fmt.lpDecimalSep = szDec;
992 if (lpFormat->lpThousandSep)
994 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
995 fmt.lpThousandSep = szGrp;
999 if (lpszValue)
1000 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1002 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1003 cchOut = sizeof(szOut)/sizeof(WCHAR);
1005 szOut[0] = '\0';
1007 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1008 lpNumberStr ? szOut : NULL, cchOut);
1010 if (szOut[0] && lpNumberStr)
1011 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
1012 return iRet;
1015 /* Number parsing state flags */
1016 #define NF_ISNEGATIVE 0x1 /* '-' found */
1017 #define NF_ISREAL 0x2 /* '.' found */
1018 #define NF_DIGITS 0x4 /* '0'-'9' found */
1019 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1020 #define NF_ROUND 0x10 /* Number needs to be rounded */
1022 /* Formatting options for Numbers */
1023 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1024 #define NLS_NEG_LEFT 1 /* "-1.1" */
1025 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1026 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1027 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1029 /**************************************************************************
1030 * GetNumberFormatW (KERNEL32.@)
1032 * See GetNumberFormatA.
1034 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
1035 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
1036 LPWSTR lpNumberStr, int cchOut)
1038 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1039 WCHAR szNegBuff[8];
1040 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1041 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1042 INT iRet;
1044 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1045 lpFormat, lpNumberStr, cchOut);
1047 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
1048 !IsValidLocale(lcid, 0) ||
1049 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
1051 GetNumberFormatW_Error:
1052 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1053 return 0;
1056 if (!lpFormat)
1058 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1060 if (!node)
1061 goto GetNumberFormatW_Error;
1062 lpFormat = &node->fmt;
1063 lpszNegStart = lpszNeg = GetNegative(node);
1065 else
1067 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1068 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1069 lpszNegStart = lpszNeg = szNegBuff;
1071 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1073 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1075 /* Format the number backwards into a temporary buffer */
1077 szSrc = lpszValue;
1078 *szOut-- = '\0';
1080 /* Check the number for validity */
1081 while (*szSrc)
1083 if (*szSrc >= '0' && *szSrc <= '9')
1085 dwState |= NF_DIGITS;
1086 if (dwState & NF_ISREAL)
1087 dwDecimals++;
1089 else if (*szSrc == '-')
1091 if (dwState)
1092 goto GetNumberFormatW_Error; /* '-' not first character */
1093 dwState |= NF_ISNEGATIVE;
1095 else if (*szSrc == '.')
1097 if (dwState & NF_ISREAL)
1098 goto GetNumberFormatW_Error; /* More than one '.' */
1099 dwState |= NF_ISREAL;
1101 else
1102 goto GetNumberFormatW_Error; /* Invalid char */
1103 szSrc++;
1105 szSrc--; /* Point to last character */
1107 if (!(dwState & NF_DIGITS))
1108 goto GetNumberFormatW_Error; /* No digits */
1110 /* Add any trailing negative sign */
1111 if (dwState & NF_ISNEGATIVE)
1113 switch (lpFormat->NegativeOrder)
1115 case NLS_NEG_PARENS:
1116 *szOut-- = ')';
1117 break;
1118 case NLS_NEG_RIGHT:
1119 case NLS_NEG_RIGHT_SPACE:
1120 while (lpszNeg >= lpszNegStart)
1121 *szOut-- = *lpszNeg--;
1122 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1123 *szOut-- = ' ';
1124 break;
1128 /* Copy all digits up to the decimal point */
1129 if (!lpFormat->NumDigits)
1131 if (dwState & NF_ISREAL)
1133 while (*szSrc != '.') /* Don't write any decimals or a separator */
1135 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1136 dwState |= NF_ROUND;
1137 else
1138 dwState &= ~NF_ROUND;
1139 szSrc--;
1141 szSrc--;
1144 else
1146 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1148 if (dwDecimals <= lpFormat->NumDigits)
1150 dwDecimals = lpFormat->NumDigits - dwDecimals;
1151 while (dwDecimals--)
1152 *szOut-- = '0'; /* Pad to correct number of dp */
1154 else
1156 dwDecimals -= lpFormat->NumDigits;
1157 /* Skip excess decimals, and determine if we have to round the number */
1158 while (dwDecimals--)
1160 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1161 dwState |= NF_ROUND;
1162 else
1163 dwState &= ~NF_ROUND;
1164 szSrc--;
1168 if (dwState & NF_ISREAL)
1170 while (*szSrc != '.')
1172 if (dwState & NF_ROUND)
1174 if (*szSrc == '9')
1175 *szOut-- = '0'; /* continue rounding */
1176 else
1178 dwState &= ~NF_ROUND;
1179 *szOut-- = (*szSrc)+1;
1181 szSrc--;
1183 else
1184 *szOut-- = *szSrc--; /* Write existing decimals */
1186 szSrc--; /* Skip '.' */
1189 while (lpszDec >= lpFormat->lpDecimalSep)
1190 *szOut-- = *lpszDec--; /* Write decimal separator */
1193 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1195 /* Write the remaining whole number digits, including grouping chars */
1196 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1198 if (dwState & NF_ROUND)
1200 if (*szSrc == '9')
1201 *szOut-- = '0'; /* continue rounding */
1202 else
1204 dwState &= ~NF_ROUND;
1205 *szOut-- = (*szSrc)+1;
1207 szSrc--;
1209 else
1210 *szOut-- = *szSrc--;
1212 dwState |= NF_DIGITS_OUT;
1213 dwCurrentGroupCount++;
1214 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1216 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1218 while (lpszGrp >= lpFormat->lpThousandSep)
1219 *szOut-- = *lpszGrp--; /* Write grouping char */
1221 dwCurrentGroupCount = 0;
1222 if (lpFormat->Grouping == 32)
1223 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1226 if (dwState & NF_ROUND)
1228 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1230 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1231 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1233 /* Add any leading negative sign */
1234 if (dwState & NF_ISNEGATIVE)
1236 switch (lpFormat->NegativeOrder)
1238 case NLS_NEG_PARENS:
1239 *szOut-- = '(';
1240 break;
1241 case NLS_NEG_LEFT_SPACE:
1242 *szOut-- = ' ';
1243 /* Fall through */
1244 case NLS_NEG_LEFT:
1245 while (lpszNeg >= lpszNegStart)
1246 *szOut-- = *lpszNeg--;
1247 break;
1250 szOut++;
1252 iRet = strlenW(szOut) + 1;
1253 if (cchOut)
1255 if (iRet <= cchOut)
1256 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1257 else
1259 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1260 lpNumberStr[cchOut - 1] = '\0';
1261 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1262 iRet = 0;
1265 return iRet;
1268 /**************************************************************************
1269 * GetCurrencyFormatA (KERNEL32.@)
1271 * Format a currency string for a given locale.
1273 * PARAMS
1274 * lcid [I] Locale to format for
1275 * dwFlags [I] LOCALE_ flags from "winnls.h"
1276 * lpszValue [I] String to format
1277 * lpFormat [I] Formatting overrides
1278 * lpCurrencyStr [O] Destination for formatted string
1279 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1281 * NOTES
1282 * - lpszValue can contain only '0' - '9', '-' and '.'.
1283 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1284 * be formatted according to the format details returned by GetLocaleInfoA().
1285 * - This function rounds the currency if the number of decimals exceeds the
1286 * locales number of currency decimal places.
1287 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1288 * - The Ascii version of this function fails if lcid is Unicode only.
1290 * RETURNS
1291 * Success: The number of character written to lpNumberStr, or that would
1292 * have been written, if cchOut is 0.
1293 * Failure: 0. Use GetLastError() to determine the cause.
1295 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1296 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1297 LPSTR lpCurrencyStr, int cchOut)
1299 DWORD cp = CP_ACP;
1300 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1301 CURRENCYFMTW fmt;
1302 const CURRENCYFMTW *pfmt = NULL;
1303 INT iRet;
1305 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1306 lpFormat, lpCurrencyStr, cchOut);
1308 if (NLS_IsUnicodeOnlyLcid(lcid))
1310 GetCurrencyFormatA_InvalidParameter:
1311 SetLastError(ERROR_INVALID_PARAMETER);
1312 return 0;
1315 if (!(dwFlags & LOCALE_USE_CP_ACP))
1317 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1318 if (!node)
1319 goto GetCurrencyFormatA_InvalidParameter;
1320 cp = node->dwCodePage;
1323 if (lpFormat)
1325 memcpy(&fmt, lpFormat, sizeof(fmt));
1326 pfmt = &fmt;
1327 if (lpFormat->lpDecimalSep)
1329 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1330 fmt.lpDecimalSep = szDec;
1332 if (lpFormat->lpThousandSep)
1334 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1335 fmt.lpThousandSep = szGrp;
1337 if (lpFormat->lpCurrencySymbol)
1339 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1340 fmt.lpCurrencySymbol = szCy;
1344 if (lpszValue)
1345 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1347 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1348 cchOut = sizeof(szOut)/sizeof(WCHAR);
1350 szOut[0] = '\0';
1352 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1353 lpCurrencyStr ? szOut : NULL, cchOut);
1355 if (szOut[0] && lpCurrencyStr)
1356 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1357 return iRet;
1360 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1361 #define CF_PARENS 0x1 /* Parentheses */
1362 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1363 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1364 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1365 #define CF_CY_LEFT 0x10 /* '$' to the left */
1366 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1367 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1369 /**************************************************************************
1370 * GetCurrencyFormatW (KERNEL32.@)
1372 * See GetCurrencyFormatA.
1374 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1375 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1376 LPWSTR lpCurrencyStr, int cchOut)
1378 static const BYTE NLS_NegCyFormats[16] =
1380 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1381 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1382 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1383 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1384 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1385 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1386 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1387 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1388 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1389 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1390 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1391 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1392 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1393 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1394 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1395 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1397 static const BYTE NLS_PosCyFormats[4] =
1399 CF_CY_LEFT, /* $1.1 */
1400 CF_CY_RIGHT, /* 1.1$ */
1401 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1402 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1404 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1405 WCHAR szNegBuff[8];
1406 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1407 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1408 INT iRet;
1410 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1411 lpFormat, lpCurrencyStr, cchOut);
1413 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1414 !IsValidLocale(lcid, 0) ||
1415 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1416 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1417 lpFormat->PositiveOrder > 3)))
1419 GetCurrencyFormatW_Error:
1420 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1421 return 0;
1424 if (!lpFormat)
1426 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1428 if (!node)
1429 goto GetCurrencyFormatW_Error;
1430 lpFormat = &node->cyfmt;
1431 lpszNegStart = lpszNeg = GetNegative(node);
1433 else
1435 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1436 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1437 lpszNegStart = lpszNeg = szNegBuff;
1439 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1441 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1442 lpszCyStart = lpFormat->lpCurrencySymbol;
1443 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1445 /* Format the currency backwards into a temporary buffer */
1447 szSrc = lpszValue;
1448 *szOut-- = '\0';
1450 /* Check the number for validity */
1451 while (*szSrc)
1453 if (*szSrc >= '0' && *szSrc <= '9')
1455 dwState |= NF_DIGITS;
1456 if (dwState & NF_ISREAL)
1457 dwDecimals++;
1459 else if (*szSrc == '-')
1461 if (dwState)
1462 goto GetCurrencyFormatW_Error; /* '-' not first character */
1463 dwState |= NF_ISNEGATIVE;
1465 else if (*szSrc == '.')
1467 if (dwState & NF_ISREAL)
1468 goto GetCurrencyFormatW_Error; /* More than one '.' */
1469 dwState |= NF_ISREAL;
1471 else
1472 goto GetCurrencyFormatW_Error; /* Invalid char */
1473 szSrc++;
1475 szSrc--; /* Point to last character */
1477 if (!(dwState & NF_DIGITS))
1478 goto GetCurrencyFormatW_Error; /* No digits */
1480 if (dwState & NF_ISNEGATIVE)
1481 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1482 else
1483 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1485 /* Add any trailing negative or currency signs */
1486 if (dwFmt & CF_PARENS)
1487 *szOut-- = ')';
1489 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1491 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1493 case CF_MINUS_RIGHT:
1494 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1495 while (lpszNeg >= lpszNegStart)
1496 *szOut-- = *lpszNeg--;
1497 dwFmt &= ~CF_MINUS_RIGHT;
1498 break;
1500 case CF_CY_RIGHT:
1501 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1502 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1503 while (lpszCy >= lpszCyStart)
1504 *szOut-- = *lpszCy--;
1505 if (dwFmt & CF_CY_SPACE)
1506 *szOut-- = ' ';
1507 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1508 break;
1512 /* Copy all digits up to the decimal point */
1513 if (!lpFormat->NumDigits)
1515 if (dwState & NF_ISREAL)
1517 while (*szSrc != '.') /* Don't write any decimals or a separator */
1519 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1520 dwState |= NF_ROUND;
1521 else
1522 dwState &= ~NF_ROUND;
1523 szSrc--;
1525 szSrc--;
1528 else
1530 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1532 if (dwDecimals <= lpFormat->NumDigits)
1534 dwDecimals = lpFormat->NumDigits - dwDecimals;
1535 while (dwDecimals--)
1536 *szOut-- = '0'; /* Pad to correct number of dp */
1538 else
1540 dwDecimals -= lpFormat->NumDigits;
1541 /* Skip excess decimals, and determine if we have to round the number */
1542 while (dwDecimals--)
1544 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1545 dwState |= NF_ROUND;
1546 else
1547 dwState &= ~NF_ROUND;
1548 szSrc--;
1552 if (dwState & NF_ISREAL)
1554 while (*szSrc != '.')
1556 if (dwState & NF_ROUND)
1558 if (*szSrc == '9')
1559 *szOut-- = '0'; /* continue rounding */
1560 else
1562 dwState &= ~NF_ROUND;
1563 *szOut-- = (*szSrc)+1;
1565 szSrc--;
1567 else
1568 *szOut-- = *szSrc--; /* Write existing decimals */
1570 szSrc--; /* Skip '.' */
1572 while (lpszDec >= lpFormat->lpDecimalSep)
1573 *szOut-- = *lpszDec--; /* Write decimal separator */
1576 dwGroupCount = lpFormat->Grouping;
1578 /* Write the remaining whole number digits, including grouping chars */
1579 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1581 if (dwState & NF_ROUND)
1583 if (*szSrc == '9')
1584 *szOut-- = '0'; /* continue rounding */
1585 else
1587 dwState &= ~NF_ROUND;
1588 *szOut-- = (*szSrc)+1;
1590 szSrc--;
1592 else
1593 *szOut-- = *szSrc--;
1595 dwState |= NF_DIGITS_OUT;
1596 dwCurrentGroupCount++;
1597 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1599 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1601 while (lpszGrp >= lpFormat->lpThousandSep)
1602 *szOut-- = *lpszGrp--; /* Write grouping char */
1604 dwCurrentGroupCount = 0;
1607 if (dwState & NF_ROUND)
1608 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1609 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1610 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1612 /* Add any leading negative or currency sign */
1613 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1615 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1617 case CF_MINUS_LEFT:
1618 case CF_MINUS_LEFT|CF_CY_LEFT:
1619 while (lpszNeg >= lpszNegStart)
1620 *szOut-- = *lpszNeg--;
1621 dwFmt &= ~CF_MINUS_LEFT;
1622 break;
1624 case CF_CY_LEFT:
1625 case CF_CY_LEFT|CF_MINUS_BEFORE:
1626 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1627 if (dwFmt & CF_CY_SPACE)
1628 *szOut-- = ' ';
1629 while (lpszCy >= lpszCyStart)
1630 *szOut-- = *lpszCy--;
1631 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1632 break;
1635 if (dwFmt & CF_PARENS)
1636 *szOut-- = '(';
1637 szOut++;
1639 iRet = strlenW(szOut) + 1;
1640 if (cchOut)
1642 if (iRet <= cchOut)
1643 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1644 else
1646 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1647 lpCurrencyStr[cchOut - 1] = '\0';
1648 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1649 iRet = 0;
1652 return iRet;
1655 /* FIXME: Everything below here needs to move somewhere else along with the
1656 * other EnumXXX functions, when a method for storing resources for
1657 * alternate calendars is determined.
1660 /**************************************************************************
1661 * EnumDateFormatsExA (KERNEL32.@)
1663 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1664 * LOCALE_NOUSEROVERRIDE here as well?
1666 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1668 CALID cal_id;
1669 char buf[256];
1671 if (!proc)
1673 SetLastError(ERROR_INVALID_PARAMETER);
1674 return FALSE;
1677 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1678 return FALSE;
1680 switch (flags & ~LOCALE_USE_CP_ACP)
1682 case 0:
1683 case DATE_SHORTDATE:
1684 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1685 proc(buf, cal_id);
1686 break;
1688 case DATE_LONGDATE:
1689 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1690 proc(buf, cal_id);
1691 break;
1693 case DATE_YEARMONTH:
1694 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1695 proc(buf, cal_id);
1696 break;
1698 default:
1699 FIXME("Unknown date format (%d)\n", flags);
1700 SetLastError(ERROR_INVALID_PARAMETER);
1701 return FALSE;
1703 return TRUE;
1706 /**************************************************************************
1707 * EnumDateFormatsExW (KERNEL32.@)
1709 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1711 CALID cal_id;
1712 WCHAR buf[256];
1714 if (!proc)
1716 SetLastError(ERROR_INVALID_PARAMETER);
1717 return FALSE;
1720 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1721 return FALSE;
1723 switch (flags & ~LOCALE_USE_CP_ACP)
1725 case 0:
1726 case DATE_SHORTDATE:
1727 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1728 proc(buf, cal_id);
1729 break;
1731 case DATE_LONGDATE:
1732 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1733 proc(buf, cal_id);
1734 break;
1736 case DATE_YEARMONTH:
1737 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1738 proc(buf, cal_id);
1739 break;
1741 default:
1742 FIXME("Unknown date format (%d)\n", flags);
1743 SetLastError(ERROR_INVALID_PARAMETER);
1744 return FALSE;
1746 return TRUE;
1749 /**************************************************************************
1750 * EnumDateFormatsA (KERNEL32.@)
1752 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1753 * LOCALE_NOUSEROVERRIDE here as well?
1755 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1757 char buf[256];
1759 if (!proc)
1761 SetLastError(ERROR_INVALID_PARAMETER);
1762 return FALSE;
1765 switch (flags & ~LOCALE_USE_CP_ACP)
1767 case 0:
1768 case DATE_SHORTDATE:
1769 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1770 proc(buf);
1771 break;
1773 case DATE_LONGDATE:
1774 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1775 proc(buf);
1776 break;
1778 case DATE_YEARMONTH:
1779 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1780 proc(buf);
1781 break;
1783 default:
1784 FIXME("Unknown date format (%d)\n", flags);
1785 SetLastError(ERROR_INVALID_PARAMETER);
1786 return FALSE;
1788 return TRUE;
1791 /**************************************************************************
1792 * EnumDateFormatsW (KERNEL32.@)
1794 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1796 WCHAR buf[256];
1798 if (!proc)
1800 SetLastError(ERROR_INVALID_PARAMETER);
1801 return FALSE;
1804 switch (flags & ~LOCALE_USE_CP_ACP)
1806 case 0:
1807 case DATE_SHORTDATE:
1808 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1809 proc(buf);
1810 break;
1812 case DATE_LONGDATE:
1813 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1814 proc(buf);
1815 break;
1817 case DATE_YEARMONTH:
1818 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1819 proc(buf);
1820 break;
1822 default:
1823 FIXME("Unknown date format (%d)\n", flags);
1824 SetLastError(ERROR_INVALID_PARAMETER);
1825 return FALSE;
1827 return TRUE;
1830 /**************************************************************************
1831 * EnumTimeFormatsA (KERNEL32.@)
1833 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1834 * LOCALE_NOUSEROVERRIDE here as well?
1836 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1838 char buf[256];
1840 if (!proc)
1842 SetLastError(ERROR_INVALID_PARAMETER);
1843 return FALSE;
1846 switch (flags & ~LOCALE_USE_CP_ACP)
1848 case 0:
1849 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1850 proc(buf);
1851 break;
1853 default:
1854 FIXME("Unknown time format (%d)\n", flags);
1855 SetLastError(ERROR_INVALID_PARAMETER);
1856 return FALSE;
1858 return TRUE;
1861 /**************************************************************************
1862 * EnumTimeFormatsW (KERNEL32.@)
1864 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1866 WCHAR buf[256];
1868 if (!proc)
1870 SetLastError(ERROR_INVALID_PARAMETER);
1871 return FALSE;
1874 switch (flags & ~LOCALE_USE_CP_ACP)
1876 case 0:
1877 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1878 proc(buf);
1879 break;
1881 default:
1882 FIXME("Unknown time format (%d)\n", flags);
1883 SetLastError(ERROR_INVALID_PARAMETER);
1884 return FALSE;
1886 return TRUE;
1889 /******************************************************************************
1890 * NLS_EnumCalendarInfoAW <internal>
1891 * Enumerates calendar information for a specified locale.
1893 * PARAMS
1894 * calinfoproc [I] Pointer to the callback
1895 * locale [I] The locale for which to retrieve calendar information.
1896 * This parameter can be a locale identifier created by the
1897 * MAKELCID macro, or one of the following values:
1898 * LOCALE_SYSTEM_DEFAULT
1899 * Use the default system locale.
1900 * LOCALE_USER_DEFAULT
1901 * Use the default user locale.
1902 * calendar [I] The calendar for which information is requested, or
1903 * ENUM_ALL_CALENDARS.
1904 * caltype [I] The type of calendar information to be returned. Note
1905 * that only one CALTYPE value can be specified per call
1906 * of this function, except where noted.
1907 * unicode [I] Specifies if the callback expects a unicode string.
1908 * ex [I] Specifies if the callback needs the calendar identifier.
1910 * RETURNS
1911 * Success: TRUE.
1912 * Failure: FALSE. Use GetLastError() to determine the cause.
1914 * NOTES
1915 * When the ANSI version of this function is used with a Unicode-only LCID,
1916 * the call can succeed because the system uses the system code page.
1917 * However, characters that are undefined in the system code page appear
1918 * in the string as a question mark (?).
1920 * TODO
1921 * The above note should be respected by GetCalendarInfoA.
1923 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1924 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1926 WCHAR *buf, *opt = NULL, *iter = NULL;
1927 BOOL ret = FALSE;
1928 int bufSz = 200; /* the size of the buffer */
1930 if (calinfoproc == NULL)
1932 SetLastError(ERROR_INVALID_PARAMETER);
1933 return FALSE;
1936 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1937 if (buf == NULL)
1939 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1940 return FALSE;
1943 if (calendar == ENUM_ALL_CALENDARS)
1945 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1946 if (optSz > 1)
1948 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1949 if (opt == NULL)
1951 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1952 goto NLS_EnumCalendarInfoAW_Cleanup;
1954 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1955 iter = opt;
1957 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1960 while (TRUE) /* loop through calendars */
1962 do /* loop until there's no error */
1964 if (unicode)
1965 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1966 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1968 if (!ret)
1970 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1971 { /* so resize it */
1972 int newSz;
1973 if (unicode)
1974 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1975 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1976 if (bufSz >= newSz)
1978 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1979 goto NLS_EnumCalendarInfoAW_Cleanup;
1981 bufSz = newSz;
1982 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1983 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1984 if (buf == NULL)
1985 goto NLS_EnumCalendarInfoAW_Cleanup;
1986 } else goto NLS_EnumCalendarInfoAW_Cleanup;
1988 } while (!ret);
1990 /* Here we are. We pass the buffer to the correct version of
1991 * the callback. Because it's not the same number of params,
1992 * we must check for Ex, but we don't care about Unicode
1993 * because the buffer is already in the correct format.
1995 if (ex) {
1996 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
1997 } else
1998 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
2000 if (!ret) { /* the callback told to stop */
2001 ret = TRUE;
2002 break;
2005 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2006 break;
2008 calendar = 0;
2009 while ((*iter >= '0') && (*iter <= '9'))
2010 calendar = calendar * 10 + *iter++ - '0';
2012 if (*iter++ != 0)
2014 SetLastError(ERROR_BADDB);
2015 ret = FALSE;
2016 break;
2020 NLS_EnumCalendarInfoAW_Cleanup:
2021 HeapFree(GetProcessHeap(), 0, opt);
2022 HeapFree(GetProcessHeap(), 0, buf);
2023 return ret;
2026 /******************************************************************************
2027 * EnumCalendarInfoA [KERNEL32.@]
2029 * See EnumCalendarInfoAW.
2031 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2032 CALID calendar,CALTYPE caltype )
2034 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2035 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
2038 /******************************************************************************
2039 * EnumCalendarInfoW [KERNEL32.@]
2041 * See EnumCalendarInfoAW.
2043 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2044 CALID calendar,CALTYPE caltype )
2046 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2047 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
2050 /******************************************************************************
2051 * EnumCalendarInfoExA [KERNEL32.@]
2053 * See EnumCalendarInfoAW.
2055 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2056 CALID calendar,CALTYPE caltype )
2058 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2059 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
2062 /******************************************************************************
2063 * EnumCalendarInfoExW [KERNEL32.@]
2065 * See EnumCalendarInfoAW.
2067 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2068 CALID calendar,CALTYPE caltype )
2070 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2071 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);