[KERNEL32]
[reactos.git] / reactos / dll / win32 / kernel32 / misc / lcformat.c
1 /*
2 * Locale-dependent format handling
3 *
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
9 *
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.
14 *
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.
19 *
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
23 */
24
25 /*
26 * Whole file ripped from Wine's dlls\kernel\lcformat.c, rev 1.7 and is
27 * unchanged except that includes are different. I thought about adding
28 * @implemeted to each exported function, but this might make merging harder?
29 * -Gunnar
30 */
31
32 #include <k32.h>
33
34 #include "wine/config.h"
35 #include "wine/unicode.h"
36 #include <wine/debug.h>
37
38 WINE_DEFAULT_DEBUG_CHANNEL(nls);
39
40 #define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
41 #define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
42
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.
49 *
50 * Our cache takes the form of a singly linked list, whose node is below:
51 */
52 #define NLS_NUM_CACHED_STRINGS 57
53
54 typedef struct _NLS_FORMAT_NODE
55 {
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;
66
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]
75
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]
81
82 /* Write access to the cache is protected by this critical section */
83 static RTL_CRITICAL_SECTION NLS_FormatsCS;
84 static RTL_CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
85 {
86 0, 0, &NLS_FormatsCS,
87 { &NLS_FormatsCS_debug.ProcessLocksList,
88 &NLS_FormatsCS_debug.ProcessLocksList },
89 0, 0, 0, 0, 0
90 };
91 static RTL_CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
92
93 /**************************************************************************
94 * NLS_GetLocaleNumber <internal>
95 *
96 * Get a numeric locale format value.
97 */
98 static DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
99 {
100 WCHAR szBuff[80];
101 DWORD dwVal = 0;
102
103 szBuff[0] = '\0';
104 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
105
106 if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '0')
107 dwVal = (szBuff[0] - '0') * 10 + (szBuff[2] - '0');
108 else
109 {
110 const WCHAR* iter = szBuff;
111 dwVal = 0;
112 while(*iter >= '0' && *iter <= '9')
113 dwVal = dwVal * 10 + (*iter++ - '0');
114 }
115 return dwVal;
116 }
117
118 /**************************************************************************
119 * NLS_GetLocaleString <internal>
120 *
121 * Get a string locale format value.
122 */
123 static WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
124 {
125 WCHAR szBuff[80], *str;
126 DWORD dwLen;
127
128 szBuff[0] = '\0';
129 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
130 dwLen = strlenW(szBuff) + 1;
131 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
132 if (str)
133 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
134 return str;
135 }
136
137 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
138 TRACE( #type ": %d (%08x)\n", (DWORD)num, (DWORD)num)
139
140 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
141 TRACE( #type ": %s\n", debugstr_w(str))
142
143 /**************************************************************************
144 * NLS_GetFormats <internal>
145 *
146 * Calculate (and cache) the number formats for a locale.
147 */
148 static const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
149 {
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
182 };
183 static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
184 NLS_FORMAT_NODE *node = NLS_CachedFormats;
185
186 dwFlags &= LOCALE_NOUSEROVERRIDE;
187
188 TRACE("(0x%04x,0x%08x)\n", lcid, dwFlags);
189
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;
193
194 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
195 {
196 NLS_FORMAT_NODE *new_node;
197 DWORD i;
198
199 TRACE("Creating new cache entry\n");
200
201 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
202 return NULL;
203
204 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
205
206 /* Number Format */
207 new_node->lcid = lcid;
208 new_node->dwFlags = dwFlags;
209 new_node->next = NULL;
210
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);
214
215 GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
216 if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
217 {
218 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
219 new_node->fmt.Grouping);
220 new_node->fmt.Grouping = 0;
221 }
222
223 GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
224 GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
225
226 /* Currency Format */
227 new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
228 new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
229
230 GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
231
232 if (new_node->cyfmt.Grouping > 9)
233 {
234 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
235 new_node->cyfmt.Grouping);
236 new_node->cyfmt.Grouping = 0;
237 }
238
239 GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
240 if (new_node->cyfmt.NegativeOrder > 15)
241 {
242 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
243 new_node->cyfmt.NegativeOrder);
244 new_node->cyfmt.NegativeOrder = 0;
245 }
246 GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
247 if (new_node->cyfmt.PositiveOrder > 3)
248 {
249 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
250 new_node->cyfmt.PositiveOrder);
251 new_node->cyfmt.PositiveOrder = 0;
252 }
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);
256
257 /* Date/Time Format info, negative character, etc */
258 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
259 {
260 GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
261 }
262 /* Save some memory if month genitive name is the same or not present */
263 for (i = 0; i < 12; i++)
264 {
265 if (strcmpW(GetLongMonth(new_node, i), GetGenitiveMonth(new_node, i)) == 0)
266 {
267 HeapFree(GetProcessHeap(), 0, GetGenitiveMonth(new_node, i));
268 GetGenitiveMonth(new_node, i) = NULL;
269 }
270 }
271
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';
274
275 /* Now add the computed format to the cache */
276 RtlEnterCriticalSection(&NLS_FormatsCS);
277
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;
282
283 if (!node)
284 {
285 node = NLS_CachedFormats = new_node; /* Empty list */
286 new_node = NULL;
287 }
288 else if (node->lcid != lcid || node->dwFlags != dwFlags)
289 {
290 node->next = new_node; /* Not in the list, add to end */
291 node = new_node;
292 new_node = NULL;
293 }
294
295 RtlLeaveCriticalSection(&NLS_FormatsCS);
296
297 if (new_node)
298 {
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.
301 */
302 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); 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);
310 }
311 }
312 return node;
313 }
314
315 /**************************************************************************
316 * NLS_IsUnicodeOnlyLcid <internal>
317 *
318 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
319 */
320 BOOL NLS_IsUnicodeOnlyLcid(LCID lcid)
321 {
322 lcid = ConvertDefaultLocale(lcid);
323
324 switch (PRIMARYLANGID(lcid))
325 {
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%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
337 return TRUE;
338 default:
339 return FALSE;
340 }
341 }
342
343 /*
344 * Formatting of dates, times, numbers and currencies.
345 */
346
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')
350
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)
356
357 /******************************************************************************
358 * NLS_GetDateTimeFormatW <internal>
359 *
360 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
361 *
362 * FIXME
363 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
364 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
365 */
366 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
367 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
368 LPWSTR lpStr, INT cchOut)
369 {
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 */
376
377 /* Verify our arguments */
378 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
379 goto invalid_parameter;
380
381 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
382 {
383 if (lpFormat &&
384 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
385 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
386 {
387 goto invalid_flags;
388 }
389
390 if (dwFlags & DATE_DATEVARSONLY)
391 {
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");
396
397 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
398 {
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;
409 }
410 }
411 }
412
413 if (!lpFormat)
414 {
415 /* Use the appropriate default format */
416 if (dwFlags & DATE_DATEVARSONLY)
417 {
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);
424 }
425 else
426 lpFormat = GetTime(node);
427 }
428
429 if (!lpTime)
430 {
431 GetLocalTime(&st); /* Default to current time */
432 lpTime = &st;
433 }
434 else
435 {
436 if (dwFlags & DATE_DATEVARSONLY)
437 {
438 FILETIME ftTmp;
439
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;
445
446 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
447 goto invalid_parameter;
448
449 FileTimeToSystemTime(&ftTmp, &st);
450 lpTime = &st;
451 }
452
453 if (dwFlags & TIME_TIMEVARSONLY)
454 {
455 /* Verify the time */
456 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
457 goto invalid_parameter;
458 }
459 }
460
461 /* Format the output */
462 while (*lpFormat)
463 {
464 if (IsLiteralMarker(*lpFormat))
465 {
466 /* Start of a literal string */
467 lpFormat++;
468
469 /* Loop until the end of the literal marker or end of the string */
470 while (*lpFormat)
471 {
472 if (IsLiteralMarker(*lpFormat))
473 {
474 lpFormat++;
475 if (!IsLiteralMarker(*lpFormat))
476 break; /* Terminating literal marker */
477 }
478
479 if (!cchOut)
480 cchWritten++; /* Count size only */
481 else if (cchWritten >= cchOut)
482 goto overrun;
483 else if (!bSkipping)
484 {
485 lpStr[cchWritten] = *lpFormat;
486 cchWritten++;
487 }
488 lpFormat++;
489 }
490 }
491 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
492 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
493 {
494 WCHAR buff[32], fmtChar;
495 LPCWSTR szAdd = NULL;
496 DWORD dwVal = 0;
497 int count = 0, dwLen;
498
499 bSkipping = FALSE;
500
501 fmtChar = *lpFormat;
502 while (*lpFormat == fmtChar)
503 {
504 count++;
505 lpFormat++;
506 }
507 buff[0] = '\0';
508
509 if (fmtChar != 'M') d_dd_formatted = FALSE;
510 switch(fmtChar)
511 {
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
518 {
519 dwVal = lpTime->wDay;
520 szAdd = buff;
521 d_dd_formatted = TRUE;
522 }
523 break;
524
525 case 'M':
526 if (count >= 4)
527 {
528 LPCWSTR genitive = GetGenitiveMonth(node, lpTime->wMonth - 1);
529 if (genitive)
530 {
531 if (d_dd_formatted)
532 {
533 szAdd = genitive;
534 break;
535 }
536 else
537 {
538 LPCWSTR format = lpFormat;
539 /* Look forward now, if next format pattern is for day genitive
540 name should be used */
541 while (*format)
542 {
543 /* Skip parts within markers */
544 if (IsLiteralMarker(*format))
545 {
546 ++format;
547 while (*format)
548 {
549 if (IsLiteralMarker(*format))
550 {
551 ++format;
552 if (!IsLiteralMarker(*format)) break;
553 }
554 }
555 }
556 if (*format != ' ') break;
557 ++format;
558 }
559 /* Only numeric day form matters */
560 if (*format && *format == 'd')
561 {
562 INT dcount = 1;
563 while (*++format == 'd') dcount++;
564 if (dcount < 3)
565 {
566 szAdd = genitive;
567 break;
568 }
569 }
570 }
571 }
572 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
573 }
574 else if (count == 3)
575 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
576 else
577 {
578 dwVal = lpTime->wMonth;
579 szAdd = buff;
580 }
581 break;
582
583 case 'y':
584 if (count >= 4)
585 {
586 count = 4;
587 dwVal = lpTime->wYear;
588 }
589 else
590 {
591 count = count > 2 ? 2 : count;
592 dwVal = lpTime->wYear % 100;
593 }
594 szAdd = buff;
595 break;
596
597 case 'g':
598 if (count == 2)
599 {
600 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
601 * When it is fixed, this string should be cached in 'node'.
602 */
603 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
604 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
605 }
606 else
607 {
608 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
609 }
610 szAdd = buff;
611 break;
612
613 case 'h':
614 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
615 {
616 count = count > 2 ? 2 : count;
617 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
618 szAdd = buff;
619 break;
620 }
621 /* .. fall through if we are forced to output in 24 hour format */
622
623 case 'H':
624 count = count > 2 ? 2 : count;
625 dwVal = lpTime->wHour;
626 szAdd = buff;
627 break;
628
629 case 'm':
630 if (dwFlags & TIME_NOMINUTESORSECONDS)
631 {
632 cchWritten = lastFormatPos; /* Skip */
633 bSkipping = TRUE;
634 }
635 else
636 {
637 count = count > 2 ? 2 : count;
638 dwVal = lpTime->wMinute;
639 szAdd = buff;
640 }
641 break;
642
643 case 's':
644 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
645 {
646 cchWritten = lastFormatPos; /* Skip */
647 bSkipping = TRUE;
648 }
649 else
650 {
651 count = count > 2 ? 2 : count;
652 dwVal = lpTime->wSecond;
653 szAdd = buff;
654 }
655 break;
656
657 case 't':
658 if (dwFlags & TIME_NOTIMEMARKER)
659 {
660 cchWritten = lastFormatPos; /* Skip */
661 bSkipping = TRUE;
662 }
663 else
664 {
665 if (count == 1)
666 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
667 else
668 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
669 }
670 break;
671 }
672
673 if (szAdd == buff && buff[0] == '\0')
674 {
675 static const WCHAR fmtW[] = {'%','.','*','d',0};
676 /* We have a numeric value to add */
677 snprintfW(buff, sizeof(buff)/sizeof(WCHAR), fmtW, count, dwVal);
678 }
679
680 dwLen = szAdd ? strlenW(szAdd) : 0;
681
682 if (cchOut && dwLen)
683 {
684 if (cchWritten + dwLen < cchOut)
685 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
686 else
687 {
688 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
689 goto overrun;
690 }
691 }
692 cchWritten += dwLen;
693 lastFormatPos = cchWritten; /* Save position of last output format text */
694 }
695 else
696 {
697 /* Literal character */
698 if (!cchOut)
699 cchWritten++; /* Count size only */
700 else if (cchWritten >= cchOut)
701 goto overrun;
702 else if (!bSkipping || *lpFormat == ' ')
703 {
704 lpStr[cchWritten] = *lpFormat;
705 cchWritten++;
706 }
707 lpFormat++;
708 }
709 }
710
711 /* Final string terminator and sanity check */
712 if (cchOut)
713 {
714 if (cchWritten >= cchOut)
715 goto overrun;
716 else
717 lpStr[cchWritten] = '\0';
718 }
719 cchWritten++; /* Include terminating NUL */
720
721 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
722 return cchWritten;
723
724 overrun:
725 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
726 SetLastError(ERROR_INSUFFICIENT_BUFFER);
727 return 0;
728
729 invalid_parameter:
730 SetLastError(ERROR_INVALID_PARAMETER);
731 return 0;
732
733 invalid_flags:
734 SetLastError(ERROR_INVALID_FLAGS);
735 return 0;
736 }
737
738 /******************************************************************************
739 * NLS_GetDateTimeFormatA <internal>
740 *
741 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
742 */
743 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
744 const SYSTEMTIME* lpTime,
745 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
746 {
747 DWORD cp = CP_ACP;
748 WCHAR szFormat[128], szOut[128];
749 INT iRet;
750
751 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
752 debugstr_a(lpFormat), lpStr, cchOut);
753
754 if (NLS_IsUnicodeOnlyLcid(lcid))
755 {
756 SetLastError(ERROR_INVALID_PARAMETER);
757 return 0;
758 }
759
760 if (!(dwFlags & LOCALE_USE_CP_ACP))
761 {
762 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
763 if (!node)
764 {
765 SetLastError(ERROR_INVALID_PARAMETER);
766 return 0;
767 }
768
769 cp = node->dwCodePage;
770 }
771
772 if (lpFormat)
773 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
774
775 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
776 cchOut = sizeof(szOut)/sizeof(WCHAR);
777
778 szOut[0] = '\0';
779
780 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
781 lpStr ? szOut : NULL, cchOut);
782
783 if (lpStr)
784 {
785 if (szOut[0])
786 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
787 else if (cchOut && iRet)
788 *lpStr = '\0';
789 }
790 return iRet;
791 }
792
793 /******************************************************************************
794 * GetDateFormatA [KERNEL32.@]
795 *
796 * Format a date for a given locale.
797 *
798 * PARAMS
799 * lcid [I] Locale to format for
800 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
801 * lpTime [I] Date to format
802 * lpFormat [I] Format string, or NULL to use the system defaults
803 * lpDateStr [O] Destination for formatted string
804 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
805 *
806 * NOTES
807 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
808 * details returned by GetLocaleInfoA() and modified by dwFlags.
809 * - lpFormat is a string of characters and formatting tokens. Any characters
810 * in the string are copied verbatim to lpDateStr, with tokens being replaced
811 * by the date values they represent.
812 * - The following tokens have special meanings in a date format string:
813 *| Token Meaning
814 *| ----- -------
815 *| d Single digit day of the month (no leading 0)
816 *| dd Double digit day of the month
817 *| ddd Short name for the day of the week
818 *| dddd Long name for the day of the week
819 *| M Single digit month of the year (no leading 0)
820 *| MM Double digit month of the year
821 *| MMM Short name for the month of the year
822 *| MMMM Long name for the month of the year
823 *| y Double digit year number (no leading 0)
824 *| yy Double digit year number
825 *| yyyy Four digit year number
826 *| gg Era string, for example 'AD'.
827 * - To output any literal character that could be misidentified as a token,
828 * enclose it in single quotes.
829 * - The Ascii version of this function fails if lcid is Unicode only.
830 *
831 * RETURNS
832 * Success: The number of character written to lpDateStr, or that would
833 * have been written, if cchOut is 0.
834 * Failure: 0. Use GetLastError() to determine the cause.
835 */
836 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
837 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
838 {
839 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
840 debugstr_a(lpFormat), lpDateStr, cchOut);
841
842 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
843 lpFormat, lpDateStr, cchOut);
844 }
845
846
847 /******************************************************************************
848 * GetDateFormatW [KERNEL32.@]
849 *
850 * See GetDateFormatA.
851 */
852 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
853 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
854 {
855 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
856 debugstr_w(lpFormat), lpDateStr, cchOut);
857
858 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
859 lpFormat, lpDateStr, cchOut);
860 }
861
862 /******************************************************************************
863 * GetTimeFormatA [KERNEL32.@]
864 *
865 * Format a time for a given locale.
866 *
867 * PARAMS
868 * lcid [I] Locale to format for
869 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
870 * lpTime [I] Time to format
871 * lpFormat [I] Formatting overrides
872 * lpTimeStr [O] Destination for formatted string
873 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
874 *
875 * NOTES
876 * - If lpFormat is NULL, lpszValue will be formatted according to the format
877 * details returned by GetLocaleInfoA() and modified by dwFlags.
878 * - lpFormat is a string of characters and formatting tokens. Any characters
879 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
880 * by the time values they represent.
881 * - The following tokens have special meanings in a time format string:
882 *| Token Meaning
883 *| ----- -------
884 *| h Hours with no leading zero (12-hour clock)
885 *| hh Hours with full two digits (12-hour clock)
886 *| H Hours with no leading zero (24-hour clock)
887 *| HH Hours with full two digits (24-hour clock)
888 *| m Minutes with no leading zero
889 *| mm Minutes with full two digits
890 *| s Seconds with no leading zero
891 *| ss Seconds with full two digits
892 *| t Short time marker (e.g. "A" or "P")
893 *| tt Long time marker (e.g. "AM", "PM")
894 * - To output any literal character that could be misidentified as a token,
895 * enclose it in single quotes.
896 * - The Ascii version of this function fails if lcid is Unicode only.
897 *
898 * RETURNS
899 * Success: The number of character written to lpTimeStr, or that would
900 * have been written, if cchOut is 0.
901 * Failure: 0. Use GetLastError() to determine the cause.
902 */
903 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
904 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
905 {
906 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
907 debugstr_a(lpFormat), lpTimeStr, cchOut);
908
909 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
910 lpFormat, lpTimeStr, cchOut);
911 }
912
913 /******************************************************************************
914 * GetTimeFormatW [KERNEL32.@]
915 *
916 * See GetTimeFormatA.
917 */
918 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
919 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
920 {
921 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
922 debugstr_w(lpFormat), lpTimeStr, cchOut);
923
924 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
925 lpFormat, lpTimeStr, cchOut);
926 }
927
928 /**************************************************************************
929 * GetNumberFormatA (KERNEL32.@)
930 *
931 * Format a number string for a given locale.
932 *
933 * PARAMS
934 * lcid [I] Locale to format for
935 * dwFlags [I] LOCALE_ flags from "winnls.h"
936 * lpszValue [I] String to format
937 * lpFormat [I] Formatting overrides
938 * lpNumberStr [O] Destination for formatted string
939 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
940 *
941 * NOTES
942 * - lpszValue can contain only '0' - '9', '-' and '.'.
943 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
944 * be formatted according to the format details returned by GetLocaleInfoA().
945 * - This function rounds the number string if the number of decimals exceeds the
946 * locales normal number of decimal places.
947 * - If cchOut is 0, this function does not write to lpNumberStr.
948 * - The Ascii version of this function fails if lcid is Unicode only.
949 *
950 * RETURNS
951 * Success: The number of character written to lpNumberStr, or that would
952 * have been written, if cchOut is 0.
953 * Failure: 0. Use GetLastError() to determine the cause.
954 */
955 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
956 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
957 LPSTR lpNumberStr, int cchOut)
958 {
959 DWORD cp = CP_ACP;
960 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
961 NUMBERFMTW fmt;
962 const NUMBERFMTW *pfmt = NULL;
963 INT iRet;
964
965 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
966 lpFormat, lpNumberStr, cchOut);
967
968 if (NLS_IsUnicodeOnlyLcid(lcid))
969 {
970 SetLastError(ERROR_INVALID_PARAMETER);
971 return 0;
972 }
973
974 if (!(dwFlags & LOCALE_USE_CP_ACP))
975 {
976 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
977 if (!node)
978 {
979 SetLastError(ERROR_INVALID_PARAMETER);
980 return 0;
981 }
982
983 cp = node->dwCodePage;
984 }
985
986 if (lpFormat)
987 {
988 memcpy(&fmt, lpFormat, sizeof(fmt));
989 pfmt = &fmt;
990 if (lpFormat->lpDecimalSep)
991 {
992 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
993 fmt.lpDecimalSep = szDec;
994 }
995 if (lpFormat->lpThousandSep)
996 {
997 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
998 fmt.lpThousandSep = szGrp;
999 }
1000 }
1001
1002 if (lpszValue)
1003 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1004
1005 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1006 cchOut = sizeof(szOut)/sizeof(WCHAR);
1007
1008 szOut[0] = '\0';
1009
1010 iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1011 lpNumberStr ? szOut : NULL, cchOut);
1012
1013 if (szOut[0] && lpNumberStr)
1014 WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
1015 return iRet;
1016 }
1017
1018 /* Number parsing state flags */
1019 #define NF_ISNEGATIVE 0x1 /* '-' found */
1020 #define NF_ISREAL 0x2 /* '.' found */
1021 #define NF_DIGITS 0x4 /* '0'-'9' found */
1022 #define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
1023 #define NF_ROUND 0x10 /* Number needs to be rounded */
1024
1025 /* Formatting options for Numbers */
1026 #define NLS_NEG_PARENS 0 /* "(1.1)" */
1027 #define NLS_NEG_LEFT 1 /* "-1.1" */
1028 #define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
1029 #define NLS_NEG_RIGHT 3 /* "1.1-" */
1030 #define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
1031
1032 /**************************************************************************
1033 * GetNumberFormatW (KERNEL32.@)
1034 *
1035 * See GetNumberFormatA.
1036 */
1037 INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
1038 LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
1039 LPWSTR lpNumberStr, int cchOut)
1040 {
1041 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1042 WCHAR szNegBuff[8];
1043 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
1044 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
1045 INT iRet;
1046
1047 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1048 lpFormat, lpNumberStr, cchOut);
1049
1050 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
1051 !IsValidLocale(lcid, 0) ||
1052 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
1053 {
1054 goto error;
1055 }
1056
1057 if (!lpFormat)
1058 {
1059 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1060
1061 if (!node)
1062 goto error;
1063 lpFormat = &node->fmt;
1064 lpszNegStart = lpszNeg = GetNegative(node);
1065 }
1066 else
1067 {
1068 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1069 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1070 lpszNegStart = lpszNeg = szNegBuff;
1071 }
1072 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1073
1074 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1075
1076 /* Format the number backwards into a temporary buffer */
1077
1078 szSrc = lpszValue;
1079 *szOut-- = '\0';
1080
1081 /* Check the number for validity */
1082 while (*szSrc)
1083 {
1084 if (*szSrc >= '0' && *szSrc <= '9')
1085 {
1086 dwState |= NF_DIGITS;
1087 if (dwState & NF_ISREAL)
1088 dwDecimals++;
1089 }
1090 else if (*szSrc == '-')
1091 {
1092 if (dwState)
1093 goto error; /* '-' not first character */
1094 dwState |= NF_ISNEGATIVE;
1095 }
1096 else if (*szSrc == '.')
1097 {
1098 if (dwState & NF_ISREAL)
1099 goto error; /* More than one '.' */
1100 dwState |= NF_ISREAL;
1101 }
1102 else
1103 goto error; /* Invalid char */
1104 szSrc++;
1105 }
1106 szSrc--; /* Point to last character */
1107
1108 if (!(dwState & NF_DIGITS))
1109 goto error; /* No digits */
1110
1111 /* Add any trailing negative sign */
1112 if (dwState & NF_ISNEGATIVE)
1113 {
1114 switch (lpFormat->NegativeOrder)
1115 {
1116 case NLS_NEG_PARENS:
1117 *szOut-- = ')';
1118 break;
1119 case NLS_NEG_RIGHT:
1120 case NLS_NEG_RIGHT_SPACE:
1121 while (lpszNeg >= lpszNegStart)
1122 *szOut-- = *lpszNeg--;
1123 if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
1124 *szOut-- = ' ';
1125 break;
1126 }
1127 }
1128
1129 /* Copy all digits up to the decimal point */
1130 if (!lpFormat->NumDigits)
1131 {
1132 if (dwState & NF_ISREAL)
1133 {
1134 while (*szSrc != '.') /* Don't write any decimals or a separator */
1135 {
1136 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1137 dwState |= NF_ROUND;
1138 else
1139 dwState &= ~NF_ROUND;
1140 szSrc--;
1141 }
1142 szSrc--;
1143 }
1144 }
1145 else
1146 {
1147 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1148
1149 if (dwDecimals <= lpFormat->NumDigits)
1150 {
1151 dwDecimals = lpFormat->NumDigits - dwDecimals;
1152 while (dwDecimals--)
1153 *szOut-- = '0'; /* Pad to correct number of dp */
1154 }
1155 else
1156 {
1157 dwDecimals -= lpFormat->NumDigits;
1158 /* Skip excess decimals, and determine if we have to round the number */
1159 while (dwDecimals--)
1160 {
1161 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1162 dwState |= NF_ROUND;
1163 else
1164 dwState &= ~NF_ROUND;
1165 szSrc--;
1166 }
1167 }
1168
1169 if (dwState & NF_ISREAL)
1170 {
1171 while (*szSrc != '.')
1172 {
1173 if (dwState & NF_ROUND)
1174 {
1175 if (*szSrc == '9')
1176 *szOut-- = '0'; /* continue rounding */
1177 else
1178 {
1179 dwState &= ~NF_ROUND;
1180 *szOut-- = (*szSrc)+1;
1181 }
1182 szSrc--;
1183 }
1184 else
1185 *szOut-- = *szSrc--; /* Write existing decimals */
1186 }
1187 szSrc--; /* Skip '.' */
1188 }
1189
1190 while (lpszDec >= lpFormat->lpDecimalSep)
1191 *szOut-- = *lpszDec--; /* Write decimal separator */
1192 }
1193
1194 dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
1195
1196 /* Write the remaining whole number digits, including grouping chars */
1197 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1198 {
1199 if (dwState & NF_ROUND)
1200 {
1201 if (*szSrc == '9')
1202 *szOut-- = '0'; /* continue rounding */
1203 else
1204 {
1205 dwState &= ~NF_ROUND;
1206 *szOut-- = (*szSrc)+1;
1207 }
1208 szSrc--;
1209 }
1210 else
1211 *szOut-- = *szSrc--;
1212
1213 dwState |= NF_DIGITS_OUT;
1214 dwCurrentGroupCount++;
1215 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1216 {
1217 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1218
1219 while (lpszGrp >= lpFormat->lpThousandSep)
1220 *szOut-- = *lpszGrp--; /* Write grouping char */
1221
1222 dwCurrentGroupCount = 0;
1223 if (lpFormat->Grouping == 32)
1224 dwGroupCount = 2; /* Indic grouping: 3 then 2 */
1225 }
1226 }
1227 if (dwState & NF_ROUND)
1228 {
1229 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1230 }
1231 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1232 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1233
1234 /* Add any leading negative sign */
1235 if (dwState & NF_ISNEGATIVE)
1236 {
1237 switch (lpFormat->NegativeOrder)
1238 {
1239 case NLS_NEG_PARENS:
1240 *szOut-- = '(';
1241 break;
1242 case NLS_NEG_LEFT_SPACE:
1243 *szOut-- = ' ';
1244 /* Fall through */
1245 case NLS_NEG_LEFT:
1246 while (lpszNeg >= lpszNegStart)
1247 *szOut-- = *lpszNeg--;
1248 break;
1249 }
1250 }
1251 szOut++;
1252
1253 iRet = strlenW(szOut) + 1;
1254 if (cchOut)
1255 {
1256 if (iRet <= cchOut)
1257 memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
1258 else
1259 {
1260 memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
1261 lpNumberStr[cchOut - 1] = '\0';
1262 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1263 iRet = 0;
1264 }
1265 }
1266 return iRet;
1267
1268 error:
1269 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1270 return 0;
1271 }
1272
1273 /**************************************************************************
1274 * GetCurrencyFormatA (KERNEL32.@)
1275 *
1276 * Format a currency string for a given locale.
1277 *
1278 * PARAMS
1279 * lcid [I] Locale to format for
1280 * dwFlags [I] LOCALE_ flags from "winnls.h"
1281 * lpszValue [I] String to format
1282 * lpFormat [I] Formatting overrides
1283 * lpCurrencyStr [O] Destination for formatted string
1284 * cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
1285 *
1286 * NOTES
1287 * - lpszValue can contain only '0' - '9', '-' and '.'.
1288 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
1289 * be formatted according to the format details returned by GetLocaleInfoA().
1290 * - This function rounds the currency if the number of decimals exceeds the
1291 * locales number of currency decimal places.
1292 * - If cchOut is 0, this function does not write to lpCurrencyStr.
1293 * - The Ascii version of this function fails if lcid is Unicode only.
1294 *
1295 * RETURNS
1296 * Success: The number of character written to lpNumberStr, or that would
1297 * have been written, if cchOut is 0.
1298 * Failure: 0. Use GetLastError() to determine the cause.
1299 */
1300 INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
1301 LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
1302 LPSTR lpCurrencyStr, int cchOut)
1303 {
1304 DWORD cp = CP_ACP;
1305 WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
1306 CURRENCYFMTW fmt;
1307 const CURRENCYFMTW *pfmt = NULL;
1308 INT iRet;
1309
1310 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
1311 lpFormat, lpCurrencyStr, cchOut);
1312
1313 if (NLS_IsUnicodeOnlyLcid(lcid))
1314 {
1315 SetLastError(ERROR_INVALID_PARAMETER);
1316 return 0;
1317 }
1318
1319 if (!(dwFlags & LOCALE_USE_CP_ACP))
1320 {
1321 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1322 if (!node)
1323 {
1324 SetLastError(ERROR_INVALID_PARAMETER);
1325 return 0;
1326 }
1327
1328 cp = node->dwCodePage;
1329 }
1330
1331 if (lpFormat)
1332 {
1333 memcpy(&fmt, lpFormat, sizeof(fmt));
1334 pfmt = &fmt;
1335 if (lpFormat->lpDecimalSep)
1336 {
1337 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
1338 fmt.lpDecimalSep = szDec;
1339 }
1340 if (lpFormat->lpThousandSep)
1341 {
1342 MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
1343 fmt.lpThousandSep = szGrp;
1344 }
1345 if (lpFormat->lpCurrencySymbol)
1346 {
1347 MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
1348 fmt.lpCurrencySymbol = szCy;
1349 }
1350 }
1351
1352 if (lpszValue)
1353 MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
1354
1355 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
1356 cchOut = sizeof(szOut)/sizeof(WCHAR);
1357
1358 szOut[0] = '\0';
1359
1360 iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
1361 lpCurrencyStr ? szOut : NULL, cchOut);
1362
1363 if (szOut[0] && lpCurrencyStr)
1364 WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
1365 return iRet;
1366 }
1367
1368 /* Formatting states for Currencies. We use flags to avoid code duplication. */
1369 #define CF_PARENS 0x1 /* Parentheses */
1370 #define CF_MINUS_LEFT 0x2 /* '-' to the left */
1371 #define CF_MINUS_RIGHT 0x4 /* '-' to the right */
1372 #define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
1373 #define CF_CY_LEFT 0x10 /* '$' to the left */
1374 #define CF_CY_RIGHT 0x20 /* '$' to the right */
1375 #define CF_CY_SPACE 0x40 /* ' ' by '$' */
1376
1377 /**************************************************************************
1378 * GetCurrencyFormatW (KERNEL32.@)
1379 *
1380 * See GetCurrencyFormatA.
1381 */
1382 INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
1383 LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
1384 LPWSTR lpCurrencyStr, int cchOut)
1385 {
1386 static const BYTE NLS_NegCyFormats[16] =
1387 {
1388 CF_PARENS|CF_CY_LEFT, /* ($1.1) */
1389 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
1390 CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
1391 CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
1392 CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
1393 CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
1394 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
1395 CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
1396 CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
1397 CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
1398 CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
1399 CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
1400 CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
1401 CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
1402 CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
1403 CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
1404 };
1405 static const BYTE NLS_PosCyFormats[4] =
1406 {
1407 CF_CY_LEFT, /* $1.1 */
1408 CF_CY_RIGHT, /* 1.1$ */
1409 CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
1410 CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
1411 };
1412 WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
1413 WCHAR szNegBuff[8];
1414 const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
1415 DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
1416 INT iRet;
1417
1418 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
1419 lpFormat, lpCurrencyStr, cchOut);
1420
1421 if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
1422 !IsValidLocale(lcid, 0) ||
1423 (lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
1424 !lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
1425 lpFormat->PositiveOrder > 3)))
1426 {
1427 goto error;
1428 }
1429
1430 if (!lpFormat)
1431 {
1432 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
1433
1434 if (!node)
1435 goto error;
1436
1437 lpFormat = &node->cyfmt;
1438 lpszNegStart = lpszNeg = GetNegative(node);
1439 }
1440 else
1441 {
1442 GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
1443 szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
1444 lpszNegStart = lpszNeg = szNegBuff;
1445 }
1446 dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
1447
1448 lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
1449 lpszCyStart = lpFormat->lpCurrencySymbol;
1450 lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
1451
1452 /* Format the currency backwards into a temporary buffer */
1453
1454 szSrc = lpszValue;
1455 *szOut-- = '\0';
1456
1457 /* Check the number for validity */
1458 while (*szSrc)
1459 {
1460 if (*szSrc >= '0' && *szSrc <= '9')
1461 {
1462 dwState |= NF_DIGITS;
1463 if (dwState & NF_ISREAL)
1464 dwDecimals++;
1465 }
1466 else if (*szSrc == '-')
1467 {
1468 if (dwState)
1469 goto error; /* '-' not first character */
1470 dwState |= NF_ISNEGATIVE;
1471 }
1472 else if (*szSrc == '.')
1473 {
1474 if (dwState & NF_ISREAL)
1475 goto error; /* More than one '.' */
1476 dwState |= NF_ISREAL;
1477 }
1478 else
1479 goto error; /* Invalid char */
1480 szSrc++;
1481 }
1482 szSrc--; /* Point to last character */
1483
1484 if (!(dwState & NF_DIGITS))
1485 goto error; /* No digits */
1486
1487 if (dwState & NF_ISNEGATIVE)
1488 dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
1489 else
1490 dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
1491
1492 /* Add any trailing negative or currency signs */
1493 if (dwFmt & CF_PARENS)
1494 *szOut-- = ')';
1495
1496 while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
1497 {
1498 switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
1499 {
1500 case CF_MINUS_RIGHT:
1501 case CF_MINUS_RIGHT|CF_CY_RIGHT:
1502 while (lpszNeg >= lpszNegStart)
1503 *szOut-- = *lpszNeg--;
1504 dwFmt &= ~CF_MINUS_RIGHT;
1505 break;
1506
1507 case CF_CY_RIGHT:
1508 case CF_MINUS_BEFORE|CF_CY_RIGHT:
1509 case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
1510 while (lpszCy >= lpszCyStart)
1511 *szOut-- = *lpszCy--;
1512 if (dwFmt & CF_CY_SPACE)
1513 *szOut-- = ' ';
1514 dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
1515 break;
1516 }
1517 }
1518
1519 /* Copy all digits up to the decimal point */
1520 if (!lpFormat->NumDigits)
1521 {
1522 if (dwState & NF_ISREAL)
1523 {
1524 while (*szSrc != '.') /* Don't write any decimals or a separator */
1525 {
1526 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1527 dwState |= NF_ROUND;
1528 else
1529 dwState &= ~NF_ROUND;
1530 szSrc--;
1531 }
1532 szSrc--;
1533 }
1534 }
1535 else
1536 {
1537 LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
1538
1539 if (dwDecimals <= lpFormat->NumDigits)
1540 {
1541 dwDecimals = lpFormat->NumDigits - dwDecimals;
1542 while (dwDecimals--)
1543 *szOut-- = '0'; /* Pad to correct number of dp */
1544 }
1545 else
1546 {
1547 dwDecimals -= lpFormat->NumDigits;
1548 /* Skip excess decimals, and determine if we have to round the number */
1549 while (dwDecimals--)
1550 {
1551 if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
1552 dwState |= NF_ROUND;
1553 else
1554 dwState &= ~NF_ROUND;
1555 szSrc--;
1556 }
1557 }
1558
1559 if (dwState & NF_ISREAL)
1560 {
1561 while (*szSrc != '.')
1562 {
1563 if (dwState & NF_ROUND)
1564 {
1565 if (*szSrc == '9')
1566 *szOut-- = '0'; /* continue rounding */
1567 else
1568 {
1569 dwState &= ~NF_ROUND;
1570 *szOut-- = (*szSrc)+1;
1571 }
1572 szSrc--;
1573 }
1574 else
1575 *szOut-- = *szSrc--; /* Write existing decimals */
1576 }
1577 szSrc--; /* Skip '.' */
1578 }
1579 while (lpszDec >= lpFormat->lpDecimalSep)
1580 *szOut-- = *lpszDec--; /* Write decimal separator */
1581 }
1582
1583 dwGroupCount = lpFormat->Grouping;
1584
1585 /* Write the remaining whole number digits, including grouping chars */
1586 while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
1587 {
1588 if (dwState & NF_ROUND)
1589 {
1590 if (*szSrc == '9')
1591 *szOut-- = '0'; /* continue rounding */
1592 else
1593 {
1594 dwState &= ~NF_ROUND;
1595 *szOut-- = (*szSrc)+1;
1596 }
1597 szSrc--;
1598 }
1599 else
1600 *szOut-- = *szSrc--;
1601
1602 dwState |= NF_DIGITS_OUT;
1603 dwCurrentGroupCount++;
1604 if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount && *szSrc != '-')
1605 {
1606 LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
1607
1608 while (lpszGrp >= lpFormat->lpThousandSep)
1609 *szOut-- = *lpszGrp--; /* Write grouping char */
1610
1611 dwCurrentGroupCount = 0;
1612 }
1613 }
1614 if (dwState & NF_ROUND)
1615 *szOut-- = '1'; /* e.g. .6 > 1.0 */
1616 else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
1617 *szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
1618
1619 /* Add any leading negative or currency sign */
1620 while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
1621 {
1622 switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
1623 {
1624 case CF_MINUS_LEFT:
1625 case CF_MINUS_LEFT|CF_CY_LEFT:
1626 while (lpszNeg >= lpszNegStart)
1627 *szOut-- = *lpszNeg--;
1628 dwFmt &= ~CF_MINUS_LEFT;
1629 break;
1630
1631 case CF_CY_LEFT:
1632 case CF_CY_LEFT|CF_MINUS_BEFORE:
1633 case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
1634 if (dwFmt & CF_CY_SPACE)
1635 *szOut-- = ' ';
1636 while (lpszCy >= lpszCyStart)
1637 *szOut-- = *lpszCy--;
1638 dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
1639 break;
1640 }
1641 }
1642 if (dwFmt & CF_PARENS)
1643 *szOut-- = '(';
1644 szOut++;
1645
1646 iRet = strlenW(szOut) + 1;
1647 if (cchOut)
1648 {
1649 if (iRet <= cchOut)
1650 memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
1651 else
1652 {
1653 memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
1654 lpCurrencyStr[cchOut - 1] = '\0';
1655 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1656 iRet = 0;
1657 }
1658 }
1659 return iRet;
1660
1661 error:
1662 SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
1663 return 0;
1664 }
1665
1666 /* FIXME: Everything below here needs to move somewhere else along with the
1667 * other EnumXXX functions, when a method for storing resources for
1668 * alternate calendars is determined.
1669 */
1670
1671 /**************************************************************************
1672 * EnumDateFormatsExA (KERNEL32.@)
1673 *
1674 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1675 * LOCALE_NOUSEROVERRIDE here as well?
1676 */
1677 BOOL WINAPI EnumDateFormatsExA(DATEFMT_ENUMPROCEXA proc, LCID lcid, DWORD flags)
1678 {
1679 CALID cal_id;
1680 char buf[256];
1681
1682 if (!proc)
1683 {
1684 SetLastError(ERROR_INVALID_PARAMETER);
1685 return FALSE;
1686 }
1687
1688 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1689 return FALSE;
1690
1691 switch (flags & ~LOCALE_USE_CP_ACP)
1692 {
1693 case 0:
1694 case DATE_SHORTDATE:
1695 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1696 proc(buf, cal_id);
1697 break;
1698
1699 case DATE_LONGDATE:
1700 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1701 proc(buf, cal_id);
1702 break;
1703
1704 case DATE_YEARMONTH:
1705 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1706 proc(buf, cal_id);
1707 break;
1708
1709 default:
1710 FIXME("Unknown date format (%d)\n", flags);
1711 SetLastError(ERROR_INVALID_PARAMETER);
1712 return FALSE;
1713 }
1714 return TRUE;
1715 }
1716
1717 /**************************************************************************
1718 * EnumDateFormatsExW (KERNEL32.@)
1719 */
1720 BOOL WINAPI EnumDateFormatsExW(DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags)
1721 {
1722 CALID cal_id;
1723 WCHAR buf[256];
1724
1725 if (!proc)
1726 {
1727 SetLastError(ERROR_INVALID_PARAMETER);
1728 return FALSE;
1729 }
1730
1731 if (!GetLocaleInfoW(lcid, LOCALE_ICALENDARTYPE|LOCALE_RETURN_NUMBER, (LPWSTR)&cal_id, sizeof(cal_id)/sizeof(WCHAR)))
1732 return FALSE;
1733
1734 switch (flags & ~LOCALE_USE_CP_ACP)
1735 {
1736 case 0:
1737 case DATE_SHORTDATE:
1738 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1739 proc(buf, cal_id);
1740 break;
1741
1742 case DATE_LONGDATE:
1743 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1744 proc(buf, cal_id);
1745 break;
1746
1747 case DATE_YEARMONTH:
1748 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1749 proc(buf, cal_id);
1750 break;
1751
1752 default:
1753 FIXME("Unknown date format (%d)\n", flags);
1754 SetLastError(ERROR_INVALID_PARAMETER);
1755 return FALSE;
1756 }
1757 return TRUE;
1758 }
1759
1760 /**************************************************************************
1761 * EnumDateFormatsA (KERNEL32.@)
1762 *
1763 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1764 * LOCALE_NOUSEROVERRIDE here as well?
1765 */
1766 BOOL WINAPI EnumDateFormatsA(DATEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1767 {
1768 char buf[256];
1769
1770 if (!proc)
1771 {
1772 SetLastError(ERROR_INVALID_PARAMETER);
1773 return FALSE;
1774 }
1775
1776 switch (flags & ~LOCALE_USE_CP_ACP)
1777 {
1778 case 0:
1779 case DATE_SHORTDATE:
1780 if (GetLocaleInfoA(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1781 proc(buf);
1782 break;
1783
1784 case DATE_LONGDATE:
1785 if (GetLocaleInfoA(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1786 proc(buf);
1787 break;
1788
1789 case DATE_YEARMONTH:
1790 if (GetLocaleInfoA(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1791 proc(buf);
1792 break;
1793
1794 default:
1795 FIXME("Unknown date format (%d)\n", flags);
1796 SetLastError(ERROR_INVALID_PARAMETER);
1797 return FALSE;
1798 }
1799 return TRUE;
1800 }
1801
1802 /**************************************************************************
1803 * EnumDateFormatsW (KERNEL32.@)
1804 */
1805 BOOL WINAPI EnumDateFormatsW(DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1806 {
1807 WCHAR buf[256];
1808
1809 if (!proc)
1810 {
1811 SetLastError(ERROR_INVALID_PARAMETER);
1812 return FALSE;
1813 }
1814
1815 switch (flags & ~LOCALE_USE_CP_ACP)
1816 {
1817 case 0:
1818 case DATE_SHORTDATE:
1819 if (GetLocaleInfoW(lcid, LOCALE_SSHORTDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1820 proc(buf);
1821 break;
1822
1823 case DATE_LONGDATE:
1824 if (GetLocaleInfoW(lcid, LOCALE_SLONGDATE | (flags & LOCALE_USE_CP_ACP), buf, 256))
1825 proc(buf);
1826 break;
1827
1828 case DATE_YEARMONTH:
1829 if (GetLocaleInfoW(lcid, LOCALE_SYEARMONTH | (flags & LOCALE_USE_CP_ACP), buf, 256))
1830 proc(buf);
1831 break;
1832
1833 default:
1834 FIXME("Unknown date format (%d)\n", flags);
1835 SetLastError(ERROR_INVALID_PARAMETER);
1836 return FALSE;
1837 }
1838 return TRUE;
1839 }
1840
1841 /**************************************************************************
1842 * EnumTimeFormatsA (KERNEL32.@)
1843 *
1844 * FIXME: MSDN mentions only LOCALE_USE_CP_ACP, should we handle
1845 * LOCALE_NOUSEROVERRIDE here as well?
1846 */
1847 BOOL WINAPI EnumTimeFormatsA(TIMEFMT_ENUMPROCA proc, LCID lcid, DWORD flags)
1848 {
1849 char buf[256];
1850
1851 if (!proc)
1852 {
1853 SetLastError(ERROR_INVALID_PARAMETER);
1854 return FALSE;
1855 }
1856
1857 switch (flags & ~LOCALE_USE_CP_ACP)
1858 {
1859 case 0:
1860 if (GetLocaleInfoA(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1861 proc(buf);
1862 break;
1863
1864 default:
1865 FIXME("Unknown time format (%d)\n", flags);
1866 SetLastError(ERROR_INVALID_PARAMETER);
1867 return FALSE;
1868 }
1869 return TRUE;
1870 }
1871
1872 /**************************************************************************
1873 * EnumTimeFormatsW (KERNEL32.@)
1874 */
1875 BOOL WINAPI EnumTimeFormatsW(TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags)
1876 {
1877 WCHAR buf[256];
1878
1879 if (!proc)
1880 {
1881 SetLastError(ERROR_INVALID_PARAMETER);
1882 return FALSE;
1883 }
1884
1885 switch (flags & ~LOCALE_USE_CP_ACP)
1886 {
1887 case 0:
1888 if (GetLocaleInfoW(lcid, LOCALE_STIMEFORMAT | (flags & LOCALE_USE_CP_ACP), buf, 256))
1889 proc(buf);
1890 break;
1891
1892 default:
1893 FIXME("Unknown time format (%d)\n", flags);
1894 SetLastError(ERROR_INVALID_PARAMETER);
1895 return FALSE;
1896 }
1897 return TRUE;
1898 }
1899
1900 /******************************************************************************
1901 * NLS_EnumCalendarInfoAW <internal>
1902 * Enumerates calendar information for a specified locale.
1903 *
1904 * PARAMS
1905 * calinfoproc [I] Pointer to the callback
1906 * locale [I] The locale for which to retrieve calendar information.
1907 * This parameter can be a locale identifier created by the
1908 * MAKELCID macro, or one of the following values:
1909 * LOCALE_SYSTEM_DEFAULT
1910 * Use the default system locale.
1911 * LOCALE_USER_DEFAULT
1912 * Use the default user locale.
1913 * calendar [I] The calendar for which information is requested, or
1914 * ENUM_ALL_CALENDARS.
1915 * caltype [I] The type of calendar information to be returned. Note
1916 * that only one CALTYPE value can be specified per call
1917 * of this function, except where noted.
1918 * unicode [I] Specifies if the callback expects a unicode string.
1919 * ex [I] Specifies if the callback needs the calendar identifier.
1920 *
1921 * RETURNS
1922 * Success: TRUE.
1923 * Failure: FALSE. Use GetLastError() to determine the cause.
1924 *
1925 * NOTES
1926 * When the ANSI version of this function is used with a Unicode-only LCID,
1927 * the call can succeed because the system uses the system code page.
1928 * However, characters that are undefined in the system code page appear
1929 * in the string as a question mark (?).
1930 *
1931 * TODO
1932 * The above note should be respected by GetCalendarInfoA.
1933 */
1934 static BOOL NLS_EnumCalendarInfoAW(void *calinfoproc, LCID locale,
1935 CALID calendar, CALTYPE caltype, BOOL unicode, BOOL ex )
1936 {
1937 WCHAR *buf, *opt = NULL, *iter = NULL;
1938 BOOL ret = FALSE;
1939 int bufSz = 200; /* the size of the buffer */
1940
1941 if (calinfoproc == NULL)
1942 {
1943 SetLastError(ERROR_INVALID_PARAMETER);
1944 return FALSE;
1945 }
1946
1947 buf = HeapAlloc(GetProcessHeap(), 0, bufSz);
1948 if (buf == NULL)
1949 {
1950 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1951 return FALSE;
1952 }
1953
1954 if (calendar == ENUM_ALL_CALENDARS)
1955 {
1956 int optSz = GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, NULL, 0);
1957 if (optSz > 1)
1958 {
1959 opt = HeapAlloc(GetProcessHeap(), 0, optSz * sizeof(WCHAR));
1960 if (opt == NULL)
1961 {
1962 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1963 goto cleanup;
1964 }
1965 if (GetLocaleInfoW(locale, LOCALE_IOPTIONALCALENDAR, opt, optSz))
1966 iter = opt;
1967 }
1968 calendar = NLS_GetLocaleNumber(locale, LOCALE_ICALENDARTYPE);
1969 }
1970
1971 while (TRUE) /* loop through calendars */
1972 {
1973 do /* loop until there's no error */
1974 {
1975 if (unicode)
1976 ret = GetCalendarInfoW(locale, calendar, caltype, buf, bufSz / sizeof(WCHAR), NULL);
1977 else ret = GetCalendarInfoA(locale, calendar, caltype, (CHAR*)buf, bufSz / sizeof(CHAR), NULL);
1978
1979 if (!ret)
1980 {
1981 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1982 { /* so resize it */
1983 int newSz;
1984 if (unicode)
1985 newSz = GetCalendarInfoW(locale, calendar, caltype, NULL, 0, NULL) * sizeof(WCHAR);
1986 else newSz = GetCalendarInfoA(locale, calendar, caltype, NULL, 0, NULL) * sizeof(CHAR);
1987 if (bufSz >= newSz)
1988 {
1989 ERR("Buffer resizing disorder: was %d, requested %d.\n", bufSz, newSz);
1990 goto cleanup;
1991 }
1992 bufSz = newSz;
1993 WARN("Buffer too small; resizing to %d bytes.\n", bufSz);
1994 buf = HeapReAlloc(GetProcessHeap(), 0, buf, bufSz);
1995 if (buf == NULL)
1996 goto cleanup;
1997 } else goto cleanup;
1998 }
1999 } while (!ret);
2000
2001 /* Here we are. We pass the buffer to the correct version of
2002 * the callback. Because it's not the same number of params,
2003 * we must check for Ex, but we don't care about Unicode
2004 * because the buffer is already in the correct format.
2005 */
2006 if (ex) {
2007 ret = ((CALINFO_ENUMPROCEXW)calinfoproc)(buf, calendar);
2008 } else
2009 ret = ((CALINFO_ENUMPROCW)calinfoproc)(buf);
2010
2011 if (!ret) { /* the callback told to stop */
2012 ret = TRUE;
2013 break;
2014 }
2015
2016 if ((iter == NULL) || (*iter == 0)) /* no more calendars */
2017 break;
2018
2019 calendar = 0;
2020 while ((*iter >= '0') && (*iter <= '9'))
2021 calendar = calendar * 10 + *iter++ - '0';
2022
2023 if (*iter++ != 0)
2024 {
2025 SetLastError(ERROR_BADDB);
2026 ret = FALSE;
2027 break;
2028 }
2029 }
2030
2031 cleanup:
2032 HeapFree(GetProcessHeap(), 0, opt);
2033 HeapFree(GetProcessHeap(), 0, buf);
2034 return ret;
2035 }
2036
2037 /******************************************************************************
2038 * EnumCalendarInfoA [KERNEL32.@]
2039 *
2040 * See EnumCalendarInfoAW.
2041 */
2042 BOOL WINAPI EnumCalendarInfoA( CALINFO_ENUMPROCA calinfoproc,LCID locale,
2043 CALID calendar,CALTYPE caltype )
2044 {
2045 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2046 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, FALSE);
2047 }
2048
2049 /******************************************************************************
2050 * EnumCalendarInfoW [KERNEL32.@]
2051 *
2052 * See EnumCalendarInfoAW.
2053 */
2054 BOOL WINAPI EnumCalendarInfoW( CALINFO_ENUMPROCW calinfoproc,LCID locale,
2055 CALID calendar,CALTYPE caltype )
2056 {
2057 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2058 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, FALSE);
2059 }
2060
2061 /******************************************************************************
2062 * EnumCalendarInfoExA [KERNEL32.@]
2063 *
2064 * See EnumCalendarInfoAW.
2065 */
2066 BOOL WINAPI EnumCalendarInfoExA( CALINFO_ENUMPROCEXA calinfoproc,LCID locale,
2067 CALID calendar,CALTYPE caltype )
2068 {
2069 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2070 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, FALSE, TRUE);
2071 }
2072
2073 /******************************************************************************
2074 * EnumCalendarInfoExW [KERNEL32.@]
2075 *
2076 * See EnumCalendarInfoAW.
2077 */
2078 BOOL WINAPI EnumCalendarInfoExW( CALINFO_ENUMPROCEXW calinfoproc,LCID locale,
2079 CALID calendar,CALTYPE caltype )
2080 {
2081 TRACE("(%p,0x%08x,0x%08x,0x%08x)\n", calinfoproc, locale, calendar, caltype);
2082 return NLS_EnumCalendarInfoAW(calinfoproc, locale, calendar, caltype, TRUE, TRUE);
2083 }