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