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