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
33 #include "wine/debug.h"
36 #include "kernel_private.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(nls
);
40 #define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
41 #define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
43 /* Since calculating the formatting data for each locale is time-consuming,
44 * we get the format data for each locale only once and cache it in memory.
45 * We cache both the system default and user overridden data, after converting
46 * them into the formats that the functions here expect. Since these functions
47 * will typically be called with only a small number of the total locales
48 * installed, the memory overhead is minimal while the speedup is significant.
50 * Our cache takes the form of a singly linked list, whose node is below:
52 #define NLS_NUM_CACHED_STRINGS 57
54 typedef struct _NLS_FORMAT_NODE
56 LCID lcid
; /* Locale Id */
57 DWORD dwFlags
; /* 0 or LOCALE_NOUSEROVERRIDE */
58 DWORD dwCodePage
; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
59 NUMBERFMTW fmt
; /* Default format for numbers */
60 CURRENCYFMTW cyfmt
; /* Default format for currencies */
61 LPWSTR lppszStrings
[NLS_NUM_CACHED_STRINGS
]; /* Default formats,day/month names */
62 WCHAR szShortAM
[2]; /* Short 'AM' marker */
63 WCHAR szShortPM
[2]; /* Short 'PM' marker */
64 struct _NLS_FORMAT_NODE
*next
;
67 /* Macros to get particular data strings from a format node */
68 #define GetNegative(fmt) fmt->lppszStrings[0]
69 #define GetLongDate(fmt) fmt->lppszStrings[1]
70 #define GetShortDate(fmt) fmt->lppszStrings[2]
71 #define GetTime(fmt) fmt->lppszStrings[3]
72 #define GetAM(fmt) fmt->lppszStrings[54]
73 #define GetPM(fmt) fmt->lppszStrings[55]
74 #define GetYearMonth(fmt) fmt->lppszStrings[56]
76 #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
77 #define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
78 #define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
79 #define GetGenitiveMonth(fmt,mth) fmt->lppszStrings[30 + mth]
80 #define GetShortMonth(fmt,mth) fmt->lppszStrings[42 + mth]
82 /* Write access to the cache is protected by this critical section */
83 static CRITICAL_SECTION NLS_FormatsCS
;
84 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug
=
87 { &NLS_FormatsCS_debug
.ProcessLocksList
,
88 &NLS_FormatsCS_debug
.ProcessLocksList
},
89 0, 0, { (DWORD_PTR
)(__FILE__
": NLS_Formats") }
91 static CRITICAL_SECTION NLS_FormatsCS
= { &NLS_FormatsCS_debug
, -1, 0, 0, 0, 0 };
93 /**************************************************************************
94 * NLS_GetLocaleNumber <internal>
96 * Get a numeric locale format value.
98 static DWORD
NLS_GetLocaleNumber(LCID lcid
, DWORD dwFlags
)
104 GetLocaleInfoW(lcid
, dwFlags
, szBuff
, ARRAY_SIZE(szBuff
));
106 if (szBuff
[0] && szBuff
[1] == ';' && szBuff
[2] != '0')
107 dwVal
= (szBuff
[0] - '0') * 10 + (szBuff
[2] - '0');
110 const WCHAR
* iter
= szBuff
;
112 while(*iter
>= '0' && *iter
<= '9')
113 dwVal
= dwVal
* 10 + (*iter
++ - '0');
118 /**************************************************************************
119 * NLS_GetLocaleString <internal>
121 * Get a string locale format value.
123 static WCHAR
* NLS_GetLocaleString(LCID lcid
, DWORD dwFlags
)
125 WCHAR szBuff
[80], *str
;
129 GetLocaleInfoW(lcid
, dwFlags
, szBuff
, ARRAY_SIZE(szBuff
));
130 dwLen
= lstrlenW(szBuff
) + 1;
131 str
= HeapAlloc(GetProcessHeap(), 0, dwLen
* sizeof(WCHAR
));
133 memcpy(str
, szBuff
, dwLen
* sizeof(WCHAR
));
137 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
138 TRACE( #type ": %d (%08x)\n", (DWORD)num, (DWORD)num)
140 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
141 TRACE( #type ": %s\n", debugstr_w(str))
143 /**************************************************************************
144 * NLS_GetFormats <internal>
146 * Calculate (and cache) the number formats for a locale.
148 static const NLS_FORMAT_NODE
*NLS_GetFormats(LCID lcid
, DWORD dwFlags
)
150 /* GetLocaleInfo() identifiers for cached formatting strings */
151 static const LCTYPE NLS_LocaleIndices
[] = {
152 LOCALE_SNEGATIVESIGN
,
153 LOCALE_SLONGDATE
, LOCALE_SSHORTDATE
,
155 LOCALE_SDAYNAME1
, LOCALE_SDAYNAME2
, LOCALE_SDAYNAME3
,
156 LOCALE_SDAYNAME4
, LOCALE_SDAYNAME5
, LOCALE_SDAYNAME6
, LOCALE_SDAYNAME7
,
157 LOCALE_SABBREVDAYNAME1
, LOCALE_SABBREVDAYNAME2
, LOCALE_SABBREVDAYNAME3
,
158 LOCALE_SABBREVDAYNAME4
, LOCALE_SABBREVDAYNAME5
, LOCALE_SABBREVDAYNAME6
,
159 LOCALE_SABBREVDAYNAME7
,
160 LOCALE_SMONTHNAME1
, LOCALE_SMONTHNAME2
, LOCALE_SMONTHNAME3
,
161 LOCALE_SMONTHNAME4
, LOCALE_SMONTHNAME5
, LOCALE_SMONTHNAME6
,
162 LOCALE_SMONTHNAME7
, LOCALE_SMONTHNAME8
, LOCALE_SMONTHNAME9
,
163 LOCALE_SMONTHNAME10
, LOCALE_SMONTHNAME11
, LOCALE_SMONTHNAME12
,
164 LOCALE_SMONTHNAME1
| LOCALE_RETURN_GENITIVE_NAMES
,
165 LOCALE_SMONTHNAME2
| LOCALE_RETURN_GENITIVE_NAMES
,
166 LOCALE_SMONTHNAME3
| LOCALE_RETURN_GENITIVE_NAMES
,
167 LOCALE_SMONTHNAME4
| LOCALE_RETURN_GENITIVE_NAMES
,
168 LOCALE_SMONTHNAME5
| LOCALE_RETURN_GENITIVE_NAMES
,
169 LOCALE_SMONTHNAME6
| LOCALE_RETURN_GENITIVE_NAMES
,
170 LOCALE_SMONTHNAME7
| LOCALE_RETURN_GENITIVE_NAMES
,
171 LOCALE_SMONTHNAME8
| LOCALE_RETURN_GENITIVE_NAMES
,
172 LOCALE_SMONTHNAME9
| LOCALE_RETURN_GENITIVE_NAMES
,
173 LOCALE_SMONTHNAME10
| LOCALE_RETURN_GENITIVE_NAMES
,
174 LOCALE_SMONTHNAME11
| LOCALE_RETURN_GENITIVE_NAMES
,
175 LOCALE_SMONTHNAME12
| LOCALE_RETURN_GENITIVE_NAMES
,
176 LOCALE_SABBREVMONTHNAME1
, LOCALE_SABBREVMONTHNAME2
, LOCALE_SABBREVMONTHNAME3
,
177 LOCALE_SABBREVMONTHNAME4
, LOCALE_SABBREVMONTHNAME5
, LOCALE_SABBREVMONTHNAME6
,
178 LOCALE_SABBREVMONTHNAME7
, LOCALE_SABBREVMONTHNAME8
, LOCALE_SABBREVMONTHNAME9
,
179 LOCALE_SABBREVMONTHNAME10
, LOCALE_SABBREVMONTHNAME11
, LOCALE_SABBREVMONTHNAME12
,
180 LOCALE_S1159
, LOCALE_S2359
,
183 static NLS_FORMAT_NODE
*NLS_CachedFormats
= NULL
;
184 NLS_FORMAT_NODE
*node
= NLS_CachedFormats
;
186 dwFlags
&= LOCALE_NOUSEROVERRIDE
;
188 TRACE("(0x%04x,0x%08x)\n", lcid
, dwFlags
);
190 /* See if we have already cached the locales number format */
191 while (node
&& (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
) && node
->next
)
194 if (!node
|| node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
)
196 NLS_FORMAT_NODE
*new_node
;
199 TRACE("Creating new cache entry\n");
201 if (!(new_node
= HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE
))))
204 GET_LOCALE_NUMBER(new_node
->dwCodePage
, LOCALE_IDEFAULTANSICODEPAGE
);
207 new_node
->lcid
= lcid
;
208 new_node
->dwFlags
= dwFlags
;
209 new_node
->next
= NULL
;
211 GET_LOCALE_NUMBER(new_node
->fmt
.NumDigits
, LOCALE_IDIGITS
);
212 GET_LOCALE_NUMBER(new_node
->fmt
.LeadingZero
, LOCALE_ILZERO
);
213 GET_LOCALE_NUMBER(new_node
->fmt
.NegativeOrder
, LOCALE_INEGNUMBER
);
215 GET_LOCALE_NUMBER(new_node
->fmt
.Grouping
, LOCALE_SGROUPING
);
216 if (new_node
->fmt
.Grouping
> 9 && new_node
->fmt
.Grouping
!= 32)
218 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
219 new_node
->fmt
.Grouping
);
220 new_node
->fmt
.Grouping
= 0;
223 GET_LOCALE_STRING(new_node
->fmt
.lpDecimalSep
, LOCALE_SDECIMAL
);
224 GET_LOCALE_STRING(new_node
->fmt
.lpThousandSep
, LOCALE_STHOUSAND
);
226 /* Currency Format */
227 new_node
->cyfmt
.NumDigits
= new_node
->fmt
.NumDigits
;
228 new_node
->cyfmt
.LeadingZero
= new_node
->fmt
.LeadingZero
;
230 GET_LOCALE_NUMBER(new_node
->cyfmt
.Grouping
, LOCALE_SGROUPING
);
232 if (new_node
->cyfmt
.Grouping
> 9)
234 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
235 new_node
->cyfmt
.Grouping
);
236 new_node
->cyfmt
.Grouping
= 0;
239 GET_LOCALE_NUMBER(new_node
->cyfmt
.NegativeOrder
, LOCALE_INEGCURR
);
240 if (new_node
->cyfmt
.NegativeOrder
> 15)
242 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
243 new_node
->cyfmt
.NegativeOrder
);
244 new_node
->cyfmt
.NegativeOrder
= 0;
246 GET_LOCALE_NUMBER(new_node
->cyfmt
.PositiveOrder
, LOCALE_ICURRENCY
);
247 if (new_node
->cyfmt
.PositiveOrder
> 3)
249 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
250 new_node
->cyfmt
.PositiveOrder
);
251 new_node
->cyfmt
.PositiveOrder
= 0;
253 GET_LOCALE_STRING(new_node
->cyfmt
.lpDecimalSep
, LOCALE_SMONDECIMALSEP
);
254 GET_LOCALE_STRING(new_node
->cyfmt
.lpThousandSep
, LOCALE_SMONTHOUSANDSEP
);
255 GET_LOCALE_STRING(new_node
->cyfmt
.lpCurrencySymbol
, LOCALE_SCURRENCY
);
257 /* Date/Time Format info, negative character, etc */
258 for (i
= 0; i
< ARRAY_SIZE(NLS_LocaleIndices
); i
++)
260 GET_LOCALE_STRING(new_node
->lppszStrings
[i
], NLS_LocaleIndices
[i
]);
262 /* Save some memory if month genitive name is the same or not present */
263 for (i
= 0; i
< 12; i
++)
265 if (wcscmp(GetLongMonth(new_node
, i
), GetGenitiveMonth(new_node
, i
)) == 0)
267 HeapFree(GetProcessHeap(), 0, GetGenitiveMonth(new_node
, i
));
268 GetGenitiveMonth(new_node
, i
) = NULL
;
272 new_node
->szShortAM
[0] = GetAM(new_node
)[0]; new_node
->szShortAM
[1] = '\0';
273 new_node
->szShortPM
[0] = GetPM(new_node
)[0]; new_node
->szShortPM
[1] = '\0';
275 /* Now add the computed format to the cache */
276 RtlEnterCriticalSection(&NLS_FormatsCS
);
278 /* Search again: We may have raced to add the node */
279 node
= NLS_CachedFormats
;
280 while (node
&& (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
) && node
->next
)
285 node
= NLS_CachedFormats
= new_node
; /* Empty list */
288 else if (node
->lcid
!= lcid
|| node
->dwFlags
!= dwFlags
)
290 node
->next
= new_node
; /* Not in the list, add to end */
295 RtlLeaveCriticalSection(&NLS_FormatsCS
);
299 /* We raced and lost: The node was already added by another thread.
300 * node points to the currently cached node, so free new_node.
302 for (i
= 0; i
< ARRAY_SIZE(NLS_LocaleIndices
); i
++)
303 HeapFree(GetProcessHeap(), 0, new_node
->lppszStrings
[i
]);
304 HeapFree(GetProcessHeap(), 0, new_node
->fmt
.lpDecimalSep
);
305 HeapFree(GetProcessHeap(), 0, new_node
->fmt
.lpThousandSep
);
306 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpDecimalSep
);
307 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpThousandSep
);
308 HeapFree(GetProcessHeap(), 0, new_node
->cyfmt
.lpCurrencySymbol
);
309 HeapFree(GetProcessHeap(), 0, new_node
);
315 /**************************************************************************
316 * NLS_IsUnicodeOnlyLcid <internal>
318 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
320 static BOOL
NLS_IsUnicodeOnlyLcid(LCID lcid
)
322 lcid
= ConvertDefaultLocale(lcid
);
324 switch (PRIMARYLANGID(lcid
))
336 TRACE("lcid 0x%08x: langid 0x%4x is Unicode Only\n", lcid
, PRIMARYLANGID(lcid
));
344 * Formatting of dates, times, numbers and currencies.
347 #define IsLiteralMarker(p) (p == '\'')
348 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
349 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
351 /* Only the following flags can be given if a date/time format is specified */
352 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY)
353 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
354 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
357 /******************************************************************************
358 * NLS_GetDateTimeFormatW <internal>
360 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
363 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
364 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
366 static INT
NLS_GetDateTimeFormatW(LCID lcid
, DWORD dwFlags
,
367 const SYSTEMTIME
* lpTime
, LPCWSTR lpFormat
,
368 LPWSTR lpStr
, INT cchOut
)
370 const NLS_FORMAT_NODE
*node
;
373 INT lastFormatPos
= 0;
374 BOOL bSkipping
= FALSE
; /* Skipping text around marker? */
375 BOOL d_dd_formatted
= FALSE
; /* previous formatted part was for d or dd */
377 /* Verify our arguments */
378 if ((cchOut
&& !lpStr
) || !(node
= NLS_GetFormats(lcid
, dwFlags
)))
379 goto invalid_parameter
;
381 if (dwFlags
& ~(DATE_DATEVARSONLY
|TIME_TIMEVARSONLY
))
384 ((dwFlags
& DATE_DATEVARSONLY
&& dwFlags
& ~DATE_FORMAT_FLAGS
) ||
385 (dwFlags
& TIME_TIMEVARSONLY
&& dwFlags
& ~TIME_FORMAT_FLAGS
)))
390 if (dwFlags
& DATE_DATEVARSONLY
)
392 if ((dwFlags
& (DATE_LTRREADING
|DATE_RTLREADING
)) == (DATE_LTRREADING
|DATE_RTLREADING
))
394 else if (dwFlags
& (DATE_LTRREADING
|DATE_RTLREADING
))
395 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
397 switch (dwFlags
& (DATE_SHORTDATE
|DATE_LONGDATE
|DATE_YEARMONTH
))
415 /* Use the appropriate default format */
416 if (dwFlags
& DATE_DATEVARSONLY
)
418 if (dwFlags
& DATE_YEARMONTH
)
419 lpFormat
= GetYearMonth(node
);
420 else if (dwFlags
& DATE_LONGDATE
)
421 lpFormat
= GetLongDate(node
);
423 lpFormat
= GetShortDate(node
);
426 lpFormat
= GetTime(node
);
431 GetLocalTime(&st
); /* Default to current time */
436 if (dwFlags
& DATE_DATEVARSONLY
)
440 /* Verify the date and correct the D.O.W. if needed */
441 memset(&st
, 0, sizeof(st
));
442 st
.wYear
= lpTime
->wYear
;
443 st
.wMonth
= lpTime
->wMonth
;
444 st
.wDay
= lpTime
->wDay
;
446 if (st
.wDay
> 31 || st
.wMonth
> 12 || !SystemTimeToFileTime(&st
, &ftTmp
))
447 goto invalid_parameter
;
449 FileTimeToSystemTime(&ftTmp
, &st
);
453 if (dwFlags
& TIME_TIMEVARSONLY
)
455 /* Verify the time */
456 if (lpTime
->wHour
> 24 || lpTime
->wMinute
> 59 || lpTime
->wSecond
> 59)
457 goto invalid_parameter
;
461 /* Format the output */
464 if (IsLiteralMarker(*lpFormat
))
466 /* Start of a literal string */
469 /* Loop until the end of the literal marker or end of the string */
472 if (IsLiteralMarker(*lpFormat
))
475 if (!IsLiteralMarker(*lpFormat
))
476 break; /* Terminating literal marker */
480 cchWritten
++; /* Count size only */
481 else if (cchWritten
>= cchOut
)
485 lpStr
[cchWritten
] = *lpFormat
;
491 else if ((dwFlags
& DATE_DATEVARSONLY
&& IsDateFmtChar(*lpFormat
)) ||
492 (dwFlags
& TIME_TIMEVARSONLY
&& IsTimeFmtChar(*lpFormat
)))
494 WCHAR buff
[32], fmtChar
;
495 LPCWSTR szAdd
= NULL
;
497 int count
= 0, dwLen
;
502 while (*lpFormat
== fmtChar
)
509 if (fmtChar
!= 'M') d_dd_formatted
= FALSE
;
514 szAdd
= GetLongDay(node
, (lpTime
->wDayOfWeek
+ 6) % 7);
516 szAdd
= GetShortDay(node
, (lpTime
->wDayOfWeek
+ 6) % 7);
519 dwVal
= lpTime
->wDay
;
521 d_dd_formatted
= TRUE
;
528 LPCWSTR genitive
= GetGenitiveMonth(node
, lpTime
->wMonth
- 1);
538 LPCWSTR format
= lpFormat
;
539 /* Look forward now, if next format pattern is for day genitive
540 name should be used */
543 /* Skip parts within markers */
544 if (IsLiteralMarker(*format
))
549 if (IsLiteralMarker(*format
))
552 if (!IsLiteralMarker(*format
)) break;
556 if (*format
!= ' ') break;
559 /* Only numeric day form matters */
563 while (*++format
== 'd') dcount
++;
572 szAdd
= GetLongMonth(node
, lpTime
->wMonth
- 1);
575 szAdd
= GetShortMonth(node
, lpTime
->wMonth
- 1);
578 dwVal
= lpTime
->wMonth
;
587 dwVal
= lpTime
->wYear
;
591 count
= count
> 2 ? 2 : count
;
592 dwVal
= lpTime
->wYear
% 100;
600 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
601 * When it is fixed, this string should be cached in 'node'.
603 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
604 buff
[0] = 'A'; buff
[1] = 'D'; buff
[2] = '\0';
608 buff
[0] = 'g'; buff
[1] = '\0'; /* Add a literal 'g' */
614 if (!(dwFlags
& TIME_FORCE24HOURFORMAT
))
616 count
= count
> 2 ? 2 : count
;
617 dwVal
= lpTime
->wHour
== 0 ? 12 : (lpTime
->wHour
- 1) % 12 + 1;
621 /* .. fall through if we are forced to output in 24 hour format */
624 count
= count
> 2 ? 2 : count
;
625 dwVal
= lpTime
->wHour
;
630 if (dwFlags
& TIME_NOMINUTESORSECONDS
)
632 cchWritten
= lastFormatPos
; /* Skip */
637 count
= count
> 2 ? 2 : count
;
638 dwVal
= lpTime
->wMinute
;
644 if (dwFlags
& (TIME_NOSECONDS
|TIME_NOMINUTESORSECONDS
))
646 cchWritten
= lastFormatPos
; /* Skip */
651 count
= count
> 2 ? 2 : count
;
652 dwVal
= lpTime
->wSecond
;
658 if (dwFlags
& TIME_NOTIMEMARKER
)
660 cchWritten
= lastFormatPos
; /* Skip */
666 szAdd
= lpTime
->wHour
< 12 ? node
->szShortAM
: node
->szShortPM
;
668 szAdd
= lpTime
->wHour
< 12 ? GetAM(node
) : GetPM(node
);
673 if (szAdd
== buff
&& buff
[0] == '\0')
675 /* We have a numeric value to add */
676 swprintf(buff
, ARRAY_SIZE(buff
), L
"%.*d", count
, dwVal
);
679 dwLen
= szAdd
? lstrlenW(szAdd
) : 0;
683 if (cchWritten
+ dwLen
< cchOut
)
684 memcpy(lpStr
+ cchWritten
, szAdd
, dwLen
* sizeof(WCHAR
));
687 memcpy(lpStr
+ cchWritten
, szAdd
, (cchOut
- cchWritten
) * sizeof(WCHAR
));
692 lastFormatPos
= cchWritten
; /* Save position of last output format text */
696 /* Literal character */
698 cchWritten
++; /* Count size only */
699 else if (cchWritten
>= cchOut
)
701 else if (!bSkipping
|| *lpFormat
== ' ')
703 lpStr
[cchWritten
] = *lpFormat
;
710 /* Final string terminator and sanity check */
713 if (cchWritten
>= cchOut
)
716 lpStr
[cchWritten
] = '\0';
718 cchWritten
++; /* Include terminating NUL */
720 TRACE("returning length=%d, output=%s\n", cchWritten
, debugstr_w(lpStr
));
724 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
725 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
729 SetLastError(ERROR_INVALID_PARAMETER
);
733 SetLastError(ERROR_INVALID_FLAGS
);
737 /******************************************************************************
738 * NLS_GetDateTimeFormatA <internal>
740 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
742 static INT
NLS_GetDateTimeFormatA(LCID lcid
, DWORD dwFlags
,
743 const SYSTEMTIME
* lpTime
,
744 LPCSTR lpFormat
, LPSTR lpStr
, INT cchOut
)
747 WCHAR szFormat
[128], szOut
[128];
750 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid
, dwFlags
, lpTime
,
751 debugstr_a(lpFormat
), lpStr
, cchOut
);
753 if (NLS_IsUnicodeOnlyLcid(lcid
))
755 SetLastError(ERROR_INVALID_PARAMETER
);
759 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
761 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
764 SetLastError(ERROR_INVALID_PARAMETER
);
768 cp
= node
->dwCodePage
;
772 MultiByteToWideChar(cp
, 0, lpFormat
, -1, szFormat
, ARRAY_SIZE(szFormat
));
774 if (cchOut
> (int) ARRAY_SIZE(szOut
))
775 cchOut
= ARRAY_SIZE(szOut
);
779 iRet
= NLS_GetDateTimeFormatW(lcid
, dwFlags
, lpTime
, lpFormat
? szFormat
: NULL
,
780 lpStr
? szOut
: NULL
, cchOut
);
785 WideCharToMultiByte(cp
, 0, szOut
, iRet
? -1 : cchOut
, lpStr
, cchOut
, 0, 0);
786 else if (cchOut
&& iRet
)
792 /******************************************************************************
793 * GetDateFormatA [KERNEL32.@]
795 * Format a date for a given locale.
798 * lcid [I] Locale to format for
799 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
800 * lpTime [I] Date to format
801 * lpFormat [I] Format string, or NULL to use the system defaults
802 * lpDateStr [O] Destination for formatted string
803 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
806 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
807 * details returned by GetLocaleInfoA() and modified by dwFlags.
808 * - lpFormat is a string of characters and formatting tokens. Any characters
809 * in the string are copied verbatim to lpDateStr, with tokens being replaced
810 * by the date values they represent.
811 * - The following tokens have special meanings in a date format string:
814 *| d Single digit day of the month (no leading 0)
815 *| dd Double digit day of the month
816 *| ddd Short name for the day of the week
817 *| dddd Long name for the day of the week
818 *| M Single digit month of the year (no leading 0)
819 *| MM Double digit month of the year
820 *| MMM Short name for the month of the year
821 *| MMMM Long name for the month of the year
822 *| y Double digit year number (no leading 0)
823 *| yy Double digit year number
824 *| yyyy Four digit year number
825 *| gg Era string, for example 'AD'.
826 * - To output any literal character that could be misidentified as a token,
827 * enclose it in single quotes.
828 * - The Ascii version of this function fails if lcid is Unicode only.
831 * Success: The number of character written to lpDateStr, or that would
832 * have been written, if cchOut is 0.
833 * Failure: 0. Use GetLastError() to determine the cause.
835 INT WINAPI
GetDateFormatA( LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
836 LPCSTR lpFormat
, LPSTR lpDateStr
, INT cchOut
)
838 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
839 debugstr_a(lpFormat
), lpDateStr
, cchOut
);
841 return NLS_GetDateTimeFormatA(lcid
, dwFlags
| DATE_DATEVARSONLY
, lpTime
,
842 lpFormat
, lpDateStr
, cchOut
);
845 /******************************************************************************
846 * GetDateFormatEx [KERNEL32.@]
848 * Format a date for a given locale.
851 * localename [I] Locale to format for
852 * flags [I] LOCALE_ and DATE_ flags from "winnls.h"
853 * date [I] Date to format
854 * format [I] Format string, or NULL to use the locale defaults
855 * outbuf [O] Destination for formatted string
856 * bufsize [I] Size of outbuf, or 0 to calculate the resulting size
857 * calendar [I] Reserved, must be NULL
859 * See GetDateFormatA for notes.
862 * Success: The number of characters written to outbuf, or that would have
863 * been written if bufsize is 0.
864 * Failure: 0. Use GetLastError() to determine the cause.
866 INT WINAPI
GetDateFormatEx(LPCWSTR localename
, DWORD flags
,
867 const SYSTEMTIME
* date
, LPCWSTR format
,
868 LPWSTR outbuf
, INT bufsize
, LPCWSTR calendar
)
870 TRACE("(%s,0x%08x,%p,%s,%p,%d,%s)\n", debugstr_w(localename
), flags
,
871 date
, debugstr_w(format
), outbuf
, bufsize
, debugstr_w(calendar
));
873 /* Parameter is currently reserved and Windows errors if set */
874 if (calendar
!= NULL
)
876 SetLastError(ERROR_INVALID_PARAMETER
);
880 return NLS_GetDateTimeFormatW(LocaleNameToLCID(localename
, 0),
881 flags
| DATE_DATEVARSONLY
, date
, format
,
885 /******************************************************************************
886 * GetDateFormatW [KERNEL32.@]
888 * See GetDateFormatA.
890 INT WINAPI
GetDateFormatW(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
891 LPCWSTR lpFormat
, LPWSTR lpDateStr
, INT cchOut
)
893 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid
, dwFlags
, lpTime
,
894 debugstr_w(lpFormat
), lpDateStr
, cchOut
);
896 return NLS_GetDateTimeFormatW(lcid
, dwFlags
|DATE_DATEVARSONLY
, lpTime
,
897 lpFormat
, lpDateStr
, cchOut
);
900 /******************************************************************************
901 * GetTimeFormatA [KERNEL32.@]
903 * Format a time for a given locale.
906 * lcid [I] Locale to format for
907 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
908 * lpTime [I] Time to format
909 * lpFormat [I] Formatting overrides
910 * lpTimeStr [O] Destination for formatted string
911 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
914 * - If lpFormat is NULL, lpszValue will be formatted according to the format
915 * details returned by GetLocaleInfoA() and modified by dwFlags.
916 * - lpFormat is a string of characters and formatting tokens. Any characters
917 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
918 * by the time values they represent.
919 * - The following tokens have special meanings in a time format string:
922 *| h Hours with no leading zero (12-hour clock)
923 *| hh Hours with full two digits (12-hour clock)
924 *| H Hours with no leading zero (24-hour clock)
925 *| HH Hours with full two digits (24-hour clock)
926 *| m Minutes with no leading zero
927 *| mm Minutes with full two digits
928 *| s Seconds with no leading zero
929 *| ss Seconds with full two digits
930 *| t Short time marker (e.g. "A" or "P")
931 *| tt Long time marker (e.g. "AM", "PM")
932 * - To output any literal character that could be misidentified as a token,
933 * enclose it in single quotes.
934 * - The Ascii version of this function fails if lcid is Unicode only.
937 * Success: The number of character written to lpTimeStr, or that would
938 * have been written, if cchOut is 0.
939 * Failure: 0. Use GetLastError() to determine the cause.
941 INT WINAPI
GetTimeFormatA(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
942 LPCSTR lpFormat
, LPSTR lpTimeStr
, INT cchOut
)
944 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
945 debugstr_a(lpFormat
), lpTimeStr
, cchOut
);
947 return NLS_GetDateTimeFormatA(lcid
, dwFlags
|TIME_TIMEVARSONLY
, lpTime
,
948 lpFormat
, lpTimeStr
, cchOut
);
951 /******************************************************************************
952 * GetTimeFormatEx [KERNEL32.@]
954 * Format a date for a given locale.
957 * localename [I] Locale to format for
958 * flags [I] LOCALE_ and TIME_ flags from "winnls.h"
959 * time [I] Time to format
960 * format [I] Formatting overrides
961 * outbuf [O] Destination for formatted string
962 * bufsize [I] Size of outbuf, or 0 to calculate the resulting size
964 * See GetTimeFormatA for notes.
967 * Success: The number of characters written to outbuf, or that would have
968 * have been written if bufsize is 0.
969 * Failure: 0. Use GetLastError() to determine the cause.
971 INT WINAPI
GetTimeFormatEx(LPCWSTR localename
, DWORD flags
,
972 const SYSTEMTIME
* time
, LPCWSTR format
,
973 LPWSTR outbuf
, INT bufsize
)
975 TRACE("(%s,0x%08x,%p,%s,%p,%d)\n", debugstr_w(localename
), flags
, time
,
976 debugstr_w(format
), outbuf
, bufsize
);
978 return NLS_GetDateTimeFormatW(LocaleNameToLCID(localename
, 0),
979 flags
| TIME_TIMEVARSONLY
, time
, format
,
983 /******************************************************************************
984 * GetTimeFormatW [KERNEL32.@]
986 * See GetTimeFormatA.
988 INT WINAPI
GetTimeFormatW(LCID lcid
, DWORD dwFlags
, const SYSTEMTIME
* lpTime
,
989 LPCWSTR lpFormat
, LPWSTR lpTimeStr
, INT cchOut
)
991 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid
, dwFlags
, lpTime
,
992 debugstr_w(lpFormat
), lpTimeStr
, cchOut
);
994 return NLS_GetDateTimeFormatW(lcid
, dwFlags
|TIME_TIMEVARSONLY
, lpTime
,
995 lpFormat
, lpTimeStr
, cchOut
);
998 /**************************************************************************
999 * GetNumberFormatA (KERNEL32.@)
1001 * Format a number string for a given locale.
1004 * lcid [I] Locale to format for
1005 * dwFlags [I] LOCALE_ flags from "winnls.h"
1006 * lpszValue [I] String to format
1007 * lpFormat [I] Formatting overrides
1008 * lpNumberStr [O] Destination for formatted string
1009 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
1012 * - lpszValue can contain only '0' - '9', '-' and '.'.
1013 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1014 * be formatted according to the format details returned by GetLocaleInfoA().
1015 * - This function rounds the number string if the number of decimals exceeds the
1016 * locales normal number of decimal places.
1017 * - If cchOut is 0, this function does not write to lpNumberStr.
1018 * - The Ascii version of this function fails if lcid is Unicode only.
1021 * Success: The number of character written to lpNumberStr, or that would
1022 * have been written, if cchOut is 0.
1023 * Failure: 0. Use GetLastError() to determine the cause.
1025 INT WINAPI
GetNumberFormatA(LCID lcid
, DWORD dwFlags
,
1026 LPCSTR lpszValue
, const NUMBERFMTA
*lpFormat
,
1027 LPSTR lpNumberStr
, int cchOut
)
1030 WCHAR szDec
[8], szGrp
[8], szIn
[128], szOut
[128];
1032 const NUMBERFMTW
*pfmt
= NULL
;
1035 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_a(lpszValue
),
1036 lpFormat
, lpNumberStr
, cchOut
);
1038 if (NLS_IsUnicodeOnlyLcid(lcid
))
1040 SetLastError(ERROR_INVALID_PARAMETER
);
1044 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
1046 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1049 SetLastError(ERROR_INVALID_PARAMETER
);
1053 cp
= node
->dwCodePage
;
1058 memcpy(&fmt
, lpFormat
, sizeof(fmt
));
1060 if (lpFormat
->lpDecimalSep
)
1062 MultiByteToWideChar(cp
, 0, lpFormat
->lpDecimalSep
, -1, szDec
, ARRAY_SIZE(szDec
));
1063 fmt
.lpDecimalSep
= szDec
;
1065 if (lpFormat
->lpThousandSep
)
1067 MultiByteToWideChar(cp
, 0, lpFormat
->lpThousandSep
, -1, szGrp
, ARRAY_SIZE(szGrp
));
1068 fmt
.lpThousandSep
= szGrp
;
1073 MultiByteToWideChar(cp
, 0, lpszValue
, -1, szIn
, ARRAY_SIZE(szIn
));
1075 if (cchOut
> (int) ARRAY_SIZE(szOut
))
1076 cchOut
= ARRAY_SIZE(szOut
);
1080 iRet
= GetNumberFormatW(lcid
, dwFlags
, lpszValue
? szIn
: NULL
, pfmt
,
1081 lpNumberStr
? szOut
: NULL
, cchOut
);
1083 if (szOut
[0] && lpNumberStr
)
1084 WideCharToMultiByte(cp
, 0, szOut
, -1, lpNumberStr
, cchOut
, 0, 0);
1088 /* Number parsing state flags */
1089 #define NF_ISNEGATIVE 0x1 /* '-' found */
1090 #define NF_ISREAL 0x2 /* '.' found */
1091 #define NF_DIGITS 0x4 /* '0'-'9' found */
1092 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1093 #define NF_ROUND 0x10 /* Number needs to be rounded */
1095 /* Formatting options for Numbers */
1096 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1097 #define NLS_NEG_LEFT 1 /* "-1.1" */
1098 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1099 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1100 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1102 /**************************************************************************
1103 * GetNumberFormatW (KERNEL32.@)
1105 * See GetNumberFormatA.
1107 INT WINAPI
GetNumberFormatW(LCID lcid
, DWORD dwFlags
,
1108 LPCWSTR lpszValue
, const NUMBERFMTW
*lpFormat
,
1109 LPWSTR lpNumberStr
, int cchOut
)
1111 WCHAR szBuff
[128], *szOut
= szBuff
+ ARRAY_SIZE(szBuff
) - 1;
1113 const WCHAR
*lpszNeg
= NULL
, *lpszNegStart
, *szSrc
;
1114 DWORD dwState
= 0, dwDecimals
= 0, dwGroupCount
= 0, dwCurrentGroupCount
= 0;
1117 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_w(lpszValue
),
1118 lpFormat
, lpNumberStr
, cchOut
);
1120 if (!lpszValue
|| cchOut
< 0 || (cchOut
> 0 && !lpNumberStr
) ||
1121 !IsValidLocale(lcid
, 0) ||
1122 (lpFormat
&& (dwFlags
|| !lpFormat
->lpDecimalSep
|| !lpFormat
->lpThousandSep
)))
1129 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1133 lpFormat
= &node
->fmt
;
1134 lpszNegStart
= lpszNeg
= GetNegative(node
);
1138 GetLocaleInfoW(lcid
, LOCALE_SNEGATIVESIGN
|(dwFlags
& LOCALE_NOUSEROVERRIDE
),
1139 szNegBuff
, ARRAY_SIZE(szNegBuff
));
1140 lpszNegStart
= lpszNeg
= szNegBuff
;
1142 lpszNeg
= lpszNeg
+ lstrlenW(lpszNeg
) - 1;
1144 dwFlags
&= (LOCALE_NOUSEROVERRIDE
|LOCALE_USE_CP_ACP
);
1146 /* Format the number backwards into a temporary buffer */
1151 /* Check the number for validity */
1154 if (*szSrc
>= '0' && *szSrc
<= '9')
1156 dwState
|= NF_DIGITS
;
1157 if (dwState
& NF_ISREAL
)
1160 else if (*szSrc
== '-')
1163 goto error
; /* '-' not first character */
1164 dwState
|= NF_ISNEGATIVE
;
1166 else if (*szSrc
== '.')
1168 if (dwState
& NF_ISREAL
)
1169 goto error
; /* More than one '.' */
1170 dwState
|= NF_ISREAL
;
1173 goto error
; /* Invalid char */
1176 szSrc
--; /* Point to last character */
1178 if (!(dwState
& NF_DIGITS
))
1179 goto error
; /* No digits */
1181 /* Add any trailing negative sign */
1182 if (dwState
& NF_ISNEGATIVE
)
1184 switch (lpFormat
->NegativeOrder
)
1186 case NLS_NEG_PARENS
:
1190 case NLS_NEG_RIGHT_SPACE
:
1191 while (lpszNeg
>= lpszNegStart
)
1192 *szOut
-- = *lpszNeg
--;
1193 if (lpFormat
->NegativeOrder
== NLS_NEG_RIGHT_SPACE
)
1199 /* Copy all digits up to the decimal point */
1200 if (!lpFormat
->NumDigits
)
1202 if (dwState
& NF_ISREAL
)
1204 while (*szSrc
!= '.') /* Don't write any decimals or a separator */
1206 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1207 dwState
|= NF_ROUND
;
1209 dwState
&= ~NF_ROUND
;
1217 LPWSTR lpszDec
= lpFormat
->lpDecimalSep
+ lstrlenW(lpFormat
->lpDecimalSep
) - 1;
1219 if (dwDecimals
<= lpFormat
->NumDigits
)
1221 dwDecimals
= lpFormat
->NumDigits
- dwDecimals
;
1222 while (dwDecimals
--)
1223 *szOut
-- = '0'; /* Pad to correct number of dp */
1227 dwDecimals
-= lpFormat
->NumDigits
;
1228 /* Skip excess decimals, and determine if we have to round the number */
1229 while (dwDecimals
--)
1231 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1232 dwState
|= NF_ROUND
;
1234 dwState
&= ~NF_ROUND
;
1239 if (dwState
& NF_ISREAL
)
1241 while (*szSrc
!= '.')
1243 if (dwState
& NF_ROUND
)
1246 *szOut
-- = '0'; /* continue rounding */
1249 dwState
&= ~NF_ROUND
;
1250 *szOut
-- = (*szSrc
)+1;
1255 *szOut
-- = *szSrc
--; /* Write existing decimals */
1257 szSrc
--; /* Skip '.' */
1260 while (lpszDec
>= lpFormat
->lpDecimalSep
)
1261 *szOut
-- = *lpszDec
--; /* Write decimal separator */
1264 dwGroupCount
= lpFormat
->Grouping
== 32 ? 3 : lpFormat
->Grouping
;
1266 /* Write the remaining whole number digits, including grouping chars */
1267 while (szSrc
>= lpszValue
&& *szSrc
>= '0' && *szSrc
<= '9')
1269 if (dwState
& NF_ROUND
)
1272 *szOut
-- = '0'; /* continue rounding */
1275 dwState
&= ~NF_ROUND
;
1276 *szOut
-- = (*szSrc
)+1;
1281 *szOut
-- = *szSrc
--;
1283 dwState
|= NF_DIGITS_OUT
;
1284 dwCurrentGroupCount
++;
1285 if (szSrc
>= lpszValue
&& dwCurrentGroupCount
== dwGroupCount
&& *szSrc
!= '-')
1287 LPWSTR lpszGrp
= lpFormat
->lpThousandSep
+ lstrlenW(lpFormat
->lpThousandSep
) - 1;
1289 while (lpszGrp
>= lpFormat
->lpThousandSep
)
1290 *szOut
-- = *lpszGrp
--; /* Write grouping char */
1292 dwCurrentGroupCount
= 0;
1293 if (lpFormat
->Grouping
== 32)
1294 dwGroupCount
= 2; /* Indic grouping: 3 then 2 */
1297 if (dwState
& NF_ROUND
)
1299 *szOut
-- = '1'; /* e.g. .6 > 1.0 */
1301 else if (!(dwState
& NF_DIGITS_OUT
) && lpFormat
->LeadingZero
)
1302 *szOut
-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1304 /* Add any leading negative sign */
1305 if (dwState
& NF_ISNEGATIVE
)
1307 switch (lpFormat
->NegativeOrder
)
1309 case NLS_NEG_PARENS
:
1312 case NLS_NEG_LEFT_SPACE
:
1316 while (lpszNeg
>= lpszNegStart
)
1317 *szOut
-- = *lpszNeg
--;
1323 iRet
= lstrlenW(szOut
) + 1;
1327 memcpy(lpNumberStr
, szOut
, iRet
* sizeof(WCHAR
));
1330 memcpy(lpNumberStr
, szOut
, cchOut
* sizeof(WCHAR
));
1331 lpNumberStr
[cchOut
- 1] = '\0';
1332 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1339 SetLastError(lpFormat
&& dwFlags
? ERROR_INVALID_FLAGS
: ERROR_INVALID_PARAMETER
);
1343 /**************************************************************************
1344 * GetNumberFormatEx (KERNEL32.@)
1346 INT WINAPI
GetNumberFormatEx(LPCWSTR name
, DWORD flags
,
1347 LPCWSTR value
, const NUMBERFMTW
*format
,
1348 LPWSTR number
, int numout
)
1352 TRACE("(%s,0x%08x,%s,%p,%p,%d)\n", debugstr_w(name
), flags
,
1353 debugstr_w(value
), format
, number
, numout
);
1355 lcid
= LocaleNameToLCID(name
, 0);
1359 return GetNumberFormatW(lcid
, flags
, value
, format
, number
, numout
);
1362 /**************************************************************************
1363 * GetCurrencyFormatA (KERNEL32.@)
1365 * Format a currency string for a given locale.
1368 * lcid [I] Locale to format for
1369 * dwFlags [I] LOCALE_ flags from "winnls.h"
1370 * lpszValue [I] String to format
1371 * lpFormat [I] Formatting overrides
1372 * lpCurrencyStr [O] Destination for formatted string
1373 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1376 * - lpszValue can contain only '0' - '9', '-' and '.'.
1377 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1378 * be formatted according to the format details returned by GetLocaleInfoA().
1379 * - This function rounds the currency if the number of decimals exceeds the
1380 * locales number of currency decimal places.
1381 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1382 * - The Ascii version of this function fails if lcid is Unicode only.
1385 * Success: The number of character written to lpNumberStr, or that would
1386 * have been written, if cchOut is 0.
1387 * Failure: 0. Use GetLastError() to determine the cause.
1389 INT WINAPI
GetCurrencyFormatA(LCID lcid
, DWORD dwFlags
,
1390 LPCSTR lpszValue
, const CURRENCYFMTA
*lpFormat
,
1391 LPSTR lpCurrencyStr
, int cchOut
)
1394 WCHAR szDec
[8], szGrp
[8], szCy
[8], szIn
[128], szOut
[128];
1396 const CURRENCYFMTW
*pfmt
= NULL
;
1399 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_a(lpszValue
),
1400 lpFormat
, lpCurrencyStr
, cchOut
);
1402 if (NLS_IsUnicodeOnlyLcid(lcid
))
1404 SetLastError(ERROR_INVALID_PARAMETER
);
1408 if (!(dwFlags
& LOCALE_USE_CP_ACP
))
1410 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1413 SetLastError(ERROR_INVALID_PARAMETER
);
1417 cp
= node
->dwCodePage
;
1422 memcpy(&fmt
, lpFormat
, sizeof(fmt
));
1424 if (lpFormat
->lpDecimalSep
)
1426 MultiByteToWideChar(cp
, 0, lpFormat
->lpDecimalSep
, -1, szDec
, ARRAY_SIZE(szDec
));
1427 fmt
.lpDecimalSep
= szDec
;
1429 if (lpFormat
->lpThousandSep
)
1431 MultiByteToWideChar(cp
, 0, lpFormat
->lpThousandSep
, -1, szGrp
, ARRAY_SIZE(szGrp
));
1432 fmt
.lpThousandSep
= szGrp
;
1434 if (lpFormat
->lpCurrencySymbol
)
1436 MultiByteToWideChar(cp
, 0, lpFormat
->lpCurrencySymbol
, -1, szCy
, ARRAY_SIZE(szCy
));
1437 fmt
.lpCurrencySymbol
= szCy
;
1442 MultiByteToWideChar(cp
, 0, lpszValue
, -1, szIn
, ARRAY_SIZE(szIn
));
1444 if (cchOut
> (int) ARRAY_SIZE(szOut
))
1445 cchOut
= ARRAY_SIZE(szOut
);
1449 iRet
= GetCurrencyFormatW(lcid
, dwFlags
, lpszValue
? szIn
: NULL
, pfmt
,
1450 lpCurrencyStr
? szOut
: NULL
, cchOut
);
1452 if (szOut
[0] && lpCurrencyStr
)
1453 WideCharToMultiByte(cp
, 0, szOut
, -1, lpCurrencyStr
, cchOut
, 0, 0);
1457 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1458 #define CF_PARENS 0x1 /* Parentheses */
1459 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1460 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1461 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1462 #define CF_CY_LEFT 0x10 /* '$' to the left */
1463 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1464 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1466 /**************************************************************************
1467 * GetCurrencyFormatW (KERNEL32.@)
1469 * See GetCurrencyFormatA.
1471 INT WINAPI
GetCurrencyFormatW(LCID lcid
, DWORD dwFlags
,
1472 LPCWSTR lpszValue
, const CURRENCYFMTW
*lpFormat
,
1473 LPWSTR lpCurrencyStr
, int cchOut
)
1475 static const BYTE NLS_NegCyFormats
[16] =
1477 CF_PARENS
|CF_CY_LEFT
, /* ($1.1) */
1478 CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
, /* -$1.1 */
1479 CF_MINUS_LEFT
|CF_CY_LEFT
, /* $-1.1 */
1480 CF_MINUS_RIGHT
|CF_CY_LEFT
, /* $1.1- */
1481 CF_PARENS
|CF_CY_RIGHT
, /* (1.1$) */
1482 CF_MINUS_LEFT
|CF_CY_RIGHT
, /* -1.1$ */
1483 CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
, /* 1.1-$ */
1484 CF_MINUS_RIGHT
|CF_CY_RIGHT
, /* 1.1$- */
1485 CF_MINUS_LEFT
|CF_CY_RIGHT
|CF_CY_SPACE
, /* -1.1 $ */
1486 CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
|CF_CY_SPACE
, /* -$ 1.1 */
1487 CF_MINUS_RIGHT
|CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1 $- */
1488 CF_MINUS_RIGHT
|CF_CY_LEFT
|CF_CY_SPACE
, /* $ 1.1- */
1489 CF_MINUS_LEFT
|CF_CY_LEFT
|CF_CY_SPACE
, /* $ -1.1 */
1490 CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1- $ */
1491 CF_PARENS
|CF_CY_LEFT
|CF_CY_SPACE
, /* ($ 1.1) */
1492 CF_PARENS
|CF_CY_RIGHT
|CF_CY_SPACE
, /* (1.1 $) */
1494 static const BYTE NLS_PosCyFormats
[4] =
1496 CF_CY_LEFT
, /* $1.1 */
1497 CF_CY_RIGHT
, /* 1.1$ */
1498 CF_CY_LEFT
|CF_CY_SPACE
, /* $ 1.1 */
1499 CF_CY_RIGHT
|CF_CY_SPACE
, /* 1.1 $ */
1501 WCHAR szBuff
[128], *szOut
= szBuff
+ ARRAY_SIZE(szBuff
) - 1;
1503 const WCHAR
*lpszNeg
= NULL
, *lpszNegStart
, *szSrc
, *lpszCy
, *lpszCyStart
;
1504 DWORD dwState
= 0, dwDecimals
= 0, dwGroupCount
= 0, dwCurrentGroupCount
= 0, dwFmt
;
1507 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid
, dwFlags
, debugstr_w(lpszValue
),
1508 lpFormat
, lpCurrencyStr
, cchOut
);
1510 if (!lpszValue
|| cchOut
< 0 || (cchOut
> 0 && !lpCurrencyStr
) ||
1511 !IsValidLocale(lcid
, 0) ||
1512 (lpFormat
&& (dwFlags
|| !lpFormat
->lpDecimalSep
|| !lpFormat
->lpThousandSep
||
1513 !lpFormat
->lpCurrencySymbol
|| lpFormat
->NegativeOrder
> 15 ||
1514 lpFormat
->PositiveOrder
> 3)))
1521 const NLS_FORMAT_NODE
*node
= NLS_GetFormats(lcid
, dwFlags
);
1526 lpFormat
= &node
->cyfmt
;
1527 lpszNegStart
= lpszNeg
= GetNegative(node
);
1531 GetLocaleInfoW(lcid
, LOCALE_SNEGATIVESIGN
|(dwFlags
& LOCALE_NOUSEROVERRIDE
),
1532 szNegBuff
, ARRAY_SIZE(szNegBuff
));
1533 lpszNegStart
= lpszNeg
= szNegBuff
;
1535 dwFlags
&= (LOCALE_NOUSEROVERRIDE
|LOCALE_USE_CP_ACP
);
1537 lpszNeg
= lpszNeg
+ lstrlenW(lpszNeg
) - 1;
1538 lpszCyStart
= lpFormat
->lpCurrencySymbol
;
1539 lpszCy
= lpszCyStart
+ lstrlenW(lpszCyStart
) - 1;
1541 /* Format the currency backwards into a temporary buffer */
1546 /* Check the number for validity */
1549 if (*szSrc
>= '0' && *szSrc
<= '9')
1551 dwState
|= NF_DIGITS
;
1552 if (dwState
& NF_ISREAL
)
1555 else if (*szSrc
== '-')
1558 goto error
; /* '-' not first character */
1559 dwState
|= NF_ISNEGATIVE
;
1561 else if (*szSrc
== '.')
1563 if (dwState
& NF_ISREAL
)
1564 goto error
; /* More than one '.' */
1565 dwState
|= NF_ISREAL
;
1568 goto error
; /* Invalid char */
1571 szSrc
--; /* Point to last character */
1573 if (!(dwState
& NF_DIGITS
))
1574 goto error
; /* No digits */
1576 if (dwState
& NF_ISNEGATIVE
)
1577 dwFmt
= NLS_NegCyFormats
[lpFormat
->NegativeOrder
];
1579 dwFmt
= NLS_PosCyFormats
[lpFormat
->PositiveOrder
];
1581 /* Add any trailing negative or currency signs */
1582 if (dwFmt
& CF_PARENS
)
1585 while (dwFmt
& (CF_MINUS_RIGHT
|CF_CY_RIGHT
))
1587 switch (dwFmt
& (CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
))
1589 case CF_MINUS_RIGHT
:
1590 case CF_MINUS_RIGHT
|CF_CY_RIGHT
:
1591 while (lpszNeg
>= lpszNegStart
)
1592 *szOut
-- = *lpszNeg
--;
1593 dwFmt
&= ~CF_MINUS_RIGHT
;
1597 case CF_MINUS_BEFORE
|CF_CY_RIGHT
:
1598 case CF_MINUS_RIGHT
|CF_MINUS_BEFORE
|CF_CY_RIGHT
:
1599 while (lpszCy
>= lpszCyStart
)
1600 *szOut
-- = *lpszCy
--;
1601 if (dwFmt
& CF_CY_SPACE
)
1603 dwFmt
&= ~(CF_CY_RIGHT
|CF_MINUS_BEFORE
);
1608 /* Copy all digits up to the decimal point */
1609 if (!lpFormat
->NumDigits
)
1611 if (dwState
& NF_ISREAL
)
1613 while (*szSrc
!= '.') /* Don't write any decimals or a separator */
1615 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1616 dwState
|= NF_ROUND
;
1618 dwState
&= ~NF_ROUND
;
1626 LPWSTR lpszDec
= lpFormat
->lpDecimalSep
+ lstrlenW(lpFormat
->lpDecimalSep
) - 1;
1628 if (dwDecimals
<= lpFormat
->NumDigits
)
1630 dwDecimals
= lpFormat
->NumDigits
- dwDecimals
;
1631 while (dwDecimals
--)
1632 *szOut
-- = '0'; /* Pad to correct number of dp */
1636 dwDecimals
-= lpFormat
->NumDigits
;
1637 /* Skip excess decimals, and determine if we have to round the number */
1638 while (dwDecimals
--)
1640 if (*szSrc
>= '5' || (*szSrc
== '4' && (dwState
& NF_ROUND
)))
1641 dwState
|= NF_ROUND
;
1643 dwState
&= ~NF_ROUND
;
1648 if (dwState
& NF_ISREAL
)
1650 while (*szSrc
!= '.')
1652 if (dwState
& NF_ROUND
)
1655 *szOut
-- = '0'; /* continue rounding */
1658 dwState
&= ~NF_ROUND
;
1659 *szOut
-- = (*szSrc
)+1;
1664 *szOut
-- = *szSrc
--; /* Write existing decimals */
1666 szSrc
--; /* Skip '.' */
1668 while (lpszDec
>= lpFormat
->lpDecimalSep
)
1669 *szOut
-- = *lpszDec
--; /* Write decimal separator */
1672 dwGroupCount
= lpFormat
->Grouping
;
1674 /* Write the remaining whole number digits, including grouping chars */
1675 while (szSrc
>= lpszValue
&& *szSrc
>= '0' && *szSrc
<= '9')
1677 if (dwState
& NF_ROUND
)
1680 *szOut
-- = '0'; /* continue rounding */
1683 dwState
&= ~NF_ROUND
;
1684 *szOut
-- = (*szSrc
)+1;
1689 *szOut
-- = *szSrc
--;
1691 dwState
|= NF_DIGITS_OUT
;
1692 dwCurrentGroupCount
++;
1693 if (szSrc
>= lpszValue
&& dwCurrentGroupCount
== dwGroupCount
&& *szSrc
!= '-')
1695 LPWSTR lpszGrp
= lpFormat
->lpThousandSep
+ lstrlenW(lpFormat
->lpThousandSep
) - 1;
1697 while (lpszGrp
>= lpFormat
->lpThousandSep
)
1698 *szOut
-- = *lpszGrp
--; /* Write grouping char */
1700 dwCurrentGroupCount
= 0;
1703 if (dwState
& NF_ROUND
)
1704 *szOut
-- = '1'; /* e.g. .6 > 1.0 */
1705 else if (!(dwState
& NF_DIGITS_OUT
) && lpFormat
->LeadingZero
)
1706 *szOut
-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1708 /* Add any leading negative or currency sign */
1709 while (dwFmt
& (CF_MINUS_LEFT
|CF_CY_LEFT
))
1711 switch (dwFmt
& (CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
))
1714 case CF_MINUS_LEFT
|CF_CY_LEFT
:
1715 while (lpszNeg
>= lpszNegStart
)
1716 *szOut
-- = *lpszNeg
--;
1717 dwFmt
&= ~CF_MINUS_LEFT
;
1721 case CF_CY_LEFT
|CF_MINUS_BEFORE
:
1722 case CF_MINUS_LEFT
|CF_MINUS_BEFORE
|CF_CY_LEFT
:
1723 if (dwFmt
& CF_CY_SPACE
)
1725 while (lpszCy
>= lpszCyStart
)
1726 *szOut
-- = *lpszCy
--;
1727 dwFmt
&= ~(CF_CY_LEFT
|CF_MINUS_BEFORE
);
1731 if (dwFmt
& CF_PARENS
)
1735 iRet
= lstrlenW(szOut
) + 1;
1739 memcpy(lpCurrencyStr
, szOut
, iRet
* sizeof(WCHAR
));
1742 memcpy(lpCurrencyStr
, szOut
, cchOut
* sizeof(WCHAR
));
1743 lpCurrencyStr
[cchOut
- 1] = '\0';
1744 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1751 SetLastError(lpFormat
&& dwFlags
? ERROR_INVALID_FLAGS
: ERROR_INVALID_PARAMETER
);
1755 /***********************************************************************
1756 * GetCurrencyFormatEx (KERNEL32.@)
1758 int WINAPI
GetCurrencyFormatEx(LPCWSTR localename
, DWORD flags
, LPCWSTR value
,
1759 const CURRENCYFMTW
*format
, LPWSTR str
, int len
)
1761 TRACE("(%s,0x%08x,%s,%p,%p,%d)\n", debugstr_w(localename
), flags
,
1762 debugstr_w(value
), format
, str
, len
);
1764 return GetCurrencyFormatW( LocaleNameToLCID(localename
, 0), flags
, value
, format
, str
, len
);
1767 /*********************************************************************
1768 * GetCalendarInfoA (KERNEL32.@)
1770 int WINAPI
GetCalendarInfoA( LCID lcid
, CALID id
, CALTYPE type
, LPSTR data
, int size
, DWORD
*val
)
1772 int ret
, sizeW
= size
;
1773 LPWSTR dataW
= NULL
;
1775 if (NLS_IsUnicodeOnlyLcid(lcid
))
1777 SetLastError(ERROR_INVALID_PARAMETER
);
1780 if (!size
&& !(type
& CAL_RETURN_NUMBER
)) sizeW
= GetCalendarInfoW( lcid
, id
, type
, NULL
, 0, NULL
);
1781 if (!(dataW
= HeapAlloc(GetProcessHeap(), 0, sizeW
* sizeof(WCHAR
)))) return 0;
1783 ret
= GetCalendarInfoW( lcid
, id
, type
, dataW
, sizeW
, val
);
1784 if(ret
&& dataW
&& data
)
1785 ret
= WideCharToMultiByte( CP_ACP
, 0, dataW
, -1, data
, size
, NULL
, NULL
);
1786 else if (type
& CAL_RETURN_NUMBER
)
1787 ret
*= sizeof(WCHAR
);
1788 HeapFree( GetProcessHeap(), 0, dataW
);
1792 /*********************************************************************
1793 * SetCalendarInfoA (KERNEL32.@)
1795 int WINAPI
SetCalendarInfoA( LCID lcid
, CALID id
, CALTYPE type
, LPCSTR data
)
1797 FIXME("(%08x,%08x,%08x,%s): stub\n", lcid
, id
, type
, debugstr_a(data
));