]> granicus.if.org Git - postgresql/commitdiff
setlocale() on Windows doesn't work correctly if the locale name contains
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 15 Apr 2011 17:48:10 +0000 (20:48 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 15 Apr 2011 17:48:10 +0000 (20:48 +0300)
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

index f1b51bf870c6470942e9eb04d6c306e7e87e9594..1ea5ae1deefa3ae797a0b60041bb543f38eb3f53 100644 (file)
@@ -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