2 * msvcrt.dll locale functions
4 * Copyright 2000 Jon Griffiths
11 #include "msvcrt/locale.h"
13 DEFAULT_DEBUG_CHANNEL(msvcrt
);
15 /* FIXME: Need to hold locale for each LC_* type and aggregate
16 * string to produce lc_all.
18 #define MAX_ELEM_LEN 64 /* Max length of country/language/CP string */
19 #define MAX_LOCALE_LENGTH 256
20 char MSVCRT_current_lc_all
[MAX_LOCALE_LENGTH
];
21 LCID MSVCRT_current_lc_all_lcid
;
22 int MSVCRT_current_lc_all_cp
;
25 extern CRITICAL_SECTION MSVCRT_locale_cs
;
26 #define LOCK_LOCALE EnterCriticalSection(&MSVCRT_locale_cs)
27 #define UNLOCK_LOCALE LeaveCriticalSection(&MSVCRT_locale_cs)
29 /* ctype data modified when the locale changes */
30 extern WORD MSVCRT__ctype
[257];
31 extern WORD MSVCRT_current_ctype
[257];
32 extern WORD
* MSVCRT__pctype
;
34 /* mbctype data modified when the locale changes */
35 extern int MSVCRT___mb_cur_max
;
36 extern unsigned char MSVCRT_mbctype
[257];
38 #define MSVCRT_LEADBYTE 0x8000
40 /* Friendly country strings & iso codes for synonym support.
41 * Based on MS documentation for setlocale().
43 static const char* _country_synonyms
[] =
51 "United Kingdom","GB",
52 "United-Kingdom","GB",
61 /* INTERNAL: Map a synonym to an ISO code */
62 static void remap_synonym(char *name
)
65 for (i
= 0; i
< sizeof(_country_synonyms
)/sizeof(char*); i
+= 2 )
67 if (!strcasecmp(_country_synonyms
[i
],name
))
69 TRACE(":Mapping synonym %s to %s\n",name
,_country_synonyms
[i
+1]);
70 name
[0] = _country_synonyms
[i
+1][0];
71 name
[1] = _country_synonyms
[i
+1][1];
78 /* Note: Flags are weighted in order of matching importance */
79 #define FOUND_LANGUAGE 0x4
80 #define FOUND_COUNTRY 0x2
81 #define FOUND_CODEPAGE 0x1
84 char search_language
[MAX_ELEM_LEN
];
85 char search_country
[MAX_ELEM_LEN
];
86 char search_codepage
[MAX_ELEM_LEN
];
87 char found_language
[MAX_ELEM_LEN
];
88 char found_country
[MAX_ELEM_LEN
];
89 char found_codepage
[MAX_ELEM_LEN
];
90 unsigned int match_flags
;
94 #define CONTINUE_LOOKING TRUE
95 #define STOP_LOOKING FALSE
97 /* INTERNAL: Get and compare locale info with a given string */
98 static int compare_info(LCID lcid
, DWORD flags
, char* buff
, const char* cmp
)
101 GetLocaleInfoA(lcid
, flags
|LOCALE_NOUSEROVERRIDE
,buff
, MAX_ELEM_LEN
);
102 if (!buff
[0] || !cmp
[0])
104 /* Partial matches are allowed, e.g. "Germ" matches "Germany" */
105 return !strncasecmp(cmp
, buff
, strlen(cmp
));
109 find_best_locale_proc(HMODULE hModule WINE_UNUSED
, LPCSTR type WINE_UNUSED
,
110 LPCSTR name WINE_UNUSED
, WORD LangID
, LONG lParam
)
112 locale_search_t
*res
= (locale_search_t
*)lParam
;
113 const LCID lcid
= MAKELCID(LangID
, SORT_DEFAULT
);
114 char buff
[MAX_ELEM_LEN
];
115 unsigned int flags
= 0;
117 if(PRIMARYLANGID(LangID
) == LANG_NEUTRAL
)
118 return CONTINUE_LOOKING
;
121 if (compare_info(lcid
,LOCALE_SISO639LANGNAME
,buff
,res
->search_language
) ||
122 compare_info(lcid
,LOCALE_SABBREVLANGNAME
,buff
,res
->search_language
) ||
123 compare_info(lcid
,LOCALE_SENGLANGUAGE
,buff
,res
->search_language
))
125 TRACE(":Found language: %s->%s\n", res
->search_language
, buff
);
126 flags
|= FOUND_LANGUAGE
;
127 memcpy(res
->found_language
,res
->search_language
,MAX_ELEM_LEN
);
129 else if (res
->match_flags
& FOUND_LANGUAGE
)
131 return CONTINUE_LOOKING
;
135 if (compare_info(lcid
,LOCALE_SISO3166CTRYNAME
,buff
,res
->search_country
) ||
136 compare_info(lcid
,LOCALE_SABBREVCTRYNAME
,buff
,res
->search_country
) ||
137 compare_info(lcid
,LOCALE_SENGCOUNTRY
,buff
,res
->search_country
))
139 TRACE("Found country:%s->%s\n", res
->search_country
, buff
);
140 flags
|= FOUND_COUNTRY
;
141 memcpy(res
->found_country
,res
->search_country
,MAX_ELEM_LEN
);
143 else if (res
->match_flags
& FOUND_COUNTRY
)
145 return CONTINUE_LOOKING
;
149 if (compare_info(lcid
,LOCALE_IDEFAULTCODEPAGE
,buff
,res
->search_codepage
) ||
150 (compare_info(lcid
,LOCALE_IDEFAULTANSICODEPAGE
,buff
,res
->search_codepage
)))
152 TRACE("Found codepage:%s->%s\n", res
->search_codepage
, buff
);
153 flags
|= FOUND_CODEPAGE
;
154 memcpy(res
->found_codepage
,res
->search_codepage
,MAX_ELEM_LEN
);
156 else if (res
->match_flags
& FOUND_CODEPAGE
)
158 return CONTINUE_LOOKING
;
161 if (flags
> res
->match_flags
)
163 /* Found a better match than previously */
164 res
->match_flags
= flags
;
165 res
->found_lang_id
= LangID
;
167 if (flags
& (FOUND_LANGUAGE
& FOUND_COUNTRY
& FOUND_CODEPAGE
))
169 TRACE(":found exact locale match\n");
172 return CONTINUE_LOOKING
;
175 extern int atoi(const char *);
177 /* Internal: Find the LCID for a locale specification */
178 static LCID
MSVCRT_locale_to_LCID(locale_search_t
* locale
)
181 EnumResourceLanguagesA(GetModuleHandleA("KERNEL32"), RT_STRINGA
,
182 (LPCSTR
)LOCALE_ILANGUAGE
,find_best_locale_proc
,
185 if (!locale
->match_flags
)
188 /* If we were given something that didn't match, fail */
189 if (locale
->search_country
[0] && !(locale
->match_flags
& FOUND_COUNTRY
))
192 lcid
= MAKELCID(locale
->found_lang_id
, SORT_DEFAULT
);
194 /* Populate partial locale, translating LCID to locale string elements */
195 if (!locale
->found_codepage
[0])
197 /* Even if a codepage is not enumerated for a locale
198 * it can be set if valid */
199 if (locale
->search_codepage
[0])
201 if (IsValidCodePage(atoi(locale
->search_codepage
)))
202 memcpy(locale
->found_codepage
,locale
->search_codepage
,MAX_ELEM_LEN
);
205 /* Special codepage values: OEM & ANSI */
206 if (strcasecmp(locale
->search_codepage
,"OCP"))
208 GetLocaleInfoA(lcid
, LOCALE_IDEFAULTCODEPAGE
,
209 locale
->found_codepage
, MAX_ELEM_LEN
);
211 if (strcasecmp(locale
->search_codepage
,"ACP"))
213 GetLocaleInfoA(lcid
, LOCALE_IDEFAULTANSICODEPAGE
,
214 locale
->found_codepage
, MAX_ELEM_LEN
);
219 if (!atoi(locale
->found_codepage
))
225 /* Prefer ANSI codepages if present */
226 GetLocaleInfoA(lcid
, LOCALE_IDEFAULTANSICODEPAGE
,
227 locale
->found_codepage
, MAX_ELEM_LEN
);
228 if (!locale
->found_codepage
[0] || !atoi(locale
->found_codepage
))
229 GetLocaleInfoA(lcid
, LOCALE_IDEFAULTCODEPAGE
,
230 locale
->found_codepage
, MAX_ELEM_LEN
);
233 GetLocaleInfoA(lcid
, LOCALE_SENGLANGUAGE
|LOCALE_NOUSEROVERRIDE
,
234 locale
->found_language
, MAX_ELEM_LEN
);
235 GetLocaleInfoA(lcid
, LOCALE_SENGCOUNTRY
|LOCALE_NOUSEROVERRIDE
,
236 locale
->found_country
, MAX_ELEM_LEN
);
240 extern int snprintf(char *, int, const char *, ...);
242 /* INTERNAL: Set ctype behaviour for a codepage */
243 static void msvcrt_set_ctype(unsigned int codepage
, LCID lcid
)
247 memset(&cp
, 0, sizeof(CPINFO
));
249 if (GetCPInfo(codepage
, &cp
))
253 unsigned char *traverse
= (unsigned char *)cp
.LeadByte
;
255 memset(MSVCRT_current_ctype
, 0, sizeof(MSVCRT__ctype
));
256 MSVCRT_current_lc_all_cp
= codepage
;
258 /* Switch ctype macros to MBCS if needed */
259 MSVCRT___mb_cur_max
= cp
.MaxCharSize
;
261 /* Set remaining ctype flags: FIXME: faster way to do this? */
263 for (i
= 0; i
< 256; i
++)
265 if (!(MSVCRT__pctype
[i
] & MSVCRT_LEADBYTE
))
268 GetStringTypeA(lcid
, CT_CTYPE1
, str
, 1, MSVCRT__pctype
+ i
);
272 /* Set leadbyte flags */
273 while (traverse
[0] || traverse
[1])
275 for( i
= traverse
[0]; i
<= traverse
[1]; i
++ )
276 MSVCRT_current_ctype
[i
+1] |= MSVCRT_LEADBYTE
;
283 /*********************************************************************
284 * setlocale (MSVCRT.@)
286 char* MSVCRT_setlocale(int category
, const char* locale
)
290 int haveLang
, haveCountry
, haveCP
;
294 TRACE("(%d %s)\n",category
,locale
);
296 if (category
< MSVCRT_LC_MIN
|| category
> MSVCRT_LC_MAX
)
301 /* Report the current Locale */
302 return MSVCRT_current_lc_all
;
307 if (locale
[0] == 'L' && locale
[1] == 'C' && locale
[2] == '_')
309 FIXME(":restore previous locale not implemented!\n");
310 /* FIXME: Easiest way to do this is parse the string and
311 * call this function recursively with its elements,
312 * Where they differ for each lc_ type.
315 return MSVCRT_current_lc_all
;
318 /* Default Locale: Special case handling */
319 if (!strlen(locale
) || ((toupper(locale
[0]) == 'C') && !locale
[1]))
321 MSVCRT_current_lc_all
[0] = 'C';
322 MSVCRT_current_lc_all
[1] = '\0';
323 MSVCRT_current_lc_all_cp
= GetACP();
327 lc_all
= 1; /* Fall through all cases ... */
328 case MSVCRT_LC_COLLATE
:
330 case MSVCRT_LC_CTYPE
:
331 /* Restore C locale ctype info */
332 MSVCRT___mb_cur_max
= 1;
333 memcpy(MSVCRT_current_ctype
, MSVCRT__ctype
, sizeof(MSVCRT__ctype
));
334 memset(MSVCRT_mbctype
, 0, sizeof(MSVCRT_mbctype
));
336 case MSVCRT_LC_MONETARY
:
338 case MSVCRT_LC_NUMERIC
:
344 return MSVCRT_current_lc_all
;
347 /* Get locale elements */
348 haveLang
= haveCountry
= haveCP
= 0;
349 memset(&lc
,0,sizeof(lc
));
351 next
= strchr(locale
,'_');
352 if (next
&& next
!= locale
)
355 strncpy(lc
.search_language
,locale
,next
-locale
);
356 locale
+= next
-locale
+1;
359 next
= strchr(locale
,'.');
366 strncpy(lc
.search_codepage
, locale
, MAX_ELEM_LEN
);
373 strncpy(lc
.search_country
,locale
,next
-locale
);
374 locale
+= next
-locale
+1;
379 strncpy(lc
.search_language
,locale
,next
-locale
);
380 locale
+= next
-locale
+1;
382 strncpy(lc
.search_codepage
, locale
, MAX_ELEM_LEN
);
390 strncpy(lc
.search_country
, locale
, MAX_ELEM_LEN
);
395 strncpy(lc
.search_language
, locale
, MAX_ELEM_LEN
);
400 remap_synonym(lc
.search_country
);
402 if (haveCP
&& !haveCountry
&& !haveLang
)
404 FIXME(":Codepage only locale not implemented\n");
405 /* FIXME: Use default lang/country and skip locale_to_LCID()
412 lcid
= MSVCRT_locale_to_LCID(&lc
);
414 TRACE(":found LCID %ld\n",lcid
);
422 MSVCRT_current_lc_all_lcid
= lcid
;
424 snprintf(MSVCRT_current_lc_all
,MAX_LOCALE_LENGTH
,"%s_%s.%s",
425 lc
.found_language
,lc
.found_country
,lc
.found_codepage
);
429 lc_all
= 1; /* Fall through all cases ... */
430 case MSVCRT_LC_COLLATE
:
432 case MSVCRT_LC_CTYPE
:
433 msvcrt_set_ctype(atoi(lc
.found_codepage
),lcid
);
435 case MSVCRT_LC_MONETARY
:
437 case MSVCRT_LC_NUMERIC
:
443 return MSVCRT_current_lc_all
;
447 /*********************************************************************
448 * _Getdays (MSVCRT.@)
450 const char* _Getdays(void)
452 static const char *MSVCRT_days
= ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:"
453 "Wednesday:Thu:Thursday:Fri:Friday:Sat:Saturday";
454 /* FIXME: Use locale */
455 TRACE("(void) semi-stub\n");
459 /*********************************************************************
460 * _Getmonths (MSVCRT.@)
462 const char* _Getmonths(void)
464 static const char *MSVCRT_months
= ":Jan:January:Feb:February:Mar:March:Apr:"
465 "April:May:May:Jun:June:Jul:July:Aug:August:Sep:September:Oct:"
466 "October:Nov:November:Dec:December";
467 /* FIXME: Use locale */
468 TRACE("(void) semi-stub\n");
469 return MSVCRT_months
;
472 /*********************************************************************
473 * _Getnames (MSVCRT.@)
475 const char* _Getnames(void)
478 TRACE("(void) stub\n");
482 /*********************************************************************
483 * _Strftime (MSVCRT.@)
485 const char* _Strftime(char *out
, unsigned int len
, const char *fmt
,
486 const void *tm
, void *foo
)
489 TRACE("(%p %d %s %p %p) stub\n", out
, len
, fmt
, tm
, foo
);
493 /* FIXME: MBCP probably belongs in mbcs.c */
495 /*********************************************************************
496 * _setmbcp (MSVCRT.@)
498 void _setmbcp(int cp
)
501 if (MSVCRT_current_lc_all_cp
!= cp
)
503 /* FIXME: set ctype behaviour for this cp */
504 MSVCRT_current_lc_all_cp
= cp
;
509 /*********************************************************************
510 * _getmbcp (MSVCRT.@)
514 return MSVCRT_current_lc_all_cp
;