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
26 #include "wine/port.h"
35 #include "wine/unicode.h"
36 #include "wine/debug.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;
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 =
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)
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');
113 const WCHAR* iter = szBuff;
115 while(*iter >= '0' && *iter <= '9')
116 dwVal = dwVal * 10 + (*iter++ - '0');
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;
132 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
133 dwLen = strlenW(szBuff) + 1;
134 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
136 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
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,
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,
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)
197 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
199 NLS_FORMAT_NODE *new_node;
202 TRACE("Creating new cache entry\n");
204 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
207 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
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)
288 node = NLS_CachedFormats = new_node; /* Empty list */
291 else if (node->lcid != lcid || node->dwFlags != dwFlags)
293 node->next = new_node; /* Not in the list, add to end */
298 RtlLeaveCriticalSection(&NLS_FormatsCS);
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);
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))
339 TRACE("lcid 0x%08x: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
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| \
360 /******************************************************************************
361 * NLS_GetDateTimeFormatW <internal>
363 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
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;
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))
387 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
388 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
393 if (dwFlags & DATE_DATEVARSONLY)
395 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
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))
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);
426 lpFormat = GetShortDate(node);
429 lpFormat = GetTime(node);
434 GetLocalTime(&st); /* Default to current time */
439 if (dwFlags & DATE_DATEVARSONLY)
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);
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 */
467 if (IsLiteralMarker(*lpFormat))
469 /* Start of a literal string */
472 /* Loop until the end of the literal marker or end of the string */
475 if (IsLiteralMarker(*lpFormat))
478 if (!IsLiteralMarker(*lpFormat))
479 break; /* Terminating literal marker */
483 cchWritten++; /* Count size only */
484 else if (cchWritten >= cchOut)
488 lpStr[cchWritten] = *lpFormat;
494 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
495 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
497 WCHAR buff[32], fmtChar;
498 LPCWSTR szAdd = NULL;
500 int count = 0, dwLen;
505 while (*lpFormat == fmtChar)
512 if (fmtChar != 'M') d_dd_formatted = FALSE;
517 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
519 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
522 dwVal = lpTime->wDay;
524 d_dd_formatted = TRUE;
531 LPCWSTR genitive = GetGenitiveMonth(node, lpTime->wMonth - 1);
541 LPCWSTR format = lpFormat;
542 /* Look forward now, if next format pattern is for day genitive
543 name should be used */
546 /* Skip parts within markers */
547 if (IsLiteralMarker(*format))
552 if (IsLiteralMarker(*format))
555 if (!IsLiteralMarker(*format)) break;
559 if (*format != ' ') break;
562 /* Only numeric day form matters */
563 if (*format && *format == 'd')
566 while (*++format == 'd') dcount++;
575 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
578 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
581 dwVal = lpTime->wMonth;
590 dwVal = lpTime->wYear;
594 count = count > 2 ? 2 : count;
595 dwVal = lpTime->wYear % 100;
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';
611 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
617 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
619 count = count > 2 ? 2 : count;
620 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
624 /* .. fall through if we are forced to output in 24 hour format */
627 count = count > 2 ? 2 : count;
628 dwVal = lpTime->wHour;
633 if (dwFlags & TIME_NOMINUTESORSECONDS)
635 cchWritten = lastFormatPos; /* Skip */
640 count = count > 2 ? 2 : count;
641 dwVal = lpTime->wMinute;
647 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
649 cchWritten = lastFormatPos; /* Skip */
654 count = count > 2 ? 2 : count;
655 dwVal = lpTime->wSecond;
661 if (dwFlags & TIME_NOTIMEMARKER)
663 cchWritten = lastFormatPos; /* Skip */
669 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
671 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
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;
687 if (cchWritten + dwLen < cchOut)
688 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
691 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
696 lastFormatPos = cchWritten; /* Save position of last output format text */
700 /* Literal character */
702 cchWritten++; /* Count size only */
703 else if (cchWritten >= cchOut)
705 else if (!bSkipping || *lpFormat == ' ')
707 lpStr[cchWritten] = *lpFormat;
714 /* Final string terminator and sanity check */
717 if (cchWritten >= cchOut)
720 lpStr[cchWritten] = '\0';
722 cchWritten++; /* Include terminating NUL */
724 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
728 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
729 SetLastError(ERROR_INSUFFICIENT_BUFFER);
733 SetLastError(ERROR_INVALID_PARAMETER);
737 SetLastError(ERROR_INVALID_FLAGS);
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)
751 WCHAR szFormat[128], szOut[128];
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);
763 if (!(dwFlags & LOCALE_USE_CP_ACP))
765 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
768 SetLastError(ERROR_INVALID_PARAMETER);
772 cp = node->dwCodePage;
776 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
778 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
779 cchOut = sizeof(szOut)/sizeof(WCHAR);
783 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
784 lpStr ? szOut : NULL, cchOut);
789 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
790 else if (cchOut && iRet)
796 /******************************************************************************
797 * GetDateFormatA [KERNEL32.@]
799 * Format a date for a given locale.
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
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:
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.
835 * Success: The number of character written to lpDateStr, or that would
836 * have been written, if cchOut is 0.
837 * Failure: 0. Use GetLastError() to determine the cause.
839 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
840 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
842 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
843 debugstr_a(lpFormat), lpDateStr, cchOut);
845 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
846 lpFormat, lpDateStr, cchOut);
850 /******************************************************************************
851 * GetDateFormatW [KERNEL32.@]
853 * See GetDateFormatA.
855 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
856 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
858 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
859 debugstr_w(lpFormat), lpDateStr, cchOut);
861 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
862 lpFormat, lpDateStr, cchOut);
865 /******************************************************************************
866 * GetTimeFormatA [KERNEL32.@]
868 * Format a time for a given locale.
871 * lcid [I] Locale to format for
872 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
873 * lpTime [I] Time to format
874 * lpFormat [I] Formatting overrides
875 * lpTimeStr [O] Destination for formatted string
876 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
879 * - If lpFormat is NULL, lpszValue will be formatted according to the format
880 * details returned by GetLocaleInfoA() and modified by dwFlags.
881 * - lpFormat is a string of characters and formatting tokens. Any characters
882 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
883 * by the time values they represent.
884 * - The following tokens have special meanings in a time format string:
887 *| h Hours with no leading zero (12-hour clock)
888 *| hh Hours with full two digits (12-hour clock)
889 *| H Hours with no leading zero (24-hour clock)
890 *| HH Hours with full two digits (24-hour clock)
891 *| m Minutes with no leading zero
892 *| mm Minutes with full two digits
893 *| s Seconds with no leading zero
894 *| ss Seconds with full two digits
895 *| t Short time marker (e.g. "A" or "P")
896 *| tt Long time marker (e.g. "AM", "PM")
897 * - To output any literal character that could be misidentified as a token,
898 * enclose it in single quotes.
899 * - The Ascii version of this function fails if lcid is Unicode only.
902 * Success: The number of character written to lpTimeStr, or that would
903 * have been written, if cchOut is 0.
904 * Failure: 0. Use GetLastError() to determine the cause.
906 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
907 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
909 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
910 debugstr_a(lpFormat), lpTimeStr, cchOut);
912 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
913 lpFormat, lpTimeStr, cchOut);
916 /******************************************************************************
917 * GetTimeFormatW [KERNEL32.@]
919 * See GetTimeFormatA.
921 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
922 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
924 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
925 debugstr_w(lpFormat), lpTimeStr, cchOut);
927 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
928 lpFormat, lpTimeStr, cchOut);
931 /**************************************************************************
932 * GetNumberFormatA (KERNEL32.@)
934 * Format a number string for a given locale.
937 * lcid [I] Locale to format for
938 * dwFlags [I] LOCALE_ flags from "winnls.h"
939 * lpszValue [I] String to format
940 * lpFormat [I] Formatting overrides
941 * lpNumberStr [O] Destination for formatted string
942 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
945 * - lpszValue can contain only '0' - '9', '-' and '.'.
946 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
947 * be formatted according to the format details returned by GetLocaleInfoA().
948 * - This function rounds the number string if the number of decimals exceeds the
949 * locales normal number of decimal places.
950 * - If cchOut is 0, this function does not write to lpNumberStr.
951 * - The Ascii version of this function fails if lcid is Unicode only.
954 * Success: The number of character written to lpNumberStr, or that would
955 * have been written, if cchOut is 0.
956 * Failure: 0. Use GetLastError() to determine the cause.
958 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
959 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
960 LPSTR lpNumberStr, int cchOut)
963 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
965 const NUMBERFMTW *pfmt = NULL;
968 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
969 lpFormat, lpNumberStr, cchOut);
971 if (NLS_IsUnicodeOnlyLcid(lcid))
973 SetLastError(ERROR_INVALID_PARAMETER);
977 if (!(dwFlags & LOCALE_USE_CP_ACP))
979 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
982 SetLastError(ERROR_INVALID_PARAMETER);
986 cp = node->dwCodePage;
991 memcpy(&fmt, lpFormat, sizeof(fmt));
993 if (lpFormat->lpDecimalSep)
995 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
996 fmt.lpDecimalSep = szDec;
998 if (lpFormat->lpThousandSep)
1000 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1001 fmt.lpThousandSep = szGrp;
1006 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1008 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1009 cchOut = sizeof(szOut)/sizeof(WCHAR);
1013 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1014 lpNumberStr ? szOut : NULL, cchOut);
1016 if (szOut[0] && lpNumberStr)
1017 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
1021 /* Number parsing state flags */
1022 #define NF_ISNEGATIVE 0x1 /* '-' found */
1023 #define NF_ISREAL 0x2 /* '.' found */
1024 #define NF_DIGITS 0x4 /* '0'-'9' found */
1025 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1026 #define NF_ROUND 0x10 /* Number needs to be rounded */
1028 /* Formatting options for Numbers */
1029 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1030 #define NLS_NEG_LEFT 1 /* "-1.1" */
1031 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1032 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1033 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1035 /**************************************************************************
1036 * GetNumberFormatW (KERNEL32.@)
1038 * See GetNumberFormatA.
1040 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
1041 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
1042 LPWSTR lpNumberStr, int cchOut)
1044 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1046 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1047 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1050 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1051 lpFormat, lpNumberStr, cchOut);
1053 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
1054 !IsValidLocale(lcid, 0) ||
1055 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
1062 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1066 lpFormat = &node->fmt;
1067 lpszNegStart = lpszNeg = GetNegative(node);
1071 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1072 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1073 lpszNegStart = lpszNeg = szNegBuff;
1075 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1077 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1079 /* Format the number backwards into a temporary buffer */
1084 /* Check the number for validity */
1087 if (*szSrc >= '0' && *szSrc <= '9')
1089 dwState |= NF_DIGITS;
1090 if (dwState & NF_ISREAL)
1093 else if (*szSrc == '-')
1096 goto error; /* '-' not first character */
1097 dwState |= NF_ISNEGATIVE;
1099 else if (*szSrc == '.')
1101 if (dwState & NF_ISREAL)
1102 goto error; /* More than one '.' */
1103 dwState |= NF_ISREAL;
1106 goto error; /* Invalid char */
1109 szSrc--; /* Point to last character */
1111 if (!(dwState & NF_DIGITS))
1112 goto error; /* No digits */
1114 /* Add any trailing negative sign */
1115 if (dwState & NF_ISNEGATIVE)
1117 switch (lpFormat->NegativeOrder)
1119 case NLS_NEG_PARENS:
1123 case NLS_NEG_RIGHT_SPACE:
1124 while (lpszNeg >= lpszNegStart)
1125 *szOut-- = *lpszNeg--;
1126 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1132 /* Copy all digits up to the decimal point */
1133 if (!lpFormat->NumDigits)
1135 if (dwState & NF_ISREAL)
1137 while (*szSrc != '.') /* Don't write any decimals or a separator */
1139 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1140 dwState |= NF_ROUND;
1142 dwState &= ~NF_ROUND;
1150 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1152 if (dwDecimals <= lpFormat->NumDigits)
1154 dwDecimals = lpFormat->NumDigits - dwDecimals;
1155 while (dwDecimals--)
1156 *szOut-- = '0'; /* Pad to correct number of dp */
1160 dwDecimals -= lpFormat->NumDigits;
1161 /* Skip excess decimals, and determine if we have to round the number */
1162 while (dwDecimals--)
1164 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1165 dwState |= NF_ROUND;
1167 dwState &= ~NF_ROUND;
1172 if (dwState & NF_ISREAL)
1174 while (*szSrc != '.')
1176 if (dwState & NF_ROUND)
1179 *szOut-- = '0'; /* continue rounding */
1182 dwState &= ~NF_ROUND;
1183 *szOut-- = (*szSrc)+1;
1188 *szOut-- = *szSrc--; /* Write existing decimals */
1190 szSrc--; /* Skip '.' */
1193 while (lpszDec >= lpFormat->lpDecimalSep)
1194 *szOut-- = *lpszDec--; /* Write decimal separator */
1197 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1199 /* Write the remaining whole number digits, including grouping chars */
1200 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1202 if (dwState & NF_ROUND)
1205 *szOut-- = '0'; /* continue rounding */
1208 dwState &= ~NF_ROUND;
1209 *szOut-- = (*szSrc)+1;
1214 *szOut-- = *szSrc--;
1216 dwState |= NF_DIGITS_OUT;
1217 dwCurrentGroupCount++;
1218 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1220 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1222 while (lpszGrp >= lpFormat->lpThousandSep)
1223 *szOut-- = *lpszGrp--; /* Write grouping char */
1225 dwCurrentGroupCount = 0;
1226 if (lpFormat->Grouping == 32)
1227 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1230 if (dwState & NF_ROUND)
1232 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1234 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1235 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1237 /* Add any leading negative sign */
1238 if (dwState & NF_ISNEGATIVE)
1240 switch (lpFormat->NegativeOrder)
1242 case NLS_NEG_PARENS:
1245 case NLS_NEG_LEFT_SPACE:
1249 while (lpszNeg >= lpszNegStart)
1250 *szOut-- = *lpszNeg--;
1256 iRet = strlenW(szOut) + 1;
1260 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1263 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1264 lpNumberStr[cchOut - 1] = '\0';
1265 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1272 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1276 /**************************************************************************
1277 * GetCurrencyFormatA (KERNEL32.@)
1279 * Format a currency string for a given locale.
1282 * lcid [I] Locale to format for
1283 * dwFlags [I] LOCALE_ flags from "winnls.h"
1284 * lpszValue [I] String to format
1285 * lpFormat [I] Formatting overrides
1286 * lpCurrencyStr [O] Destination for formatted string
1287 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1290 * - lpszValue can contain only '0' - '9', '-' and '.'.
1291 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1292 * be formatted according to the format details returned by GetLocaleInfoA().
1293 * - This function rounds the currency if the number of decimals exceeds the
1294 * locales number of currency decimal places.
1295 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1296 * - The Ascii version of this function fails if lcid is Unicode only.
1299 * Success: The number of character written to lpNumberStr, or that would
1300 * have been written, if cchOut is 0.
1301 * Failure: 0. Use GetLastError() to determine the cause.
1303 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1304 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1305 LPSTR lpCurrencyStr, int cchOut)
1308 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1310 const CURRENCYFMTW *pfmt = NULL;
1313 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1314 lpFormat, lpCurrencyStr, cchOut);
1316 if (NLS_IsUnicodeOnlyLcid(lcid))
1318 SetLastError(ERROR_INVALID_PARAMETER);
1322 if (!(dwFlags & LOCALE_USE_CP_ACP))
1324 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1327 SetLastError(ERROR_INVALID_PARAMETER);
1331 cp = node->dwCodePage;
1336 memcpy(&fmt, lpFormat, sizeof(fmt));
1338 if (lpFormat->lpDecimalSep)
1340 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1341 fmt.lpDecimalSep = szDec;
1343 if (lpFormat->lpThousandSep)
1345 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1346 fmt.lpThousandSep = szGrp;
1348 if (lpFormat->lpCurrencySymbol)
1350 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1351 fmt.lpCurrencySymbol = szCy;
1356 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1358 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1359 cchOut = sizeof(szOut)/sizeof(WCHAR);
1363 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1364 lpCurrencyStr ? szOut : NULL, cchOut);
1366 if (szOut[0] && lpCurrencyStr)
1367 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1371 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1372 #define CF_PARENS 0x1 /* Parentheses */
1373 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1374 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1375 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1376 #define CF_CY_LEFT 0x10 /* '$' to the left */
1377 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1378 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1380 /**************************************************************************
1381 * GetCurrencyFormatW (KERNEL32.@)
1383 * See GetCurrencyFormatA.
1385 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1386 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1387 LPWSTR lpCurrencyStr, int cchOut)
1389 static const BYTE NLS_NegCyFormats[16] =
1391 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1392 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1393 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1394 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1395 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1396 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1397 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1398 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1399 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1400 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1401 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1402 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1403 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1404 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1405 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1406 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1408 static const BYTE NLS_PosCyFormats[4] =
1410 CF_CY_LEFT, /* $1.1 */
1411 CF_CY_RIGHT, /* 1.1$ */
1412 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1413 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1415 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1417 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1418 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1421 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1422 lpFormat, lpCurrencyStr, cchOut);
1424 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1425 !IsValidLocale(lcid, 0) ||
1426 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1427 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1428 lpFormat->PositiveOrder > 3)))
1435 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1440 lpFormat = &node->cyfmt;
1441 lpszNegStart = lpszNeg = GetNegative(node);
1445 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1446 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1447 lpszNegStart = lpszNeg = szNegBuff;
1449 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1451 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1452 lpszCyStart = lpFormat->lpCurrencySymbol;
1453 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1455 /* Format the currency backwards into a temporary buffer */
1460 /* Check the number for validity */
1463 if (*szSrc >= '0' && *szSrc <= '9')
1465 dwState |= NF_DIGITS;
1466 if (dwState & NF_ISREAL)
1469 else if (*szSrc == '-')
1472 goto error; /* '-' not first character */
1473 dwState |= NF_ISNEGATIVE;
1475 else if (*szSrc == '.')
1477 if (dwState & NF_ISREAL)
1478 goto error; /* More than one '.' */
1479 dwState |= NF_ISREAL;
1482 goto error; /* Invalid char */
1485 szSrc--; /* Point to last character */
1487 if (!(dwState & NF_DIGITS))
1488 goto error; /* No digits */
1490 if (dwState & NF_ISNEGATIVE)
1491 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1493 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1495 /* Add any trailing negative or currency signs */
1496 if (dwFmt & CF_PARENS)
1499 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1501 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1503 case CF_MINUS_RIGHT:
1504 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1505 while (lpszNeg >= lpszNegStart)
1506 *szOut-- = *lpszNeg--;
1507 dwFmt &= ~CF_MINUS_RIGHT;
1511 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1512 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1513 while (lpszCy >= lpszCyStart)
1514 *szOut-- = *lpszCy--;
1515 if (dwFmt & CF_CY_SPACE)
1517 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1522 /* Copy all digits up to the decimal point */
1523 if (!lpFormat->NumDigits)
1525 if (dwState & NF_ISREAL)
1527 while (*szSrc != '.') /* Don't write any decimals or a separator */
1529 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1530 dwState |= NF_ROUND;
1532 dwState &= ~NF_ROUND;
1540 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1542 if (dwDecimals <= lpFormat->NumDigits)
1544 dwDecimals = lpFormat->NumDigits - dwDecimals;
1545 while (dwDecimals--)
1546 *szOut-- = '0'; /* Pad to correct number of dp */
1550 dwDecimals -= lpFormat->NumDigits;
1551 /* Skip excess decimals, and determine if we have to round the number */
1552 while (dwDecimals--)
1554 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1555 dwState |= NF_ROUND;
1557 dwState &= ~NF_ROUND;
1562 if (dwState & NF_ISREAL)
1564 while (*szSrc != '.')
1566 if (dwState & NF_ROUND)
1569 *szOut-- = '0'; /* continue rounding */
1572 dwState &= ~NF_ROUND;
1573 *szOut-- = (*szSrc)+1;
1578 *szOut-- = *szSrc--; /* Write existing decimals */
1580 szSrc--; /* Skip '.' */
1582 while (lpszDec >= lpFormat->lpDecimalSep)
1583 *szOut-- = *lpszDec--; /* Write decimal separator */
1586 dwGroupCount = lpFormat->Grouping;
1588 /* Write the remaining whole number digits, including grouping chars */
1589 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1591 if (dwState & NF_ROUND)
1594 *szOut-- = '0'; /* continue rounding */
1597 dwState &= ~NF_ROUND;
1598 *szOut-- = (*szSrc)+1;
1603 *szOut-- = *szSrc--;
1605 dwState |= NF_DIGITS_OUT;
1606 dwCurrentGroupCount++;
1607 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1609 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1611 while (lpszGrp >= lpFormat->lpThousandSep)
1612 *szOut-- = *lpszGrp--; /* Write grouping char */
1614 dwCurrentGroupCount = 0;
1617 if (dwState & NF_ROUND)
1618 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1619 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1620 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1622 /* Add any leading negative or currency sign */
1623 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1625 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1628 case CF_MINUS_LEFT|CF_CY_LEFT:
1629 while (lpszNeg >= lpszNegStart)
1630 *szOut-- = *lpszNeg--;
1631 dwFmt &= ~CF_MINUS_LEFT;
1635 case CF_CY_LEFT|CF_MINUS_BEFORE:
1636 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1637 if (dwFmt & CF_CY_SPACE)
1639 while (lpszCy >= lpszCyStart)
1640 *szOut-- = *lpszCy--;
1641 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1645 if (dwFmt & CF_PARENS)
1649 iRet = strlenW(szOut) + 1;
1653 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1656 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1657 lpCurrencyStr[cchOut - 1] = '\0';
1658 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1665 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1669 /* FIXME: Everything below here needs to move somewhere else along with the
1670 * other EnumXXX functions, when a method for storing resources for
1671 * alternate calendars is determined.
1674 /**************************************************************************
1675 * EnumDateFormatsExA (KERNEL32.@)
1677 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1678 * LOCALE_NOUSEROVERRIDE here as well?
1680 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1687 SetLastError(ERROR_INVALID_PARAMETER);
1691 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1694 switch (flags & ~LOCALE_USE_CP_ACP)
1697 case DATE_SHORTDATE:
1698 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1703 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1707 case DATE_YEARMONTH:
1708 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1713 FIXME("Unknown date format (%d)\n", flags);
1714 SetLastError(ERROR_INVALID_PARAMETER);
1720 /**************************************************************************
1721 * EnumDateFormatsExW (KERNEL32.@)
1723 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1730 SetLastError(ERROR_INVALID_PARAMETER);
1734 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1737 switch (flags & ~LOCALE_USE_CP_ACP)
1740 case DATE_SHORTDATE:
1741 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1746 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1750 case DATE_YEARMONTH:
1751 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1756 FIXME("Unknown date format (%d)\n", flags);
1757 SetLastError(ERROR_INVALID_PARAMETER);
1763 /**************************************************************************
1764 * EnumDateFormatsA (KERNEL32.@)
1766 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1767 * LOCALE_NOUSEROVERRIDE here as well?
1769 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1775 SetLastError(ERROR_INVALID_PARAMETER);
1779 switch (flags & ~LOCALE_USE_CP_ACP)
1782 case DATE_SHORTDATE:
1783 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1788 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1792 case DATE_YEARMONTH:
1793 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1798 FIXME("Unknown date format (%d)\n", flags);
1799 SetLastError(ERROR_INVALID_PARAMETER);
1805 /**************************************************************************
1806 * EnumDateFormatsW (KERNEL32.@)
1808 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1814 SetLastError(ERROR_INVALID_PARAMETER);
1818 switch (flags & ~LOCALE_USE_CP_ACP)
1821 case DATE_SHORTDATE:
1822 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1827 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1831 case DATE_YEARMONTH:
1832 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1837 FIXME("Unknown date format (%d)\n", flags);
1838 SetLastError(ERROR_INVALID_PARAMETER);
1844 /**************************************************************************
1845 * EnumTimeFormatsA (KERNEL32.@)
1847 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1848 * LOCALE_NOUSEROVERRIDE here as well?
1850 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1856 SetLastError(ERROR_INVALID_PARAMETER);
1860 switch (flags & ~LOCALE_USE_CP_ACP)
1863 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1868 FIXME("Unknown time format (%d)\n", flags);
1869 SetLastError(ERROR_INVALID_PARAMETER);
1875 /**************************************************************************
1876 * EnumTimeFormatsW (KERNEL32.@)
1878 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1884 SetLastError(ERROR_INVALID_PARAMETER);
1888 switch (flags & ~LOCALE_USE_CP_ACP)
1891 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1896 FIXME("Unknown time format (%d)\n", flags);
1897 SetLastError(ERROR_INVALID_PARAMETER);
1903 /******************************************************************************
1904 * NLS_EnumCalendarInfoAW <internal>
1905 * Enumerates calendar information for a specified locale.
1908 * calinfoproc [I] Pointer to the callback
1909 * locale [I] The locale for which to retrieve calendar information.
1910 * This parameter can be a locale identifier created by the
1911 * MAKELCID macro, or one of the following values:
1912 * LOCALE_SYSTEM_DEFAULT
1913 * Use the default system locale.
1914 * LOCALE_USER_DEFAULT
1915 * Use the default user locale.
1916 * calendar [I] The calendar for which information is requested, or
1917 * ENUM_ALL_CALENDARS.
1918 * caltype [I] The type of calendar information to be returned. Note
1919 * that only one CALTYPE value can be specified per call
1920 * of this function, except where noted.
1921 * unicode [I] Specifies if the callback expects a unicode string.
1922 * ex [I] Specifies if the callback needs the calendar identifier.
1926 * Failure: FALSE. Use GetLastError() to determine the cause.
1929 * When the ANSI version of this function is used with a Unicode-only LCID,
1930 * the call can succeed because the system uses the system code page.
1931 * However, characters that are undefined in the system code page appear
1932 * in the string as a question mark (?).
1935 * The above note should be respected by GetCalendarInfoA.
1937 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1938 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1940 WCHAR *buf, *opt = NULL, *iter = NULL;
1942 int bufSz = 200; /* the size of the buffer */
1944 if (calinfoproc == NULL)
1946 SetLastError(ERROR_INVALID_PARAMETER);
1950 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1953 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1957 if (calendar == ENUM_ALL_CALENDARS)
1959 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1962 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1965 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1968 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1971 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1974 while (TRUE) /* loop through calendars */
1976 do /* loop until there's no error */
1979 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1980 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1984 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1985 { /* so resize it */
1988 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1989 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1992 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1996 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1997 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
2000 } else goto cleanup;
2004 /* Here we are. We pass the buffer to the correct version of
2005 * the callback. Because it's not the same number of params,
2006 * we must check for Ex, but we don't care about Unicode
2007 * because the buffer is already in the correct format.
2010 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
2012 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
2014 if (!ret) { /* the callback told to stop */
2019 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2023 while ((*iter >= '0') && (*iter <= '9'))
2024 calendar = calendar * 10 + *iter++ - '0';
2028 SetLastError(ERROR_BADDB);
2035 HeapFree(GetProcessHeap(), 0, opt);
2036 HeapFree(GetProcessHeap(), 0, buf);
2040 /******************************************************************************
2041 * EnumCalendarInfoA [KERNEL32.@]
2043 * See EnumCalendarInfoAW.
2045 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2046 CALID calendar,CALTYPE caltype )
2048 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2049 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
2052 /******************************************************************************
2053 * EnumCalendarInfoW [KERNEL32.@]
2055 * See EnumCalendarInfoAW.
2057 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2058 CALID calendar,CALTYPE caltype )
2060 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2061 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
2064 /******************************************************************************
2065 * EnumCalendarInfoExA [KERNEL32.@]
2067 * See EnumCalendarInfoAW.
2069 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2070 CALID calendar,CALTYPE caltype )
2072 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2073 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
2076 /******************************************************************************
2077 * EnumCalendarInfoExW [KERNEL32.@]
2079 * See EnumCalendarInfoAW.
2081 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2082 CALID calendar,CALTYPE caltype )
2084 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2085 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);