2 * National Language Support library
4 * Copyright 1995 Martin von Loewis
5 * Copyright 1998 David Lee Lambert
6 * Copyright 2000 Julio César Gázquez
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "wine/port.h"
37 #include "wine/unicode.h"
42 #include "wine/debug.h"
44 WINE_DEFAULT_DEBUG_CHANNEL(nls
);
47 /******************************************************************************
48 * ConvertDefaultLocale (KERNEL32.@)
50 LCID WINAPI
ConvertDefaultLocale (LCID lcid
)
52 { case LOCALE_SYSTEM_DEFAULT
:
53 return GetSystemDefaultLCID();
54 case LOCALE_USER_DEFAULT
:
55 return GetUserDefaultLCID();
57 return MAKELCID (LANG_NEUTRAL
, SUBLANG_NEUTRAL
);
59 return MAKELANGID( PRIMARYLANGID(lcid
), SUBLANG_NEUTRAL
);
63 /******************************************************************************
64 * IsValidLocale [KERNEL32.@]
66 BOOL WINAPI
IsValidLocale(LCID lcid
,DWORD flags
)
68 /* check if language is registered in the kernel32 resources */
69 if(!FindResourceExW(GetModuleHandleA("KERNEL32"), RT_STRINGW
, (LPCWSTR
)LOCALE_ILANGUAGE
, LOWORD(lcid
)))
75 static BOOL CALLBACK
EnumResourceLanguagesProcW(HMODULE hModule
, LPCWSTR type
,
76 LPCWSTR name
, WORD LangID
, LONG lParam
)
80 LOCALE_ENUMPROCW lpfnLocaleEnum
= (LOCALE_ENUMPROCW
)lParam
;
81 sprintf(bufA
, "%08x", (UINT
)LangID
);
82 MultiByteToWideChar(CP_ACP
, 0, bufA
, -1, bufW
, sizeof(bufW
)/sizeof(bufW
[0]));
83 return lpfnLocaleEnum(bufW
);
86 /******************************************************************************
87 * EnumSystemLocalesW [KERNEL32.@]
89 BOOL WINAPI
EnumSystemLocalesW( LOCALE_ENUMPROCW lpfnLocaleEnum
,
92 TRACE("(%p,%08lx)\n", lpfnLocaleEnum
,flags
);
94 EnumResourceLanguagesW(GetModuleHandleA("KERNEL32"), RT_STRINGW
,
95 (LPCWSTR
)LOCALE_ILANGUAGE
, EnumResourceLanguagesProcW
,
96 (LONG
)lpfnLocaleEnum
);
101 static BOOL CALLBACK
EnumResourceLanguagesProcA(HMODULE hModule
, LPCSTR type
,
102 LPCSTR name
, WORD LangID
, LONG lParam
)
105 LOCALE_ENUMPROCA lpfnLocaleEnum
= (LOCALE_ENUMPROCA
)lParam
;
106 sprintf(bufA
, "%08x", (UINT
)LangID
);
107 return lpfnLocaleEnum(bufA
);
110 /******************************************************************************
111 * EnumSystemLocalesA [KERNEL32.@]
113 BOOL WINAPI
EnumSystemLocalesA(LOCALE_ENUMPROCA lpfnLocaleEnum
,
116 TRACE("(%p,%08lx)\n", lpfnLocaleEnum
,flags
);
118 EnumResourceLanguagesA(GetModuleHandleA("KERNEL32"), RT_STRINGA
,
119 (LPCSTR
)LOCALE_ILANGUAGE
, EnumResourceLanguagesProcA
,
120 (LONG
)lpfnLocaleEnum
);
125 /***********************************************************************
126 * VerLanguageNameA [KERNEL32.@]
128 DWORD WINAPI
VerLanguageNameA( UINT wLang
, LPSTR szLang
, UINT nSize
)
133 return GetLocaleInfoA(MAKELCID(wLang
, SORT_DEFAULT
), LOCALE_SENGLANGUAGE
, szLang
, nSize
);
136 /***********************************************************************
137 * VerLanguageNameW [KERNEL32.@]
139 DWORD WINAPI
VerLanguageNameW( UINT wLang
, LPWSTR szLang
, UINT nSize
)
144 return GetLocaleInfoW(MAKELCID(wLang
, SORT_DEFAULT
), LOCALE_SENGLANGUAGE
, szLang
, nSize
);
148 static const unsigned char LCM_Unicode_LUT
[] = {
182 7 , 29, /* " - 34 */ /* " */
187 6 , 128, /* ' - 39 */
193 6 , 130, /* - - 45 */
197 12 , 33, /* 1 - 49 */
198 12 , 51, /* 2 - 50 */
199 12 , 70, /* 3 - 51 */
200 12 , 88, /* 4 - 52 */
201 12 , 106, /* 5 - 53 */
202 12 , 125, /* 6 - 54 */
203 12 , 144, /* 7 - 55 */
204 12 , 162, /* 8 - 56 */
205 12 , 180, /* 9 - 57 */
215 14 , 10, /* C - 67 */
216 14 , 26, /* D - 68 */
217 14 , 33, /* E - 69 */
218 14 , 35, /* F - 70 */
219 14 , 37, /* G - 71 */
220 14 , 44, /* H - 72 */
221 14 , 50, /* I - 73 */
222 14 , 53, /* J - 74 */
223 14 , 54, /* K - 75 */
224 14 , 72, /* L - 76 */
225 14 , 81, /* M - 77 */
226 14 , 112, /* N - 78 */
227 14 , 124, /* O - 79 */
228 14 , 126, /* P - 80 */
229 14 , 137, /* Q - 81 */
230 14 , 138, /* R - 82 */
231 14 , 145, /* S - 83 */
232 14 , 153, /* T - 84 */
233 14 , 159, /* U - 85 */
234 14 , 162, /* V - 86 */
235 14 , 164, /* W - 87 */
236 14 , 166, /* X - 88 */
237 14 , 167, /* Y - 89 */
238 14 , 169, /* Z - 90 */
247 14 , 10, /* c - 99 */
248 14 , 26, /* d - 100 */
249 14 , 33, /* e - 101 */
250 14 , 35, /* f - 102 */
251 14 , 37, /* g - 103 */
252 14 , 44, /* h - 104 */
253 14 , 50, /* i - 105 */
254 14 , 53, /* j - 106 */
255 14 , 54, /* k - 107 */
256 14 , 72, /* l - 108 */
257 14 , 81, /* m - 109 */
258 14 , 112, /* n - 110 */
259 14 , 124, /* o - 111 */
260 14 , 126, /* p - 112 */
261 14 , 137, /* q - 113 */
262 14 , 138, /* r - 114 */
263 14 , 145, /* s - 115 */
264 14 , 153, /* t - 116 */
265 14 , 159, /* u - 117 */
266 14 , 162, /* v - 118 */
267 14 , 164, /* w - 119 */
268 14 , 166, /* x - 120 */
269 14 , 167, /* y - 121 */
270 14 , 169, /* z - 122 */
271 7 , 74, /* { - 123 */
272 7 , 76, /* | - 124 */
273 7 , 78, /* } - 125 */
274 7 , 80, /* ~ - 126 */
275 6 , 29, /* \x7f - 127 */
276 6 , 30, /* € - 128 */
277 6 , 31, /* � - 129 */
278 7 , 123, /* ‚ - 130 */
279 14 , 35, /* ƒ - 131 */
280 7 , 127, /* „ - 132 */
281 10 , 21, /* … - 133 */
282 10 , 15, /* † - 134 */
283 10 , 16, /* ‡ - 135 */
284 7 , 67, /* ˆ - 136 */
285 10 , 22, /* ‰ - 137 */
286 14 , 145, /* Š - 138 */
287 7 , 136, /* ‹ - 139 */
288 14 + 16 , 124, /* Œ - 140 */
289 6 , 43, /* � - 141 */
290 6 , 44, /* Ž - 142 */
291 6 , 45, /* � - 143 */
292 6 , 46, /* � - 144 */
293 7 , 121, /* ‘ - 145 */
294 7 , 122, /* ’ - 146 */
295 7 , 125, /* “ - 147 */
296 7 , 126, /* ” - 148 */
297 10 , 17, /* • - 149 */
298 6 , 137, /* – - 150 */
299 6 , 139, /* — - 151 */
300 7 , 93, /* ˜ - 152 */
301 14 , 156, /* ™ - 153 */
302 14 , 145, /* š - 154 */
303 7 , 137, /* › - 155 */
304 14 + 16 , 124, /* œ - 156 */
305 6 , 59, /* � - 157 */
306 6 , 60, /* ž - 158 */
307 14 , 167, /* Ÿ - 159 */
309 7 , 81, /* ¡ - 161 */
310 10 , 2, /* ¢ - 162 */
311 10 , 3, /* £ - 163 */
312 10 , 4, /* ¤ - 164 */
313 10 , 5, /* ¥ - 165 */
314 7 , 82, /* ¦ - 166 */
315 10 , 6, /* § - 167 */
316 7 , 83, /* ¨ - 168 */
317 10 , 7, /* © - 169 */
318 14 , 2, /* ª - 170 */
319 8 , 24, /* « - 171 */
320 10 , 8, /* ¬ - 172 */
321 6 , 131, /* - 173 */
322 10 , 9, /* ® - 174 */
323 7 , 84, /* ¯ - 175 */
324 10 , 10, /* ° - 176 */
325 8 , 23, /* ± - 177 */
326 12 , 51, /* ² - 178 */
327 12 , 70, /* ³ - 179 */
328 7 , 85, /* ´ - 180 */
329 10 , 11, /* µ - 181 */
330 10 , 12, /* ¶ - 182 */
331 10 , 13, /* · - 183 */
332 7 , 86, /* ¸ - 184 */
333 12 , 33, /* ¹ - 185 */
334 14 , 124, /* º - 186 */
335 8 , 26, /* » - 187 */
336 12 , 21, /* ¼ - 188 */
337 12 , 25, /* ½ - 189 */
338 12 , 29, /* ¾ - 190 */
339 7 , 87, /* ¿ - 191 */
340 14 , 2, /* À - 192 */
341 14 , 2, /* Á - 193 */
342 14 , 2, /* Â - 194 */
343 14 , 2, /* Ã - 195 */
344 14 , 2, /* Ä - 196 */
345 14 , 2, /* Å - 197 */
346 14 + 16 , 2, /* Æ - 198 */
347 14 , 10, /* Ç - 199 */
348 14 , 33, /* È - 200 */
349 14 , 33, /* É - 201 */
350 14 , 33, /* Ê - 202 */
351 14 , 33, /* Ë - 203 */
352 14 , 50, /* Ì - 204 */
353 14 , 50, /* Í - 205 */
354 14 , 50, /* Î - 206 */
355 14 , 50, /* Ï - 207 */
356 14 , 26, /* Ð - 208 */
357 14 , 112, /* Ñ - 209 */
358 14 , 124, /* Ò - 210 */
359 14 , 124, /* Ó - 211 */
360 14 , 124, /* Ô - 212 */
361 14 , 124, /* Õ - 213 */
362 14 , 124, /* Ö - 214 */
363 8 , 28, /* × - 215 */
364 14 , 124, /* Ø - 216 */
365 14 , 159, /* Ù - 217 */
366 14 , 159, /* Ú - 218 */
367 14 , 159, /* Û - 219 */
368 14 , 159, /* Ü - 220 */
369 14 , 167, /* Ý - 221 */
370 14 + 32 , 153, /* Þ - 222 */
371 14 + 48 , 145, /* ß - 223 */
372 14 , 2, /* à - 224 */
373 14 , 2, /* á - 225 */
374 14 , 2, /* â - 226 */
375 14 , 2, /* ã - 227 */
376 14 , 2, /* ä - 228 */
377 14 , 2, /* å - 229 */
378 14 + 16 , 2, /* æ - 230 */
379 14 , 10, /* ç - 231 */
380 14 , 33, /* è - 232 */
381 14 , 33, /* é - 233 */
382 14 , 33, /* ê - 234 */
383 14 , 33, /* ë - 235 */
384 14 , 50, /* ì - 236 */
385 14 , 50, /* í - 237 */
386 14 , 50, /* î - 238 */
387 14 , 50, /* ï - 239 */
388 14 , 26, /* ð - 240 */
389 14 , 112, /* ñ - 241 */
390 14 , 124, /* ò - 242 */
391 14 , 124, /* ó - 243 */
392 14 , 124, /* ô - 244 */
393 14 , 124, /* õ - 245 */
394 14 , 124, /* ö - 246 */
395 8 , 29, /* ÷ - 247 */
396 14 , 124, /* ø - 248 */
397 14 , 159, /* ù - 249 */
398 14 , 159, /* ú - 250 */
399 14 , 159, /* û - 251 */
400 14 , 159, /* ü - 252 */
401 14 , 167, /* ý - 253 */
402 14 + 32 , 153, /* þ - 254 */
403 14 , 167 /* ÿ - 255 */ };
405 static const unsigned char LCM_Unicode_LUT_2
[] = { 33, 44, 145 };
407 #define LCM_Diacritic_Start 131
409 static const unsigned char LCM_Diacritic_LUT
[] = {
537 /******************************************************************************
538 * OLE2NLS_isPunctuation [INTERNAL]
540 static int OLE2NLS_isPunctuation(unsigned char c
)
542 /* "punctuation character" in this context is a character which is
543 considered "less important" during word sort comparison.
544 See LCMapString implementation for the precise definition
545 of "less important". */
547 return (LCM_Unicode_LUT
[-2+2*c
]==6);
550 /******************************************************************************
551 * OLE2NLS_isNonSpacing [INTERNAL]
553 static int OLE2NLS_isNonSpacing(unsigned char c
)
555 /* This function is used by LCMapStringA. Characters
556 for which it returns true are ignored when mapping a
557 string with NORM_IGNORENONSPACE */
558 return ((c
==136) || (c
==170) || (c
==186));
561 /******************************************************************************
562 * OLE2NLS_isSymbol [INTERNAL]
563 * FIXME: handle current locale
565 static int OLE2NLS_isSymbol(unsigned char c
)
567 /* This function is used by LCMapStringA. Characters
568 for which it returns true are ignored when mapping a
569 string with NORM_IGNORESYMBOLS */
570 return ( (c
!=0) && !(isalpha(c
) || isdigit(c
)) );
573 /******************************************************************************
574 * identity [Internal]
576 static int identity(int c
)
581 /*************************************************************************
582 * LCMapStringA [KERNEL32.@]
584 * Convert a string, or generate a sort key from it.
586 * If (mapflags & LCMAP_SORTKEY), the function will generate
587 * a sort key for the source string. Else, it will convert it
588 * accordingly to the flags LCMAP_UPPERCASE, LCMAP_LOWERCASE,...
592 * Success : length of the result string.
595 * If called with scrlen = -1, the function will compute the length
596 * of the 0-terminated string strsrc by itself.
598 * If called with dstlen = 0, returns the buffer length that
601 * NORM_IGNOREWIDTH means to compare ASCII and wide characters
602 * as if they are equal.
603 * In the only code page implemented so far, there may not be
604 * wide characters in strings passed to LCMapStringA,
605 * so there is nothing to be done for this flag.
607 INT WINAPI
LCMapStringA(
608 LCID lcid
, /* [in] locale identifier created with MAKELCID;
609 LOCALE_SYSTEM_DEFAULT and LOCALE_USER_DEFAULT are
610 predefined values. */
611 DWORD mapflags
, /* [in] flags */
612 LPCSTR srcstr
, /* [in] source buffer */
613 INT srclen
, /* [in] source length */
614 LPSTR dststr
, /* [out] destination buffer */
615 INT dstlen
) /* [in] destination buffer length */
619 TRACE("(0x%04lx,0x%08lx,%s,%d,%p,%d)\n",
620 lcid
,mapflags
,debugstr_an(srcstr
,srclen
),srclen
,dststr
,dstlen
);
622 if ( ((dstlen
!=0) && (dststr
==NULL
)) || (srcstr
==NULL
) )
624 ERR("(src=%s,dest=%s): Invalid NULL string\n",
625 debugstr_an(srcstr
,srclen
), dststr
);
626 SetLastError(ERROR_INVALID_PARAMETER
);
630 srclen
= strlen(srcstr
) + 1 ; /* (include final '\0') */
632 #define LCMAPSTRINGA_SUPPORTED_FLAGS (LCMAP_UPPERCASE | \
636 NORM_IGNORENONSPACE | \
640 /* FIXME: as long as we don't support Katakana nor Hiragana
641 * characters, we can support NORM_IGNOREKANATYPE
643 if (mapflags
& ~LCMAPSTRINGA_SUPPORTED_FLAGS
)
645 FIXME("(0x%04lx,0x%08lx,%p,%d,%p,%d): "
646 "unimplemented flags: 0x%08lx\n",
653 mapflags
& ~LCMAPSTRINGA_SUPPORTED_FLAGS
657 if ( !(mapflags
& LCMAP_SORTKEY
) )
660 int (*f
)(int) = identity
;
661 int flag_ignorenonspace
= mapflags
& NORM_IGNORENONSPACE
;
662 int flag_ignoresymbols
= mapflags
& NORM_IGNORESYMBOLS
;
664 if (flag_ignorenonspace
|| flag_ignoresymbols
)
666 /* For some values of mapflags, the length of the resulting
667 string is not known at this point. Windows does map the string
668 and does not SetLastError ERROR_INSUFFICIENT_BUFFER in
672 /* Compute required length */
673 for (i
=j
=0; i
< srclen
; i
++)
675 if ( !(flag_ignorenonspace
&& OLE2NLS_isNonSpacing(srcstr
[i
]))
676 && !(flag_ignoresymbols
&& OLE2NLS_isSymbol(srcstr
[i
])) )
688 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
692 if (mapflags
& LCMAP_UPPERCASE
)
694 else if (mapflags
& LCMAP_LOWERCASE
)
696 /* FIXME: NORM_IGNORENONSPACE requires another conversion */
697 for (i
=j
=0; (i
<srclen
) && (j
<dstlen
) ; i
++)
699 if ( !(flag_ignorenonspace
&& OLE2NLS_isNonSpacing(srcstr
[i
]))
700 && !(flag_ignoresymbols
&& OLE2NLS_isSymbol(srcstr
[i
])) )
702 dststr
[j
] = (CHAR
) f(srcstr
[i
]);
709 /* FIXME: This function completely ignores the "lcid" parameter. */
710 /* else ... (mapflags & LCMAP_SORTKEY) */
715 int delayed_punctuation_len
=0;
716 char *case_component
;
717 char *diacritic_component
;
718 char *delayed_punctuation_component
;
720 int flag_stringsort
= mapflags
& SORT_STRINGSORT
;
722 /* compute how much room we will need */
723 for (i
=0;i
<srclen
;i
++)
726 unsigned char source_char
= srcstr
[i
];
727 if (source_char
!='\0')
729 if (flag_stringsort
|| !OLE2NLS_isPunctuation(source_char
))
732 if ( LCM_Unicode_LUT
[-2+2*source_char
] & ~15 )
733 unicode_len
++; /* double letter */
737 delayed_punctuation_len
++;
741 if (isupper(source_char
))
742 case_len
=unicode_len
;
744 ofs
= source_char
- LCM_Diacritic_Start
;
745 if ((ofs
>=0) && (LCM_Diacritic_LUT
[ofs
]!=2))
746 diacritic_len
=unicode_len
;
749 if (mapflags
& NORM_IGNORECASE
)
751 if (mapflags
& NORM_IGNORENONSPACE
)
754 room
= 2 * unicode_len
/* "unicode" component */
755 + diacritic_len
/* "diacritic" component */
756 + case_len
/* "case" component */
757 + 4 * delayed_punctuation_len
/* punctuation in word sort mode */
758 + 4 /* four '\1' separators */
759 + 1 ; /* terminal '\0' */
762 else if (dstlen
<room
)
764 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
768 /*FIXME the Pointercheck should not be nessesary */
769 if (IsBadWritePtr (dststr
,room
))
770 { ERR("bad destination buffer (dststr) : %p,%d\n",dststr
,dstlen
);
771 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
775 /* locate each component, write separators */
776 diacritic_component
= dststr
+ 2*unicode_len
;
777 *diacritic_component
++ = '\1';
778 case_component
= diacritic_component
+ diacritic_len
;
779 *case_component
++ = '\1';
780 delayed_punctuation_component
= case_component
+ case_len
;
781 *delayed_punctuation_component
++ = '\1';
782 *delayed_punctuation_component
++ = '\1';
784 /* read source string char by char, write
785 corresponding weight in each component. */
786 for (i
=0,count
=0;i
<srclen
;i
++)
788 unsigned char source_char
=srcstr
[i
];
789 if (source_char
!='\0')
792 type
= LCM_Unicode_LUT
[-2+2*source_char
];
793 longcode
= type
>> 4;
795 if (!flag_stringsort
&& OLE2NLS_isPunctuation(source_char
))
797 WORD encrypted_location
= (1<<15) + 7 + 4*count
;
798 *delayed_punctuation_component
++ = (unsigned char) (encrypted_location
>>8);
799 *delayed_punctuation_component
++ = (unsigned char) (encrypted_location
&255);
800 /* big-endian is used here because it lets string comparison be
801 compatible with numerical comparison */
803 *delayed_punctuation_component
++ = type
;
804 *delayed_punctuation_component
++ = LCM_Unicode_LUT
[-1+2*source_char
];
805 /* assumption : a punctuation character is never a
806 double or accented letter */
810 dststr
[2*count
] = type
;
811 dststr
[2*count
+1] = LCM_Unicode_LUT
[-1+2*source_char
];
815 case_component
[count
] = ( isupper(source_char
) ? 18 : 2 ) ;
816 if (count
<diacritic_len
)
817 diacritic_component
[count
] = 2; /* assumption: a double letter
821 dststr
[2*count
] = type
;
822 dststr
[2*count
+1] = *(LCM_Unicode_LUT_2
- 1 + longcode
);
823 /* 16 in the first column of LCM_Unicode_LUT --> longcode = 1
824 32 in the first column of LCM_Unicode_LUT --> longcode = 2
825 48 in the first column of LCM_Unicode_LUT --> longcode = 3 */
829 case_component
[count
] = ( isupper(source_char
) ? 18 : 2 ) ;
830 if (count
<diacritic_len
)
832 int ofs
= source_char
- LCM_Diacritic_Start
;
833 diacritic_component
[count
] = (ofs
>=0 ? LCM_Diacritic_LUT
[ofs
] : 2);
839 dststr
[room
-1] = '\0';
844 /*************************************************************************
845 * LCMapStringW [KERNEL32.@]
847 * Convert a string, or generate a sort key from it.
851 * See LCMapStringA for documentation
853 INT WINAPI
LCMapStringW(
854 LCID lcid
,DWORD mapflags
,LPCWSTR srcstr
,INT srclen
,LPWSTR dststr
,
859 TRACE("(0x%04lx,0x%08lx,%p,%d,%p,%d)\n",
860 lcid
, mapflags
, srcstr
, srclen
, dststr
, dstlen
);
862 if ( ((dstlen
!=0) && (dststr
==NULL
)) || (srcstr
==NULL
) )
864 ERR("(src=%p,dst=%p): Invalid NULL string\n", srcstr
, dststr
);
865 SetLastError(ERROR_INVALID_PARAMETER
);
869 srclen
= strlenW(srcstr
)+1;
871 /* FIXME: Both this function and it's companion LCMapStringA()
872 * completely ignore the "lcid" parameter. In place of the "lcid"
873 * parameter the application must set the "LC_COLLATE" or "LC_ALL"
874 * environment variable prior to invoking this function. */
875 if (mapflags
& LCMAP_SORTKEY
)
877 /* Possible values of LC_COLLATE. */
878 char *lc_collate_default
= 0; /* value prior to this function */
879 char *lc_collate_env
= 0; /* value retrieved from the environment */
881 /* General purpose index into strings of any type. */
884 /* Lengths of various strings where the length is measured in
885 * wide characters for wide character strings and in bytes for
886 * native strings. The lengths include the NULL terminator. */
887 size_t returned_len
= 0;
888 size_t src_native_len
= 0;
889 size_t dst_native_len
= 0;
890 size_t dststr_libc_len
= 0;
892 /* Native (character set determined by locale) versions of the
893 * strings source and destination strings. */
894 LPSTR src_native
= 0;
895 LPSTR dst_native
= 0;
897 /* Version of the source and destination strings using the
898 * "wchar_t" Unicode data type needed by various libc functions. */
899 wchar_t *srcstr_libc
= 0;
900 wchar_t *dststr_libc
= 0;
902 if(!(srcstr_libc
= (wchar_t *)HeapAlloc(GetProcessHeap(), 0,
903 srclen
* sizeof(wchar_t))))
905 ERR("Unable to allocate %d bytes for srcstr_libc\n",
906 srclen
* sizeof(wchar_t));
907 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
911 /* Convert source string to a libc Unicode string. */
912 for(str_idx
= 0; str_idx
< srclen
; str_idx
++)
914 srcstr_libc
[str_idx
] = srcstr
[str_idx
];
917 /* src_native should contain at most 3 bytes for each
918 * multibyte characters in the original srcstr string. */
919 src_native_len
= 3 * srclen
;
920 if(!(src_native
= (LPSTR
)HeapAlloc(GetProcessHeap(), 0,
923 ERR("Unable to allocate %d bytes for src_native\n", src_native_len
);
924 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
925 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
929 /* FIXME: Prior to to setting the LC_COLLATE locale category the
930 * current value is backed up so it can be restored after the
931 * last LC_COLLATE sensitive function returns.
933 * Even though the locale is adjusted for a minimum amount of
934 * time a race condition exists where other threads may be
935 * affected if they invoke LC_COLLATE sensitive functions. One
936 * possible solution is to wrap all LC_COLLATE sensitive Wine
937 * functions, like LCMapStringW(), in a mutex.
939 * Another enhancement to the following would be to set the
940 * LC_COLLATE locale category as a function of the "lcid"
941 * parameter instead of the "LC_COLLATE" environment variable. */
942 if(!(lc_collate_default
= setlocale(LC_COLLATE
, NULL
)))
944 ERR("Unable to query the LC_COLLATE catagory\n");
945 SetLastError(ERROR_INVALID_PARAMETER
);
946 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
947 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
951 if(!(lc_collate_env
= setlocale(LC_COLLATE
, "")))
953 ERR("Unable to inherit the LC_COLLATE locale category from the "
954 "environment. The \"LC_COLLATE\" environment variable is "
955 "\"%s\".\n", getenv("LC_COLLATE") ?
956 getenv("LC_COLLATE") : "<unset>");
957 SetLastError(ERROR_INVALID_PARAMETER
);
958 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
959 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
963 TRACE("lc_collate_default = %s\n", lc_collate_default
);
964 TRACE("lc_collate_env = %s\n", lc_collate_env
);
966 /* Convert the libc Unicode string to a native multibyte character
968 returned_len
= wcstombs(src_native
, srcstr_libc
, src_native_len
) + 1;
969 if(returned_len
== 0)
971 ERR("wcstombs failed. The string specified (%s) may contain an invalid character.\n",
973 SetLastError(ERROR_INVALID_PARAMETER
);
974 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
975 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
976 setlocale(LC_COLLATE
, lc_collate_default
);
979 else if(returned_len
> src_native_len
)
981 src_native
[src_native_len
- 1] = 0;
982 ERR("wcstombs returned a string (%s) that was longer (%d bytes) "
983 "than expected (%d bytes).\n", src_native
, returned_len
,
986 /* Since this is an internal error I'm not sure what the correct
988 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
990 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
991 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
992 setlocale(LC_COLLATE
, lc_collate_default
);
995 src_native_len
= returned_len
;
997 TRACE("src_native = %s src_native_len = %d\n",
998 src_native
, src_native_len
);
1000 /* dst_native seems to contain at most 4 bytes for each byte in
1001 * the original src_native string. Change if need be since this
1002 * isn't documented by the strxfrm() man page. */
1003 dst_native_len
= 4 * src_native_len
;
1004 if(!(dst_native
= (LPSTR
)HeapAlloc(GetProcessHeap(), 0, dst_native_len
)))
1006 ERR("Unable to allocate %d bytes for dst_native\n", dst_native_len
);
1007 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1008 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1009 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1010 setlocale(LC_COLLATE
, lc_collate_default
);
1014 /* The actual translation is done by the following call to
1015 * strxfrm(). The surrounding code could have been simplified
1016 * by calling wcsxfrm() instead except that wcsxfrm() is not
1017 * available on older Linux systems (RedHat 4.1 with
1020 * Also, it is possible that the translation could be done by
1021 * various tables as it is done in LCMapStringA(). However, I'm
1022 * not sure what those tables are. */
1023 returned_len
= strxfrm(dst_native
, src_native
, dst_native_len
) + 1;
1025 if(returned_len
> dst_native_len
)
1027 dst_native
[dst_native_len
- 1] = 0;
1028 ERR("strxfrm returned a string (%s) that was longer (%d bytes) "
1029 "than expected (%d bytes).\n", dst_native
, returned_len
,
1032 /* Since this is an internal error I'm not sure what the correct
1034 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1036 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1037 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1038 if(dst_native
) HeapFree(GetProcessHeap(), 0, dst_native
);
1039 setlocale(LC_COLLATE
, lc_collate_default
);
1042 dst_native_len
= returned_len
;
1044 TRACE("dst_native = %s dst_native_len = %d\n",
1045 dst_native
, dst_native_len
);
1047 dststr_libc_len
= dst_native_len
;
1048 if(!(dststr_libc
= (wchar_t *)HeapAlloc(GetProcessHeap(), 0,
1049 dststr_libc_len
* sizeof(wchar_t))))
1051 ERR("Unable to allocate %d bytes for dststr_libc\n",
1052 dststr_libc_len
* sizeof(wchar_t));
1053 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1054 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1055 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1056 if(dst_native
) HeapFree(GetProcessHeap(), 0, dst_native
);
1057 setlocale(LC_COLLATE
, lc_collate_default
);
1061 /* Convert the native multibyte string to a libc Unicode string. */
1062 returned_len
= mbstowcs(dststr_libc
, dst_native
, dst_native_len
) + 1;
1064 /* Restore LC_COLLATE now that the last LC_COLLATE sensitive
1065 * function has returned. */
1066 setlocale(LC_COLLATE
, lc_collate_default
);
1068 if(returned_len
== 0)
1070 ERR("mbstowcs failed. The native version of the translated string "
1071 "(%s) may contain an invalid character.\n", dst_native
);
1072 SetLastError(ERROR_INVALID_PARAMETER
);
1073 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1074 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1075 if(dst_native
) HeapFree(GetProcessHeap(), 0, dst_native
);
1076 if(dststr_libc
) HeapFree(GetProcessHeap(), 0, dststr_libc
);
1081 if(returned_len
> dstlen
)
1083 ERR("mbstowcs returned a string that was longer (%d chars) "
1084 "than the buffer provided (%d chars).\n", returned_len
,
1086 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1087 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1088 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1089 if(dst_native
) HeapFree(GetProcessHeap(), 0, dst_native
);
1090 if(dststr_libc
) HeapFree(GetProcessHeap(), 0, dststr_libc
);
1093 dstlen
= returned_len
;
1095 /* Convert a libc Unicode string to the destination string. */
1096 for(str_idx
= 0; str_idx
< dstlen
; str_idx
++)
1098 dststr
[str_idx
] = dststr_libc
[str_idx
];
1100 TRACE("1st 4 int sized chunks of dststr = %x %x %x %x\n",
1101 *(((int *)dststr
) + 0),
1102 *(((int *)dststr
) + 1),
1103 *(((int *)dststr
) + 2),
1104 *(((int *)dststr
) + 3));
1108 dstlen
= returned_len
;
1110 TRACE("dstlen (return) = %d\n", dstlen
);
1111 if(srcstr_libc
) HeapFree(GetProcessHeap(), 0, srcstr_libc
);
1112 if(src_native
) HeapFree(GetProcessHeap(), 0, src_native
);
1113 if(dst_native
) HeapFree(GetProcessHeap(), 0, dst_native
);
1114 if(dststr_libc
) HeapFree(GetProcessHeap(), 0, dststr_libc
);
1119 int (*f
)(int)=identity
;
1125 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1129 if (mapflags
& LCMAP_UPPERCASE
)
1131 else if (mapflags
& LCMAP_LOWERCASE
)
1133 for (i
=0; i
< srclen
; i
++)
1134 dststr
[i
] = (WCHAR
) f(srcstr
[i
]);
1140 /***********************************************************************
1141 * OLE2NLS_EstimateMappingLength
1143 * Estimates the number of characters required to hold the string
1144 * computed by LCMapStringA.
1146 * The size is always over-estimated, with a fixed limit on the
1147 * amount of estimation error.
1149 * Note that len == -1 is not permitted.
1151 static inline int OLE2NLS_EstimateMappingLength(LCID lcid
, DWORD dwMapFlags
,
1152 LPCSTR str
, DWORD len
)
1154 /* Estimate only for small strings to keep the estimation error from
1155 * becoming too large. */
1156 if (len
< 128) return len
* 8 + 5;
1157 else return LCMapStringA(lcid
, dwMapFlags
, str
, len
, NULL
, 0);
1160 /******************************************************************************
1161 * CompareStringA [KERNEL32.@]
1162 * Compares two strings using locale
1166 * success: CSTR_LESS_THAN, CSTR_EQUAL, CSTR_GREATER_THAN
1171 * Defaults to a word sort, but uses a string sort if
1172 * SORT_STRINGSORT is set.
1173 * Calls SetLastError for ERROR_INVALID_FLAGS, ERROR_INVALID_PARAMETER.
1177 * This implementation ignores the locale
1181 * Quite inefficient.
1183 int WINAPI
CompareStringA(
1184 LCID lcid
, /* [in] locale ID */
1185 DWORD fdwStyle
, /* [in] comparison-style options */
1186 LPCSTR s1
, /* [in] first string */
1187 int l1
, /* [in] length of first string */
1188 LPCSTR s2
, /* [in] second string */
1189 int l2
) /* [in] length of second string */
1191 int mapstring_flags
;
1195 TRACE("%s and %s\n",
1196 debugstr_an (s1
,l1
), debugstr_an (s2
,l2
));
1198 if ( (s1
==NULL
) || (s2
==NULL
) )
1200 ERR("(s1=%s,s2=%s): Invalid NULL string\n",
1201 debugstr_an(s1
,l1
), debugstr_an(s2
,l2
));
1202 SetLastError(ERROR_INVALID_PARAMETER
);
1206 if(fdwStyle
& NORM_IGNORESYMBOLS
)
1207 FIXME("IGNORESYMBOLS not supported\n");
1209 if (l1
== -1) l1
= strlen(s1
);
1210 if (l2
== -1) l2
= strlen(s2
);
1212 mapstring_flags
= LCMAP_SORTKEY
| fdwStyle
;
1213 len1
= OLE2NLS_EstimateMappingLength(lcid
, mapstring_flags
, s1
, l1
);
1214 len2
= OLE2NLS_EstimateMappingLength(lcid
, mapstring_flags
, s2
, l2
);
1216 if ((len1
==0)||(len2
==0))
1217 return 0; /* something wrong happened */
1219 sk1
= (LPSTR
)HeapAlloc(GetProcessHeap(), 0, len1
+ len2
);
1221 if ( (!LCMapStringA(lcid
,mapstring_flags
,s1
,l1
,sk1
,len1
))
1222 || (!LCMapStringA(lcid
,mapstring_flags
,s2
,l2
,sk2
,len2
)) )
1224 ERR("Bug in LCmapStringA.\n");
1229 /* strcmp doesn't necessarily return -1, 0, or 1 */
1230 result
= strcmp(sk1
,sk2
);
1232 HeapFree(GetProcessHeap(),0,sk1
);
1239 /* must be greater, if we reach this point */
1243 /******************************************************************************
1244 * CompareStringW [KERNEL32.@]
1245 * This implementation ignores the locale
1246 * FIXME : Does only string sort. Should
1247 * be reimplemented the same way as CompareStringA.
1249 int WINAPI
CompareStringW(LCID lcid
, DWORD fdwStyle
,
1250 LPCWSTR s1
, int l1
, LPCWSTR s2
, int l2
)
1253 if(fdwStyle
& NORM_IGNORENONSPACE
)
1254 FIXME("IGNORENONSPACE not supported\n");
1255 if(fdwStyle
& NORM_IGNORESYMBOLS
)
1256 FIXME("IGNORESYMBOLS not supported\n");
1258 if(s1
==NULL
|| s2
==NULL
)
1260 SetLastError(ERROR_INVALID_PARAMETER
);
1263 /* Is strcmp defaulting to string sort or to word sort?? */
1264 /* FIXME: Handle NORM_STRINGSORT */
1265 l1
= (l1
==-1)?strlenW(s1
):l1
;
1266 l2
= (l2
==-1)?strlenW(s2
):l2
;
1267 len
= l1
<l2
? l1
:l2
;
1268 ret
= (fdwStyle
& NORM_IGNORECASE
) ? strncmpiW(s1
,s2
,len
) : strncmpW(s1
,s2
,len
);
1269 /* not equal, return 1 or 3 */
1271 /* need to translate result */
1272 return ((int)ret
< 0) ? 1 : 3;
1274 /* same len, return 2 */
1275 if(l1
==l2
) return 2;
1276 /* the longer one is lexically greater */
1277 return (l1
<l2
)? 1 : 3;
1280 /***********************************************************************
1281 * lstrcmp (KERNEL32.@)
1282 * lstrcmpA (KERNEL32.@)
1284 INT WINAPI
lstrcmpA( LPCSTR str1
, LPCSTR str2
)
1286 return CompareStringA(LOCALE_SYSTEM_DEFAULT
,0,str1
,-1,str2
,-1) - 2 ;
1290 /***********************************************************************
1291 * lstrcmpW (KERNEL32.@)
1292 * FIXME : should call CompareStringW, when it is implemented.
1293 * This implementation is not "word sort", as it should.
1295 INT WINAPI
lstrcmpW( LPCWSTR str1
, LPCWSTR str2
)
1297 TRACE("%s and %s\n",
1298 debugstr_w (str1
), debugstr_w (str2
));
1299 if (!str1
|| !str2
) {
1300 SetLastError(ERROR_INVALID_PARAMETER
);
1303 while (*str1
&& (*str1
== *str2
)) { str1
++; str2
++; }
1304 return (INT
)(*str1
- *str2
);
1308 /***********************************************************************
1309 * lstrcmpi (KERNEL32.@)
1310 * lstrcmpiA (KERNEL32.@)
1312 INT WINAPI
lstrcmpiA( LPCSTR str1
, LPCSTR str2
)
1313 { TRACE("strcmpi %s and %s\n",
1314 debugstr_a (str1
), debugstr_a (str2
));
1315 return CompareStringA(LOCALE_SYSTEM_DEFAULT
,NORM_IGNORECASE
,str1
,-1,str2
,-1)-2;
1319 /***********************************************************************
1320 * lstrcmpiW (KERNEL32.@)
1322 INT WINAPI
lstrcmpiW( LPCWSTR str1
, LPCWSTR str2
)
1324 if (!str1
|| !str2
) {
1325 SetLastError(ERROR_INVALID_PARAMETER
);
1328 return strcmpiW( str1
, str2
);
1332 /******************************************************************************
1333 * OLE_GetFormatW [Internal]
1335 * dateformat is set TRUE if being called for a date, false for a time
1337 This function implements stuff for GetDateFormat() and
1340 d single-digit (no leading zero) day (of month)
1341 dd two-digit day (of month)
1342 ddd short day-of-week name
1343 dddd long day-of-week name
1344 M single-digit month
1346 MMM short month name
1347 MMMM full month name
1348 y two-digit year, no leading 0
1350 yyyy four-digit year
1352 h hours with no leading zero (12-hour)
1353 hh hours with full two digits
1354 H hours with no leading zero (24-hour)
1355 HH hours with full two digits
1356 m minutes with no leading zero
1357 mm minutes with full two digits
1358 s seconds with no leading zero
1359 ss seconds with full two digits
1360 t time marker (A or P)
1361 tt time marker (AM, PM)
1362 '' used to quote literal characters
1363 '' (within a quoted string) indicates a literal '
1365 If TIME_NOMINUTESORSECONDS or TIME_NOSECONDS is specified, the function
1366 removes the separator(s) preceding the minutes and/or seconds element(s).
1368 If TIME_NOTIMEMARKER is specified, the function removes the separator(s)
1369 preceding and following the time marker.
1371 If TIME_FORCE24HOURFORMAT is specified, the function displays any existing
1372 time marker, unless the TIME_NOTIMEMARKER flag is also set.
1374 These functions REQUIRE valid locale, date, and format.
1376 If the time or date is invalid, return 0 and set ERROR_INVALID_PARAMETER
1378 Return value is the number of characters written, or if outlen is zero
1379 it is the number of characters required for the output including
1380 the terminating null.
1382 static INT
OLE_GetFormatW(LCID locale
, DWORD flags
, DWORD tflags
,
1383 const SYSTEMTIME
* xtime
,
1385 LPWSTR output
, INT outlen
, int dateformat
)
1388 INT lastFormatPos
; /* the position in the output buffer of */
1389 /* the end of the output from the last formatting */
1391 BOOL dropUntilNextFormattingChar
= FALSE
; /* TIME_NOTIMEMARKER drops
1392 all of the text around the dropped marker,
1393 eg. "h@!t@!m" becomes "hm" */
1395 /* make a debug report */
1396 TRACE("args: 0x%lx, 0x%lx, 0x%lx, time(d=%d,h=%d,m=%d,s=%d), fmt:%s (at %p), "
1397 "%p with max len %d\n",
1398 locale
, flags
, tflags
,
1399 xtime
->wDay
, xtime
->wHour
, xtime
->wMinute
, xtime
->wSecond
,
1400 debugstr_w(format
), format
, output
, outlen
);
1402 /* initialize state variables */
1407 /* Literal string: Maybe terminated early by a \0 */
1408 if (*format
== (WCHAR
) '\'')
1412 /* We loop while we haven't reached the end of the format string */
1413 /* and until we haven't found another "'" character */
1416 /* we found what might be the close single quote mark */
1417 /* we need to make sure there isn't another single quote */
1418 /* after it, if there is we need to skip over this quote mark */
1419 /* as the user is trying to put a single quote mark in their output */
1420 if (*format
== (WCHAR
) '\'')
1423 if (*format
!= '\'')
1425 break; /* It was a terminating quote */
1429 /* if outlen is zero then we are couting the number of */
1430 /* characters of space we need to output this text, don't */
1431 /* modify the output buffer */
1434 outpos
++; /* We are counting */;
1435 } else if (outpos
>= outlen
)
1440 /* even drop literal strings */
1441 if(!dropUntilNextFormattingChar
)
1443 output
[outpos
] = *format
;
1449 } else if ( (dateformat
&& (*format
=='d' ||
1453 (!dateformat
&& (*format
=='H' ||
1458 /* if processing a date and we have a date formatting character, OR */
1459 /* if we are processing a time and we have a time formatting character */
1468 /* clear out the drop text flag if we are in here */
1469 dropUntilNextFormattingChar
= FALSE
;
1471 /* count up the number of the same letter values in a row that */
1472 /* we get, this lets us distinguish between "s" and "ss" and it */
1473 /* eliminates the duplicate to simplify the below case statement */
1474 for (count
= 1; *format
== type
; format
++)
1477 buf
[0] = 0; /* always null terminate the buffer */
1483 GetLocaleInfoW(locale
,
1484 LOCALE_SDAYNAME1
+ (xtime
->wDayOfWeek
+6)%7,
1485 buf
, sizeof(buf
)/sizeof(WCHAR
) );
1486 } else if (count
== 3) {
1487 GetLocaleInfoW(locale
,
1488 LOCALE_SABBREVDAYNAME1
+
1489 (xtime
->wDayOfWeek
+6)%7,
1490 buf
, sizeof(buf
)/sizeof(WCHAR
) );
1492 sprintf( tmp
, "%.*d", count
, xtime
->wDay
);
1493 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1499 GetLocaleInfoW(locale
, LOCALE_SMONTHNAME1
+
1500 xtime
->wMonth
-1, buf
,
1501 sizeof(buf
)/sizeof(WCHAR
) );
1502 } else if (count
== 3) {
1503 GetLocaleInfoW(locale
, LOCALE_SABBREVMONTHNAME1
+
1504 xtime
->wMonth
-1, buf
,
1505 sizeof(buf
)/sizeof(WCHAR
) );
1507 sprintf( tmp
, "%.*d", count
, xtime
->wMonth
);
1508 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1513 sprintf( tmp
, "%d", xtime
->wYear
);
1515 sprintf( tmp
, "%.*d", count
> 2 ? 2 : count
, xtime
->wYear
% 100 );
1517 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1522 FIXME("LOCALE_ICALENDARTYPE unimplemented\n");
1523 strcpy( tmp
, "AD" );
1525 /* Win API sez we copy it verbatim */
1528 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1532 /* fallthrough if we are forced to output in 24 hour format */
1533 if(!(tflags
& TIME_FORCE24HOURFORMAT
))
1535 /* hours 1:00-12:00 --- is this right? */
1536 /* NOTE: 0000 hours is also 12 midnight */
1537 sprintf( tmp
, "%.*d", count
> 2 ? 2 : count
,
1538 xtime
->wHour
== 0 ? 12 : (xtime
->wHour
-1)%12 +1);
1539 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1543 sprintf( tmp
, "%.*d", count
> 2 ? 2 : count
, xtime
->wHour
);
1544 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1548 /* if TIME_NOMINUTESORSECONDS don't display minutes */
1549 if(!(tflags
& TIME_NOMINUTESORSECONDS
))
1551 sprintf( tmp
, "%.*d", count
> 2 ? 2 : count
, xtime
->wMinute
);
1552 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1555 outpos
= lastFormatPos
;
1560 /* if we have a TIME_NOSECONDS or TIME_NOMINUTESORSECONDS
1561 flag then don't display seconds */
1562 if(!(tflags
& TIME_NOSECONDS
) && !(tflags
&
1563 TIME_NOMINUTESORSECONDS
))
1565 sprintf( tmp
, "%.*d", count
> 2 ? 2 : count
, xtime
->wSecond
);
1566 MultiByteToWideChar( CP_ACP
, 0, tmp
, -1, buf
, sizeof(buf
)/sizeof(WCHAR
) );
1569 outpos
= lastFormatPos
;
1574 if(!(tflags
& TIME_NOTIMEMARKER
))
1576 GetLocaleInfoW(locale
, (xtime
->wHour
< 12) ?
1577 LOCALE_S1159
: LOCALE_S2359
,
1585 outpos
= lastFormatPos
; /* remove any prior text up until
1586 the output due to formatting characters */
1587 dropUntilNextFormattingChar
= TRUE
; /* drop everything
1588 until we hit the next formatting character */
1593 /* cat buf onto the output */
1594 buflen
= strlenW(buf
);
1596 /* we are counting how many characters we need for output */
1597 /* don't modify the output buffer... */
1599 /* We are counting */;
1600 else if (outpos
+ buflen
< outlen
) {
1601 strcpyW( output
+ outpos
, buf
);
1603 lstrcpynW( output
+ outpos
, buf
, outlen
- outpos
);
1604 /* Is this an undocumented feature we are supporting? */
1608 lastFormatPos
= outpos
; /* record the end of the formatting text we just output */
1609 } else /* we are processing a NON formatting character */
1611 /* a literal character */
1614 outpos
++; /* We are counting */;
1616 else if (outpos
>= outlen
)
1620 else /* just copy the character into the output buffer */
1622 /* unless we are dropping characters */
1623 if(!dropUntilNextFormattingChar
)
1625 output
[outpos
] = *format
;
1633 /* final string terminator and sanity check */
1635 /* We are counting */;
1636 else if (outpos
>= outlen
)
1639 output
[outpos
] = '\0';
1641 outpos
++; /* add one for the terminating null character */
1643 TRACE(" returning %d %s\n", outpos
, debugstr_w(output
));
1647 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
1648 WARN(" buffer overflow\n");
1653 /******************************************************************************
1654 * GetDateFormatA [KERNEL32.@]
1655 * Makes an ASCII string of the date
1657 * Acts the same as GetDateFormatW(), except that it's ASCII.
1659 INT WINAPI
GetDateFormatA(LCID locale
,DWORD flags
,
1660 const SYSTEMTIME
* xtime
,
1661 LPCSTR format
, LPSTR date
, INT datelen
)
1664 LPWSTR wformat
= NULL
;
1665 LPWSTR wdate
= NULL
;
1669 wformat
= HeapAlloc(GetProcessHeap(), 0,
1670 (strlen(format
) + 1) * sizeof(wchar_t));
1672 MultiByteToWideChar(CP_ACP
, 0, format
, -1, wformat
, strlen(format
) + 1);
1675 if (date
&& datelen
)
1677 wdate
= HeapAlloc(GetProcessHeap(), 0,
1678 (datelen
+ 1) * sizeof(wchar_t));
1681 ret
= GetDateFormatW(locale
, flags
, xtime
, wformat
, wdate
, datelen
);
1685 WideCharToMultiByte(CP_ACP
, 0, wdate
, ret
, date
, datelen
, NULL
, NULL
);
1686 HeapFree(GetProcessHeap(), 0, wdate
);
1691 HeapFree(GetProcessHeap(), 0, wformat
);
1697 /******************************************************************************
1698 * GetDateFormatW [KERNEL32.@]
1699 * Makes a Unicode string of the date
1701 * This function uses format to format the date, or, if format
1702 * is NULL, uses the default for the locale. format is a string
1703 * of literal fields and characters as follows:
1705 * - d single-digit (no leading zero) day (of month)
1706 * - dd two-digit day (of month)
1707 * - ddd short day-of-week name
1708 * - dddd long day-of-week name
1709 * - M single-digit month
1710 * - MM two-digit month
1711 * - MMM short month name
1712 * - MMMM full month name
1713 * - y two-digit year, no leading 0
1714 * - yy two-digit year
1715 * - yyyy four-digit year
1718 * Accepts & returns sizes as counts of Unicode characters.
1720 INT WINAPI
GetDateFormatW(LCID locale
,DWORD flags
,
1721 const SYSTEMTIME
* xtime
,
1723 LPWSTR date
, INT datelen
)
1725 WCHAR format_buf
[40];
1728 LPSYSTEMTIME thistime
;
1734 TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",
1735 locale
,flags
,xtime
,debugstr_w(format
),date
,datelen
);
1738 if (flags
&& format
) /* if lpFormat is non-null then flags must be zero */
1740 SetLastError (ERROR_INVALID_FLAGS
);
1743 if (datelen
&& !date
)
1745 SetLastError (ERROR_INVALID_PARAMETER
);
1750 locale
= LOCALE_SYSTEM_DEFAULT
;
1753 if (locale
== LOCALE_SYSTEM_DEFAULT
)
1755 thislocale
= GetSystemDefaultLCID();
1756 } else if (locale
== LOCALE_USER_DEFAULT
)
1758 thislocale
= GetUserDefaultLCID();
1761 thislocale
= locale
;
1764 /* check for invalid flag combinations */
1765 if((flags
& DATE_LTRREADING
) && (flags
& DATE_RTLREADING
))
1767 SetLastError (ERROR_INVALID_FLAGS
);
1771 /* DATE_SHORTDATE, DATE_LONGDATE and DATE_YEARMONTH are mutually */
1773 if((flags
& (DATE_SHORTDATE
|DATE_LONGDATE
|DATE_YEARMONTH
))
1774 && !((flags
& DATE_SHORTDATE
) ^ (flags
&
1775 DATE_LONGDATE
) ^ (flags
& DATE_YEARMONTH
)))
1777 SetLastError (ERROR_INVALID_FLAGS
);
1781 /* if the user didn't pass in a pointer to the current time we read it */
1788 /* NOTE: check here before we perform the SystemTimeToFileTime conversion */
1789 /* because this conversion will fix invalid time values */
1790 /* check to see if the time/date is valid */
1791 /* set ERROR_INVALID_PARAMETER and return 0 if invalid */
1792 if((xtime
->wDay
> 31) || (xtime
->wDayOfWeek
> 6) || (xtime
->wMonth
> 12))
1794 SetLastError(ERROR_INVALID_PARAMETER
);
1798 /* Silently correct wDayOfWeek by transforming to FileTime and back again */
1799 res
=SystemTimeToFileTime(xtime
,&ft
);
1801 /* Check year(?)/month and date for range and set ERROR_INVALID_PARAMETER on error */
1804 SetLastError(ERROR_INVALID_PARAMETER
);
1807 FileTimeToSystemTime(&ft
,&t
);
1814 GetLocaleInfoW(thislocale
, ((flags
&DATE_LONGDATE
)
1816 : LOCALE_SSHORTDATE
),
1817 format_buf
, sizeof(format_buf
)/sizeof(*format_buf
));
1818 thisformat
= format_buf
;
1821 thisformat
= format
;
1825 ret
= OLE_GetFormatW(thislocale
, flags
, 0, thistime
, thisformat
,
1829 TRACE("GetDateFormatW() returning %d, with data=%s\n",
1830 ret
, debugstr_w(date
));
1834 /**************************************************************************
1835 * EnumDateFormatsA (KERNEL32.@)
1837 BOOL WINAPI
EnumDateFormatsA(
1838 DATEFMT_ENUMPROCA lpDateFmtEnumProc
, LCID Locale
, DWORD dwFlags
)
1840 LCID Loc
= GetUserDefaultLCID();
1841 if(!lpDateFmtEnumProc
)
1843 SetLastError(ERROR_INVALID_PARAMETER
);
1850 case 0x00000407: /* (Loc,"de_DE") */
1854 case DATE_SHORTDATE
:
1855 if(!(*lpDateFmtEnumProc
)("dd.MM.yy")) return TRUE
;
1856 if(!(*lpDateFmtEnumProc
)("d.M.yyyy")) return TRUE
;
1857 if(!(*lpDateFmtEnumProc
)("d.MM.yy")) return TRUE
;
1858 if(!(*lpDateFmtEnumProc
)("d.M.yy")) return TRUE
;
1861 if(!(*lpDateFmtEnumProc
)("dddd,d. MMMM yyyy")) return TRUE
;
1862 if(!(*lpDateFmtEnumProc
)("d. MMMM yyyy")) return TRUE
;
1863 if(!(*lpDateFmtEnumProc
)("d. MMM yyyy")) return TRUE
;
1866 FIXME("Unknown date format (%ld)\n", dwFlags
);
1867 SetLastError(ERROR_INVALID_PARAMETER
);
1872 case 0x0000040c: /* (Loc,"fr_FR") */
1876 case DATE_SHORTDATE
:
1877 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1878 if(!(*lpDateFmtEnumProc
)("dd.MM.yy")) return TRUE
;
1879 if(!(*lpDateFmtEnumProc
)("dd-MM-yy")) return TRUE
;
1880 if(!(*lpDateFmtEnumProc
)("dd/MM/yyyy")) return TRUE
;
1883 if(!(*lpDateFmtEnumProc
)("dddd d MMMM yyyy")) return TRUE
;
1884 if(!(*lpDateFmtEnumProc
)("d MMM yy")) return TRUE
;
1885 if(!(*lpDateFmtEnumProc
)("d MMMM yyyy")) return TRUE
;
1888 FIXME("Unknown date format (%ld)\n", dwFlags
);
1889 SetLastError(ERROR_INVALID_PARAMETER
);
1894 case 0x00000c0c: /* (Loc,"fr_CA") */
1898 case DATE_SHORTDATE
:
1899 if(!(*lpDateFmtEnumProc
)("yy-MM-dd")) return TRUE
;
1900 if(!(*lpDateFmtEnumProc
)("dd-MM-yy")) return TRUE
;
1901 if(!(*lpDateFmtEnumProc
)("yy MM dd")) return TRUE
;
1902 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1905 if(!(*lpDateFmtEnumProc
)("d MMMM, yyyy")) return TRUE
;
1906 if(!(*lpDateFmtEnumProc
)("d MMM yyyy")) return TRUE
;
1909 FIXME("Unknown date format (%ld)\n", dwFlags
);
1910 SetLastError(ERROR_INVALID_PARAMETER
);
1915 case 0x00000809: /* (Loc,"en_UK") */
1919 case DATE_SHORTDATE
:
1920 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1921 if(!(*lpDateFmtEnumProc
)("dd/MM/yyyy")) return TRUE
;
1922 if(!(*lpDateFmtEnumProc
)("d/M/yy")) return TRUE
;
1923 if(!(*lpDateFmtEnumProc
)("d.M.yy")) return TRUE
;
1926 if(!(*lpDateFmtEnumProc
)("dd MMMM yyyy")) return TRUE
;
1927 if(!(*lpDateFmtEnumProc
)("d MMMM yyyy")) return TRUE
;
1930 FIXME("Unknown date format (%ld)\n", dwFlags
);
1931 SetLastError(ERROR_INVALID_PARAMETER
);
1936 case 0x00000c09: /* (Loc,"en_AU") */
1940 case DATE_SHORTDATE
:
1941 if(!(*lpDateFmtEnumProc
)("d/MM/yy")) return TRUE
;
1942 if(!(*lpDateFmtEnumProc
)("d/M/yy")) return TRUE
;
1943 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1946 if(!(*lpDateFmtEnumProc
)("dddd,d MMMM yyyy")) return TRUE
;
1947 if(!(*lpDateFmtEnumProc
)("d MMMM yyyy")) return TRUE
;
1950 FIXME("Unknown date format (%ld)\n", dwFlags
);
1951 SetLastError(ERROR_INVALID_PARAMETER
);
1956 case 0x00001009: /* (Loc,"en_CA") */
1960 case DATE_SHORTDATE
:
1961 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1962 if(!(*lpDateFmtEnumProc
)("d/M/yy")) return TRUE
;
1963 if(!(*lpDateFmtEnumProc
)("yy-MM-dd")) return TRUE
;
1964 if(!(*lpDateFmtEnumProc
)("M/dd/yy")) return TRUE
;
1967 if(!(*lpDateFmtEnumProc
)("d-MMM-yy")) return TRUE
;
1968 if(!(*lpDateFmtEnumProc
)("MMMM d, yyyy")) return TRUE
;
1971 FIXME("Unknown date format (%ld)\n", dwFlags
);
1972 SetLastError(ERROR_INVALID_PARAMETER
);
1977 case 0x00001409: /* (Loc,"en_NZ") */
1981 case DATE_SHORTDATE
:
1982 if(!(*lpDateFmtEnumProc
)("d/MM/yy")) return TRUE
;
1983 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
1984 if(!(*lpDateFmtEnumProc
)("d.MM.yy")) return TRUE
;
1987 if(!(*lpDateFmtEnumProc
)("d MMMM yyyy")) return TRUE
;
1988 if(!(*lpDateFmtEnumProc
)("dddd, d MMMM yyyy")) return TRUE
;
1991 FIXME("Unknown date format (%ld)\n", dwFlags
);
1992 SetLastError(ERROR_INVALID_PARAMETER
);
1997 case 0x00001809: /* (Loc,"en_IE") */
2001 case DATE_SHORTDATE
:
2002 if(!(*lpDateFmtEnumProc
)("dd/MM/yy")) return TRUE
;
2003 if(!(*lpDateFmtEnumProc
)("d/M/yy")) return TRUE
;
2004 if(!(*lpDateFmtEnumProc
)("d.M.yy")) return TRUE
;
2007 if(!(*lpDateFmtEnumProc
)("dd MMMM yyyy")) return TRUE
;
2008 if(!(*lpDateFmtEnumProc
)("d MMMM yyyy")) return TRUE
;
2011 FIXME("Unknown date format (%ld)\n", dwFlags
);
2012 SetLastError(ERROR_INVALID_PARAMETER
);
2017 case 0x00001c09: /* (Loc,"en_ZA") */
2021 case DATE_SHORTDATE
:
2022 if(!(*lpDateFmtEnumProc
)("yy/MM/dd")) return TRUE
;
2025 if(!(*lpDateFmtEnumProc
)("dd MMMM yyyy")) return TRUE
;
2028 FIXME("Unknown date format (%ld)\n", dwFlags
);
2029 SetLastError(ERROR_INVALID_PARAMETER
);
2034 case 0x00002009: /* (Loc,"en_JM") */
2038 case DATE_SHORTDATE
:
2039 if(!(*lpDateFmtEnumProc
)("dd/MM/yyyy")) return TRUE
;
2042 if(!(*lpDateFmtEnumProc
)("dddd,MMMM dd,yyyy")) return TRUE
;
2043 if(!(*lpDateFmtEnumProc
)("MMMM dd,yyyy")) return TRUE
;
2044 if(!(*lpDateFmtEnumProc
)("dddd,dd MMMM,yyyy")) return TRUE
;
2045 if(!(*lpDateFmtEnumProc
)("dd MMMM,yyyy")) return TRUE
;
2048 FIXME("Unknown date format (%ld)\n", dwFlags
);
2049 SetLastError(ERROR_INVALID_PARAMETER
);
2054 case 0x00002809: /* (Loc,"en_BZ") */
2055 case 0x00002c09: /* (Loc,"en_TT") */
2059 case DATE_SHORTDATE
:
2060 if(!(*lpDateFmtEnumProc
)("dd/MM/yyyy")) return TRUE
;
2063 if(!(*lpDateFmtEnumProc
)("dddd,dd MMMM yyyy")) return TRUE
;
2066 FIXME("Unknown date format (%ld)\n", dwFlags
);
2067 SetLastError(ERROR_INVALID_PARAMETER
);
2072 default: /* default to US English "en_US" */
2076 case DATE_SHORTDATE
:
2077 if(!(*lpDateFmtEnumProc
)("M/d/yy")) return TRUE
;
2078 if(!(*lpDateFmtEnumProc
)("M/d/yyyy")) return TRUE
;
2079 if(!(*lpDateFmtEnumProc
)("MM/dd/yy")) return TRUE
;
2080 if(!(*lpDateFmtEnumProc
)("MM/dd/yyyy")) return TRUE
;
2081 if(!(*lpDateFmtEnumProc
)("yy/MM/dd")) return TRUE
;
2082 if(!(*lpDateFmtEnumProc
)("dd-MMM-yy")) return TRUE
;
2085 if(!(*lpDateFmtEnumProc
)("dddd, MMMM dd, yyyy")) return TRUE
;
2086 if(!(*lpDateFmtEnumProc
)("MMMM dd, yyyy")) return TRUE
;
2087 if(!(*lpDateFmtEnumProc
)("dddd, dd MMMM, yyyy")) return TRUE
;
2088 if(!(*lpDateFmtEnumProc
)("dd MMMM, yyyy")) return TRUE
;
2091 FIXME("Unknown date format (%ld)\n", dwFlags
);
2092 SetLastError(ERROR_INVALID_PARAMETER
);
2099 /**************************************************************************
2100 * EnumDateFormatsW (KERNEL32.@)
2102 BOOL WINAPI
EnumDateFormatsW(
2103 DATEFMT_ENUMPROCW lpDateFmtEnumProc
, LCID Locale
, DWORD dwFlags
)
2105 FIXME("(%p, %ld, %ld): stub\n", lpDateFmtEnumProc
, Locale
, dwFlags
);
2106 SetLastError(ERROR_CALL_NOT_IMPLEMENTED
);
2110 /**************************************************************************
2111 * EnumTimeFormatsA (KERNEL32.@)
2113 BOOL WINAPI
EnumTimeFormatsA(
2114 TIMEFMT_ENUMPROCA lpTimeFmtEnumProc
, LCID Locale
, DWORD dwFlags
)
2116 LCID Loc
= GetUserDefaultLCID();
2117 if(!lpTimeFmtEnumProc
)
2119 SetLastError(ERROR_INVALID_PARAMETER
);
2124 FIXME("Unknown time format (%ld)\n", dwFlags
);
2129 case 0x00000407: /* (Loc,"de_DE") */
2131 if(!(*lpTimeFmtEnumProc
)("HH.mm")) return TRUE
;
2132 if(!(*lpTimeFmtEnumProc
)("HH:mm:ss")) return TRUE
;
2133 if(!(*lpTimeFmtEnumProc
)("H:mm:ss")) return TRUE
;
2134 if(!(*lpTimeFmtEnumProc
)("H.mm")) return TRUE
;
2135 if(!(*lpTimeFmtEnumProc
)("H.mm'Uhr'")) return TRUE
;
2139 case 0x0000040c: /* (Loc,"fr_FR") */
2140 case 0x00000c0c: /* (Loc,"fr_CA") */
2142 if(!(*lpTimeFmtEnumProc
)("H:mm")) return TRUE
;
2143 if(!(*lpTimeFmtEnumProc
)("HH:mm:ss")) return TRUE
;
2144 if(!(*lpTimeFmtEnumProc
)("H:mm:ss")) return TRUE
;
2145 if(!(*lpTimeFmtEnumProc
)("HH.mm")) return TRUE
;
2146 if(!(*lpTimeFmtEnumProc
)("HH'h'mm")) return TRUE
;
2150 case 0x00000809: /* (Loc,"en_UK") */
2151 case 0x00000c09: /* (Loc,"en_AU") */
2152 case 0x00001409: /* (Loc,"en_NZ") */
2153 case 0x00001809: /* (Loc,"en_IE") */
2155 if(!(*lpTimeFmtEnumProc
)("h:mm:ss tt")) return TRUE
;
2156 if(!(*lpTimeFmtEnumProc
)("HH:mm:ss")) return TRUE
;
2157 if(!(*lpTimeFmtEnumProc
)("H:mm:ss")) return TRUE
;
2161 case 0x00001c09: /* (Loc,"en_ZA") */
2162 case 0x00002809: /* (Loc,"en_BZ") */
2163 case 0x00002c09: /* (Loc,"en_TT") */
2165 if(!(*lpTimeFmtEnumProc
)("h:mm:ss tt")) return TRUE
;
2166 if(!(*lpTimeFmtEnumProc
)("hh:mm:ss tt")) return TRUE
;
2170 default: /* default to US style "en_US" */
2172 if(!(*lpTimeFmtEnumProc
)("h:mm:ss tt")) return TRUE
;
2173 if(!(*lpTimeFmtEnumProc
)("hh:mm:ss tt")) return TRUE
;
2174 if(!(*lpTimeFmtEnumProc
)("H:mm:ss")) return TRUE
;
2175 if(!(*lpTimeFmtEnumProc
)("HH:mm:ss")) return TRUE
;
2181 /**************************************************************************
2182 * EnumTimeFormatsW (KERNEL32.@)
2184 BOOL WINAPI
EnumTimeFormatsW(
2185 TIMEFMT_ENUMPROCW lpTimeFmtEnumProc
, LCID Locale
, DWORD dwFlags
)
2187 FIXME("(%p,%ld,%ld): stub\n", lpTimeFmtEnumProc
, Locale
, dwFlags
);
2188 SetLastError(ERROR_CALL_NOT_IMPLEMENTED
);
2192 /**************************************************************************
2193 * This function is used just locally !
2194 * Description: Inverts a string.
2196 static void OLE_InvertString(char* string
)
2198 char sTmpArray
[128];
2201 for (counter
= strlen(string
); counter
> 0; counter
--)
2203 memcpy(sTmpArray
+ i
, string
+ counter
-1, 1);
2206 memcpy(sTmpArray
+ i
, "\0", 1);
2207 strcpy(string
, sTmpArray
);
2210 /***************************************************************************************
2211 * This function is used just locally !
2212 * Description: Test if the given string (psNumber) is valid or not.
2213 * The valid characters are the following:
2214 * - Characters '0' through '9'.
2215 * - One decimal point (dot) if the number is a floating-point value.
2216 * - A minus sign in the first character position if the number is
2218 * If the function succeeds, psBefore/psAfter will point to the string
2219 * on the right/left of the decimal symbol. pbNegative indicates if the
2220 * number is negative.
2222 static INT
OLE_GetNumberComponents(char* pInput
, char* psBefore
, char* psAfter
, BOOL
* pbNegative
)
2224 char sNumberSet
[] = "0123456789";
2225 BOOL bInDecimal
= FALSE
;
2227 /* Test if we do have a minus sign */
2228 if ( *pInput
== '-' )
2231 pInput
++; /* Jump to the next character. */
2234 while(*pInput
!= '\0')
2236 /* Do we have a valid numeric character */
2237 if ( strchr(sNumberSet
, *pInput
) != NULL
)
2239 if (bInDecimal
== TRUE
)
2240 *psAfter
++ = *pInput
;
2242 *psBefore
++ = *pInput
;
2246 /* Is this a decimal point (dot) */
2247 if ( *pInput
== '.' )
2249 /* Is it the first time we find it */
2250 if ((bInDecimal
== FALSE
))
2253 return -1; /* ERROR: Invalid parameter */
2257 /* It's neither a numeric character, nor a decimal point.
2258 * Thus, return an error.
2266 /* Add an End of Line character to the output buffers */
2273 /**************************************************************************
2274 * This function is used just locally !
2275 * Description: A number could be formatted using different numbers
2276 * of "digits in group" (example: 4;3;2;0).
2277 * The first parameter of this function is an array
2278 * containing the rule to be used. Its format is the following:
2279 * |NDG|DG1|DG2|...|0|
2280 * where NDG is the number of used "digits in group" and DG1, DG2,
2281 * are the corresponding "digits in group".
2282 * Thus, this function returns the grouping value in the array
2283 * pointed by the second parameter.
2285 static INT
OLE_GetGrouping(char* sRule
, INT index
)
2287 char sData
[2], sRuleSize
[2];
2288 INT nData
, nRuleSize
;
2290 memcpy(sRuleSize
, sRule
, 1);
2291 memcpy(sRuleSize
+1, "\0", 1);
2292 nRuleSize
= atoi(sRuleSize
);
2294 if (index
> 0 && index
< nRuleSize
)
2296 memcpy(sData
, sRule
+index
, 1);
2297 memcpy(sData
+1, "\0", 1);
2298 nData
= atoi(sData
);
2303 memcpy(sData
, sRule
+nRuleSize
-1, 1);
2304 memcpy(sData
+1, "\0", 1);
2305 nData
= atoi(sData
);
2311 /**************************************************************************
2312 * GetNumberFormatA (KERNEL32.@)
2314 INT WINAPI
GetNumberFormatA(LCID locale
, DWORD dwflags
,
2315 LPCSTR lpvalue
, const NUMBERFMTA
* lpFormat
,
2316 LPSTR lpNumberStr
, int cchNumber
)
2318 char sNumberDigits
[3], sDecimalSymbol
[5], sDigitsInGroup
[11], sDigitGroupSymbol
[5], sILZero
[2];
2319 INT nNumberDigits
, nNumberDecimal
, i
, j
, nCounter
, nStep
, nRuleIndex
, nGrouping
, nDigits
, retVal
, nLZ
;
2320 char sNumber
[128], sDestination
[128], sDigitsAfterDecimal
[10], sDigitsBeforeDecimal
[128];
2321 char sRule
[10], sSemiColumn
[]=";", sBuffer
[5], sNegNumber
[2];
2322 char *pStr
= NULL
, *pTmpStr
= NULL
;
2323 LCID systemDefaultLCID
;
2324 BOOL bNegative
= FALSE
;
2333 strncpy(sNumber
, lpvalue
, 128);
2334 sNumber
[127] = '\0';
2336 /* Make sure we have a valid input string, get the number
2337 * of digits before and after the decimal symbol, and check
2338 * if this is a negative number.
2340 if ( OLE_GetNumberComponents(sNumber
, sDigitsBeforeDecimal
, sDigitsAfterDecimal
, &bNegative
) != -1)
2342 nNumberDecimal
= strlen(sDigitsBeforeDecimal
);
2343 nDigits
= strlen(sDigitsAfterDecimal
);
2347 SetLastError(ERROR_INVALID_PARAMETER
);
2351 /* Which source will we use to format the string */
2352 used_operation
= RETURN_ERROR
;
2353 if (lpFormat
!= NULL
)
2356 used_operation
= USE_PARAMETER
;
2360 if (dwflags
& LOCALE_NOUSEROVERRIDE
)
2361 used_operation
= USE_SYSTEMDEFAULT
;
2363 used_operation
= USE_LOCALEINFO
;
2366 /* Load the fields we need */
2367 switch(used_operation
)
2369 case USE_LOCALEINFO
:
2370 GetLocaleInfoA(locale
, LOCALE_IDIGITS
, sNumberDigits
, sizeof(sNumberDigits
));
2371 GetLocaleInfoA(locale
, LOCALE_SDECIMAL
, sDecimalSymbol
, sizeof(sDecimalSymbol
));
2372 GetLocaleInfoA(locale
, LOCALE_SGROUPING
, sDigitsInGroup
, sizeof(sDigitsInGroup
));
2373 GetLocaleInfoA(locale
, LOCALE_STHOUSAND
, sDigitGroupSymbol
, sizeof(sDigitGroupSymbol
));
2374 GetLocaleInfoA(locale
, LOCALE_ILZERO
, sILZero
, sizeof(sILZero
));
2375 GetLocaleInfoA(locale
, LOCALE_INEGNUMBER
, sNegNumber
, sizeof(sNegNumber
));
2378 sprintf(sNumberDigits
, "%d",lpFormat
->NumDigits
);
2379 strcpy(sDecimalSymbol
, lpFormat
->lpDecimalSep
);
2380 sprintf(sDigitsInGroup
, "%d;0",lpFormat
->Grouping
);
2381 /* Win95-WinME only allow 0-9 for grouping, no matter what MSDN says. */
2382 strcpy(sDigitGroupSymbol
, lpFormat
->lpThousandSep
);
2383 sprintf(sILZero
, "%d",lpFormat
->LeadingZero
);
2384 sprintf(sNegNumber
, "%d",lpFormat
->NegativeOrder
);
2386 case USE_SYSTEMDEFAULT
:
2387 systemDefaultLCID
= GetSystemDefaultLCID();
2388 GetLocaleInfoA(systemDefaultLCID
, LOCALE_IDIGITS
, sNumberDigits
, sizeof(sNumberDigits
));
2389 GetLocaleInfoA(systemDefaultLCID
, LOCALE_SDECIMAL
, sDecimalSymbol
, sizeof(sDecimalSymbol
));
2390 GetLocaleInfoA(systemDefaultLCID
, LOCALE_SGROUPING
, sDigitsInGroup
, sizeof(sDigitsInGroup
));
2391 GetLocaleInfoA(systemDefaultLCID
, LOCALE_STHOUSAND
, sDigitGroupSymbol
, sizeof(sDigitGroupSymbol
));
2392 GetLocaleInfoA(systemDefaultLCID
, LOCALE_ILZERO
, sILZero
, sizeof(sILZero
));
2393 GetLocaleInfoA(systemDefaultLCID
, LOCALE_INEGNUMBER
, sNegNumber
, sizeof(sNegNumber
));
2396 SetLastError(ERROR_INVALID_PARAMETER
);
2400 nNumberDigits
= atoi(sNumberDigits
);
2402 /* Remove the ";" */
2405 for (nCounter
=0; nCounter
<strlen(sDigitsInGroup
); nCounter
++)
2407 if ( memcmp(sDigitsInGroup
+ nCounter
, sSemiColumn
, 1) != 0 )
2409 memcpy(sRule
+ j
, sDigitsInGroup
+ nCounter
, 1);
2414 sprintf(sBuffer
, "%d", i
);
2415 memcpy(sRule
, sBuffer
, 1); /* Number of digits in the groups ( used by OLE_GetGrouping() ) */
2416 memcpy(sRule
+ j
, "\0", 1);
2418 /* First, format the digits before the decimal. */
2419 if ((nNumberDecimal
>0) && (atoi(sDigitsBeforeDecimal
) != 0))
2421 /* Working on an inverted string is easier ! */
2422 OLE_InvertString(sDigitsBeforeDecimal
);
2424 nStep
= nCounter
= i
= j
= 0;
2426 nGrouping
= OLE_GetGrouping(sRule
, nRuleIndex
);
2427 if (nGrouping
== 0) /* If the first grouping is zero */
2428 nGrouping
= nNumberDecimal
; /* Don't do grouping */
2430 /* Here, we will loop until we reach the end of the string.
2431 * An internal counter (j) is used in order to know when to
2432 * insert the "digit group symbol".
2434 while (nNumberDecimal
> 0)
2436 i
= nCounter
+ nStep
;
2437 memcpy(sDestination
+ i
, sDigitsBeforeDecimal
+ nCounter
, 1);
2443 if (nRuleIndex
< sRule
[0])
2445 nGrouping
= OLE_GetGrouping(sRule
, nRuleIndex
);
2446 memcpy(sDestination
+ i
+1, sDigitGroupSymbol
, strlen(sDigitGroupSymbol
));
2447 nStep
+= strlen(sDigitGroupSymbol
);
2453 memcpy(sDestination
+ i
+1, "\0", 1);
2454 /* Get the string in the right order ! */
2455 OLE_InvertString(sDestination
);
2459 nLZ
= atoi(sILZero
);
2462 /* Use 0.xxx instead of .xxx */
2463 memcpy(sDestination
, "0", 1);
2464 memcpy(sDestination
+1, "\0", 1);
2467 memcpy(sDestination
, "\0", 1);
2471 /* Second, format the digits after the decimal. */
2473 nCounter
= nNumberDigits
;
2474 if ( (nDigits
>0) && (pStr
= strstr (sNumber
, ".")) )
2476 i
= strlen(sNumber
) - strlen(pStr
) + 1;
2477 strncpy ( sDigitsAfterDecimal
, sNumber
+ i
, nNumberDigits
);
2478 j
= strlen(sDigitsAfterDecimal
);
2479 if (j
< nNumberDigits
)
2480 nCounter
= nNumberDigits
-j
;
2482 for (i
=0;i
<nCounter
;i
++)
2483 memcpy(sDigitsAfterDecimal
+i
+j
, "0", 1);
2484 memcpy(sDigitsAfterDecimal
+ nNumberDigits
, "\0", 1);
2486 i
= strlen(sDestination
);
2487 j
= strlen(sDigitsAfterDecimal
);
2488 /* Finally, construct the resulting formatted string. */
2490 for (nCounter
=0; nCounter
<i
; nCounter
++)
2491 memcpy(sNumber
+ nCounter
, sDestination
+ nCounter
, 1);
2493 memcpy(sNumber
+ nCounter
, sDecimalSymbol
, strlen(sDecimalSymbol
));
2496 memcpy(sNumber
+ nCounter
+i
+strlen(sDecimalSymbol
), sDigitsAfterDecimal
+ i
, 1);
2497 memcpy(sNumber
+ nCounter
+i
+ (i
? strlen(sDecimalSymbol
) : 0), "\0", 1);
2499 /* Is it a negative number */
2500 if (bNegative
== TRUE
)
2502 i
= atoi(sNegNumber
);
2503 pStr
= sDestination
;
2509 while (*sNumber
!= '\0')
2510 *pStr
++ = *pTmpStr
++;
2515 while (*pTmpStr
!= '\0')
2516 *pStr
++ = *pTmpStr
++;
2521 while (*pTmpStr
!= '\0')
2522 *pStr
++ = *pTmpStr
++;
2525 while (*pTmpStr
!= '\0')
2526 *pStr
++ = *pTmpStr
++;
2530 while (*pTmpStr
!= '\0')
2531 *pStr
++ = *pTmpStr
++;
2536 while (*pTmpStr
!= '\0')
2537 *pStr
++ = *pTmpStr
++;
2542 strcpy(sDestination
, sNumber
);
2544 /* If cchNumber is zero, then returns the number of bytes or characters
2545 * required to hold the formatted number string
2547 retVal
= strlen(sDestination
) + 1;
2550 memcpy( lpNumberStr
, sDestination
, min(cchNumber
, retVal
) );
2551 if (cchNumber
< retVal
) {
2553 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
2559 /**************************************************************************
2560 * GetNumberFormatW (KERNEL32.@)
2562 INT WINAPI
GetNumberFormatW(LCID locale
, DWORD dwflags
,
2563 LPCWSTR lpvalue
, const NUMBERFMTW
* lpFormat
,
2564 LPWSTR lpNumberStr
, int cchNumber
)
2566 FIXME("%s: stub, no reformatting done\n",debugstr_w(lpvalue
));
2568 lstrcpynW( lpNumberStr
, lpvalue
, cchNumber
);
2569 return cchNumber
? strlenW( lpNumberStr
) : 0;
2572 /**************************************************************************
2573 * GetCurrencyFormatA (KERNEL32.@)
2575 INT WINAPI
GetCurrencyFormatA(LCID locale
, DWORD dwflags
,
2576 LPCSTR lpvalue
, const CURRENCYFMTA
* lpFormat
,
2577 LPSTR lpCurrencyStr
, int cchCurrency
)
2579 UINT nPosOrder
, nNegOrder
;
2581 char sDestination
[128], sNegOrder
[8], sPosOrder
[8], sCurrencySymbol
[8];
2582 char *pDestination
= sDestination
;
2583 char sNumberFormated
[128];
2584 char *pNumberFormated
= sNumberFormated
;
2585 LCID systemDefaultLCID
;
2586 BOOL bIsPositive
= FALSE
, bValidFormat
= FALSE
;
2595 NUMBERFMTA numberFmt
;
2597 /* Which source will we use to format the string */
2598 used_operation
= RETURN_ERROR
;
2599 if (lpFormat
!= NULL
)
2602 used_operation
= USE_PARAMETER
;
2606 if (dwflags
& LOCALE_NOUSEROVERRIDE
)
2607 used_operation
= USE_SYSTEMDEFAULT
;
2609 used_operation
= USE_LOCALEINFO
;
2612 /* Load the fields we need */
2613 switch(used_operation
)
2615 case USE_LOCALEINFO
:
2616 /* Specific to CURRENCYFMTA */
2617 GetLocaleInfoA(locale
, LOCALE_INEGCURR
, sNegOrder
, sizeof(sNegOrder
));
2618 GetLocaleInfoA(locale
, LOCALE_ICURRENCY
, sPosOrder
, sizeof(sPosOrder
));
2619 GetLocaleInfoA(locale
, LOCALE_SCURRENCY
, sCurrencySymbol
, sizeof(sCurrencySymbol
));
2621 nPosOrder
= atoi(sPosOrder
);
2622 nNegOrder
= atoi(sNegOrder
);
2625 /* Specific to CURRENCYFMTA */
2626 nNegOrder
= lpFormat
->NegativeOrder
;
2627 nPosOrder
= lpFormat
->PositiveOrder
;
2628 strcpy(sCurrencySymbol
, lpFormat
->lpCurrencySymbol
);
2630 case USE_SYSTEMDEFAULT
:
2631 systemDefaultLCID
= GetSystemDefaultLCID();
2632 /* Specific to CURRENCYFMTA */
2633 GetLocaleInfoA(systemDefaultLCID
, LOCALE_INEGCURR
, sNegOrder
, sizeof(sNegOrder
));
2634 GetLocaleInfoA(systemDefaultLCID
, LOCALE_ICURRENCY
, sPosOrder
, sizeof(sPosOrder
));
2635 GetLocaleInfoA(systemDefaultLCID
, LOCALE_SCURRENCY
, sCurrencySymbol
, sizeof(sCurrencySymbol
));
2637 nPosOrder
= atoi(sPosOrder
);
2638 nNegOrder
= atoi(sNegOrder
);
2641 SetLastError(ERROR_INVALID_PARAMETER
);
2645 /* Construct a temporary number format structure */
2646 if (lpFormat
!= NULL
)
2648 numberFmt
.NumDigits
= lpFormat
->NumDigits
;
2649 numberFmt
.LeadingZero
= lpFormat
->LeadingZero
;
2650 numberFmt
.Grouping
= lpFormat
->Grouping
;
2651 numberFmt
.NegativeOrder
= 0;
2652 numberFmt
.lpDecimalSep
= lpFormat
->lpDecimalSep
;
2653 numberFmt
.lpThousandSep
= lpFormat
->lpThousandSep
;
2654 bValidFormat
= TRUE
;
2657 /* Make a call to GetNumberFormatA() */
2658 if (*lpvalue
== '-')
2660 bIsPositive
= FALSE
;
2661 retVal
= GetNumberFormatA(locale
,0,lpvalue
+1,(bValidFormat
)?&numberFmt
:NULL
,pNumberFormated
,128);
2666 retVal
= GetNumberFormatA(locale
,0,lpvalue
,(bValidFormat
)?&numberFmt
:NULL
,pNumberFormated
,128);
2672 /* construct the formatted string */
2677 case 0: /* Prefix, no separation */
2678 strcpy (pDestination
, sCurrencySymbol
);
2679 strcat (pDestination
, pNumberFormated
);
2681 case 1: /* Suffix, no separation */
2682 strcpy (pDestination
, pNumberFormated
);
2683 strcat (pDestination
, sCurrencySymbol
);
2685 case 2: /* Prefix, 1 char separation */
2686 strcpy (pDestination
, sCurrencySymbol
);
2687 strcat (pDestination
, " ");
2688 strcat (pDestination
, pNumberFormated
);
2690 case 3: /* Suffix, 1 char separation */
2691 strcpy (pDestination
, pNumberFormated
);
2692 strcat (pDestination
, " ");
2693 strcat (pDestination
, sCurrencySymbol
);
2696 SetLastError(ERROR_INVALID_PARAMETER
);
2700 else /* negative number */
2704 case 0: /* format: ($1.1) */
2705 strcpy (pDestination
, "(");
2706 strcat (pDestination
, sCurrencySymbol
);
2707 strcat (pDestination
, pNumberFormated
);
2708 strcat (pDestination
, ")");
2710 case 1: /* format: -$1.1 */
2711 strcpy (pDestination
, "-");
2712 strcat (pDestination
, sCurrencySymbol
);
2713 strcat (pDestination
, pNumberFormated
);
2715 case 2: /* format: $-1.1 */
2716 strcpy (pDestination
, sCurrencySymbol
);
2717 strcat (pDestination
, "-");
2718 strcat (pDestination
, pNumberFormated
);
2720 case 3: /* format: $1.1- */
2721 strcpy (pDestination
, sCurrencySymbol
);
2722 strcat (pDestination
, pNumberFormated
);
2723 strcat (pDestination
, "-");
2725 case 4: /* format: (1.1$) */
2726 strcpy (pDestination
, "(");
2727 strcat (pDestination
, pNumberFormated
);
2728 strcat (pDestination
, sCurrencySymbol
);
2729 strcat (pDestination
, ")");
2731 case 5: /* format: -1.1$ */
2732 strcpy (pDestination
, "-");
2733 strcat (pDestination
, pNumberFormated
);
2734 strcat (pDestination
, sCurrencySymbol
);
2736 case 6: /* format: 1.1-$ */
2737 strcpy (pDestination
, pNumberFormated
);
2738 strcat (pDestination
, "-");
2739 strcat (pDestination
, sCurrencySymbol
);
2741 case 7: /* format: 1.1$- */
2742 strcpy (pDestination
, pNumberFormated
);
2743 strcat (pDestination
, sCurrencySymbol
);
2744 strcat (pDestination
, "-");
2746 case 8: /* format: -1.1 $ */
2747 strcpy (pDestination
, "-");
2748 strcat (pDestination
, pNumberFormated
);
2749 strcat (pDestination
, " ");
2750 strcat (pDestination
, sCurrencySymbol
);
2752 case 9: /* format: -$ 1.1 */
2753 strcpy (pDestination
, "-");
2754 strcat (pDestination
, sCurrencySymbol
);
2755 strcat (pDestination
, " ");
2756 strcat (pDestination
, pNumberFormated
);
2758 case 10: /* format: 1.1 $- */
2759 strcpy (pDestination
, pNumberFormated
);
2760 strcat (pDestination
, " ");
2761 strcat (pDestination
, sCurrencySymbol
);
2762 strcat (pDestination
, "-");
2764 case 11: /* format: $ 1.1- */
2765 strcpy (pDestination
, sCurrencySymbol
);
2766 strcat (pDestination
, " ");
2767 strcat (pDestination
, pNumberFormated
);
2768 strcat (pDestination
, "-");
2770 case 12: /* format: $ -1.1 */
2771 strcpy (pDestination
, sCurrencySymbol
);
2772 strcat (pDestination
, " ");
2773 strcat (pDestination
, "-");
2774 strcat (pDestination
, pNumberFormated
);
2776 case 13: /* format: 1.1- $ */
2777 strcpy (pDestination
, pNumberFormated
);
2778 strcat (pDestination
, "-");
2779 strcat (pDestination
, " ");
2780 strcat (pDestination
, sCurrencySymbol
);
2782 case 14: /* format: ($ 1.1) */
2783 strcpy (pDestination
, "(");
2784 strcat (pDestination
, sCurrencySymbol
);
2785 strcat (pDestination
, " ");
2786 strcat (pDestination
, pNumberFormated
);
2787 strcat (pDestination
, ")");
2789 case 15: /* format: (1.1 $) */
2790 strcpy (pDestination
, "(");
2791 strcat (pDestination
, pNumberFormated
);
2792 strcat (pDestination
, " ");
2793 strcat (pDestination
, sCurrencySymbol
);
2794 strcat (pDestination
, ")");
2797 SetLastError(ERROR_INVALID_PARAMETER
);
2802 retVal
= strlen(pDestination
) + 1;
2806 memcpy( lpCurrencyStr
, pDestination
, min(cchCurrency
, retVal
) );
2807 if (cchCurrency
< retVal
) {
2809 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
2815 /**************************************************************************
2816 * GetCurrencyFormatW (KERNEL32.@)
2818 INT WINAPI
GetCurrencyFormatW(LCID locale
, DWORD dwflags
,
2819 LPCWSTR lpvalue
, const CURRENCYFMTW
* lpFormat
,
2820 LPWSTR lpCurrencyStr
, int cchCurrency
)
2822 FIXME("This API function is NOT implemented !\n");
2826 /******************************************************************************
2827 * OLE2NLS_CheckLocale [intern]
2829 static LCID
OLE2NLS_CheckLocale (LCID locale
)
2832 { locale
= LOCALE_SYSTEM_DEFAULT
;
2835 if (locale
== LOCALE_SYSTEM_DEFAULT
)
2836 { return GetSystemDefaultLCID();
2838 else if (locale
== LOCALE_USER_DEFAULT
)
2839 { return GetUserDefaultLCID();
2845 /******************************************************************************
2846 * GetTimeFormatA [KERNEL32.@]
2847 * Makes an ASCII string of the time
2849 * Formats date according to format, or locale default if format is
2850 * NULL. The format consists of literal characters and fields as follows:
2852 * h hours with no leading zero (12-hour)
2853 * hh hours with full two digits
2854 * H hours with no leading zero (24-hour)
2855 * HH hours with full two digits
2856 * m minutes with no leading zero
2857 * mm minutes with full two digits
2858 * s seconds with no leading zero
2859 * ss seconds with full two digits
2860 * t time marker (A or P)
2861 * tt time marker (AM, PM)
2865 GetTimeFormatA(LCID locale
, /* [in] */
2866 DWORD flags
, /* [in] */
2867 const SYSTEMTIME
* xtime
, /* [in] */
2868 LPCSTR format
, /* [in] */
2869 LPSTR timestr
, /* [out] */
2870 INT timelen
/* [in] */)
2873 LPWSTR wformat
= NULL
;
2874 LPWSTR wtime
= NULL
;
2878 wformat
= HeapAlloc(GetProcessHeap(), 0,
2879 (strlen(format
) + 1) * sizeof(wchar_t));
2880 MultiByteToWideChar(CP_ACP
, 0, format
, -1, wformat
, strlen(format
) + 1);
2883 if (timestr
&& timelen
)
2885 wtime
= HeapAlloc(GetProcessHeap(), 0,
2886 (timelen
+ 1) * sizeof(wchar_t));
2889 ret
= GetTimeFormatW(locale
, flags
, xtime
, wformat
, wtime
, timelen
);
2893 WideCharToMultiByte(CP_ACP
, 0, wtime
, ret
, timestr
, timelen
, NULL
, NULL
);
2894 HeapFree(GetProcessHeap(), 0, wtime
);
2899 HeapFree(GetProcessHeap(), 0, wformat
);
2906 /******************************************************************************
2907 * GetTimeFormatW [KERNEL32.@]
2908 * Makes a Unicode string of the time
2910 * NOTE: See OLE_GetFormatW() for further documentation
2913 GetTimeFormatW(LCID locale
, /* [in] */
2914 DWORD flags
, /* [in] */
2915 const SYSTEMTIME
* xtime
, /* [in] */
2916 LPCWSTR format
, /* [in] */
2917 LPWSTR timestr
, /* [out] */
2918 INT timelen
/* [in] */)
2919 { WCHAR format_buf
[40];
2922 const SYSTEMTIME
* thistime
;
2924 DWORD thisflags
=LOCALE_STIMEFORMAT
; /* standard timeformat */
2927 TRACE("GetTimeFormat(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",locale
,flags
,
2928 xtime
,debugstr_w(format
),timestr
,timelen
);
2930 thislocale
= OLE2NLS_CheckLocale ( locale
);
2932 /* if the user didn't specify a format we use the default */
2933 /* format for this locale */
2936 if (flags
& LOCALE_NOUSEROVERRIDE
) /* use system default */
2938 thislocale
= GetSystemDefaultLCID();
2940 GetLocaleInfoW(thislocale
, thisflags
, format_buf
, 40);
2941 thisformat
= format_buf
;
2945 /* if non-null format and LOCALE_NOUSEROVERRIDE then fail */
2946 /* NOTE: this could be either invalid flags or invalid parameter */
2947 /* windows sets it to invalid flags */
2948 if (flags
& LOCALE_NOUSEROVERRIDE
)
2950 SetLastError(ERROR_INVALID_FLAGS
);
2954 thisformat
= format
;
2957 if (xtime
== NULL
) /* NULL means use the current local time */
2963 /* check time values */
2964 if((xtime
->wHour
> 24) || (xtime
->wMinute
>= 60) || (xtime
->wSecond
>= 60))
2966 SetLastError(ERROR_INVALID_PARAMETER
);
2973 ret
= OLE_GetFormatW(thislocale
, thisflags
, flags
, thistime
, thisformat
,
2974 timestr
, timelen
, 0);
2978 /******************************************************************************
2979 * EnumCalendarInfoA [KERNEL32.@]
2981 BOOL WINAPI
EnumCalendarInfoA(
2982 CALINFO_ENUMPROCA calinfoproc
,LCID locale
,CALID calendar
,CALTYPE caltype
2984 FIXME("(%p,0x%04lx,0x%08lx,0x%08lx),stub!\n",calinfoproc
,locale
,calendar
,caltype
);