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