From d5a7bf8c11c8b66c822bbb1a6c90e1a14425bd6e Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Fri, 15 Apr 2011 20:48:10 +0300 Subject: [PATCH] setlocale() on Windows doesn't work correctly if the locale name contains apostrophes or dots. There isn't much hope of Microsoft fixing it any time soon, it's been like that for ages, so we better work around it. So, map a few common Windows locale names known to cause problems to aliases that work. --- src/bin/initdb/initdb.c | 89 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index f1b51bf870..1ea5ae1dee 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -185,6 +185,8 @@ static int locale_date_order(const char *locale); static bool check_locale_name(const char *locale); static bool check_locale_encoding(const char *locale, int encoding); static void setlocales(void); +static void strreplace(char *str, char *needle, char *replacement); +static char *localemap(char *locale); static void usage(const char *progname); #ifdef WIN32 @@ -2250,6 +2252,79 @@ check_locale_encoding(const char *locale, int user_enc) return true; } +/* + * Replace 'needle' with 'replacement' in 'str' . Note that the replacement + * is done in-place, so 'replacement' must be shorter than 'needle'. + */ +static void +strreplace(char *str, char *needle, char *replacement) +{ + char *s; + + s = strstr(str, needle); + if (s != NULL) + { + int replacementlen = strlen(replacement); + char *rest = s + strlen(needle); + + memcpy(s, replacement, replacementlen); + memmove(s + replacementlen, rest, strlen(rest) + 1); + } +} + +/* + * Windows has a problem with locale names that have a dot or apostrophe in + * the country name. For example: + * + * "Chinese (Traditional)_Hong Kong S.A.R..950" + * + * For some reason, setlocale() doesn't accept that. Fortunately, Windows' + * setlocale() accepts various alternative names for such countries, so we + * map the full country names to accepted aliases. + * + * The returned string is always malloc'd - if no mapping is done it is + * just a malloc'd copy of the original. + */ +static char * +localemap(char *locale) +{ + locale = xstrdup(locale); + +#ifdef WIN32 + /* + * Map the full country name to an abbreviation that setlocale() accepts + * "China" and "HKG" are listed here: + * http://msdn.microsoft.com/en-us/library/cdax410z%28v=vs.71%29.aspx + * (Country/Region Strings). + * + * "ARE" is the ISO-3166 three-letter code for U.A.E. It is not on the + * above list, but seems to work anyway. + */ + strreplace(locale, "People's Republic of China", "China"); + strreplace(locale, "Hong Kong S.A.R.", "HKG"); + strreplace(locale, "U.A.E.", "ARE"); + + /* + * The ISO-3166 country code for Macau S.A.R. is MAC, but Windows doesn't + * seem to recognize that. And Macau isn't listed in the table of + * accepted abbreviations linked above. + * + * Fortunately, "ZHM" seems to be accepted as an alias for + * "Chinese (Traditional)_Macau S.A.R..950", so we use that. Note that + * it's unlike HKG and ARE, ZHM is an alias for the whole locale name, + * not just the country part. I'm not sure where that "ZHM" comes from, + * must be some legacy naming scheme. But hey, it works. + * + * Some versions of Windows spell it "Macau", others "Macao". + */ + strreplace(locale, "Chinese (Traditional)_Macau S.A.R..950", "ZHM"); + strreplace(locale, "Chinese_Macau S.A.R..950", "ZHM"); + strreplace(locale, "Chinese (Traditional)_Macao S.A.R..950", "ZHM"); + strreplace(locale, "Chinese_Macao S.A.R..950", "ZHM"); +#endif + + return locale; +} /* * set up the locale variables @@ -2282,25 +2357,25 @@ setlocales(void) */ if (strlen(lc_ctype) == 0 || !check_locale_name(lc_ctype)) - lc_ctype = xstrdup(setlocale(LC_CTYPE, NULL)); + lc_ctype = localemap(setlocale(LC_CTYPE, NULL)); if (strlen(lc_collate) == 0 || !check_locale_name(lc_collate)) - lc_collate = xstrdup(setlocale(LC_COLLATE, NULL)); + lc_collate = localemap(setlocale(LC_COLLATE, NULL)); if (strlen(lc_numeric) == 0 || !check_locale_name(lc_numeric)) - lc_numeric = xstrdup(setlocale(LC_NUMERIC, NULL)); + lc_numeric = localemap(setlocale(LC_NUMERIC, NULL)); if (strlen(lc_time) == 0 || !check_locale_name(lc_time)) - lc_time = xstrdup(setlocale(LC_TIME, NULL)); + lc_time = localemap(setlocale(LC_TIME, NULL)); if (strlen(lc_monetary) == 0 || !check_locale_name(lc_monetary)) - lc_monetary = xstrdup(setlocale(LC_MONETARY, NULL)); + lc_monetary = localemap(setlocale(LC_MONETARY, NULL)); if (strlen(lc_messages) == 0 || !check_locale_name(lc_messages)) #if defined(LC_MESSAGES) && !defined(WIN32) { /* when available get the current locale setting */ - lc_messages = xstrdup(setlocale(LC_MESSAGES, NULL)); + lc_messages = localemap(setlocale(LC_MESSAGES, NULL)); } #else { /* when not available, get the CTYPE setting */ - lc_messages = xstrdup(setlocale(LC_CTYPE, NULL)); + lc_messages = localemap(setlocale(LC_CTYPE, NULL)); } #endif