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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 45
57 typedef struct _NLS_FORMAT_NODE
59 LCID lcid
; /* Locale Id */
60 DWORD dwFlags
; /* 0 or LOCALE_NOUSEROVERRIDE */
61 DWORD dwCodePage
; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
62 NUMBERFMTW fmt
; /* Default format for numbers */
63 CURRENCYFMTW cyfmt
; /* Default format for currencies */
64 LPWSTR lppszStrings
[NLS_NUM_CACHED_STRINGS
]; /* Default formats,day/month names */
65 WCHAR szShortAM
[2]; /* Short 'AM' marker */
66 WCHAR szShortPM
[2]; /* Short 'PM' marker */
67 struct _NLS_FORMAT_NODE
*next
;
70 /* Macros to get particular data strings from a format node */
71 #define GetNegative(fmt) fmt->lppszStrings[0]
72 #define GetLongDate(fmt) fmt->lppszStrings[1]
73 #define GetShortDate(fmt) fmt->lppszStrings[2]
74 #define GetTime(fmt) fmt->lppszStrings[3]
75 #define GetAM(fmt) fmt->lppszStrings[42]
76 #define GetPM(fmt) fmt->lppszStrings[43]
77 #define GetYearMonth(fmt) fmt->lppszStrings[44]
79 #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
80 #define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
81 #define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
82 #define GetShortMonth(fmt,mth) fmt->lppszStrings[30 + mth]
84 /* Write access to the cache is protected by this critical section */
85 static CRITICAL_SECTION NLS_FormatsCS
;
86 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug
=
89 { &NLS_FormatsCS_debug
.ProcessLocksList
,
90 &NLS_FormatsCS_debug
.ProcessLocksList
},
91 0, 0, { (DWORD_PTR
)(__FILE__
": NLS_Formats") }
93 static CRITICAL_SECTION NLS_FormatsCS
= { &NLS_FormatsCS_debug
, -1, 0, 0, 0, 0 };
95 /**************************************************************************
96 * NLS_GetLocaleNumber <internal>
98 * Get a numeric locale format value.
100 static DWORD
NLS_GetLocaleNumber(LCID lcid
, DWORD dwFlags
)
106 GetLocaleInfoW(lcid
, dwFlags
, szBuff
, sizeof(szBuff
) / sizeof(WCHAR
));
108 if (szBuff
[0] && szBuff
[1] == ';' && szBuff
[2] != '0')
109 dwVal
= (szBuff
[0] - '0') * 10 + (szBuff
[2] - '0');
112 const WCHAR
* iter
= szBuff
;
114 while(*iter
>= '0' && *iter
<= '9')
115 dwVal
= dwVal
* 10 + (*iter
++ - '0');
120 /**************************************************************************
121 * NLS_GetLocaleString <internal>
123 * Get a string locale format value.
125 static WCHAR
* NLS_GetLocaleString(LCID lcid
, DWORD dwFlags
)
127 WCHAR szBuff
[80], *str
;
131 GetLocaleInfoW(lcid
, dwFlags
, szBuff
, sizeof(szBuff
) / sizeof(WCHAR
));
132 dwLen
= strlenW(szBuff
) + 1;
133 str
= HeapAlloc(GetProcessHeap(), 0, dwLen
* sizeof(WCHAR
));
135 memcpy(str
, szBuff
, dwLen
* sizeof(WCHAR
));
139 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
140 TRACE( #type ": %ld (%08lx)\n", (DWORD)num, (DWORD)num)
142 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
143 TRACE( #type ": '%s'\n", debugstr_w(str))
145 /**************************************************************************
146 * NLS_GetFormats <internal>
148 * Calculate (and cache) the number formats for a locale.
150 static const NLS_FORMAT_NODE
*NLS_GetFormats(LCID lcid
, DWORD dwFlags
)
152 /* GetLocaleInfo() identifiers for cached formatting strings */
153 static const USHORT NLS_LocaleIndices
[] = {
154 LOCALE_SNEGATIVESIGN
,
155 LOCALE_SLONGDATE
, LOCALE_SSHORTDATE
,
157 LOCALE_SDAYNAME1
, LOCALE_SDAYNAME2
, LOCALE_SDAYNAME3
,
158 LOCALE_SDAYNAME4
, LOCALE_SDAYNAME5
, LOCALE_SDAYNAME6
, LOCALE_SDAYNAME7
,
159 LOCALE_SABBREVDAYNAME1
, LOCALE_SABBREVDAYNAME2
, LOCALE_SABBREVDAYNAME3
,
160 LOCALE_SABBREVDAYNAME4
, LOCALE_SABBREVDAYNAME5
, LOCALE_SABBREVDAYNAME6
,
161 LOCALE_SABBREVDAYNAME7
,
162 LOCALE_SMONTHNAME1
, LOCALE_SMONTHNAME2
, LOCALE_SMONTHNAME3
,
163 LOCALE_SMONTHNAME4
, LOCALE_SMONTHNAME5
, LOCALE_SMONTHNAME6
,
164 LOCALE_SMONTHNAME7
, LOCALE_SMONTHNAME8
, LOCALE_SMONTHNAME9
,
165 LOCALE_SMONTHNAME10
, LOCALE_SMONTHNAME11
, LOCALE_SMONTHNAME12
,
166 LOCALE_SABBREVMONTHNAME1
, LOCALE_SABBREVMONTHNAME2
, LOCALE_SABBREVMONTHNAME3
,
167 LOCALE_SABBREVMONTHNAME4
, LOCALE_SABBREVMONTHNAME5
, LOCALE_SABBREVMONTHNAME6
,
168 LOCALE_SABBREVMONTHNAME7
, LOCALE_SABBREVMONTHNAME8
, LOCALE_SABBREVMONTHNAME9
,
169 LOCALE_SABBREVMONTHNAME10
, LOCALE_SABBREVMONTHNAME11
, LOCALE_SABBREVMONTHNAME12
,
170 LOCALE_S1159
, LOCALE_S2359
,
173 static NLS_FORMAT_NODE
*NLS_CachedFormats
= NULL
;
174 NLS_FORMAT_NODE
*node
= NLS_CachedFormats
;
176 dwFlags
&= LOCALE_NOUSEROVERRIDE
;
178 TRACE("(0x%04lx,0x%08lx)\n", lcid
, dwFlags
);
180 /* See if we have already cached the locales number format */
181 while (node
&& (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
) && node
->next
)
184 if (!node
|| node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
)
186 NLS_FORMAT_NODE
*new_node
;
189 TRACE("Creating new cache entry\n");
191 if (!(new_node
= HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE
))))
194 GET_LOCALE_NUMBER(new_node
->dwCodePage
, LOCALE_IDEFAULTANSICODEPAGE
);
197 new_node
->lcid
= lcid
;
198 new_node
->dwFlags
= dwFlags
;
199 new_node
->next
= NULL
;
201 GET_LOCALE_NUMBER(new_node
->fmt
.NumDigits
, LOCALE_IDIGITS
);
202 GET_LOCALE_NUMBER(new_node
->fmt
.LeadingZero
, LOCALE_ILZERO
);
203 GET_LOCALE_NUMBER(new_node
->fmt
.NegativeOrder
, LOCALE_INEGNUMBER
);
205 GET_LOCALE_NUMBER(new_node
->fmt
.Grouping
, LOCALE_SGROUPING
);
206 if (new_node
->fmt
.Grouping
> 9 && new_node
->fmt
.Grouping
!= 32)
208 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
209 new_node
->fmt
.Grouping
);
210 new_node
->fmt
.Grouping
= 0;
213 GET_LOCALE_STRING(new_node
->fmt
.lpDecimalSep
, LOCALE_SDECIMAL
);
214 GET_LOCALE_STRING(new_node
->fmt
.lpThousandSep
, LOCALE_STHOUSAND
);
216 /* Currency Format */
217 new_node
->cyfmt
.NumDigits
= new_node
->fmt
.NumDigits
;
218 new_node
->cyfmt
.LeadingZero
= new_node
->fmt
.LeadingZero
;
220 GET_LOCALE_NUMBER(new_node
->cyfmt
.Grouping
, LOCALE_SGROUPING
);
222 if (new_node
->cyfmt
.Grouping
> 9)
224 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
225 new_node
->cyfmt
.Grouping
);
226 new_node
->cyfmt
.Grouping
= 0;
229 GET_LOCALE_NUMBER(new_node
->cyfmt
.NegativeOrder
, LOCALE_INEGCURR
);
230 if (new_node
->cyfmt
.NegativeOrder
> 15)
232 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
233 new_node
->cyfmt
.NegativeOrder
);
234 new_node
->cyfmt
.NegativeOrder
= 0;
236 GET_LOCALE_NUMBER(new_node
->cyfmt
.PositiveOrder
, LOCALE_ICURRENCY
);
237 if (new_node
->cyfmt
.PositiveOrder
> 3)
239 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
240 new_node
->cyfmt
.PositiveOrder
);
241 new_node
->cyfmt
.PositiveOrder
= 0;
243 GET_LOCALE_STRING(new_node
->cyfmt
.lpDecimalSep
, LOCALE_SMONDECIMALSEP
);
244 GET_LOCALE_STRING(new_node
->cyfmt
.lpThousandSep
, LOCALE_SMONTHOUSANDSEP
);
245 GET_LOCALE_STRING(new_node
->cyfmt
.lpCurrencySymbol
, LOCALE_SCURRENCY
);
247 /* Date/Time Format info, negative character, etc */
248 for (i
= 0; i
< sizeof(NLS_LocaleIndices
)/sizeof(NLS_LocaleIndices
[0]); i
++)
250 GET_LOCALE_STRING(new_node
->lppszStrings
[i
], NLS_LocaleIndices
[i
]);
252 new_node
->szShortAM
[0] = GetAM(new_node
)[0]; new_node
->szShortAM
[1] = '\0';
253 new_node
->szShortPM
[0] = GetPM(new_node
)[0]; new_node
->szShortPM
[1] = '\0';
255 /* Now add the computed format to the cache */
256 RtlEnterCriticalSection(&NLS_FormatsCS
);
258 /* Search again: We may have raced to add the node */
259 node
= NLS_CachedFormats
;
260 while (node
&& (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
) && node
->next
)
265 node
= NLS_CachedFormats
= new_node
; /* Empty list */
268 else if (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
)
270 node
->next
= new_node
; /* Not in the list, add to end */
275 RtlLeaveCriticalSection(&NLS_FormatsCS
);
279 /* We raced and lost: The node was already added by another thread.
280 * node points to the currently cached node, so free new_node.
282 for (i
= 0; i
< sizeof(NLS_LocaleIndices
)/sizeof(NLS_LocaleIndices
[0]); i
++)
283 HeapFree(GetProcessHeap(), 0, new_node
->lppszStrings
[i
]);
284 HeapFree(GetProcessHeap(), 0, new_node
->fmt
.lpDecimalSep
);
285 HeapFree(GetProcessHeap(), 0, new_node
->fmt
.lpThousandSep
);
286 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpDecimalSep
);
287 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpThousandSep
);
288 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpCurrencySymbol
);
289 HeapFree(GetProcessHeap(), 0, new_node
);
295 /**************************************************************************
296 * NLS_IsUnicodeOnlyLcid <internal>
298 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
300 BOOL
NLS_IsUnicodeOnlyLcid(LCID lcid
)
302 switch (PRIMARYLANGID(lcid
))
314 TRACE("lcid 0x%08lx: langid 0x%4x is Unicode Only\n", lcid
, PRIMARYLANGID(lcid
));
322 * Formatting of dates, times, numbers and currencies.
325 #define IsLiteralMarker(p) (p == '\'')
326 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
327 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
329 /* Only the following flags can be given if a date/time format is specified */
330 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY|LOCALE_NOUSEROVERRIDE)
331 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
332 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
333 TIME_NOTIMEMARKER|LOCALE_NOUSEROVERRIDE)
335 /******************************************************************************
336 * NLS_GetDateTimeFormatW <internal>
338 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
341 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
342 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
344 static INT
NLS_GetDateTimeFormatW(LCID lcid
, DWORD dwFlags
,
345 const SYSTEMTIME
* lpTime
, LPCWSTR lpFormat
,
346 LPWSTR lpStr
, INT cchOut
)
348 const NLS_FORMAT_NODE
*node
;
351 INT lastFormatPos
= 0;
352 BOOL bSkipping
= FALSE
; /* Skipping text around marker? */
354 /* Verify our arguments */
355 if ((cchOut
&& !lpStr
) || !(node
= NLS_GetFormats(lcid
, dwFlags
)))
357 NLS_GetDateTimeFormatW_InvalidParameter
:
358 SetLastError(ERROR_INVALID_PARAMETER
);
362 if (dwFlags
& ~(DATE_DATEVARSONLY
|TIME_TIMEVARSONLY
))
365 ((dwFlags
& DATE_DATEVARSONLY
&& dwFlags
& ~DATE_FORMAT_FLAGS
) ||
366 (dwFlags
& TIME_TIMEVARSONLY
&& dwFlags
& ~TIME_FORMAT_FLAGS
)))
368 NLS_GetDateTimeFormatW_InvalidFlags
:
369 SetLastError(ERROR_INVALID_FLAGS
);
373 if (dwFlags
& DATE_DATEVARSONLY
)
375 if ((dwFlags
& (DATE_LTRREADING
|DATE_RTLREADING
)) == (DATE_LTRREADING
|DATE_RTLREADING
))
376 goto NLS_GetDateTimeFormatW_InvalidFlags
;
377 else if (dwFlags
& (DATE_LTRREADING
|DATE_RTLREADING
))
378 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
380 switch (dwFlags
& (DATE_SHORTDATE
|DATE_LONGDATE
|DATE_YEARMONTH
))
388 goto NLS_GetDateTimeFormatW_InvalidFlags
;
391 goto NLS_GetDateTimeFormatW_InvalidFlags
;
398 /* Use the appropriate default format */
399 if (dwFlags
& DATE_DATEVARSONLY
)
401 if (dwFlags
& DATE_YEARMONTH
)
402 lpFormat
= GetYearMonth(node
);
403 else if (dwFlags
& DATE_LONGDATE
)
404 lpFormat
= GetLongDate(node
);
406 lpFormat
= GetShortDate(node
);
409 lpFormat
= GetTime(node
);
414 GetLocalTime(&st
); /* Default to current time */
419 if (dwFlags
& DATE_DATEVARSONLY
)
423 /* Verify the date and correct the D.O.W. if needed */
424 memset(&st
, 0, sizeof(st
));
425 st
.wYear
= lpTime
->wYear
;
426 st
.wMonth
= lpTime
->wMonth
;
427 st
.wDay
= lpTime
->wDay
;
429 if (st
.wDay
> 31 || st
.wMonth
> 12 || !SystemTimeToFileTime(&st
, &ftTmp
))
430 goto NLS_GetDateTimeFormatW_InvalidParameter
;
432 FileTimeToSystemTime(&ftTmp
, &st
);
436 if (dwFlags
& TIME_TIMEVARSONLY
)
438 /* Verify the time */
439 if (lpTime
->wHour
> 24 || lpTime
->wMinute
> 59 || lpTime
->wSecond
> 59)
440 goto NLS_GetDateTimeFormatW_InvalidParameter
;
444 /* Format the output */
447 if (IsLiteralMarker(*lpFormat
))
449 /* Start of a literal string */
452 /* Loop until the end of the literal marker or end of the string */
455 if (IsLiteralMarker(*lpFormat
))
458 if (!IsLiteralMarker(*lpFormat
))
459 break; /* Terminating literal marker */
463 cchWritten
++; /* Count size only */
464 else if (cchWritten
>= cchOut
)
465 goto NLS_GetDateTimeFormatW_Overrun
;
468 lpStr
[cchWritten
] = *lpFormat
;
474 else if ((dwFlags
& DATE_DATEVARSONLY
&& IsDateFmtChar(*lpFormat
)) ||
475 (dwFlags
& TIME_TIMEVARSONLY
&& IsTimeFmtChar(*lpFormat
)))
478 WCHAR buff
[32], fmtChar
;
479 LPCWSTR szAdd
= NULL
;
481 int count
= 0, dwLen
;
486 while (*lpFormat
== fmtChar
)
497 szAdd
= GetLongDay(node
, (lpTime
->wDayOfWeek
+ 6) % 7);
499 szAdd
= GetShortDay(node
, (lpTime
->wDayOfWeek
+ 6) % 7);
502 dwVal
= lpTime
->wDay
;
509 szAdd
= GetLongMonth(node
, lpTime
->wMonth
- 1);
511 szAdd
= GetShortMonth(node
, lpTime
->wMonth
- 1);
514 dwVal
= lpTime
->wMonth
;
523 dwVal
= lpTime
->wYear
;
527 count
= count
> 2 ? 2 : count
;
528 dwVal
= lpTime
->wYear
% 100;
536 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
537 * When it is fixed, this string should be cached in 'node'.
539 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
540 buff
[0] = 'A'; buff
[1] = 'D'; buff
[2] = '\0';
544 buff
[0] = 'g'; buff
[1] = '\0'; /* Add a literal 'g' */
550 if (!(dwFlags
& TIME_FORCE24HOURFORMAT
))
552 count
= count
> 2 ? 2 : count
;
553 dwVal
= lpTime
->wHour
== 0 ? 12 : (lpTime
->wHour
- 1) % 12 + 1;
557 /* .. fall through if we are forced to output in 24 hour format */
560 count
= count
> 2 ? 2 : count
;
561 dwVal
= lpTime
->wHour
;
566 if (dwFlags
& TIME_NOMINUTESORSECONDS
)
568 cchWritten
= lastFormatPos
; /* Skip */
573 count
= count
> 2 ? 2 : count
;
574 dwVal
= lpTime
->wMinute
;
580 if (dwFlags
& (TIME_NOSECONDS
|TIME_NOMINUTESORSECONDS
))
582 cchWritten
= lastFormatPos
; /* Skip */
587 count
= count
> 2 ? 2 : count
;
588 dwVal
= lpTime
->wSecond
;
594 if (dwFlags
& TIME_NOTIMEMARKER
)
596 cchWritten
= lastFormatPos
; /* Skip */
602 szAdd
= lpTime
->wHour
< 12 ? node
->szShortAM
: node
->szShortPM
;
604 szAdd
= lpTime
->wHour
< 12 ? GetAM(node
) : GetPM(node
);
609 if (szAdd
== buff
&& buff
[0] == '\0')
611 /* We have a numeric value to add */
612 sprintf(buffA
, "%.*ld", count
, dwVal
);
613 MultiByteToWideChar(CP_ACP
, 0, buffA
, -1, buff
, sizeof(buff
)/sizeof(WCHAR
));
616 dwLen
= szAdd
? strlenW(szAdd
) : 0;
620 if (cchWritten
+ dwLen
< cchOut
)
621 memcpy(lpStr
+ cchWritten
, szAdd
, dwLen
* sizeof(WCHAR
));
624 memcpy(lpStr
+ cchWritten
, szAdd
, (cchOut
- cchWritten
) * sizeof(WCHAR
));
625 goto NLS_GetDateTimeFormatW_Overrun
;
629 lastFormatPos
= cchWritten
; /* Save position of last output format text */
633 /* Literal character */
635 cchWritten
++; /* Count size only */
636 else if (cchWritten
>= cchOut
)
637 goto NLS_GetDateTimeFormatW_Overrun
;
638 else if (!bSkipping
|| *lpFormat
== ' ')
640 lpStr
[cchWritten
] = *lpFormat
;
647 /* Final string terminator and sanity check */
650 if (cchWritten
>= cchOut
)
651 goto NLS_GetDateTimeFormatW_Overrun
;
653 lpStr
[cchWritten
] = '\0';
655 cchWritten
++; /* Include terminating NUL */
657 TRACE("returning length=%d, ouput='%s'\n", cchWritten
, debugstr_w(lpStr
));
660 NLS_GetDateTimeFormatW_Overrun
:
661 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
662 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
666 /******************************************************************************
667 * NLS_GetDateTimeFormatA <internal>
669 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
671 static INT
NLS_GetDateTimeFormatA(LCID lcid
, DWORD dwFlags
,
672 const SYSTEMTIME
* lpTime
,
673 LPCSTR lpFormat
, LPSTR lpStr
, INT cchOut
)
676 WCHAR szFormat
[128], szOut
[128];
679 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid
, dwFlags
, lpTime
,
680 debugstr_a(lpFormat
), lpStr
, cchOut
);
682 if (NLS_IsUnicodeOnlyLcid(lcid
))
684 GetDateTimeFormatA_InvalidParameter
:
685 SetLastError(ERROR_INVALID_PARAMETER
);
689 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
691 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
693 goto GetDateTimeFormatA_InvalidParameter
;
694 cp
= node
->dwCodePage
;
698 MultiByteToWideChar(cp
, 0, lpFormat
, -1, szFormat
, sizeof(szFormat
)/sizeof(WCHAR
));
700 if (cchOut
> (int)(sizeof(szOut
)/sizeof(WCHAR
)))
701 cchOut
= sizeof(szOut
)/sizeof(WCHAR
);
705 iRet
= NLS_GetDateTimeFormatW(lcid
, dwFlags
, lpTime
, lpFormat
? szFormat
: NULL
,
706 lpStr
? szOut
: NULL
, cchOut
);
711 WideCharToMultiByte(cp
, 0, szOut
, -1, lpStr
, cchOut
, 0, 0);
712 else if (cchOut
&& iRet
)
718 /******************************************************************************
719 * GetDateFormatA [KERNEL32.@]
721 * Format a date for a given locale.
724 * lcid [I] Locale to format for
725 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
726 * lpTime [I] Date to format
727 * lpFormat [I] Format string, or NULL to use the system defaults
728 * lpDateStr [O] Destination for formatted string
729 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
732 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
733 * details returned by GetLocaleInfoA() and modified by dwFlags.
734 * - lpFormat is a string of characters and formatting tokens. Any characters
735 * in the string are copied verbatim to lpDateStr, with tokens being replaced
736 * by the date values they represent.
737 * - The following tokens have special meanings in a date format string:
740 *| d Single digit day of the month (no leading 0)
741 *| dd Double digit day of the month
742 *| ddd Short name for the day of the week
743 *| dddd Long name for the day of the week
744 *| M Single digit month of the year (no leading 0)
745 *| MM Double digit month of the year
746 *| MMM Short name for the month of the year
747 *| MMMM Long name for the month of the year
748 *| y Double digit year number (no leading 0)
749 *| yy Double digit year number
750 *| yyyy Four digit year number
751 *| gg Era string, for example 'AD'.
752 * - To output any literal character that could be misidentified as a token,
753 * enclose it in single quotes.
754 * - The Ascii version of this function fails if lcid is Unicode only.
757 * Success: The number of character written to lpDateStr, or that would
758 * have been written, if cchOut is 0.
759 * Failure: 0. Use GetLastError() to determine the cause.
761 INT WINAPI
GetDateFormatA( LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
762 LPCSTR lpFormat
, LPSTR lpDateStr
, INT cchOut
)
764 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
765 debugstr_a(lpFormat
), lpDateStr
, cchOut
);
767 return NLS_GetDateTimeFormatA(lcid
, dwFlags
| DATE_DATEVARSONLY
, lpTime
,
768 lpFormat
, lpDateStr
, cchOut
);
772 /******************************************************************************
773 * GetDateFormatW [KERNEL32.@]
775 * See GetDateFormatA.
777 INT WINAPI
GetDateFormatW(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
778 LPCWSTR lpFormat
, LPWSTR lpDateStr
, INT cchOut
)
780 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid
, dwFlags
, lpTime
,
781 debugstr_w(lpFormat
), lpDateStr
, cchOut
);
783 return NLS_GetDateTimeFormatW(lcid
, dwFlags
|DATE_DATEVARSONLY
, lpTime
,
784 lpFormat
, lpDateStr
, cchOut
);
787 /******************************************************************************
788 * GetTimeFormatA [KERNEL32.@]
790 * Format a time for a given locale.
793 * lcid [I] Locale to format for
794 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
795 * lpTime [I] Time to format
796 * lpFormat [I] Formatting overrides
797 * lpTimeStr [O] Destination for formatted string
798 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
801 * - If lpFormat is NULL, lpszValue will be formatted according to the format
802 * details returned by GetLocaleInfoA() and modified by dwFlags.
803 * - lpFormat is a string of characters and formatting tokens. Any characters
804 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
805 * by the time values they represent.
806 * - The following tokens have special meanings in a time format string:
809 *| h Hours with no leading zero (12-hour clock)
810 *| hh Hours with full two digits (12-hour clock)
811 *| H Hours with no leading zero (24-hour clock)
812 *| HH Hours with full two digits (24-hour clock)
813 *| m Minutes with no leading zero
814 *| mm Minutes with full two digits
815 *| s Seconds with no leading zero
816 *| ss Seconds with full two digits
817 *| t Short time marker (e.g. "A" or "P")
818 *| tt Long time marker (e.g. "AM", "PM")
819 * - To output any literal character that could be misidentified as a token,
820 * enclose it in single quotes.
821 * - The Ascii version of this function fails if lcid is Unicode only.
824 * Success: The number of character written to lpTimeStr, or that would
825 * have been written, if cchOut is 0.
826 * Failure: 0. Use GetLastError() to determine the cause.
828 INT WINAPI
GetTimeFormatA(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
829 LPCSTR lpFormat
, LPSTR lpTimeStr
, INT cchOut
)
831 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
832 debugstr_a(lpFormat
), lpTimeStr
, cchOut
);
834 return NLS_GetDateTimeFormatA(lcid
, dwFlags
|TIME_TIMEVARSONLY
, lpTime
,
835 lpFormat
, lpTimeStr
, cchOut
);
838 /******************************************************************************
839 * GetTimeFormatW [KERNEL32.@]
841 * See GetTimeFormatA.
843 INT WINAPI
GetTimeFormatW(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
844 LPCWSTR lpFormat
, LPWSTR lpTimeStr
, INT cchOut
)
846 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
847 debugstr_w(lpFormat
), lpTimeStr
, cchOut
);
849 return NLS_GetDateTimeFormatW(lcid
, dwFlags
|TIME_TIMEVARSONLY
, lpTime
,
850 lpFormat
, lpTimeStr
, cchOut
);
853 /**************************************************************************
854 * GetNumberFormatA (KERNEL32.@)
856 * Format a number string for a given locale.
859 * lcid [I] Locale to format for
860 * dwFlags [I] LOCALE_ flags from "winnls.h"
861 * lpszValue [I] String to format
862 * lpFormat [I] Formatting overrides
863 * lpNumberStr [O] Destination for formatted string
864 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
867 * - lpszValue can contain only '0' - '9', '-' and '.'.
868 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
869 * be formatted according to the format details returned by GetLocaleInfoA().
870 * - This function rounds the number string if the number of decimals exceeds the
871 * locales normal number of decimal places.
872 * - If cchOut is 0, this function does not write to lpNumberStr.
873 * - The Ascii version of this function fails if lcid is Unicode only.
876 * Success: The number of character written to lpNumberStr, or that would
877 * have been written, if cchOut is 0.
878 * Failure: 0. Use GetLastError() to determine the cause.
880 INT WINAPI
GetNumberFormatA(LCID lcid
, DWORD dwFlags
,
881 LPCSTR lpszValue
, const NUMBERFMTA
*lpFormat
,
882 LPSTR lpNumberStr
, int cchOut
)
885 WCHAR szDec
[8], szGrp
[8], szIn
[128], szOut
[128];
887 const NUMBERFMTW
*pfmt
= NULL
;
890 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_a(lpszValue
),
891 lpFormat
, lpNumberStr
, cchOut
);
893 if (NLS_IsUnicodeOnlyLcid(lcid
))
895 GetNumberFormatA_InvalidParameter
:
896 SetLastError(ERROR_INVALID_PARAMETER
);
900 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
902 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
904 goto GetNumberFormatA_InvalidParameter
;
905 cp
= node
->dwCodePage
;
910 memcpy(&fmt
, lpFormat
, sizeof(fmt
));
912 if (lpFormat
->lpDecimalSep
)
914 MultiByteToWideChar(cp
, 0, lpFormat
->lpDecimalSep
, -1, szDec
, sizeof(szDec
)/sizeof(WCHAR
));
915 fmt
.lpDecimalSep
= szDec
;
917 if (lpFormat
->lpThousandSep
)
919 MultiByteToWideChar(cp
, 0, lpFormat
->lpThousandSep
, -1, szGrp
, sizeof(szGrp
)/sizeof(WCHAR
));
920 fmt
.lpThousandSep
= szGrp
;
925 MultiByteToWideChar(cp
, 0, lpszValue
, -1, szIn
, sizeof(szIn
)/sizeof(WCHAR
));
927 if (cchOut
> (int)(sizeof(szOut
)/sizeof(WCHAR
)))
928 cchOut
= sizeof(szOut
)/sizeof(WCHAR
);
932 iRet
= GetNumberFormatW(lcid
, dwFlags
, lpszValue
? szIn
: NULL
, pfmt
,
933 lpNumberStr
? szOut
: NULL
, cchOut
);
935 if (szOut
[0] && lpNumberStr
)
936 WideCharToMultiByte(cp
, 0, szOut
, -1, lpNumberStr
, cchOut
, 0, 0);
940 /* Number parsing state flags */
941 #define NF_ISNEGATIVE 0x1 /* '-' found */
942 #define NF_ISREAL 0x2 /* '.' found */
943 #define NF_DIGITS 0x4 /* '0'-'9' found */
944 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
945 #define NF_ROUND 0x10 /* Number needs to be rounded */
947 /* Formatting options for Numbers */
948 #define NLS_NEG_PARENS 0 /* "(1.1)" */
949 #define NLS_NEG_LEFT 1 /* "-1.1" */
950 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
951 #define NLS_NEG_RIGHT 3 /* "1.1-" */
952 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
954 /**************************************************************************
955 * GetNumberFormatW (KERNEL32.@)
957 * See GetNumberFormatA.
959 INT WINAPI
GetNumberFormatW(LCID lcid
, DWORD dwFlags
,
960 LPCWSTR lpszValue
, const NUMBERFMTW
*lpFormat
,
961 LPWSTR lpNumberStr
, int cchOut
)
963 WCHAR szBuff
[128], *szOut
= szBuff
+ sizeof(szBuff
) / sizeof(WCHAR
) - 1;
965 const WCHAR
*lpszNeg
= NULL
, *lpszNegStart
, *szSrc
;
966 DWORD dwState
= 0, dwDecimals
= 0, dwGroupCount
= 0, dwCurrentGroupCount
= 0;
969 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_w(lpszValue
),
970 lpFormat
, lpNumberStr
, cchOut
);
972 if (!lpszValue
|| cchOut
< 0 || (cchOut
> 0 && !lpNumberStr
) ||
973 !IsValidLocale(lcid
, 0) ||
974 (lpFormat
&& (dwFlags
|| !lpFormat
->lpDecimalSep
|| !lpFormat
->lpThousandSep
)))
976 GetNumberFormatW_Error
:
977 SetLastError(lpFormat
&& dwFlags
? ERROR_INVALID_FLAGS
: ERROR_INVALID_PARAMETER
);
983 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
986 goto GetNumberFormatW_Error
;
987 lpFormat
= &node
->fmt
;
988 lpszNegStart
= lpszNeg
= GetNegative(node
);
992 GetLocaleInfoW(lcid
, LOCALE_SNEGATIVESIGN
|(dwFlags
& LOCALE_NOUSEROVERRIDE
),
993 szNegBuff
, sizeof(szNegBuff
)/sizeof(WCHAR
));
994 lpszNegStart
= lpszNeg
= szNegBuff
;
996 lpszNeg
= lpszNeg
+ strlenW(lpszNeg
) - 1;
998 dwFlags
&= (LOCALE_NOUSEROVERRIDE
|LOCALE_USE_CP_ACP
);
1000 /* Format the number backwards into a temporary buffer */
1005 /* Check the number for validity */
1008 if (*szSrc
>= '0' && *szSrc
<= '9')
1010 dwState
|= NF_DIGITS
;
1011 if (dwState
& NF_ISREAL
)
1014 else if (*szSrc
== '-')
1017 goto GetNumberFormatW_Error
; /* '-' not first character */
1018 dwState
|= NF_ISNEGATIVE
;
1020 else if (*szSrc
== '.')
1022 if (dwState
& NF_ISREAL
)
1023 goto GetNumberFormatW_Error
; /* More than one '.' */
1024 dwState
|= NF_ISREAL
;
1027 goto GetNumberFormatW_Error
; /* Invalid char */
1030 szSrc
--; /* Point to last character */
1032 if (!(dwState
& NF_DIGITS
))
1033 goto GetNumberFormatW_Error
; /* No digits */
1035 /* Add any trailing negative sign */
1036 if (dwState
& NF_ISNEGATIVE
)
1038 switch (lpFormat
->NegativeOrder
)
1040 case NLS_NEG_PARENS
:
1044 case NLS_NEG_RIGHT_SPACE
:
1045 while (lpszNeg
>= lpszNegStart
)
1046 *szOut
-- = *lpszNeg
--;
1047 if (lpFormat
->NegativeOrder
== NLS_NEG_RIGHT_SPACE
)
1053 /* Copy all digits up to the decimal point */
1054 if (!lpFormat
->NumDigits
)
1056 if (dwState
& NF_ISREAL
)
1058 while (*szSrc
!= '.') /* Don't write any decimals or a separator */
1060 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1061 dwState
|= NF_ROUND
;
1063 dwState
&= ~NF_ROUND
;
1071 LPWSTR lpszDec
= lpFormat
->lpDecimalSep
+ strlenW(lpFormat
->lpDecimalSep
) - 1;
1073 if (dwDecimals
<= lpFormat
->NumDigits
)
1075 dwDecimals
= lpFormat
->NumDigits
- dwDecimals
;
1076 while (dwDecimals
--)
1077 *szOut
-- = '0'; /* Pad to correct number of dp */
1081 dwDecimals
-= lpFormat
->NumDigits
;
1082 /* Skip excess decimals, and determine if we have to round the number */
1083 while (dwDecimals
--)
1085 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1086 dwState
|= NF_ROUND
;
1088 dwState
&= ~NF_ROUND
;
1093 if (dwState
& NF_ISREAL
)
1095 while (*szSrc
!= '.')
1097 if (dwState
& NF_ROUND
)
1100 *szOut
-- = '0'; /* continue rounding */
1103 dwState
&= ~NF_ROUND
;
1104 *szOut
-- = (*szSrc
)+1;
1109 *szOut
-- = *szSrc
--; /* Write existing decimals */
1111 szSrc
--; /* Skip '.' */
1114 while (lpszDec
>= lpFormat
->lpDecimalSep
)
1115 *szOut
-- = *lpszDec
--; /* Write decimal separator */
1118 dwGroupCount
= lpFormat
->Grouping
== 32 ? 3 : lpFormat
->Grouping
;
1120 /* Write the remaining whole number digits, including grouping chars */
1121 while (szSrc
>= lpszValue
&& *szSrc
>= '0' && *szSrc
<= '9')
1123 if (dwState
& NF_ROUND
)
1126 *szOut
-- = '0'; /* continue rounding */
1129 dwState
&= ~NF_ROUND
;
1130 *szOut
-- = (*szSrc
)+1;
1135 *szOut
-- = *szSrc
--;
1137 dwState
|= NF_DIGITS_OUT
;
1138 dwCurrentGroupCount
++;
1139 if (szSrc
>= lpszValue
&& dwCurrentGroupCount
== dwGroupCount
&& *szSrc
!= '-')
1141 LPWSTR lpszGrp
= lpFormat
->lpThousandSep
+ strlenW(lpFormat
->lpThousandSep
) - 1;
1143 while (lpszGrp
>= lpFormat
->lpThousandSep
)
1144 *szOut
-- = *lpszGrp
--; /* Write grouping char */
1146 dwCurrentGroupCount
= 0;
1147 if (lpFormat
->Grouping
== 32)
1148 dwGroupCount
= 2; /* Indic grouping: 3 then 2 */
1151 if (dwState
& NF_ROUND
)
1153 *szOut
-- = '1'; /* e.g. .6 > 1.0 */
1155 else if (!(dwState
& NF_DIGITS_OUT
) && lpFormat
->LeadingZero
)
1156 *szOut
-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1158 /* Add any leading negative sign */
1159 if (dwState
& NF_ISNEGATIVE
)
1161 switch (lpFormat
->NegativeOrder
)
1163 case NLS_NEG_PARENS
:
1166 case NLS_NEG_LEFT_SPACE
:
1170 while (lpszNeg
>= lpszNegStart
)
1171 *szOut
-- = *lpszNeg
--;
1177 iRet
= strlenW(szOut
) + 1;
1181 memcpy(lpNumberStr
, szOut
, iRet
* sizeof(WCHAR
));
1184 memcpy(lpNumberStr
, szOut
, cchOut
* sizeof(WCHAR
));
1185 lpNumberStr
[cchOut
- 1] = '\0';
1186 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1193 /**************************************************************************
1194 * GetCurrencyFormatA (KERNEL32.@)
1196 * Format a currency string for a given locale.
1199 * lcid [I] Locale to format for
1200 * dwFlags [I] LOCALE_ flags from "winnls.h"
1201 * lpszValue [I] String to format
1202 * lpFormat [I] Formatting overrides
1203 * lpCurrencyStr [O] Destination for formatted string
1204 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1207 * - lpszValue can contain only '0' - '9', '-' and '.'.
1208 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1209 * be formatted according to the format details returned by GetLocaleInfoA().
1210 * - This function rounds the currency if the number of decimals exceeds the
1211 * locales number of currency decimal places.
1212 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1213 * - The Ascii version of this function fails if lcid is Unicode only.
1216 * Success: The number of character written to lpNumberStr, or that would
1217 * have been written, if cchOut is 0.
1218 * Failure: 0. Use GetLastError() to determine the cause.
1220 INT WINAPI
GetCurrencyFormatA(LCID lcid
, DWORD dwFlags
,
1221 LPCSTR lpszValue
, const CURRENCYFMTA
*lpFormat
,
1222 LPSTR lpCurrencyStr
, int cchOut
)
1225 WCHAR szDec
[8], szGrp
[8], szCy
[8], szIn
[128], szOut
[128];
1227 const CURRENCYFMTW
*pfmt
= NULL
;
1230 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_a(lpszValue
),
1231 lpFormat
, lpCurrencyStr
, cchOut
);
1233 if (NLS_IsUnicodeOnlyLcid(lcid
))
1235 GetCurrencyFormatA_InvalidParameter
:
1236 SetLastError(ERROR_INVALID_PARAMETER
);
1240 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
1242 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1244 goto GetCurrencyFormatA_InvalidParameter
;
1245 cp
= node
->dwCodePage
;
1250 memcpy(&fmt
, lpFormat
, sizeof(fmt
));
1252 if (lpFormat
->lpDecimalSep
)
1254 MultiByteToWideChar(cp
, 0, lpFormat
->lpDecimalSep
, -1, szDec
, sizeof(szDec
)/sizeof(WCHAR
));
1255 fmt
.lpDecimalSep
= szDec
;
1257 if (lpFormat
->lpThousandSep
)
1259 MultiByteToWideChar(cp
, 0, lpFormat
->lpThousandSep
, -1, szGrp
, sizeof(szGrp
)/sizeof(WCHAR
));
1260 fmt
.lpThousandSep
= szGrp
;
1262 if (lpFormat
->lpCurrencySymbol
)
1264 MultiByteToWideChar(cp
, 0, lpFormat
->lpCurrencySymbol
, -1, szCy
, sizeof(szCy
)/sizeof(WCHAR
));
1265 fmt
.lpCurrencySymbol
= szCy
;
1270 MultiByteToWideChar(cp
, 0, lpszValue
, -1, szIn
, sizeof(szIn
)/sizeof(WCHAR
));
1272 if (cchOut
> (int)(sizeof(szOut
)/sizeof(WCHAR
)))
1273 cchOut
= sizeof(szOut
)/sizeof(WCHAR
);
1277 iRet
= GetCurrencyFormatW(lcid
, dwFlags
, lpszValue
? szIn
: NULL
, pfmt
,
1278 lpCurrencyStr
? szOut
: NULL
, cchOut
);
1280 if (szOut
[0] && lpCurrencyStr
)
1281 WideCharToMultiByte(cp
, 0, szOut
, -1, lpCurrencyStr
, cchOut
, 0, 0);
1285 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1286 #define CF_PARENS 0x1 /* Parentheses */
1287 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1288 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1289 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1290 #define CF_CY_LEFT 0x10 /* '$' to the left */
1291 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1292 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1294 /**************************************************************************
1295 * GetCurrencyFormatW (KERNEL32.@)
1297 * See GetCurrencyFormatA.
1299 INT WINAPI
GetCurrencyFormatW(LCID lcid
, DWORD dwFlags
,
1300 LPCWSTR lpszValue
, const CURRENCYFMTW
*lpFormat
,
1301 LPWSTR lpCurrencyStr
, int cchOut
)
1303 static const BYTE NLS_NegCyFormats
[16] =
1305 CF_PARENS
|CF_CY_LEFT
, /* ($1.1) */
1306 CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
, /* -$1.1 */
1307 CF_MINUS_LEFT
|CF_CY_LEFT
, /* $-1.1 */
1308 CF_MINUS_RIGHT
|CF_CY_LEFT
, /* $1.1- */
1309 CF_PARENS
|CF_CY_RIGHT
, /* (1.1$) */
1310 CF_MINUS_LEFT
|CF_CY_RIGHT
, /* -1.1$ */
1311 CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
, /* 1.1-$ */
1312 CF_MINUS_RIGHT
|CF_CY_RIGHT
, /* 1.1$- */
1313 CF_MINUS_LEFT
|CF_CY_RIGHT
|CF_CY_SPACE
, /* -1.1 $ */
1314 CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
|CF_CY_SPACE
, /* -$ 1.1 */
1315 CF_MINUS_RIGHT
|CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1 $- */
1316 CF_MINUS_RIGHT
|CF_CY_LEFT
|CF_CY_SPACE
, /* $ 1.1- */
1317 CF_MINUS_LEFT
|CF_CY_LEFT
|CF_CY_SPACE
, /* $ -1.1 */
1318 CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1- $ */
1319 CF_PARENS
|CF_CY_LEFT
|CF_CY_SPACE
, /* ($ 1.1) */
1320 CF_PARENS
|CF_CY_RIGHT
|CF_CY_SPACE
, /* (1.1 $) */
1322 static const BYTE NLS_PosCyFormats
[4] =
1324 CF_CY_LEFT
, /* $1.1 */
1325 CF_CY_RIGHT
, /* 1.1$ */
1326 CF_CY_LEFT
|CF_CY_SPACE
, /* $ 1.1 */
1327 CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1 $ */
1329 WCHAR szBuff
[128], *szOut
= szBuff
+ sizeof(szBuff
) / sizeof(WCHAR
) - 1;
1331 const WCHAR
*lpszNeg
= NULL
, *lpszNegStart
, *szSrc
, *lpszCy
, *lpszCyStart
;
1332 DWORD dwState
= 0, dwDecimals
= 0, dwGroupCount
= 0, dwCurrentGroupCount
= 0, dwFmt
;
1335 TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_w(lpszValue
),
1336 lpFormat
, lpCurrencyStr
, cchOut
);
1338 if (!lpszValue
|| cchOut
< 0 || (cchOut
> 0 && !lpCurrencyStr
) ||
1339 !IsValidLocale(lcid
, 0) ||
1340 (lpFormat
&& (dwFlags
|| !lpFormat
->lpDecimalSep
|| !lpFormat
->lpThousandSep
||
1341 !lpFormat
->lpCurrencySymbol
|| lpFormat
->NegativeOrder
> 15 ||
1342 lpFormat
->PositiveOrder
> 3)))
1344 GetCurrencyFormatW_Error
:
1345 SetLastError(lpFormat
&& dwFlags
? ERROR_INVALID_FLAGS
: ERROR_INVALID_PARAMETER
);
1351 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1354 goto GetCurrencyFormatW_Error
;
1355 lpFormat
= &node
->cyfmt
;
1356 lpszNegStart
= lpszNeg
= GetNegative(node
);
1360 GetLocaleInfoW(lcid
, LOCALE_SNEGATIVESIGN
|(dwFlags
& LOCALE_NOUSEROVERRIDE
),
1361 szNegBuff
, sizeof(szNegBuff
)/sizeof(WCHAR
));
1362 lpszNegStart
= lpszNeg
= szNegBuff
;
1364 dwFlags
&= (LOCALE_NOUSEROVERRIDE
|LOCALE_USE_CP_ACP
);
1366 lpszNeg
= lpszNeg
+ strlenW(lpszNeg
) - 1;
1367 lpszCyStart
= lpFormat
->lpCurrencySymbol
;
1368 lpszCy
= lpszCyStart
+ strlenW(lpszCyStart
) - 1;
1370 /* Format the currency backwards into a temporary buffer */
1375 /* Check the number for validity */
1378 if (*szSrc
>= '0' && *szSrc
<= '9')
1380 dwState
|= NF_DIGITS
;
1381 if (dwState
& NF_ISREAL
)
1384 else if (*szSrc
== '-')
1387 goto GetCurrencyFormatW_Error
; /* '-' not first character */
1388 dwState
|= NF_ISNEGATIVE
;
1390 else if (*szSrc
== '.')
1392 if (dwState
& NF_ISREAL
)
1393 goto GetCurrencyFormatW_Error
; /* More than one '.' */
1394 dwState
|= NF_ISREAL
;
1397 goto GetCurrencyFormatW_Error
; /* Invalid char */
1400 szSrc
--; /* Point to last character */
1402 if (!(dwState
& NF_DIGITS
))
1403 goto GetCurrencyFormatW_Error
; /* No digits */
1405 if (dwState
& NF_ISNEGATIVE
)
1406 dwFmt
= NLS_NegCyFormats
[lpFormat
->NegativeOrder
];
1408 dwFmt
= NLS_PosCyFormats
[lpFormat
->PositiveOrder
];
1410 /* Add any trailing negative or currency signs */
1411 if (dwFmt
& CF_PARENS
)
1414 while (dwFmt
& (CF_MINUS_RIGHT
|CF_CY_RIGHT
))
1416 switch (dwFmt
& (CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
))
1418 case CF_MINUS_RIGHT
:
1419 case CF_MINUS_RIGHT
|CF_CY_RIGHT
:
1420 while (lpszNeg
>= lpszNegStart
)
1421 *szOut
-- = *lpszNeg
--;
1422 dwFmt
&= ~CF_MINUS_RIGHT
;
1426 case CF_MINUS_BEFORE
|CF_CY_RIGHT
:
1427 case CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
:
1428 while (lpszCy
>= lpszCyStart
)
1429 *szOut
-- = *lpszCy
--;
1430 if (dwFmt
& CF_CY_SPACE
)
1432 dwFmt
&= ~(CF_CY_RIGHT
|CF_MINUS_BEFORE
);
1437 /* Copy all digits up to the decimal point */
1438 if (!lpFormat
->NumDigits
)
1440 if (dwState
& NF_ISREAL
)
1442 while (*szSrc
!= '.') /* Don't write any decimals or a separator */
1444 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1445 dwState
|= NF_ROUND
;
1447 dwState
&= ~NF_ROUND
;
1455 LPWSTR lpszDec
= lpFormat
->lpDecimalSep
+ strlenW(lpFormat
->lpDecimalSep
) - 1;
1457 if (dwDecimals
<= lpFormat
->NumDigits
)
1459 dwDecimals
= lpFormat
->NumDigits
- dwDecimals
;
1460 while (dwDecimals
--)
1461 *szOut
-- = '0'; /* Pad to correct number of dp */
1465 dwDecimals
-= lpFormat
->NumDigits
;
1466 /* Skip excess decimals, and determine if we have to round the number */
1467 while (dwDecimals
--)
1469 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1470 dwState
|= NF_ROUND
;
1472 dwState
&= ~NF_ROUND
;
1477 if (dwState
& NF_ISREAL
)
1479 while (*szSrc
!= '.')
1481 if (dwState
& NF_ROUND
)
1484 *szOut
-- = '0'; /* continue rounding */
1487 dwState
&= ~NF_ROUND
;
1488 *szOut
-- = (*szSrc
)+1;
1493 *szOut
-- = *szSrc
--; /* Write existing decimals */
1495 szSrc
--; /* Skip '.' */
1497 while (lpszDec
>= lpFormat
->lpDecimalSep
)
1498 *szOut
-- = *lpszDec
--; /* Write decimal separator */
1501 dwGroupCount
= lpFormat
->Grouping
;
1503 /* Write the remaining whole number digits, including grouping chars */
1504 while (szSrc
>= lpszValue
&& *szSrc
>= '0' && *szSrc
<= '9')
1506 if (dwState
& NF_ROUND
)
1509 *szOut
-- = '0'; /* continue rounding */
1512 dwState
&= ~NF_ROUND
;
1513 *szOut
-- = (*szSrc
)+1;
1518 *szOut
-- = *szSrc
--;
1520 dwState
|= NF_DIGITS_OUT
;
1521 dwCurrentGroupCount
++;
1522 if (szSrc
>= lpszValue
&& dwCurrentGroupCount
== dwGroupCount
)
1524 LPWSTR lpszGrp
= lpFormat
->lpThousandSep
+ strlenW(lpFormat
->lpThousandSep
) - 1;
1526 while (lpszGrp
>= lpFormat
->lpThousandSep
)
1527 *szOut
-- = *lpszGrp
--; /* Write grouping char */
1529 dwCurrentGroupCount
= 0;
1532 if (dwState
& NF_ROUND
)
1533 *szOut
-- = '1'; /* e.g. .6 > 1.0 */
1534 else if (!(dwState
& NF_DIGITS_OUT
) && lpFormat
->LeadingZero
)
1535 *szOut
-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1537 /* Add any leading negative or currency sign */
1538 while (dwFmt
& (CF_MINUS_LEFT
|CF_CY_LEFT
))
1540 switch (dwFmt
& (CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
))
1543 case CF_MINUS_LEFT
|CF_CY_LEFT
:
1544 while (lpszNeg
>= lpszNegStart
)
1545 *szOut
-- = *lpszNeg
--;
1546 dwFmt
&= ~CF_MINUS_LEFT
;
1550 case CF_CY_LEFT
|CF_MINUS_BEFORE
:
1551 case CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
:
1552 if (dwFmt
& CF_CY_SPACE
)
1554 while (lpszCy
>= lpszCyStart
)
1555 *szOut
-- = *lpszCy
--;
1556 dwFmt
&= ~(CF_CY_LEFT
|CF_MINUS_BEFORE
);
1560 if (dwFmt
& CF_PARENS
)
1564 iRet
= strlenW(szOut
) + 1;
1568 memcpy(lpCurrencyStr
, szOut
, iRet
* sizeof(WCHAR
));
1571 memcpy(lpCurrencyStr
, szOut
, cchOut
* sizeof(WCHAR
));
1572 lpCurrencyStr
[cchOut
- 1] = '\0';
1573 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1580 /* FIXME: Everything below here needs to move somewhere else along with the
1581 * other EnumXXX functions, when a method for storing resources for
1582 * alternate calendars is determined.
1585 /**************************************************************************
1586 * EnumDateFormatsExA (KERNEL32.@)
1588 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1589 * LOCALE_NOUSEROVERRIDE here as well?
1591 BOOL WINAPI
EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc
, LCID lcid
, DWORD flags
)
1598 SetLastError(ERROR_INVALID_PARAMETER
);
1602 if (!GetLocaleInfoW(lcid
, LOCALE_ICALENDARTYPE
|LOCALE_RETURN_NUMBER
, (LPWSTR
)&cal_id
, sizeof(cal_id
)/sizeof(WCHAR
)))
1605 switch (flags
& ~LOCALE_USE_CP_ACP
)
1608 case DATE_SHORTDATE
:
1609 if (GetLocaleInfoA(lcid
, LOCALE_SSHORTDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1614 if (GetLocaleInfoA(lcid
, LOCALE_SLONGDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1618 case DATE_YEARMONTH
:
1619 if (GetLocaleInfoA(lcid
, LOCALE_SYEARMONTH
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1624 FIXME("Unknown date format (%ld)\n", flags
);
1625 SetLastError(ERROR_INVALID_PARAMETER
);
1631 /**************************************************************************
1632 * EnumDateFormatsExW (KERNEL32.@)
1634 BOOL WINAPI
EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc
, LCID lcid
, DWORD flags
)
1641 SetLastError(ERROR_INVALID_PARAMETER
);
1645 if (!GetLocaleInfoW(lcid
, LOCALE_ICALENDARTYPE
|LOCALE_RETURN_NUMBER
, (LPWSTR
)&cal_id
, sizeof(cal_id
)/sizeof(WCHAR
)))
1648 switch (flags
& ~LOCALE_USE_CP_ACP
)
1651 case DATE_SHORTDATE
:
1652 if (GetLocaleInfoW(lcid
, LOCALE_SSHORTDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1657 if (GetLocaleInfoW(lcid
, LOCALE_SLONGDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1661 case DATE_YEARMONTH
:
1662 if (GetLocaleInfoW(lcid
, LOCALE_SYEARMONTH
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1667 FIXME("Unknown date format (%ld)\n", flags
);
1668 SetLastError(ERROR_INVALID_PARAMETER
);
1674 /**************************************************************************
1675 * EnumDateFormatsA (KERNEL32.@)
1677 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1678 * LOCALE_NOUSEROVERRIDE here as well?
1680 BOOL WINAPI
EnumDateFormatsA(DATEFMT_ENUMPROCA proc
, LCID lcid
, DWORD flags
)
1686 SetLastError(ERROR_INVALID_PARAMETER
);
1690 switch (flags
& ~LOCALE_USE_CP_ACP
)
1693 case DATE_SHORTDATE
:
1694 if (GetLocaleInfoA(lcid
, LOCALE_SSHORTDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1699 if (GetLocaleInfoA(lcid
, LOCALE_SLONGDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1703 case DATE_YEARMONTH
:
1704 if (GetLocaleInfoA(lcid
, LOCALE_SYEARMONTH
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1709 FIXME("Unknown date format (%ld)\n", flags
);
1710 SetLastError(ERROR_INVALID_PARAMETER
);
1716 /**************************************************************************
1717 * EnumDateFormatsW (KERNEL32.@)
1719 BOOL WINAPI
EnumDateFormatsW(DATEFMT_ENUMPROCW proc
, LCID lcid
, DWORD flags
)
1725 SetLastError(ERROR_INVALID_PARAMETER
);
1729 switch (flags
& ~LOCALE_USE_CP_ACP
)
1732 case DATE_SHORTDATE
:
1733 if (GetLocaleInfoW(lcid
, LOCALE_SSHORTDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1738 if (GetLocaleInfoW(lcid
, LOCALE_SLONGDATE
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1742 case DATE_YEARMONTH
:
1743 if (GetLocaleInfoW(lcid
, LOCALE_SYEARMONTH
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1748 FIXME("Unknown date format (%ld)\n", flags
);
1749 SetLastError(ERROR_INVALID_PARAMETER
);
1755 /**************************************************************************
1756 * EnumTimeFormatsA (KERNEL32.@)
1758 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1759 * LOCALE_NOUSEROVERRIDE here as well?
1761 BOOL WINAPI
EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc
, LCID lcid
, DWORD flags
)
1767 SetLastError(ERROR_INVALID_PARAMETER
);
1771 switch (flags
& ~LOCALE_USE_CP_ACP
)
1774 if (GetLocaleInfoA(lcid
, LOCALE_STIMEFORMAT
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1779 FIXME("Unknown time format (%ld)\n", flags
);
1780 SetLastError(ERROR_INVALID_PARAMETER
);
1786 /**************************************************************************
1787 * EnumTimeFormatsW (KERNEL32.@)
1789 BOOL WINAPI
EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc
, LCID lcid
, DWORD flags
)
1795 SetLastError(ERROR_INVALID_PARAMETER
);
1799 switch (flags
& ~LOCALE_USE_CP_ACP
)
1802 if (GetLocaleInfoW(lcid
, LOCALE_STIMEFORMAT
| (flags
& LOCALE_USE_CP_ACP
), buf
, 256))
1807 FIXME("Unknown time format (%ld)\n", flags
);
1808 SetLastError(ERROR_INVALID_PARAMETER
);
1814 /******************************************************************************
1815 * NLS_EnumCalendarInfoAW <internal>
1816 * Enumerates calendar information for a specified locale.
1819 * calinfoproc [I] Pointer to the callback
1820 * locale [I] The locale for which to retrieve calendar information.
1821 * This parameter can be a locale identifier created by the
1822 * MAKELCID macro, or one of the following values:
1823 * LOCALE_SYSTEM_DEFAULT
1824 * Use the default system locale.
1825 * LOCALE_USER_DEFAULT
1826 * Use the default user locale.
1827 * calendar [I] The calendar for which information is requested, or
1828 * ENUM_ALL_CALENDARS.
1829 * caltype [I] The type of calendar information to be returned. Note
1830 * that only one CALTYPE value can be specified per call
1831 * of this function, except where noted.
1832 * unicode [I] Specifies if the callback expects a unicode string.
1833 * ex [I] Specifies if the callback needs the calendar identifier.
1837 * Failure: FALSE. Use GetLastError() to determine the cause.
1840 * When the ANSI version of this function is used with a Unicode-only LCID,
1841 * the call can succeed because the system uses the system code page.
1842 * However, characters that are undefined in the system code page appear
1843 * in the string as a question mark (?).
1846 * The above note should be respected by GetCalendarInfoA.
1848 static BOOL
NLS_EnumCalendarInfoAW(void *calinfoproc
, LCID locale
,
1849 CALID calendar
, CALTYPE caltype
, BOOL unicode
, BOOL ex
)
1851 WCHAR
*buf
, *opt
= NULL
, *iter
= NULL
;
1853 int bufSz
= 200; /* the size of the buffer */
1855 if (calinfoproc
== NULL
)
1857 SetLastError(ERROR_INVALID_PARAMETER
);
1861 buf
= HeapAlloc(GetProcessHeap(), 0, bufSz
);
1864 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1868 if (calendar
== ENUM_ALL_CALENDARS
)
1870 int optSz
= GetLocaleInfoW(locale
, LOCALE_IOPTIONALCALENDAR
, NULL
, 0);
1873 opt
= HeapAlloc(GetProcessHeap(), 0, optSz
* sizeof(WCHAR
));
1876 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1877 goto NLS_EnumCalendarInfoAW_Cleanup
;
1879 if (GetLocaleInfoW(locale
, LOCALE_IOPTIONALCALENDAR
, opt
, optSz
))
1882 calendar
= NLS_GetLocaleNumber(locale
, LOCALE_ICALENDARTYPE
);
1885 while (TRUE
) /* loop through calendars */
1887 do /* loop until there's no error */
1890 ret
= GetCalendarInfoW(locale
, calendar
, caltype
, buf
, bufSz
/ sizeof(WCHAR
), NULL
);
1891 else ret
= GetCalendarInfoA(locale
, calendar
, caltype
, (CHAR
*)buf
, bufSz
/ sizeof(CHAR
), NULL
);
1895 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
1896 { /* so resize it */
1899 newSz
= GetCalendarInfoW(locale
, calendar
, caltype
, NULL
, 0, NULL
) * sizeof(WCHAR
);
1900 else newSz
= GetCalendarInfoA(locale
, calendar
, caltype
, NULL
, 0, NULL
) * sizeof(CHAR
);
1903 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz
, newSz
);
1904 goto NLS_EnumCalendarInfoAW_Cleanup
;
1907 WARN("Buffer too small; resizing to %d bytes.\n", bufSz
);
1908 buf
= HeapReAlloc(GetProcessHeap(), 0, buf
, bufSz
);
1910 goto NLS_EnumCalendarInfoAW_Cleanup
;
1911 } else goto NLS_EnumCalendarInfoAW_Cleanup
;
1915 /* Here we are. We pass the buffer to the correct version of
1916 * the callback. Because it's not the same number of params,
1917 * we must check for Ex, but we don't care about Unicode
1918 * because the buffer is already in the correct format.
1921 ret
= ((CALINFO_ENUMPROCEXW
)calinfoproc
)(buf
, calendar
);
1923 ret
= ((CALINFO_ENUMPROCW
)calinfoproc
)(buf
);
1925 if (!ret
) { /* the callback told to stop */
1930 if ((iter
== NULL
) || (*iter
== 0)) /* no more calendars */
1934 while ((*iter
>= '0') && (*iter
<= '9'))
1935 calendar
= calendar
* 10 + *iter
++ - '0';
1939 SetLastError(ERROR_BADDB
);
1945 NLS_EnumCalendarInfoAW_Cleanup
:
1946 HeapFree(GetProcessHeap(), 0, opt
);
1947 HeapFree(GetProcessHeap(), 0, buf
);
1951 /******************************************************************************
1952 * EnumCalendarInfoA [KERNEL32.@]
1954 * See EnumCalendarInfoAW.
1956 BOOL WINAPI
EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc
,LCID locale
,
1957 CALID calendar
,CALTYPE caltype
)
1959 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc
, locale
, calendar
, caltype
);
1960 return NLS_EnumCalendarInfoAW(calinfoproc
, locale
, calendar
, caltype
, FALSE
, FALSE
);
1963 /******************************************************************************
1964 * EnumCalendarInfoW [KERNEL32.@]
1966 * See EnumCalendarInfoAW.
1968 BOOL WINAPI
EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc
,LCID locale
,
1969 CALID calendar
,CALTYPE caltype
)
1971 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc
, locale
, calendar
, caltype
);
1972 return NLS_EnumCalendarInfoAW(calinfoproc
, locale
, calendar
, caltype
, TRUE
, FALSE
);
1975 /******************************************************************************
1976 * EnumCalendarInfoExA [KERNEL32.@]
1978 * See EnumCalendarInfoAW.
1980 BOOL WINAPI
EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc
,LCID locale
,
1981 CALID calendar
,CALTYPE caltype
)
1983 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc
, locale
, calendar
, caltype
);
1984 return NLS_EnumCalendarInfoAW(calinfoproc
, locale
, calendar
, caltype
, FALSE
, TRUE
);
1987 /******************************************************************************
1988 * EnumCalendarInfoExW [KERNEL32.@]
1990 * See EnumCalendarInfoAW.
1992 BOOL WINAPI
EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc
,LCID locale
,
1993 CALID calendar
,CALTYPE caltype
)
1995 TRACE("(%p,0x%08lx,0x%08lx,0x%08lx)\n", calinfoproc
, locale
, calendar
, caltype
);
1996 return NLS_EnumCalendarInfoAW(calinfoproc
, locale
, calendar
, caltype
, TRUE
, TRUE
);