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