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