diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 4a2fd28f475a9c10a518ab1a42c1f3866c94bc5e..7112dea0e1e0747c1385fb60232820d0083c9e18 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -239,7 +239,9 @@ check_locale(int category, const char *value)
 	/* set the locale with setlocale, to see if it accepts it. */
 	ret = (setlocale(category, value) != NULL);
 
-	setlocale(category, save);	/* assume this won't fail */
+	/* restore old value. */
+	if (!setlocale(category, save))
+		elog(WARNING, "failed to restore old locale");
 	pfree(save);
 
 	return ret;
@@ -499,13 +501,15 @@ PGLC_localeconv(void)
 	/* Try to restore internal settings */
 	if (save_lc_monetary)
 	{
-		setlocale(LC_MONETARY, save_lc_monetary);
+		if (!setlocale(LC_MONETARY, save_lc_monetary))
+			elog(WARNING, "failed to restore old locale");
 		pfree(save_lc_monetary);
 	}
 
 	if (save_lc_numeric)
 	{
-		setlocale(LC_NUMERIC, save_lc_numeric);
+		if (!setlocale(LC_NUMERIC, save_lc_numeric))
+			elog(WARNING, "failed to restore old locale");
 		pfree(save_lc_numeric);
 	}
 
@@ -513,7 +517,8 @@ PGLC_localeconv(void)
 	/* Try to restore internal ctype settings */
 	if (save_lc_ctype)
 	{
-		setlocale(LC_CTYPE, save_lc_ctype);
+		if (!setlocale(LC_CTYPE, save_lc_ctype))
+			elog(WARNING, "failed to restore old locale");
 		pfree(save_lc_ctype);
 	}
 #endif
@@ -674,7 +679,8 @@ cache_locale_time(void)
 	/* try to restore internal settings */
 	if (save_lc_time)
 	{
-		setlocale(LC_TIME, save_lc_time);
+		if (!setlocale(LC_TIME, save_lc_time))
+			elog(WARNING, "failed to restore old locale");
 		pfree(save_lc_time);
 	}
 
@@ -682,7 +688,8 @@ cache_locale_time(void)
 	/* try to restore internal ctype settings */
 	if (save_lc_ctype)
 	{
-		setlocale(LC_CTYPE, save_lc_ctype);
+		if (!setlocale(LC_CTYPE, save_lc_ctype))
+			elog(WARNING, "failed to restore old locale");
 		pfree(save_lc_ctype);
 	}
 #endif
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index c3173558945ae32d1679f3439e2d5af952b2442f..1dbd6f603a34230f73ce616f6fda4ca05850617e 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -185,7 +185,6 @@ 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 char *localemap(char *locale);
 static void usage(const char *progname);
 
 #ifdef WIN32
@@ -2286,61 +2285,6 @@ strreplace(char *str, char *needle, char *replacement)
 }
 #endif   /* WIN32 */
 
-/*
- * Windows has a problem with locale names that have a dot 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.
-	 *
-	 * "HKG" is 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, "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   /* WIN32 */
-
-	return locale;
-}
-
 /*
  * set up the locale variables
  *
@@ -2372,25 +2316,25 @@ setlocales(void)
 	 */
 
 	if (strlen(lc_ctype) == 0 || !check_locale_name(lc_ctype))
-		lc_ctype = localemap(setlocale(LC_CTYPE, NULL));
+		lc_ctype = xstrdup(setlocale(LC_CTYPE, NULL));
 	if (strlen(lc_collate) == 0 || !check_locale_name(lc_collate))
-		lc_collate = localemap(setlocale(LC_COLLATE, NULL));
+		lc_collate = xstrdup(setlocale(LC_COLLATE, NULL));
 	if (strlen(lc_numeric) == 0 || !check_locale_name(lc_numeric))
-		lc_numeric = localemap(setlocale(LC_NUMERIC, NULL));
+		lc_numeric = xstrdup(setlocale(LC_NUMERIC, NULL));
 	if (strlen(lc_time) == 0 || !check_locale_name(lc_time))
-		lc_time = localemap(setlocale(LC_TIME, NULL));
+		lc_time = xstrdup(setlocale(LC_TIME, NULL));
 	if (strlen(lc_monetary) == 0 || !check_locale_name(lc_monetary))
-		lc_monetary = localemap(setlocale(LC_MONETARY, NULL));
+		lc_monetary = xstrdup(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 = localemap(setlocale(LC_MESSAGES, NULL));
+		lc_messages = xstrdup(setlocale(LC_MESSAGES, NULL));
 	}
 #else
 	{
 		/* when not available, get the CTYPE setting */
-		lc_messages = localemap(setlocale(LC_CTYPE, NULL));
+		lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
 	}
 #endif
 
diff --git a/src/include/port.h b/src/include/port.h
index 9d742ace0f89ddeaaa878b6cfac80c7d9f26d48a..eceb4bfcd3ef8ed8c09f2123025e94bad9c9553e 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -226,16 +226,27 @@ __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
 #endif
 #endif   /* USE_REPL_SNPRINTF */
 
+#if defined(WIN32)
 /*
  * Versions of libintl >= 0.18? try to replace setlocale() with a macro
  * to their own versions.  Remove the macro, if it exists, because it
  * ends up calling the wrong version when the backend and libintl use
  * different versions of msvcrt.
  */
-#if defined(setlocale) && defined(WIN32)
+#if defined(setlocale)
 #undef setlocale
 #endif
 
+/*
+ * Define our own wrapper macro around setlocale() to work around bugs in
+ * Windows' native setlocale() function.
+ */
+extern char *pgwin32_setlocale(int category, const char *locale);
+
+#define setlocale(a,b) pgwin32_setlocale(a,b)
+
+#endif   /* WIN32 */
+
 /* Portable prompt handling */
 extern char *simple_prompt(const char *prompt, int maxlen, bool echo);
 
diff --git a/src/port/chklocale.c b/src/port/chklocale.c
index e4f3dc99e0e3e34c76c0804fa601a69cda1c3e32..cd911b84cee56a0ce33f762773f74e3a08cfd371 100644
--- a/src/port/chklocale.c
+++ b/src/port/chklocale.c
@@ -356,3 +356,109 @@ pg_get_encoding_from_locale(const char *ctype, bool write_message)
 }
 
 #endif   /* (HAVE_LANGINFO_H && CODESET) || WIN32 */
+
+#ifdef WIN32
+/*
+ * Windows has a problem with locale names that have a dot 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
+ * provide a wrapper setlocale() function that maps the troublemaking locale
+ * names to accepted aliases.
+ */
+
+#undef setlocale
+
+struct locale_map
+{
+	const char *locale_name_part;	/* string in locale name to replace */
+	const char *replacement;		/* string to replace it with */
+};
+
+static const struct locale_map locale_map_list[] = {
+
+	/*
+	 * "HKG" is 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.
+	 */
+	{ "Hong Kong S.A.R.",						"HKG" },
+	{ "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". I'm
+	 * not sure where "ZHM" comes from, must be some legacy naming scheme. But
+	 * hey, it works.
+	 *
+	 * Note that unlike HKG and ARE, ZHM is an alias for the *whole* locale
+	 * name, not just the country part.
+	 *
+	 * Some versions of Windows spell it "Macau", others "Macao".
+	 */
+	{ "Chinese (Traditional)_Macau S.A.R..950",	"ZHM" },
+	{ "Chinese_Macau S.A.R..950",				"ZHM" },
+	{ "Chinese (Traditional)_Macao S.A.R..950",	"ZHM" },
+	{ "Chinese_Macao S.A.R..950",				"ZHM" }
+};
+
+char *
+pgwin32_setlocale(int category, const char *locale)
+{
+	char	   *result;
+	char	   *alias;
+	int			i;
+
+	if (locale == NULL)
+		return setlocale(category, locale);
+
+	/* Check if the locale name matches any of the problematic ones. */
+	alias = NULL;
+	for (i = 0; i < lengthof(locale_map_list); i++)
+	{
+		const char *needle = locale_map_list[i].locale_name_part;
+		const char *replacement = locale_map_list[i].replacement;
+		char	   *match;
+
+		match = strstr(locale, needle);
+		if (match != NULL)
+		{
+			/* Found a match. Replace the matched string. */
+			int		matchpos = match - locale;
+			int		replacementlen = strlen(replacement);
+			char   *rest = match + strlen(needle);
+			int		restlen = strlen(rest);
+
+			alias = malloc(matchpos + replacementlen + restlen + 1);
+			if (!alias)
+				return NULL;
+
+			memcpy(&alias[0], &locale[0], matchpos);
+			memcpy(&alias[matchpos], replacement, replacementlen);
+			memcpy(&alias[matchpos + replacementlen], rest, restlen + 1); /* includes null terminator */
+
+			break;
+		}
+	}
+
+	/* Call the real setlocale() function */
+	if (alias)
+	{
+		result = setlocale(category, alias);
+		free(alias);
+	}
+	else
+		result = setlocale(category, locale);
+
+	return result;
+}
+
+#endif   /* WIN32 */