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