1 /*
2 * Locale-dependent format handling
3 *
4 * Copyright 1995 Martin von Loewis
5 * Copyright 1998 David Lee Lambert
6 * Copyright 2000 Julio César Gázquez
7 * Copyright 2003 Jon Griffiths
8 * Copyright 2005 Dmitry Timoshkov
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 #include "config.h"
26 #include "wine/port.h"
27
28 #include <string.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wine/unicode.h"
36 #include "wine/debug.h"
37 #include "winternl.h"
38
39 #include "kernel_private.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(nls);
42
43 #define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
44 #define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
45
46 /* Since calculating the formatting data for each locale is time-consuming,
47 * we get the format data for each locale only once and cache it in memory.
48 * We cache both the system default and user overridden data, after converting
49 * them into the formats that the functions here expect. Since these functions
50 * will typically be called with only a small number of the total locales
51 * installed, the memory overhead is minimal while the speedup is significant.
52 *
53 * Our cache takes the form of a singly linked list, whose node is below:
54 */
55 #define NLS_NUM_CACHED_STRINGS 45
56
57 typedef struct _NLS_FORMAT_NODE
58 {
59 LCID lcid; /* Locale Id */
60 DWORD dwFlags; /* 0 or LOCALE_NOUSEROVERRIDE */
61 DWORD dwCodePage; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
62 NUMBERFMTW fmt; /* Default format for numbers */
63 CURRENCYFMTW cyfmt; /* Default format for currencies */
64 LPWSTR lppszStrings[NLS_NUM_CACHED_STRINGS]; /* Default formats,day/month names */
65 WCHAR szShortAM[2]; /* Short 'AM' marker */
66 WCHAR szShortPM[2]; /* Short 'PM' marker */
67 struct _NLS_FORMAT_NODE *next;
68 } NLS_FORMAT_NODE;
69
70 /* Macros to get particular data strings from a format node */
71 #define GetNegative(fmt) fmt->lppszStrings[0]
72 #define GetLongDate(fmt) fmt->lppszStrings[1]
73 #define GetShortDate(fmt) fmt->lppszStrings[2]
74 #define GetTime(fmt) fmt->lppszStrings[3]
75 #define GetAM(fmt) fmt->lppszStrings[42]
76 #define GetPM(fmt) fmt->lppszStrings[43]
77 #define GetYearMonth(fmt) fmt->lppszStrings[44]
78
79 #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
80 #define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
81 #define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
82 #define GetShortMonth(fmt,mth) fmt->lppszStrings[30 + mth]
83
84 /* Write access to the cache is protected by this critical section */
85 static CRITICAL_SECTION NLS_FormatsCS;
86 static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
87 {
88 0, 0, &NLS_FormatsCS,
89 { &NLS_FormatsCS_debug.ProcessLocksList,
90 &NLS_FormatsCS_debug.ProcessLocksList },
91 0, 0, { (DWORD_PTR)(__FILE__ ": NLS_Formats") }
92 };
93 static CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
94
95 /**************************************************************************
96 * NLS_GetLocaleNumber <internal>
97 *
98 * Get a numeric locale format value.
99 */
100 static DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
101 {
102 WCHAR szBuff[80];
103 DWORD dwVal = 0;
104
105 szBuff[0] = '\0';
106 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
107
108 if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '')
109 dwVal = (szBuff[0] - '') * 10 + (szBuff[2] - '');
110 else
111 {
112 const WCHAR* iter = szBuff;
113 dwVal = 0;
114 while(*iter >= '' && *iter <= '9')
115 dwVal = dwVal * 10 + (*iter++ - '');
116 }
117 return dwVal;
118 }
119
120 /**************************************************************************
121 * NLS_GetLocaleString <internal>
122 *
123 * Get a string locale format value.
124 */
125 static WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
126 {
127 WCHAR szBuff[80], *str;
128 DWORD dwLen;
129
130 szBuff[0] = '\0';
131 GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
132 dwLen = strlenW(szBuff) + 1;
133 str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
134 if (str)
135 memcpy(str, szBuff, dwLen * sizeof(WCHAR));
136 return str;
137 }
138
139 #define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
140 TRACE( #type ": %d (%08x)\n", (DWORD)num, (DWORD)num)
141
142 #define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
143 TRACE( #type ": %s\n", debugstr_w(str))
144
145 /**************************************************************************
146 * NLS_GetFormats <internal>
147 *
148 * Calculate (and cache) the number formats for a locale.
149 */
150 static const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
151 {
152 /* GetLocaleInfo() identifiers for cached formatting strings */
153 static const USHORT NLS_LocaleIndices[] = {
154 LOCALE_SNEGATIVESIGN,
155 LOCALE_SLONGDATE, LOCALE_SSHORTDATE,
156 LOCALE_STIMEFORMAT,
157 LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
158 LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
159 LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
160 LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
161 LOCALE_SABBREVDAYNAME7,
162 LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
163 LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
164 LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
165 LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
166 LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
167 LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
168 LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
169 LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
170 LOCALE_S1159, LOCALE_S2359,
171 LOCALE_SYEARMONTH
172 };
173 static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
174 NLS_FORMAT_NODE *node = NLS_CachedFormats;
175
176 dwFlags &= LOCALE_NOUSEROVERRIDE;
177
178 TRACE("(0x%04x,0x%08x)\n", lcid, dwFlags);
179
180 /* See if we have already cached the locales number format */
181 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
182 node = node->next;
183
184 if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
185 {
186 NLS_FORMAT_NODE *new_node;
187 DWORD i;
188
189 TRACE("Creating new cache entry\n");
190
191 if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
192 return NULL;
193
194 GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
195
196 /* Number Format */
197 new_node->lcid = lcid;
198 new_node->dwFlags = dwFlags;
199 new_node->next = NULL;
200
201 GET_LOCALE_NUMBER(new_node->fmt.NumDigits, LOCALE_IDIGITS);
202 GET_LOCALE_NUMBER(new_node->fmt.LeadingZero, LOCALE_ILZERO);
203 GET_LOCALE_NUMBER(new_node->fmt.NegativeOrder, LOCALE_INEGNUMBER);
204
205 GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
206 if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
207 {
208 WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
209 new_node->fmt.Grouping);
210 new_node->fmt.Grouping = 0;
211 }
212
213 GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
214 GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
215
216 /* Currency Format */
217 new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
218 new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
219
220 GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
221
222 if (new_node->cyfmt.Grouping > 9)
223 {
224 WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
225 new_node->cyfmt.Grouping);
226 new_node->cyfmt.Grouping = 0;
227 }
228
229 GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
230 if (new_node->cyfmt.NegativeOrder > 15)
231 {
232 WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
233 new_node->cyfmt.NegativeOrder);
234 new_node->cyfmt.NegativeOrder = 0;
235 }
236 GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
237 if (new_node->cyfmt.PositiveOrder > 3)
238 {
239 WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
240 new_node->cyfmt.PositiveOrder);
241 new_node->cyfmt.PositiveOrder = 0;
242 }
243 GET_LOCALE_STRING(new_node->cyfmt.lpDecimalSep, LOCALE_SMONDECIMALSEP);
244 GET_LOCALE_STRING(new_node->cyfmt.lpThousandSep, LOCALE_SMONTHOUSANDSEP);
245 GET_LOCALE_STRING(new_node->cyfmt.lpCurrencySymbol, LOCALE_SCURRENCY);
246
247 /* Date/Time Format info, negative character, etc */
248 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
249 {
250 GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
251 }
252 new_node->szShortAM[0] = GetAM(new_node)[0]; new_node->szShortAM[1] = '\0';
253 new_node->szShortPM[0] = GetPM(new_node)[0]; new_node->szShortPM[1] = '\0';
254
255 /* Now add the computed format to the cache */
256 RtlEnterCriticalSection(&NLS_FormatsCS);
257
258 /* Search again: We may have raced to add the node */
259 node = NLS_CachedFormats;
260 while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
261 node = node->next;
262
263 if (!node)
264 {
265 node = NLS_CachedFormats = new_node; /* Empty list */
266 new_node = NULL;
267 }
268 else if (node->lcid != lcid || node->dwFlags != dwFlags)
269 {
270 node->next = new_node; /* Not in the list, add to end */
271 node = new_node;
272 new_node = NULL;
273 }
274
275 RtlLeaveCriticalSection(&NLS_FormatsCS);
276
277 if (new_node)
278 {
279 /* We raced and lost: The node was already added by another thread.
280 * node points to the currently cached node, so free new_node.
281 */
282 for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
283 HeapFree(GetProcessHeap(), 0, new_node->lppszStrings[i]);
284 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpDecimalSep);
285 HeapFree(GetProcessHeap(), 0, new_node->fmt.lpThousandSep);
286 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpDecimalSep);
287 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpThousandSep);
288 HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpCurrencySymbol);
289 HeapFree(GetProcessHeap(), 0, new_node);
290 }
291 }
292 return node;
293 }
294
295 /**************************************************************************
296 * NLS_IsUnicodeOnlyLcid <internal>
297 *
298 * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
299 */
300 BOOL NLS_IsUnicodeOnlyLcid(LCID lcid)
301 {
302 lcid = ConvertDefaultLocale(lcid);
303
304 switch (PRIMARYLANGID(lcid))
305 {
306 case LANG_ARMENIAN:
307 case LANG_DIVEHI:
308 case LANG_GEORGIAN:
309 case LANG_GUJARATI:
310 case LANG_HINDI:
311 case LANG_KANNADA:
312 case LANG_KONKANI:
313 case LANG_MARATHI:
314 case LANG_PUNJABI:
315 case LANG_SANSKRIT:
316 TRACE("lcid 0x%08x: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
317 return TRUE;
318 default:
319 return FALSE;
320 }
321 }
322
323 /*
324 * Formatting of dates, times, numbers and currencies.
325 */
326
327 #define IsLiteralMarker(p) (p == '\'')
328 #define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
329 #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't')
330
331 /* Only the following flags can be given if a date/time format is specified */
332 #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY)
333 #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
334 TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
335 TIME_NOTIMEMARKER)
336
337 /******************************************************************************
338 * NLS_GetDateTimeFormatW <internal>
339 *
340 * Performs the formatting for GetDateFormatW/GetTimeFormatW.
341 *
342 * FIXME
343 * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
344 * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
345 */
346 static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
347 const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
348 LPWSTR lpStr, INT cchOut)
349 {
350 const NLS_FORMAT_NODE *node;
351 SYSTEMTIME st;
352 INT cchWritten = 0;
353 INT lastFormatPos = 0;
354 BOOL bSkipping = FALSE; /* Skipping text around marker? */
355
356 /* Verify our arguments */
357 if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
358 {
359 NLS_GetDateTimeFormatW_InvalidParameter:
360 SetLastError(ERROR_INVALID_PARAMETER);
361 return 0;
362 }
363
364 if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
365 {
366 if (lpFormat &&
367 ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
368 (dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
369 {
370 NLS_GetDateTimeFormatW_InvalidFlags:
371 SetLastError(ERROR_INVALID_FLAGS);
372 return 0;
373 }
374
375 if (dwFlags & DATE_DATEVARSONLY)
376 {
377 if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
378 goto NLS_GetDateTimeFormatW_InvalidFlags;
379 else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
380 FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
381
382 switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
383 {
384 case 0:
385 break;
386 case DATE_SHORTDATE:
387 case DATE_LONGDATE:
388 case DATE_YEARMONTH:
389 if (lpFormat)
390 goto NLS_GetDateTimeFormatW_InvalidFlags;
391 break;
392 default:
393 goto NLS_GetDateTimeFormatW_InvalidFlags;
394 }
395 }
396 }
397
398 if (!lpFormat)
399 {
400 /* Use the appropriate default format */
401 if (dwFlags & DATE_DATEVARSONLY)
402 {
403 if (dwFlags & DATE_YEARMONTH)
404 lpFormat = GetYearMonth(node);
405 else if (dwFlags & DATE_LONGDATE)
406 lpFormat = GetLongDate(node);
407 else
408 lpFormat = GetShortDate(node);
409 }
410 else
411 lpFormat = GetTime(node);
412 }
413
414 if (!lpTime)
415 {
416 GetLocalTime(&st); /* Default to current time */
417 lpTime = &st;
418 }
419 else
420 {
421 if (dwFlags & DATE_DATEVARSONLY)
422 {
423 FILETIME ftTmp;
424
425 /* Verify the date and correct the D.O.W. if needed */
426 memset(&st, 0, sizeof(st));
427 st.wYear = lpTime->wYear;
428 st.wMonth = lpTime->wMonth;
429 st.wDay = lpTime->wDay;
430
431 if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
432 goto NLS_GetDateTimeFormatW_InvalidParameter;
433
434 FileTimeToSystemTime(&ftTmp, &st);
435 lpTime = &st;
436 }
437
438 if (dwFlags & TIME_TIMEVARSONLY)
439 {
440 /* Verify the time */
441 if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
442 goto NLS_GetDateTimeFormatW_InvalidParameter;
443 }
444 }
445
446 /* Format the output */
447 while (*lpFormat)
448 {
449 if (IsLiteralMarker(*lpFormat))
450 {
451 /* Start of a literal string */
452 lpFormat++;
453
454 /* Loop until the end of the literal marker or end of the string */
455 while (*lpFormat)
456 {
457 if (IsLiteralMarker(*lpFormat))
458 {
459 lpFormat++;
460 if (!IsLiteralMarker(*lpFormat))
461 break; /* Terminating literal marker */
462 }
463
464 if (!cchOut)
465 cchWritten++; /* Count size only */
466 else if (cchWritten >= cchOut)
467 goto NLS_GetDateTimeFormatW_Overrun;
468 else if (!bSkipping)
469 {
470 lpStr[cchWritten] = *lpFormat;
471 cchWritten++;
472 }
473 lpFormat++;
474 }
475 }
476 else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
477 (dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
478 {
479 char buffA[32];
480 WCHAR buff[32], fmtChar;
481 LPCWSTR szAdd = NULL;
482 DWORD dwVal = 0;
483 int count = 0, dwLen;
484
485 bSkipping = FALSE;
486
487 fmtChar = *lpFormat;
488 while (*lpFormat == fmtChar)
489 {
490 count++;
491 lpFormat++;
492 }
493 buff[0] = '\0';
494
495 switch(fmtChar)
496 {
497 case 'd':
498 if (count >= 4)
499 szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
500 else if (count == 3)
501 szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
502 else
503 {
504 dwVal = lpTime->wDay;
505 szAdd = buff;
506 }
507 break;
508
509 case 'M':
510 if (count >= 4)
511 szAdd = GetLongMonth(node, lpTime->wMonth - 1);
512 else if (count == 3)
513 szAdd = GetShortMonth(node, lpTime->wMonth - 1);
514 else
515 {
516 dwVal = lpTime->wMonth;
517 szAdd = buff;
518 }
519 break;
520
521 case 'y':
522 if (count >= 4)
523 {
524 count = 4;
525 dwVal = lpTime->wYear;
526 }
527 else
528 {
529 count = count > 2 ? 2 : count;
530 dwVal = lpTime->wYear % 100;
531 }
532 szAdd = buff;
533 break;
534
535 case 'g':
536 if (count == 2)
537 {
538 /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
539 * When it is fixed, this string should be cached in 'node'.
540 */
541 FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
542 buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
543 }
544 else
545 {
546 buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
547 }
548 szAdd = buff;
549 break;
550
551 case 'h':
552 if (!(dwFlags & TIME_FORCE24HOURFORMAT))
553 {
554 count = count > 2 ? 2 : count;
555 dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
556 szAdd = buff;
557 break;
558 }
559 /* .. fall through if we are forced to output in 24 hour format */
560
561 case 'H':
562 count = count > 2 ? 2 : count;
563 dwVal = lpTime->wHour;
564 szAdd = buff;
565 break;
566
567 case 'm':
568 if (dwFlags & TIME_NOMINUTESORSECONDS)
569 {
570 cchWritten = lastFormatPos; /* Skip */
571 bSkipping = TRUE;
572 }
573 else
574 {
575 count = count > 2 ? 2 : count;
576 dwVal = lpTime->wMinute;
577 szAdd = buff;
578 }
579 break;
580
581 case 's':
582 if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
583 {
584 cchWritten = lastFormatPos; /* Skip */
585 bSkipping = TRUE;
586 }
587 else
588 {
589 count = count > 2 ? 2 : count;
590 dwVal = lpTime->wSecond;
591 szAdd = buff;
592 }
593 break;
594
595 case 't':
596 if (dwFlags & TIME_NOTIMEMARKER)
597 {
598 cchWritten = lastFormatPos; /* Skip */
599 bSkipping = TRUE;
600 }
601 else
602 {
603 if (count == 1)
604 szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
605 else
606 szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
607 }
608 break;
609 }
610
611 if (szAdd == buff && buff[0] == '\0')
612 {
613 /* We have a numeric value to add */
614 sprintf(buffA, "%.*d", count, dwVal);
615 MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
616 }
617
618 dwLen = szAdd ? strlenW(szAdd) : 0;
619
620 if (cchOut && dwLen)
621 {
622 if (cchWritten + dwLen < cchOut)
623 memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
624 else
625 {
626 memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
627 goto NLS_GetDateTimeFormatW_Overrun;
628 }
629 }
630 cchWritten += dwLen;
631 lastFormatPos = cchWritten; /* Save position of last output format text */
632 }
633 else
634 {
635 /* Literal character */
636 if (!cchOut)
637 cchWritten++; /* Count size only */
638 else if (cchWritten >= cchOut)
639 goto NLS_GetDateTimeFormatW_Overrun;
640 else if (!bSkipping || *lpFormat == ' ')
641 {
642 lpStr[cchWritten] = *lpFormat;
643 cchWritten++;
644 }
645 lpFormat++;
646 }
647 }
648
649 /* Final string terminator and sanity check */
650 if (cchOut)
651 {
652 if (cchWritten >= cchOut)
653 goto NLS_GetDateTimeFormatW_Overrun;
654 else
655 lpStr[cchWritten] = '\0';
656 }
657 cchWritten++; /* Include terminating NUL */
658
659 TRACE("returning length=%d, ouput=%s\n", cchWritten, debugstr_w(lpStr));
660 return cchWritten;
661
662 NLS_GetDateTimeFormatW_Overrun:
663 TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
664 SetLastError(ERROR_INSUFFICIENT_BUFFER);
665 return 0;
666 }
667
668 /******************************************************************************
669 * NLS_GetDateTimeFormatA <internal>
670 *
671 * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
672 */
673 static INT NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
674 const SYSTEMTIME* lpTime,
675 LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
676 {
677 DWORD cp = CP_ACP;
678 WCHAR szFormat[128], szOut[128];
679 INT iRet;
680
681 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
682 debugstr_a(lpFormat), lpStr, cchOut);
683
684 if (NLS_IsUnicodeOnlyLcid(lcid))
685 {
686 GetDateTimeFormatA_InvalidParameter:
687 SetLastError(ERROR_INVALID_PARAMETER);
688 return 0;
689 }
690
691 if (!(dwFlags & LOCALE_USE_CP_ACP))
692 {
693 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
694 if (!node)
695 goto GetDateTimeFormatA_InvalidParameter;
696 cp = node->dwCodePage;
697 }
698
699 if (lpFormat)
700 MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
701
702 if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
703 cchOut = sizeof(szOut)/sizeof(WCHAR);
704
705 szOut[0] = '\0';
706
707 iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
708 lpStr ? szOut : NULL, cchOut);
709
710 if (lpStr)
711 {
712 if (szOut[0])
713 WideCharToMultiByte(cp, 0, szOut, iRet ? -1 : cchOut, lpStr, cchOut, 0, 0);
714 else if (cchOut && iRet)
715 *lpStr = '\0';
716 }
717 return iRet;
718 }
719
720 /******************************************************************************
721 * GetDateFormatA [KERNEL32.@]
722 *
723 * Format a date for a given locale.
724 *
725 * PARAMS
726 * lcid [I] Locale to format for
727 * dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
728 * lpTime [I] Date to format
729 * lpFormat [I] Format string, or NULL to use the system defaults
730 * lpDateStr [O] Destination for formatted string
731 * cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
732 *
733 * NOTES
734 * - If lpFormat is NULL, lpDateStr will be formatted according to the format
735 * details returned by GetLocaleInfoA() and modified by dwFlags.
736 * - lpFormat is a string of characters and formatting tokens. Any characters
737 * in the string are copied verbatim to lpDateStr, with tokens being replaced
738 * by the date values they represent.
739 * - The following tokens have special meanings in a date format string:
740 *| Token Meaning
741 *| ----- -------
742 *| d Single digit day of the month (no leading 0)
743 *| dd Double digit day of the month
744 *| ddd Short name for the day of the week
745 *| dddd Long name for the day of the week
746 *| M Single digit month of the year (no leading 0)
747 *| MM Double digit month of the year
748 *| MMM Short name for the month of the year
749 *| MMMM Long name for the month of the year
750 *| y Double digit year number (no leading 0)
751 *| yy Double digit year number
752 *| yyyy Four digit year number
753 *| gg Era string, for example 'AD'.
754 * - To output any literal character that could be misidentified as a token,
755 * enclose it in single quotes.
756 * - The Ascii version of this function fails if lcid is Unicode only.
757 *
758 * RETURNS
759 * Success: The number of character written to lpDateStr, or that would
760 * have been written, if cchOut is 0.
761 * Failure: 0. Use GetLastError() to determine the cause.
762 */
763 INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
764 LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
765 {
766 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
767 debugstr_a(lpFormat), lpDateStr, cchOut);
768
769 return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
770 lpFormat, lpDateStr, cchOut);
771 }
772
773
774 /******************************************************************************
775 * GetDateFormatW [KERNEL32.@]
776 *
777 * See GetDateFormatA.
778 */
779 INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
780 LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
781 {
782 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
783 debugstr_w(lpFormat), lpDateStr, cchOut);
784
785 return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
786 lpFormat, lpDateStr, cchOut);
787 }
788
789 /******************************************************************************
790 * GetTimeFormatA [KERNEL32.@]
791 *
792 * Format a time for a given locale.
793 *
794 * PARAMS
795 * lcid [I] Locale to format for
796 * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
797 * lpTime [I] Time to format
798 * lpFormat [I] Formatting overrides
799 * lpTimeStr [O] Destination for formatted string
800 * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
801 *
802 * NOTES
803 * - If lpFormat is NULL, lpszValue will be formatted according to the format
804 * details returned by GetLocaleInfoA() and modified by dwFlags.
805 * - lpFormat is a string of characters and formatting tokens. Any characters
806 * in the string are copied verbatim to lpTimeStr, with tokens being replaced
807 * by the time values they represent.
808 * - The following tokens have special meanings in a time format string:
809 *| Token Meaning
810 *| ----- -------
811 *| h Hours with no leading zero (12-hour clock)
812 *| hh Hours with full two digits (12-hour clock)
813 *| H Hours with no leading zero (24-hour clock)
814 *| HH Hours with full two digits (24-hour clock)
815 *| m Minutes with no leading zero
816 *| mm Minutes with full two digits
817 *| s Seconds with no leading zero
818 *| ss Seconds with full two digits
819 *| t Short time marker (e.g. "A" or "P")
820 *| tt Long time marker (e.g. "AM", "PM")
821 * - To output any literal character that could be misidentified as a token,
822 * enclose it in single quotes.
823 * - The Ascii version of this function fails if lcid is Unicode only.
824 *
825 * RETURNS
826 * Success: The number of character written to lpTimeStr, or that would
827 * have been written, if cchOut is 0.
828 * Failure: 0. Use GetLastError() to determine the cause.
829 */
830 INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
831 LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
832 {
833 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
834 debugstr_a(lpFormat), lpTimeStr, cchOut);
835
836 return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
837 lpFormat, lpTimeStr, cchOut);
838 }
839
840 /******************************************************************************
841 * GetTimeFormatW [KERNEL32.@]
842 *
843 * See GetTimeFormatA.
844 */
845 INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
846 LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
847 {
848 TRACE("(0x%04x,0x%08x,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
849 debugstr_w(lpFormat), lpTimeStr, cchOut);
850
851 return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
852 lpFormat, lpTimeStr, cchOut);
853 }
854
855 /**************************************************************************
856 * GetNumberFormatA (KERNEL32.@)
857 *
858 * Format a number string for a given locale.
859 *
860 * PARAMS
861 * lcid [I] Locale to format for
862 * dwFlags [I] LOCALE_ flags from "winnls.h"
863 * lpszValue [I] String to format
864 * lpFormat [I] Formatting overrides
865 * lpNumberStr [O] Destination for formatted string
866 * cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
867 *
868 * NOTES
869 * - lpszValue can contain only '' - '9', '-' and '.'.
870 * - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
871 * be formatted according to the format details returned by GetLocaleInfoA().
872 * - This function rounds the number string if the number of decimals exceeds the
873 * locales normal number of decimal places.
874 * - If cchOut is 0, this function does not write to lpNumberStr.
875 * - The Ascii version of this function fails if lcid is Unicode only.
876 *
877 * RETURNS
878 * Success: The number of character written to lpNumberStr, or that would
879 * have been written, if cchOut is 0.
880 * Failure: 0. Use GetLastError() to determine the cause.
881 */
882 INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
883 LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
884 LPSTR lpNumberStr, int cchOut)
885 {
886 DWORD cp = CP_ACP;
887 WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
888 NUMBERFMTW fmt;
889 const NUMBERFMTW *pfmt = NULL;
890 INT iRet;
891
892 TRACE("(0x%04x,0x%08x,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
893 lpFormat, lpNumberStr, cchOut);
894
895 if (NLS_IsUnicodeOnlyLcid(lcid))
896 {
897 GetNumberFormatA_InvalidParameter:
898 SetLastError(ERROR_INVALID_PARAMETER);
899 return 0;
900 }
901
902 if (!(dwFlags & LOCALE_USE_CP_ACP))
903 {
904 const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
905 if (!node)
906 goto GetNumberFormatA_InvalidParameter;
907 cp = node->dwCodePage;
908 }
909
910 if (lpFormat)
911 {
912 memcpy(&fmt, lpFormat, sizeof(fmt));
913 pfmt = &fmt;
914 if (lpFormat->lpDecimalSep)
915 {
916 MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
917 fmt.lpDecimalSep = szDec;