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