Release 6.15.
[wine.git] / dlls / kernel32 / lcformat.c
bloba9dfbf09be24e119ca7187e7d5b1d9f322a6ba44
1 /*
2 * Locale-dependent format handling
4 * Copyright 1995 Martin von Loewis
5 * Copyright 1998 David Lee Lambert
6 * Copyright 2000 Julio César Gázquez
7 * Copyright 2003 Jon Griffiths
8 * Copyright 2005 Dmitry Timoshkov
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 #include <string.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winnls.h"
33 #include "wine/debug.h"
34 #include "winternl.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;
65 } NLS_FORMAT_NODE;
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 =
86 0, 0, &NLS_FormatsCS,
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)
100 WCHAR szBuff[80];
101 DWORD dwVal = 0;
103 szBuff[0] = '\0';
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');
108 else
110 const WCHAR* iter = szBuff;
111 dwVal = 0;
112 while(*iter >= '0' && *iter <= '9')
113 dwVal = dwVal * 10 + (*iter++ - '0');
115 return dwVal;
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;
126 DWORD dwLen;
128 szBuff[0] = '\0';
129 GetLocaleInfoW(lcid, dwFlags, szBuff, ARRAY_SIZE(szBuff));
130 dwLen = lstrlenW(szBuff) + 1;
131 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
132 if (str)
133 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
134 return str;
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,
154 LOCALE_STIMEFORMAT,
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,
181 LOCALE_SYEARMONTH
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)
192 node = node->next;
194 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
196 NLS_FORMAT_NODE *new_node;
197 DWORD i;
199 TRACE("Creating new cache entry\n");
201 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
202 return NULL;
204 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
206 /* Number Format */
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)
281 node = node->next;
283 if (!node)
285 node = NLS_CachedFormats = new_node; /* Empty list */
286 new_node = NULL;
288 else if (node->lcid != lcid || node->dwFlags != dwFlags)
290 node->next = new_node; /* Not in the list, add to end */
291 node = new_node;
292 new_node = NULL;
295 RtlLeaveCriticalSection(&NLS_FormatsCS);
297 if (new_node)
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);
312 return 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))
326 case LANG_ARMENIAN:
327 case LANG_DIVEHI:
328 case LANG_GEORGIAN:
329 case LANG_GUJARATI:
330 case LANG_HINDI:
331 case LANG_KANNADA:
332 case LANG_KONKANI:
333 case LANG_MARATHI:
334 case LANG_PUNJABI:
335 case LANG_SANSKRIT:
336 TRACE("lcid 0x%08x: langid 0x%04x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
337 return TRUE;
338 default:
339 return FALSE;
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| \
355 TIME_NOTIMEMARKER)
357 /******************************************************************************
358 * NLS_GetDateTimeFormatW <internal>
360 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
362 * FIXME
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;
371 SYSTEMTIME st;
372 INT cchWritten = 0;
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))
383 if (lpFormat &&
384 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
385 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
387 goto invalid_flags;
390 if (dwFlags & DATE_DATEVARSONLY)
392 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
393 goto invalid_flags;
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))
399 case 0:
400 break;
401 case DATE_SHORTDATE:
402 case DATE_LONGDATE:
403 case DATE_YEARMONTH:
404 if (lpFormat)
405 goto invalid_flags;
406 break;
407 default:
408 goto invalid_flags;
413 if (!lpFormat)
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);
422 else
423 lpFormat = GetShortDate(node);
425 else
426 lpFormat = GetTime(node);
429 if (!lpTime)
431 GetLocalTime(&st); /* Default to current time */
432 lpTime = &st;
434 else
436 if (dwFlags & DATE_DATEVARSONLY)
438 FILETIME ftTmp;
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);
450 lpTime = &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 */
462 while (*lpFormat)
464 if (IsLiteralMarker(*lpFormat))
466 /* Start of a literal string */
467 lpFormat++;
469 /* Loop until the end of the literal marker or end of the string */
470 while (*lpFormat)
472 if (IsLiteralMarker(*lpFormat))
474 lpFormat++;
475 if (!IsLiteralMarker(*lpFormat))
476 break; /* Terminating literal marker */
479 if (!cchOut)
480 cchWritten++; /* Count size only */
481 else if (cchWritten >= cchOut)
482 goto overrun;
483 else if (!bSkipping)
485 lpStr[cchWritten] = *lpFormat;
486 cchWritten++;
488 lpFormat++;
491 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
492 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
494 WCHAR buff[32], fmtChar;
495 LPCWSTR szAdd = NULL;
496 DWORD dwVal = 0;
497 int count = 0, dwLen;
499 bSkipping = FALSE;
501 fmtChar = *lpFormat;
502 while (*lpFormat == fmtChar)
504 count++;
505 lpFormat++;
507 buff[0] = '\0';
509 if (fmtChar != 'M') d_dd_formatted = FALSE;
510 switch(fmtChar)
512 case 'd':
513 if (count >= 4)
514 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
515 else if (count == 3)
516 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
517 else
519 dwVal = lpTime->wDay;
520 szAdd = buff;
521 d_dd_formatted = TRUE;
523 break;
525 case 'M':
526 if (count >= 4)
528 LPCWSTR genitive = GetGenitiveMonth(node, lpTime->wMonth - 1);
529 if (genitive)
531 if (d_dd_formatted)
533 szAdd = genitive;
534 break;
536 else
538 LPCWSTR format = lpFormat;
539 /* Look forward now, if next format pattern is for day genitive
540 name should be used */
541 while (*format)
543 /* Skip parts within markers */
544 if (IsLiteralMarker(*format))
546 ++format;
547 while (*format)
549 if (IsLiteralMarker(*format))
551 ++format;
552 if (!IsLiteralMarker(*format)) break;
556 if (*format != ' ') break;
557 ++format;
559 /* Only numeric day form matters */
560 if (*format == 'd')
562 INT dcount = 1;
563 while (*++format == 'd') dcount++;
564 if (dcount < 3)
566 szAdd = genitive;
567 break;
572 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
574 else if (count == 3)
575 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
576 else
578 dwVal = lpTime->wMonth;
579 szAdd = buff;
581 break;
583 case 'y':
584 if (count >= 4)
586 count = 4;
587 dwVal = lpTime->wYear;
589 else
591 count = count > 2 ? 2 : count;
592 dwVal = lpTime->wYear % 100;
594 szAdd = buff;
595 break;
597 case 'g':
598 if (count == 2)
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';
606 else
608 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
610 szAdd = buff;
611 break;
613 case 'h':
614 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
616 count = count > 2 ? 2 : count;
617 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
618 szAdd = buff;
619 break;
621 /* .. fall through if we are forced to output in 24 hour format */
623 case 'H':
624 count = count > 2 ? 2 : count;
625 dwVal = lpTime->wHour;
626 szAdd = buff;
627 break;
629 case 'm':
630 if (dwFlags & TIME_NOMINUTESORSECONDS)
632 cchWritten = lastFormatPos; /* Skip */
633 bSkipping = TRUE;
635 else
637 count = count > 2 ? 2 : count;
638 dwVal = lpTime->wMinute;
639 szAdd = buff;
641 break;
643 case 's':
644 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
646 cchWritten = lastFormatPos; /* Skip */
647 bSkipping = TRUE;
649 else
651 count = count > 2 ? 2 : count;
652 dwVal = lpTime->wSecond;
653 szAdd = buff;
655 break;
657 case 't':
658 if (dwFlags & TIME_NOTIMEMARKER)
660 cchWritten = lastFormatPos; /* Skip */
661 bSkipping = TRUE;
663 else
665 if (count == 1)
666 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
667 else
668 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
670 break;
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;
681 if (cchOut && dwLen)
683 if (cchWritten + dwLen < cchOut)
684 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
685 else
687 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
688 goto overrun;
691 cchWritten += dwLen;
692 lastFormatPos = cchWritten; /* Save position of last output format text */
694 else
696 /* Literal character */
697 if (!cchOut)
698 cchWritten++; /* Count size only */
699 else if (cchWritten >= cchOut)
700 goto overrun;
701 else if (!bSkipping || *lpFormat == ' ')
703 lpStr[cchWritten] = *lpFormat;
704 cchWritten++;
706 lpFormat++;
710 /* Final string terminator and sanity check */
711 if (cchOut)
713 if (cchWritten >= cchOut)
714 goto overrun;
715 else
716 lpStr[cchWritten] = '\0';
718 cchWritten++; /* Include terminating NUL */
720 TRACE("returning length=%d, output=%s\n", cchWritten, debugstr_w(lpStr));
721 return cchWritten;
723 overrun:
724 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
725 SetLastError(ERROR_INSUFFICIENT_BUFFER);
726 return 0;
728 invalid_parameter:
729 SetLastError(ERROR_INVALID_PARAMETER);
730 return 0;
732 invalid_flags:
733 SetLastError(ERROR_INVALID_FLAGS);
734 return 0;
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)
746 DWORD cp = CP_ACP;
747 WCHAR szFormat[128], szOut[128];
748 INT iRet;
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);
756 return 0;
759 if (!(dwFlags & LOCALE_USE_CP_ACP))
761 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
762 if (!node)
764 SetLastError(ERROR_INVALID_PARAMETER);
765 return 0;
768 cp = node->dwCodePage;
771 if (lpFormat)
772 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, ARRAY_SIZE(szFormat));
774 if (cchOut > (int) ARRAY_SIZE(szOut))
775 cchOut = ARRAY_SIZE(szOut);
777 szOut[0] = '\0';
779 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
780 lpStr ? szOut : NULL, cchOut);
782 if (lpStr)
784 if (szOut[0])
785 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
786 else if (cchOut && iRet)
787 *lpStr = '\0';
789 return iRet;
792 /******************************************************************************
793 * GetDateFormatA [KERNEL32.@]
795 * Format a date for a given locale.
797 * PARAMS
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
805 * NOTES
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:
812 *| Token Meaning
813 *| ----- -------
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.
830 * RETURNS
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.
850 * PARAMS
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.
861 * RETURNS
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);
877 return 0;
880 return NLS_GetDateTimeFormatW(LocaleNameToLCID(localename, 0),
881 flags | DATE_DATEVARSONLY, date, format,
882 outbuf, bufsize);
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.
905 * PARAMS
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
913 * NOTES
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:
920 *| Token Meaning
921 *| ----- -------
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.
936 * RETURNS
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.
956 * PARAMS
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.
966 * RETURNS
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,
980 outbuf, bufsize);
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.
1003 * PARAMS
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
1011 * NOTES
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.
1020 * RETURNS
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)
1029 DWORD cp = CP_ACP;
1030 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
1031 NUMBERFMTW fmt;
1032 const NUMBERFMTW *pfmt = NULL;
1033 INT iRet;
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);
1041 return 0;
1044 if (!(dwFlags & LOCALE_USE_CP_ACP))
1046 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1047 if (!node)
1049 SetLastError(ERROR_INVALID_PARAMETER);
1050 return 0;
1053 cp = node->dwCodePage;
1056 if (lpFormat)
1058 memcpy(&fmt, lpFormat, sizeof(fmt));
1059 pfmt = &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;
1072 if (lpszValue)
1073 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, ARRAY_SIZE(szIn));
1075 if (cchOut > (int) ARRAY_SIZE(szOut))
1076 cchOut = ARRAY_SIZE(szOut);
1078 szOut[0] = '\0';
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);
1085 return iRet;
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;
1112 WCHAR szNegBuff[8];
1113 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1114 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1115 INT iRet;
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)))
1124 goto error;
1127 if (!lpFormat)
1129 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1131 if (!node)
1132 goto error;
1133 lpFormat = &node->fmt;
1134 lpszNegStart = lpszNeg = GetNegative(node);
1136 else
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 */
1148 szSrc = lpszValue;
1149 *szOut-- = '\0';
1151 /* Check the number for validity */
1152 while (*szSrc)
1154 if (*szSrc >= '0' && *szSrc <= '9')
1156 dwState |= NF_DIGITS;
1157 if (dwState & NF_ISREAL)
1158 dwDecimals++;
1160 else if (*szSrc == '-')
1162 if (dwState)
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;
1172 else
1173 goto error; /* Invalid char */
1174 szSrc++;
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:
1187 *szOut-- = ')';
1188 break;
1189 case NLS_NEG_RIGHT:
1190 case NLS_NEG_RIGHT_SPACE:
1191 while (lpszNeg >= lpszNegStart)
1192 *szOut-- = *lpszNeg--;
1193 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1194 *szOut-- = ' ';
1195 break;
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;
1208 else
1209 dwState &= ~NF_ROUND;
1210 szSrc--;
1212 szSrc--;
1215 else
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 */
1225 else
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;
1233 else
1234 dwState &= ~NF_ROUND;
1235 szSrc--;
1239 if (dwState & NF_ISREAL)
1241 while (*szSrc != '.')
1243 if (dwState & NF_ROUND)
1245 if (*szSrc == '9')
1246 *szOut-- = '0'; /* continue rounding */
1247 else
1249 dwState &= ~NF_ROUND;
1250 *szOut-- = (*szSrc)+1;
1252 szSrc--;
1254 else
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)
1271 if (*szSrc == '9')
1272 *szOut-- = '0'; /* continue rounding */
1273 else
1275 dwState &= ~NF_ROUND;
1276 *szOut-- = (*szSrc)+1;
1278 szSrc--;
1280 else
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:
1310 *szOut-- = '(';
1311 break;
1312 case NLS_NEG_LEFT_SPACE:
1313 *szOut-- = ' ';
1314 /* Fall through */
1315 case NLS_NEG_LEFT:
1316 while (lpszNeg >= lpszNegStart)
1317 *szOut-- = *lpszNeg--;
1318 break;
1321 szOut++;
1323 iRet = lstrlenW(szOut) + 1;
1324 if (cchOut)
1326 if (iRet <= cchOut)
1327 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1328 else
1330 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1331 lpNumberStr[cchOut - 1] = '\0';
1332 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1333 iRet = 0;
1336 return iRet;
1338 error:
1339 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1340 return 0;
1343 /**************************************************************************
1344 * GetNumberFormatEx (KERNEL32.@)
1346 INT WINAPI GetNumberFormatEx(LPCWSTR name, DWORD flags,
1347 LPCWSTR value, const NUMBERFMTW *format,
1348 LPWSTR number, int numout)
1350 LCID lcid;
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);
1356 if (!lcid)
1357 return 0;
1359 return GetNumberFormatW(lcid, flags, value, format, number, numout);
1362 /**************************************************************************
1363 * GetCurrencyFormatA (KERNEL32.@)
1365 * Format a currency string for a given locale.
1367 * PARAMS
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
1375 * NOTES
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.
1384 * RETURNS
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)
1393 DWORD cp = CP_ACP;
1394 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1395 CURRENCYFMTW fmt;
1396 const CURRENCYFMTW *pfmt = NULL;
1397 INT iRet;
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);
1405 return 0;
1408 if (!(dwFlags & LOCALE_USE_CP_ACP))
1410 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1411 if (!node)
1413 SetLastError(ERROR_INVALID_PARAMETER);
1414 return 0;
1417 cp = node->dwCodePage;
1420 if (lpFormat)
1422 memcpy(&fmt, lpFormat, sizeof(fmt));
1423 pfmt = &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;
1441 if (lpszValue)
1442 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, ARRAY_SIZE(szIn));
1444 if (cchOut > (int) ARRAY_SIZE(szOut))
1445 cchOut = ARRAY_SIZE(szOut);
1447 szOut[0] = '\0';
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);
1454 return iRet;
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;
1502 WCHAR szNegBuff[8];
1503 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1504 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1505 INT iRet;
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)))
1516 goto error;
1519 if (!lpFormat)
1521 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1523 if (!node)
1524 goto error;
1526 lpFormat = &node->cyfmt;
1527 lpszNegStart = lpszNeg = GetNegative(node);
1529 else
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 */
1543 szSrc = lpszValue;
1544 *szOut-- = '\0';
1546 /* Check the number for validity */
1547 while (*szSrc)
1549 if (*szSrc >= '0' && *szSrc <= '9')
1551 dwState |= NF_DIGITS;
1552 if (dwState & NF_ISREAL)
1553 dwDecimals++;
1555 else if (*szSrc == '-')
1557 if (dwState)
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;
1567 else
1568 goto error; /* Invalid char */
1569 szSrc++;
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];
1578 else
1579 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1581 /* Add any trailing negative or currency signs */
1582 if (dwFmt & CF_PARENS)
1583 *szOut-- = ')';
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;
1594 break;
1596 case CF_CY_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)
1602 *szOut-- = ' ';
1603 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1604 break;
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;
1617 else
1618 dwState &= ~NF_ROUND;
1619 szSrc--;
1621 szSrc--;
1624 else
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 */
1634 else
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;
1642 else
1643 dwState &= ~NF_ROUND;
1644 szSrc--;
1648 if (dwState & NF_ISREAL)
1650 while (*szSrc != '.')
1652 if (dwState & NF_ROUND)
1654 if (*szSrc == '9')
1655 *szOut-- = '0'; /* continue rounding */
1656 else
1658 dwState &= ~NF_ROUND;
1659 *szOut-- = (*szSrc)+1;
1661 szSrc--;
1663 else
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)
1679 if (*szSrc == '9')
1680 *szOut-- = '0'; /* continue rounding */
1681 else
1683 dwState &= ~NF_ROUND;
1684 *szOut-- = (*szSrc)+1;
1686 szSrc--;
1688 else
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))
1713 case CF_MINUS_LEFT:
1714 case CF_MINUS_LEFT|CF_CY_LEFT:
1715 while (lpszNeg >= lpszNegStart)
1716 *szOut-- = *lpszNeg--;
1717 dwFmt &= ~CF_MINUS_LEFT;
1718 break;
1720 case CF_CY_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)
1724 *szOut-- = ' ';
1725 while (lpszCy >= lpszCyStart)
1726 *szOut-- = *lpszCy--;
1727 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1728 break;
1731 if (dwFmt & CF_PARENS)
1732 *szOut-- = '(';
1733 szOut++;
1735 iRet = lstrlenW(szOut) + 1;
1736 if (cchOut)
1738 if (iRet <= cchOut)
1739 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1740 else
1742 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1743 lpCurrencyStr[cchOut - 1] = '\0';
1744 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1745 iRet = 0;
1748 return iRet;
1750 error:
1751 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1752 return 0;
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);
1778 return 0;
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 );
1789 return ret;
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));
1798 return 0;