summaryrefslogtreecommitdiff
path: root/src/port/win32setlocale.c
blob: 844891df537089e2d222c5b5e2f28e008fcae30e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*-------------------------------------------------------------------------
 *
 * win32setlocale.c
 *		Wrapper to work around bugs in Windows setlocale() implementation
 *
 * Copyright (c) 2011-2012, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/port/win32setlocale.c
 *
 *
 * 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.
 *-------------------------------------------------------------------------
 */

#include "c.h"

#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;
}