ntdll: Add stub for RtlGetUnloadEventTraceEx.
[wine.git] / dlls / kernel32 / lcformat.c
blobb1dd32de137e65ebde06492cc8fc33881e96ae49
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, ARRAY_SIZE(szBuff));
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, ARRAY_SIZE(szBuff));
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 < ARRAY_SIZE(NLS_LocaleIndices); 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 < ARRAY_SIZE(NLS_LocaleIndices); 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 == '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, ARRAY_SIZE(buff), 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, output=%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, ARRAY_SIZE(szFormat));
778 if (cchOut > (int) ARRAY_SIZE(szOut))
779 cchOut = ARRAY_SIZE(szOut);
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);
849 /******************************************************************************
850 * GetDateFormatEx [KERNEL32.@]
852 * Format a date for a given locale.
854 * PARAMS
855 * localename [I] Locale to format for
856 * flags [I] LOCALE_ and DATE_ flags from "winnls.h"
857 * date [I] Date to format
858 * format [I] Format string, or NULL to use the locale defaults
859 * outbuf [O] Destination for formatted string
860 * bufsize [I] Size of outbuf, or 0 to calculate the resulting size
861 * calendar [I] Reserved, must be NULL
863 * See GetDateFormatA for notes.
865 * RETURNS
866 * Success: The number of characters written to outbuf, or that would have
867 * been written if bufsize is 0.
868 * Failure: 0. Use GetLastError() to determine the cause.
870 INT WINAPI GetDateFormatEx(LPCWSTR localename, DWORD flags,
871 const SYSTEMTIME* date, LPCWSTR format,
872 LPWSTR outbuf, INT bufsize, LPCWSTR calendar)
874 TRACE("(%s,0x%08x,%p,%s,%p,%d,%s)\n", debugstr_w(localename), flags,
875 date, debugstr_w(format), outbuf, bufsize, debugstr_w(calendar));
877 /* Parameter is currently reserved and Windows errors if set */
878 if (calendar != NULL)
880 SetLastError(ERROR_INVALID_PARAMETER);
881 return 0;
884 return NLS_GetDateTimeFormatW(LocaleNameToLCID(localename, 0),
885 flags | DATE_DATEVARSONLY, date, format,
886 outbuf, bufsize);
889 /******************************************************************************
890 * GetDateFormatW [KERNEL32.@]
892 * See GetDateFormatA.
894 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
895 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
897 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
898 debugstr_w(lpFormat), lpDateStr, cchOut);
900 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
901 lpFormat, lpDateStr, cchOut);
904 /******************************************************************************
905 * GetTimeFormatA [KERNEL32.@]
907 * Format a time for a given locale.
909 * PARAMS
910 * lcid [I] Locale to format for
911 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
912 * lpTime [I] Time to format
913 * lpFormat [I] Formatting overrides
914 * lpTimeStr [O] Destination for formatted string
915 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
917 * NOTES
918 * - If lpFormat is NULL, lpszValue will be formatted according to the format
919 * details returned by GetLocaleInfoA() and modified by dwFlags.
920 * - lpFormat is a string of characters and formatting tokens. Any characters
921 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
922 * by the time values they represent.
923 * - The following tokens have special meanings in a time format string:
924 *| Token Meaning
925 *| ----- -------
926 *| h Hours with no leading zero (12-hour clock)
927 *| hh Hours with full two digits (12-hour clock)
928 *| H Hours with no leading zero (24-hour clock)
929 *| HH Hours with full two digits (24-hour clock)
930 *| m Minutes with no leading zero
931 *| mm Minutes with full two digits
932 *| s Seconds with no leading zero
933 *| ss Seconds with full two digits
934 *| t Short time marker (e.g. "A" or "P")
935 *| tt Long time marker (e.g. "AM", "PM")
936 * - To output any literal character that could be misidentified as a token,
937 * enclose it in single quotes.
938 * - The Ascii version of this function fails if lcid is Unicode only.
940 * RETURNS
941 * Success: The number of character written to lpTimeStr, or that would
942 * have been written, if cchOut is 0.
943 * Failure: 0. Use GetLastError() to determine the cause.
945 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
946 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
948 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
949 debugstr_a(lpFormat), lpTimeStr, cchOut);
951 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
952 lpFormat, lpTimeStr, cchOut);
955 /******************************************************************************
956 * GetTimeFormatEx [KERNEL32.@]
958 * Format a date for a given locale.
960 * PARAMS
961 * localename [I] Locale to format for
962 * flags [I] LOCALE_ and TIME_ flags from "winnls.h"
963 * time [I] Time to format
964 * format [I] Formatting overrides
965 * outbuf [O] Destination for formatted string
966 * bufsize [I] Size of outbuf, or 0 to calculate the resulting size
968 * See GetTimeFormatA for notes.
970 * RETURNS
971 * Success: The number of characters written to outbuf, or that would have
972 * have been written if bufsize is 0.
973 * Failure: 0. Use GetLastError() to determine the cause.
975 INT WINAPI GetTimeFormatEx(LPCWSTR localename, DWORD flags,
976 const SYSTEMTIME* time, LPCWSTR format,
977 LPWSTR outbuf, INT bufsize)
979 TRACE("(%s,0x%08x,%p,%s,%p,%d)\n", debugstr_w(localename), flags, time,
980 debugstr_w(format), outbuf, bufsize);
982 return NLS_GetDateTimeFormatW(LocaleNameToLCID(localename, 0),
983 flags | TIME_TIMEVARSONLY, time, format,
984 outbuf, bufsize);
987 /******************************************************************************
988 * GetTimeFormatW [KERNEL32.@]
990 * See GetTimeFormatA.
992 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
993 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
995 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
996 debugstr_w(lpFormat), lpTimeStr, cchOut);
998 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
999 lpFormat, lpTimeStr, cchOut);
1002 /**************************************************************************
1003 * GetNumberFormatA (KERNEL32.@)
1005 * Format a number string for a given locale.
1007 * PARAMS
1008 * lcid [I] Locale to format for
1009 * dwFlags [I] LOCALE_ flags from "winnls.h"
1010 * lpszValue [I] String to format
1011 * lpFormat [I] Formatting overrides
1012 * lpNumberStr [O] Destination for formatted string
1013 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
1015 * NOTES
1016 * - lpszValue can contain only '0' - '9', '-' and '.'.
1017 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1018 * be formatted according to the format details returned by GetLocaleInfoA().
1019 * - This function rounds the number string if the number of decimals exceeds the
1020 * locales normal number of decimal places.
1021 * - If cchOut is 0, this function does not write to lpNumberStr.
1022 * - The Ascii version of this function fails if lcid is Unicode only.
1024 * RETURNS
1025 * Success: The number of character written to lpNumberStr, or that would
1026 * have been written, if cchOut is 0.
1027 * Failure: 0. Use GetLastError() to determine the cause.
1029 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
1030 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
1031 LPSTR lpNumberStr, int cchOut)
1033 DWORD cp = CP_ACP;
1034 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
1035 NUMBERFMTW fmt;
1036 const NUMBERFMTW *pfmt = NULL;
1037 INT iRet;
1039 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1040 lpFormat, lpNumberStr, cchOut);
1042 if (NLS_IsUnicodeOnlyLcid(lcid))
1044 SetLastError(ERROR_INVALID_PARAMETER);
1045 return 0;
1048 if (!(dwFlags & LOCALE_USE_CP_ACP))
1050 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1051 if (!node)
1053 SetLastError(ERROR_INVALID_PARAMETER);
1054 return 0;
1057 cp = node->dwCodePage;
1060 if (lpFormat)
1062 memcpy(&fmt, lpFormat, sizeof(fmt));
1063 pfmt = &fmt;
1064 if (lpFormat->lpDecimalSep)
1066 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, ARRAY_SIZE(szDec));
1067 fmt.lpDecimalSep = szDec;
1069 if (lpFormat->lpThousandSep)
1071 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, ARRAY_SIZE(szGrp));
1072 fmt.lpThousandSep = szGrp;
1076 if (lpszValue)
1077 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, ARRAY_SIZE(szIn));
1079 if (cchOut > (int) ARRAY_SIZE(szOut))
1080 cchOut = ARRAY_SIZE(szOut);
1082 szOut[0] = '\0';
1084 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1085 lpNumberStr ? szOut : NULL, cchOut);
1087 if (szOut[0] && lpNumberStr)
1088 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
1089 return iRet;
1092 /* Number parsing state flags */
1093 #define NF_ISNEGATIVE 0x1 /* '-' found */
1094 #define NF_ISREAL 0x2 /* '.' found */
1095 #define NF_DIGITS 0x4 /* '0'-'9' found */
1096 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1097 #define NF_ROUND 0x10 /* Number needs to be rounded */
1099 /* Formatting options for Numbers */
1100 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1101 #define NLS_NEG_LEFT 1 /* "-1.1" */
1102 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1103 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1104 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1106 /**************************************************************************
1107 * GetNumberFormatW (KERNEL32.@)
1109 * See GetNumberFormatA.
1111 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
1112 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
1113 LPWSTR lpNumberStr, int cchOut)
1115 WCHAR szBuff[128], *szOut = szBuff + ARRAY_SIZE(szBuff) - 1;
1116 WCHAR szNegBuff[8];
1117 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1118 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1119 INT iRet;
1121 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1122 lpFormat, lpNumberStr, cchOut);
1124 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
1125 !IsValidLocale(lcid, 0) ||
1126 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
1128 goto error;
1131 if (!lpFormat)
1133 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1135 if (!node)
1136 goto error;
1137 lpFormat = &node->fmt;
1138 lpszNegStart = lpszNeg = GetNegative(node);
1140 else
1142 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1143 szNegBuff, ARRAY_SIZE(szNegBuff));
1144 lpszNegStart = lpszNeg = szNegBuff;
1146 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1148 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1150 /* Format the number backwards into a temporary buffer */
1152 szSrc = lpszValue;
1153 *szOut-- = '\0';
1155 /* Check the number for validity */
1156 while (*szSrc)
1158 if (*szSrc >= '0' && *szSrc <= '9')
1160 dwState |= NF_DIGITS;
1161 if (dwState & NF_ISREAL)
1162 dwDecimals++;
1164 else if (*szSrc == '-')
1166 if (dwState)
1167 goto error; /* '-' not first character */
1168 dwState |= NF_ISNEGATIVE;
1170 else if (*szSrc == '.')
1172 if (dwState & NF_ISREAL)
1173 goto error; /* More than one '.' */
1174 dwState |= NF_ISREAL;
1176 else
1177 goto error; /* Invalid char */
1178 szSrc++;
1180 szSrc--; /* Point to last character */
1182 if (!(dwState & NF_DIGITS))
1183 goto error; /* No digits */
1185 /* Add any trailing negative sign */
1186 if (dwState & NF_ISNEGATIVE)
1188 switch (lpFormat->NegativeOrder)
1190 case NLS_NEG_PARENS:
1191 *szOut-- = ')';
1192 break;
1193 case NLS_NEG_RIGHT:
1194 case NLS_NEG_RIGHT_SPACE:
1195 while (lpszNeg >= lpszNegStart)
1196 *szOut-- = *lpszNeg--;
1197 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1198 *szOut-- = ' ';
1199 break;
1203 /* Copy all digits up to the decimal point */
1204 if (!lpFormat->NumDigits)
1206 if (dwState & NF_ISREAL)
1208 while (*szSrc != '.') /* Don't write any decimals or a separator */
1210 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1211 dwState |= NF_ROUND;
1212 else
1213 dwState &= ~NF_ROUND;
1214 szSrc--;
1216 szSrc--;
1219 else
1221 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1223 if (dwDecimals <= lpFormat->NumDigits)
1225 dwDecimals = lpFormat->NumDigits - dwDecimals;
1226 while (dwDecimals--)
1227 *szOut-- = '0'; /* Pad to correct number of dp */
1229 else
1231 dwDecimals -= lpFormat->NumDigits;
1232 /* Skip excess decimals, and determine if we have to round the number */
1233 while (dwDecimals--)
1235 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1236 dwState |= NF_ROUND;
1237 else
1238 dwState &= ~NF_ROUND;
1239 szSrc--;
1243 if (dwState & NF_ISREAL)
1245 while (*szSrc != '.')
1247 if (dwState & NF_ROUND)
1249 if (*szSrc == '9')
1250 *szOut-- = '0'; /* continue rounding */
1251 else
1253 dwState &= ~NF_ROUND;
1254 *szOut-- = (*szSrc)+1;
1256 szSrc--;
1258 else
1259 *szOut-- = *szSrc--; /* Write existing decimals */
1261 szSrc--; /* Skip '.' */
1264 while (lpszDec >= lpFormat->lpDecimalSep)
1265 *szOut-- = *lpszDec--; /* Write decimal separator */
1268 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1270 /* Write the remaining whole number digits, including grouping chars */
1271 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1273 if (dwState & NF_ROUND)
1275 if (*szSrc == '9')
1276 *szOut-- = '0'; /* continue rounding */
1277 else
1279 dwState &= ~NF_ROUND;
1280 *szOut-- = (*szSrc)+1;
1282 szSrc--;
1284 else
1285 *szOut-- = *szSrc--;
1287 dwState |= NF_DIGITS_OUT;
1288 dwCurrentGroupCount++;
1289 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1291 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1293 while (lpszGrp >= lpFormat->lpThousandSep)
1294 *szOut-- = *lpszGrp--; /* Write grouping char */
1296 dwCurrentGroupCount = 0;
1297 if (lpFormat->Grouping == 32)
1298 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1301 if (dwState & NF_ROUND)
1303 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1305 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1306 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1308 /* Add any leading negative sign */
1309 if (dwState & NF_ISNEGATIVE)
1311 switch (lpFormat->NegativeOrder)
1313 case NLS_NEG_PARENS:
1314 *szOut-- = '(';
1315 break;
1316 case NLS_NEG_LEFT_SPACE:
1317 *szOut-- = ' ';
1318 /* Fall through */
1319 case NLS_NEG_LEFT:
1320 while (lpszNeg >= lpszNegStart)
1321 *szOut-- = *lpszNeg--;
1322 break;
1325 szOut++;
1327 iRet = strlenW(szOut) + 1;
1328 if (cchOut)
1330 if (iRet <= cchOut)
1331 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1332 else
1334 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1335 lpNumberStr[cchOut - 1] = '\0';
1336 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1337 iRet = 0;
1340 return iRet;
1342 error:
1343 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1344 return 0;
1347 /**************************************************************************
1348 * GetNumberFormatEx (KERNEL32.@)
1350 INT WINAPI GetNumberFormatEx(LPCWSTR name, DWORD flags,
1351 LPCWSTR value, const NUMBERFMTW *format,
1352 LPWSTR number, int numout)
1354 LCID lcid;
1356 TRACE("(%s,0x%08x,%s,%p,%p,%d)\n", debugstr_w(name), flags,
1357 debugstr_w(value), format, number, numout);
1359 lcid = LocaleNameToLCID(name, 0);
1360 if (!lcid)
1361 return 0;
1363 return GetNumberFormatW(lcid, flags, value, format, number, numout);
1366 /**************************************************************************
1367 * GetCurrencyFormatA (KERNEL32.@)
1369 * Format a currency string for a given locale.
1371 * PARAMS
1372 * lcid [I] Locale to format for
1373 * dwFlags [I] LOCALE_ flags from "winnls.h"
1374 * lpszValue [I] String to format
1375 * lpFormat [I] Formatting overrides
1376 * lpCurrencyStr [O] Destination for formatted string
1377 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1379 * NOTES
1380 * - lpszValue can contain only '0' - '9', '-' and '.'.
1381 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1382 * be formatted according to the format details returned by GetLocaleInfoA().
1383 * - This function rounds the currency if the number of decimals exceeds the
1384 * locales number of currency decimal places.
1385 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1386 * - The Ascii version of this function fails if lcid is Unicode only.
1388 * RETURNS
1389 * Success: The number of character written to lpNumberStr, or that would
1390 * have been written, if cchOut is 0.
1391 * Failure: 0. Use GetLastError() to determine the cause.
1393 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1394 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1395 LPSTR lpCurrencyStr, int cchOut)
1397 DWORD cp = CP_ACP;
1398 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1399 CURRENCYFMTW fmt;
1400 const CURRENCYFMTW *pfmt = NULL;
1401 INT iRet;
1403 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1404 lpFormat, lpCurrencyStr, cchOut);
1406 if (NLS_IsUnicodeOnlyLcid(lcid))
1408 SetLastError(ERROR_INVALID_PARAMETER);
1409 return 0;
1412 if (!(dwFlags & LOCALE_USE_CP_ACP))
1414 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1415 if (!node)
1417 SetLastError(ERROR_INVALID_PARAMETER);
1418 return 0;
1421 cp = node->dwCodePage;
1424 if (lpFormat)
1426 memcpy(&fmt, lpFormat, sizeof(fmt));
1427 pfmt = &fmt;
1428 if (lpFormat->lpDecimalSep)
1430 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, ARRAY_SIZE(szDec));
1431 fmt.lpDecimalSep = szDec;
1433 if (lpFormat->lpThousandSep)
1435 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, ARRAY_SIZE(szGrp));
1436 fmt.lpThousandSep = szGrp;
1438 if (lpFormat->lpCurrencySymbol)
1440 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, ARRAY_SIZE(szCy));
1441 fmt.lpCurrencySymbol = szCy;
1445 if (lpszValue)
1446 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, ARRAY_SIZE(szIn));
1448 if (cchOut > (int) ARRAY_SIZE(szOut))
1449 cchOut = ARRAY_SIZE(szOut);
1451 szOut[0] = '\0';
1453 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1454 lpCurrencyStr ? szOut : NULL, cchOut);
1456 if (szOut[0] && lpCurrencyStr)
1457 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1458 return iRet;
1461 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1462 #define CF_PARENS 0x1 /* Parentheses */
1463 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1464 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1465 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1466 #define CF_CY_LEFT 0x10 /* '$' to the left */
1467 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1468 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1470 /**************************************************************************
1471 * GetCurrencyFormatW (KERNEL32.@)
1473 * See GetCurrencyFormatA.
1475 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1476 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1477 LPWSTR lpCurrencyStr, int cchOut)
1479 static const BYTE NLS_NegCyFormats[16] =
1481 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1482 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1483 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1484 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1485 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1486 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1487 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1488 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1489 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1490 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1491 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1492 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1493 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1494 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1495 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1496 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1498 static const BYTE NLS_PosCyFormats[4] =
1500 CF_CY_LEFT, /* $1.1 */
1501 CF_CY_RIGHT, /* 1.1$ */
1502 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1503 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1505 WCHAR szBuff[128], *szOut = szBuff + ARRAY_SIZE(szBuff) - 1;
1506 WCHAR szNegBuff[8];
1507 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1508 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1509 INT iRet;
1511 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1512 lpFormat, lpCurrencyStr, cchOut);
1514 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1515 !IsValidLocale(lcid, 0) ||
1516 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1517 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1518 lpFormat->PositiveOrder > 3)))
1520 goto error;
1523 if (!lpFormat)
1525 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1527 if (!node)
1528 goto error;
1530 lpFormat = &node->cyfmt;
1531 lpszNegStart = lpszNeg = GetNegative(node);
1533 else
1535 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1536 szNegBuff, ARRAY_SIZE(szNegBuff));
1537 lpszNegStart = lpszNeg = szNegBuff;
1539 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1541 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1542 lpszCyStart = lpFormat->lpCurrencySymbol;
1543 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1545 /* Format the currency backwards into a temporary buffer */
1547 szSrc = lpszValue;
1548 *szOut-- = '\0';
1550 /* Check the number for validity */
1551 while (*szSrc)
1553 if (*szSrc >= '0' && *szSrc <= '9')
1555 dwState |= NF_DIGITS;
1556 if (dwState & NF_ISREAL)
1557 dwDecimals++;
1559 else if (*szSrc == '-')
1561 if (dwState)
1562 goto error; /* '-' not first character */
1563 dwState |= NF_ISNEGATIVE;
1565 else if (*szSrc == '.')
1567 if (dwState & NF_ISREAL)
1568 goto error; /* More than one '.' */
1569 dwState |= NF_ISREAL;
1571 else
1572 goto error; /* Invalid char */
1573 szSrc++;
1575 szSrc--; /* Point to last character */
1577 if (!(dwState & NF_DIGITS))
1578 goto error; /* No digits */
1580 if (dwState & NF_ISNEGATIVE)
1581 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1582 else
1583 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1585 /* Add any trailing negative or currency signs */
1586 if (dwFmt & CF_PARENS)
1587 *szOut-- = ')';
1589 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1591 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1593 case CF_MINUS_RIGHT:
1594 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1595 while (lpszNeg >= lpszNegStart)
1596 *szOut-- = *lpszNeg--;
1597 dwFmt &= ~CF_MINUS_RIGHT;
1598 break;
1600 case CF_CY_RIGHT:
1601 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1602 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1603 while (lpszCy >= lpszCyStart)
1604 *szOut-- = *lpszCy--;
1605 if (dwFmt & CF_CY_SPACE)
1606 *szOut-- = ' ';
1607 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1608 break;
1612 /* Copy all digits up to the decimal point */
1613 if (!lpFormat->NumDigits)
1615 if (dwState & NF_ISREAL)
1617 while (*szSrc != '.') /* Don't write any decimals or a separator */
1619 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1620 dwState |= NF_ROUND;
1621 else
1622 dwState &= ~NF_ROUND;
1623 szSrc--;
1625 szSrc--;
1628 else
1630 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1632 if (dwDecimals <= lpFormat->NumDigits)
1634 dwDecimals = lpFormat->NumDigits - dwDecimals;
1635 while (dwDecimals--)
1636 *szOut-- = '0'; /* Pad to correct number of dp */
1638 else
1640 dwDecimals -= lpFormat->NumDigits;
1641 /* Skip excess decimals, and determine if we have to round the number */
1642 while (dwDecimals--)
1644 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1645 dwState |= NF_ROUND;
1646 else
1647 dwState &= ~NF_ROUND;
1648 szSrc--;
1652 if (dwState & NF_ISREAL)
1654 while (*szSrc != '.')
1656 if (dwState & NF_ROUND)
1658 if (*szSrc == '9')
1659 *szOut-- = '0'; /* continue rounding */
1660 else
1662 dwState &= ~NF_ROUND;
1663 *szOut-- = (*szSrc)+1;
1665 szSrc--;
1667 else
1668 *szOut-- = *szSrc--; /* Write existing decimals */
1670 szSrc--; /* Skip '.' */
1672 while (lpszDec >= lpFormat->lpDecimalSep)
1673 *szOut-- = *lpszDec--; /* Write decimal separator */
1676 dwGroupCount = lpFormat->Grouping;
1678 /* Write the remaining whole number digits, including grouping chars */
1679 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1681 if (dwState & NF_ROUND)
1683 if (*szSrc == '9')
1684 *szOut-- = '0'; /* continue rounding */
1685 else
1687 dwState &= ~NF_ROUND;
1688 *szOut-- = (*szSrc)+1;
1690 szSrc--;
1692 else
1693 *szOut-- = *szSrc--;
1695 dwState |= NF_DIGITS_OUT;
1696 dwCurrentGroupCount++;
1697 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1699 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1701 while (lpszGrp >= lpFormat->lpThousandSep)
1702 *szOut-- = *lpszGrp--; /* Write grouping char */
1704 dwCurrentGroupCount = 0;
1707 if (dwState & NF_ROUND)
1708 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1709 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1710 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1712 /* Add any leading negative or currency sign */
1713 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1715 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1717 case CF_MINUS_LEFT:
1718 case CF_MINUS_LEFT|CF_CY_LEFT:
1719 while (lpszNeg >= lpszNegStart)
1720 *szOut-- = *lpszNeg--;
1721 dwFmt &= ~CF_MINUS_LEFT;
1722 break;
1724 case CF_CY_LEFT:
1725 case CF_CY_LEFT|CF_MINUS_BEFORE:
1726 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1727 if (dwFmt & CF_CY_SPACE)
1728 *szOut-- = ' ';
1729 while (lpszCy >= lpszCyStart)
1730 *szOut-- = *lpszCy--;
1731 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1732 break;
1735 if (dwFmt & CF_PARENS)
1736 *szOut-- = '(';
1737 szOut++;
1739 iRet = strlenW(szOut) + 1;
1740 if (cchOut)
1742 if (iRet <= cchOut)
1743 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1744 else
1746 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1747 lpCurrencyStr[cchOut - 1] = '\0';
1748 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1749 iRet = 0;
1752 return iRet;
1754 error:
1755 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1756 return 0;
1759 /***********************************************************************
1760 * GetCurrencyFormatEx (KERNEL32.@)
1762 int WINAPI GetCurrencyFormatEx(LPCWSTR localename, DWORD flags, LPCWSTR value,
1763 const CURRENCYFMTW *format, LPWSTR str, int len)
1765 TRACE("(%s,0x%08x,%s,%p,%p,%d)\n", debugstr_w(localename), flags,
1766 debugstr_w(value), format, str, len);
1768 return GetCurrencyFormatW( LocaleNameToLCID(localename, 0), flags, value, format, str, len);
1772 /* FIXME: Everything below here needs to move somewhere else along with the
1773 * other EnumXXX functions, when a method for storing resources for
1774 * alternate calendars is determined.
1777 enum enum_callback_type {
1778 CALLBACK_ENUMPROC,
1779 CALLBACK_ENUMPROCEX,
1780 CALLBACK_ENUMPROCEXEX
1783 struct enumdateformats_context {
1784 enum enum_callback_type type; /* callback kind */
1785 union {
1786 DATEFMT_ENUMPROCW callback; /* user callback pointer */
1787 DATEFMT_ENUMPROCEXW callbackex;
1788 DATEFMT_ENUMPROCEXEX callbackexex;
1789 } u;
1790 LCID lcid; /* locale of interest */
1791 DWORD flags;
1792 LPARAM lParam;
1793 BOOL unicode; /* A vs W callback type, only for regular and Ex callbacks */
1796 /******************************************************************************
1797 * NLS_EnumDateFormats <internal>
1798 * Enumerates date formats for a specified locale.
1800 * PARAMS
1801 * ctxt [I] enumeration context, see 'struct enumdateformats_context'
1803 * RETURNS
1804 * Success: TRUE.
1805 * Failure: FALSE. Use GetLastError() to determine the cause.
1807 static BOOL NLS_EnumDateFormats(const struct enumdateformats_context *ctxt)
1809 WCHAR bufW[256];
1810 char bufA[256];
1811 LCTYPE lctype;
1812 CALID cal_id;
1813 INT ret;
1815 if (!ctxt->u.callback)
1817 SetLastError(ERROR_INVALID_PARAMETER);
1818 return FALSE;
1821 if (!GetLocaleInfoW(ctxt->lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1822 return FALSE;
1824 switch (ctxt->flags & ~LOCALE_USE_CP_ACP)
1826 case 0:
1827 case DATE_SHORTDATE:
1828 lctype = LOCALE_SSHORTDATE;
1829 break;
1830 case DATE_LONGDATE:
1831 lctype = LOCALE_SLONGDATE;
1832 break;
1833 case DATE_YEARMONTH:
1834 lctype = LOCALE_SYEARMONTH;
1835 break;
1836 default:
1837 FIXME("Unknown date format (0x%08x)\n", ctxt->flags);
1838 SetLastError(ERROR_INVALID_PARAMETER);
1839 return FALSE;
1842 lctype |= ctxt->flags & LOCALE_USE_CP_ACP;
1843 if (ctxt->unicode)
1844 ret = GetLocaleInfoW(ctxt->lcid, lctype, bufW, ARRAY_SIZE(bufW));
1845 else
1846 ret = GetLocaleInfoA(ctxt->lcid, lctype, bufA, ARRAY_SIZE(bufA));
1848 if (ret)
1850 switch (ctxt->type)
1852 case CALLBACK_ENUMPROC:
1853 ctxt->u.callback(ctxt->unicode ? bufW : (WCHAR*)bufA);
1854 break;
1855 case CALLBACK_ENUMPROCEX:
1856 ctxt->u.callbackex(ctxt->unicode ? bufW : (WCHAR*)bufA, cal_id);
1857 break;
1858 case CALLBACK_ENUMPROCEXEX:
1859 ctxt->u.callbackexex(bufW, cal_id, ctxt->lParam);
1860 break;
1861 default:
1866 return TRUE;
1869 /**************************************************************************
1870 * EnumDateFormatsExA (KERNEL32.@)
1872 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1873 * LOCALE_NOUSEROVERRIDE here as well?
1875 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1877 struct enumdateformats_context ctxt;
1879 ctxt.type = CALLBACK_ENUMPROCEX;
1880 ctxt.u.callbackex = (DATEFMT_ENUMPROCEXW)proc;
1881 ctxt.lcid = lcid;
1882 ctxt.flags = flags;
1883 ctxt.unicode = FALSE;
1885 return NLS_EnumDateFormats(&ctxt);
1888 /**************************************************************************
1889 * EnumDateFormatsExW (KERNEL32.@)
1891 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1893 struct enumdateformats_context ctxt;
1895 ctxt.type = CALLBACK_ENUMPROCEX;
1896 ctxt.u.callbackex = proc;
1897 ctxt.lcid = lcid;
1898 ctxt.flags = flags;
1899 ctxt.unicode = TRUE;
1901 return NLS_EnumDateFormats(&ctxt);
1904 /**************************************************************************
1905 * EnumDateFormatsA (KERNEL32.@)
1907 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1908 * LOCALE_NOUSEROVERRIDE here as well?
1910 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1912 struct enumdateformats_context ctxt;
1914 ctxt.type = CALLBACK_ENUMPROC;
1915 ctxt.u.callback = (DATEFMT_ENUMPROCW)proc;
1916 ctxt.lcid = lcid;
1917 ctxt.flags = flags;
1918 ctxt.unicode = FALSE;
1920 return NLS_EnumDateFormats(&ctxt);
1923 /**************************************************************************
1924 * EnumDateFormatsW (KERNEL32.@)
1926 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1928 struct enumdateformats_context ctxt;
1930 ctxt.type = CALLBACK_ENUMPROC;
1931 ctxt.u.callback = proc;
1932 ctxt.lcid = lcid;
1933 ctxt.flags = flags;
1934 ctxt.unicode = TRUE;
1936 return NLS_EnumDateFormats(&ctxt);
1939 /**************************************************************************
1940 * EnumDateFormatsExEx (KERNEL32.@)
1942 BOOL WINAPI EnumDateFormatsExEx(DATEFMT_ENUMPROCEXEX proc, const WCHAR *locale, DWORD flags, LPARAM lParam)
1944 struct enumdateformats_context ctxt;
1946 ctxt.type = CALLBACK_ENUMPROCEXEX;
1947 ctxt.u.callbackexex = proc;
1948 ctxt.lcid = LocaleNameToLCID(locale, 0);
1949 ctxt.flags = flags;
1950 ctxt.lParam = lParam;
1951 ctxt.unicode = TRUE;
1953 return NLS_EnumDateFormats(&ctxt);
1956 struct enumtimeformats_context {
1957 enum enum_callback_type type; /* callback kind */
1958 union {
1959 TIMEFMT_ENUMPROCW callback; /* user callback pointer */
1960 TIMEFMT_ENUMPROCEX callbackex;
1961 } u;
1962 LCID lcid; /* locale of interest */
1963 DWORD flags;
1964 LPARAM lParam;
1965 BOOL unicode; /* A vs W callback type, only for regular and Ex callbacks */
1968 static BOOL NLS_EnumTimeFormats(struct enumtimeformats_context *ctxt)
1970 WCHAR bufW[256];
1971 char bufA[256];
1972 LCTYPE lctype;
1973 INT ret;
1975 if (!ctxt->u.callback)
1977 SetLastError(ERROR_INVALID_PARAMETER);
1978 return FALSE;
1981 switch (ctxt->flags & ~LOCALE_USE_CP_ACP)
1983 case 0:
1984 lctype = LOCALE_STIMEFORMAT;
1985 break;
1986 case TIME_NOSECONDS:
1987 lctype = LOCALE_SSHORTTIME;
1988 break;
1989 default:
1990 FIXME("Unknown time format (%d)\n", ctxt->flags);
1991 SetLastError(ERROR_INVALID_PARAMETER);
1992 return FALSE;
1995 lctype |= ctxt->flags & LOCALE_USE_CP_ACP;
1996 if (ctxt->unicode)
1997 ret = GetLocaleInfoW(ctxt->lcid, lctype, bufW, ARRAY_SIZE(bufW));
1998 else
1999 ret = GetLocaleInfoA(ctxt->lcid, lctype, bufA, ARRAY_SIZE(bufA));
2001 if (ret)
2003 switch (ctxt->type)
2005 case CALLBACK_ENUMPROC:
2006 ctxt->u.callback(ctxt->unicode ? bufW : (WCHAR*)bufA);
2007 break;
2008 case CALLBACK_ENUMPROCEX:
2009 ctxt->u.callbackex(bufW, ctxt->lParam);
2010 break;
2011 default:
2016 return TRUE;
2019 /**************************************************************************
2020 * EnumTimeFormatsA (KERNEL32.@)
2022 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
2023 * LOCALE_NOUSEROVERRIDE here as well?
2025 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
2027 struct enumtimeformats_context ctxt;
2029 /* EnumTimeFormatsA doesn't support flags, EnumTimeFormatsW does. */
2030 if (flags & ~LOCALE_USE_CP_ACP)
2032 SetLastError(ERROR_INVALID_FLAGS);
2033 return FALSE;
2036 ctxt.type = CALLBACK_ENUMPROC;
2037 ctxt.u.callback = (TIMEFMT_ENUMPROCW)proc;
2038 ctxt.lcid = lcid;
2039 ctxt.flags = flags;
2040 ctxt.unicode = FALSE;
2042 return NLS_EnumTimeFormats(&ctxt);
2045 /**************************************************************************
2046 * EnumTimeFormatsW (KERNEL32.@)
2048 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
2050 struct enumtimeformats_context ctxt;
2052 ctxt.type = CALLBACK_ENUMPROC;
2053 ctxt.u.callback = proc;
2054 ctxt.lcid = lcid;
2055 ctxt.flags = flags;
2056 ctxt.unicode = TRUE;
2058 return NLS_EnumTimeFormats(&ctxt);
2061 /**************************************************************************
2062 * EnumTimeFormatsEx (KERNEL32.@)
2064 BOOL WINAPI EnumTimeFormatsEx(TIMEFMT_ENUMPROCEX proc, const WCHAR *locale, DWORD flags, LPARAM lParam)
2066 struct enumtimeformats_context ctxt;
2068 ctxt.type = CALLBACK_ENUMPROCEX;
2069 ctxt.u.callbackex = proc;
2070 ctxt.lcid = LocaleNameToLCID(locale, 0);
2071 ctxt.flags = flags;
2072 ctxt.lParam = lParam;
2073 ctxt.unicode = TRUE;
2075 return NLS_EnumTimeFormats(&ctxt);
2078 struct enumcalendar_context {
2079 enum enum_callback_type type; /* callback kind */
2080 union {
2081 CALINFO_ENUMPROCW callback; /* user callback pointer */
2082 CALINFO_ENUMPROCEXW callbackex;
2083 CALINFO_ENUMPROCEXEX callbackexex;
2084 } u;
2085 LCID lcid; /* locale of interest */
2086 CALID calendar; /* specific calendar or ENUM_ALL_CALENDARS */
2087 CALTYPE caltype; /* calendar information type */
2088 LPARAM lParam; /* user input parameter passed to callback, for ExEx case only */
2089 BOOL unicode; /* A vs W callback type, only for regular and Ex callbacks */
2092 /******************************************************************************
2093 * NLS_EnumCalendarInfo <internal>
2094 * Enumerates calendar information for a specified locale.
2096 * PARAMS
2097 * ctxt [I] enumeration context, see 'struct enumcalendar_context'
2099 * RETURNS
2100 * Success: TRUE.
2101 * Failure: FALSE. Use GetLastError() to determine the cause.
2103 * NOTES
2104 * When the ANSI version of this function is used with a Unicode-only LCID,
2105 * the call can succeed because the system uses the system code page.
2106 * However, characters that are undefined in the system code page appear
2107 * in the string as a question mark (?).
2109 * TODO
2110 * The above note should be respected by GetCalendarInfoA.
2112 static BOOL NLS_EnumCalendarInfo(const struct enumcalendar_context *ctxt)
2114 WCHAR *buf, *opt = NULL, *iter = NULL;
2115 CALID calendar = ctxt->calendar;
2116 BOOL ret = FALSE;
2117 int bufSz = 200; /* the size of the buffer */
2119 if (ctxt->u.callback == NULL)
2121 SetLastError(ERROR_INVALID_PARAMETER);
2122 return FALSE;
2125 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
2126 if (buf == NULL)
2128 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2129 return FALSE;
2132 if (calendar == ENUM_ALL_CALENDARS)
2134 int optSz = GetLocaleInfoW(ctxt->lcid, LOCALE_IOPTIONALCALENDAR, NULL, 0);
2135 if (optSz > 1)
2137 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
2138 if (opt == NULL)
2140 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2141 goto cleanup;
2143 if (GetLocaleInfoW(ctxt->lcid, LOCALE_IOPTIONALCALENDAR, opt, optSz))
2144 iter = opt;
2146 calendar = NLS_GetLocaleNumber(ctxt->lcid, LOCALE_ICALENDARTYPE);
2149 while (TRUE) /* loop through calendars */
2151 do /* loop until there's no error */
2153 if (ctxt->caltype & CAL_RETURN_NUMBER)
2154 ret = GetCalendarInfoW(ctxt->lcid, calendar, ctxt->caltype, NULL, bufSz / sizeof(WCHAR), (LPDWORD)buf);
2155 else if (ctxt->unicode)
2156 ret = GetCalendarInfoW(ctxt->lcid, calendar, ctxt->caltype, buf, bufSz / sizeof(WCHAR), NULL);
2157 else ret = GetCalendarInfoA(ctxt->lcid, calendar, ctxt->caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
2159 if (!ret)
2161 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
2162 { /* so resize it */
2163 int newSz;
2164 if (ctxt->unicode)
2165 newSz = GetCalendarInfoW(ctxt->lcid, calendar, ctxt->caltype, NULL, 0, NULL) * sizeof(WCHAR);
2166 else newSz = GetCalendarInfoA(ctxt->lcid, calendar, ctxt->caltype, NULL, 0, NULL) * sizeof(CHAR);
2167 if (bufSz >= newSz)
2169 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
2170 goto cleanup;
2172 bufSz = newSz;
2173 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
2174 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
2175 if (buf == NULL)
2176 goto cleanup;
2177 } else goto cleanup;
2179 } while (!ret);
2181 /* Here we are. We pass the buffer to the correct version of
2182 * the callback. Because it's not the same number of params,
2183 * we must check for Ex, but we don't care about Unicode
2184 * because the buffer is already in the correct format.
2186 switch (ctxt->type)
2188 case CALLBACK_ENUMPROC:
2189 ret = ctxt->u.callback(buf);
2190 break;
2191 case CALLBACK_ENUMPROCEX:
2192 ret = ctxt->u.callbackex(buf, calendar);
2193 break;
2194 case CALLBACK_ENUMPROCEXEX:
2195 ret = ctxt->u.callbackexex(buf, calendar, NULL, ctxt->lParam);
2196 break;
2197 default:
2201 if (!ret) { /* the callback told to stop */
2202 ret = TRUE;
2203 break;
2206 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2207 break;
2209 calendar = 0;
2210 while ((*iter >= '0') && (*iter <= '9'))
2211 calendar = calendar * 10 + *iter++ - '0';
2213 if (*iter++ != 0)
2215 SetLastError(ERROR_BADDB);
2216 ret = FALSE;
2217 break;
2221 cleanup:
2222 HeapFree(GetProcessHeap(), 0, opt);
2223 HeapFree(GetProcessHeap(), 0, buf);
2224 return ret;
2227 /******************************************************************************
2228 * EnumCalendarInfoA [KERNEL32.@]
2230 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2231 CALID calendar,CALTYPE caltype )
2233 struct enumcalendar_context ctxt;
2235 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2237 ctxt.type = CALLBACK_ENUMPROC;
2238 ctxt.u.callback = (CALINFO_ENUMPROCW)calinfoproc;
2239 ctxt.lcid = locale;
2240 ctxt.calendar = calendar;
2241 ctxt.caltype = caltype;
2242 ctxt.lParam = 0;
2243 ctxt.unicode = FALSE;
2244 return NLS_EnumCalendarInfo(&ctxt);
2247 /******************************************************************************
2248 * EnumCalendarInfoW [KERNEL32.@]
2250 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2251 CALID calendar,CALTYPE caltype )
2253 struct enumcalendar_context ctxt;
2255 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2257 ctxt.type = CALLBACK_ENUMPROC;
2258 ctxt.u.callback = calinfoproc;
2259 ctxt.lcid = locale;
2260 ctxt.calendar = calendar;
2261 ctxt.caltype = caltype;
2262 ctxt.lParam = 0;
2263 ctxt.unicode = TRUE;
2264 return NLS_EnumCalendarInfo(&ctxt);
2267 /******************************************************************************
2268 * EnumCalendarInfoExA [KERNEL32.@]
2270 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2271 CALID calendar,CALTYPE caltype )
2273 struct enumcalendar_context ctxt;
2275 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2277 ctxt.type = CALLBACK_ENUMPROCEX;
2278 ctxt.u.callbackex = (CALINFO_ENUMPROCEXW)calinfoproc;
2279 ctxt.lcid = locale;
2280 ctxt.calendar = calendar;
2281 ctxt.caltype = caltype;
2282 ctxt.lParam = 0;
2283 ctxt.unicode = FALSE;
2284 return NLS_EnumCalendarInfo(&ctxt);
2287 /******************************************************************************
2288 * EnumCalendarInfoExW [KERNEL32.@]
2290 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2291 CALID calendar,CALTYPE caltype )
2293 struct enumcalendar_context ctxt;
2295 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2297 ctxt.type = CALLBACK_ENUMPROCEX;
2298 ctxt.u.callbackex = calinfoproc;
2299 ctxt.lcid = locale;
2300 ctxt.calendar = calendar;
2301 ctxt.caltype = caltype;
2302 ctxt.lParam = 0;
2303 ctxt.unicode = TRUE;
2304 return NLS_EnumCalendarInfo(&ctxt);
2307 /******************************************************************************
2308 * EnumCalendarInfoExEx [KERNEL32.@]
2310 BOOL WINAPI EnumCalendarInfoExEx( CALINFO_ENUMPROCEXEX calinfoproc, LPCWSTR locale, CALID calendar,
2311 LPCWSTR reserved, CALTYPE caltype, LPARAM lParam)
2313 struct enumcalendar_context ctxt;
2315 TRACE("(%p,%s,0x%08x,%p,0x%08x,0x%ld)\n", calinfoproc, debugstr_w(locale), calendar, reserved, caltype, lParam);
2317 ctxt.type = CALLBACK_ENUMPROCEXEX;
2318 ctxt.u.callbackexex = calinfoproc;
2319 ctxt.lcid = LocaleNameToLCID(locale, 0);
2320 ctxt.calendar = calendar;
2321 ctxt.caltype = caltype;
2322 ctxt.lParam = lParam;
2323 ctxt.unicode = TRUE;
2324 return NLS_EnumCalendarInfo(&ctxt);