diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 2562eb5..9e231f9 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -971,45 +971,143 @@ cache_locale_time(void) * string. Furthermore, msvcr110.dll changed the undocumented _locale_t * content to carry locale names instead of locale identifiers. * - * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol. - * IsoLocaleName() always fails in a MinGW-built postgres.exe, so only - * Unix-style values of the lc_messages GUC can elicit localized messages. In - * particular, every lc_messages setting that initdb can select automatically - * will yield only C-locale messages. XXX This could be fixed by running the - * fully-qualified locale name through a lookup table. + * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol in + * releases before Windows 8. IsoLocaleName() always fails in a MinGW-built + * postgres.exe, so only Unix-style values of the lc_messages GUC can elicit + * localized messages. In particular, every lc_messages setting that initdb can + * select automatically will yield only C-locale messages. XXX This could be + * fixed by running the fully-qualified locale name through a lookup table or + * using EnumSystemLocalesEx() from Windows Vista/2008. * * This function returns a pointer to a static buffer bearing the converted * name or NULL if conversion fails. * - * [1] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373763.aspx - * [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373814.aspx + * [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers + * [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names */ +#if _WIN32_WINNT >= 0x0600 +/* + * Callback function for EnumSystemLocalesEx. + * Stop enumarating if a match is found for a locale with the format + * [_, e.g. English[_United States] + */ +static BOOL CALLBACK +enum_locales_fn(LPWSTR pStr, DWORD dwFlags, LPARAM lparam) +{ + wchar_t test_locale[LOCALE_NAME_MAX_LENGTH]; + + (void)(dwFlags); + memset(test_locale, 0, sizeof(test_locale)); + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME, + test_locale, LOCALE_NAME_MAX_LENGTH)) + { + wchar_t *hyphen; + wchar_t *underscore; + wchar_t **argv; + + argv = (wchar_t**) lparam; + hyphen = wcsrchr(pStr, '-'); + underscore = wcsrchr(argv[0], '_'); + if (hyphen == NULL || underscore == NULL) + { + /* Compare only, e.g. English */ + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + return FALSE; + } + } + else + { + size_t len; + + /* + * Comparing with a full string _, e.g. + * English_United States + */ + wcscat(test_locale, L"_"); + len = wcslen(test_locale); + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME, + test_locale + len, LOCALE_NAME_MAX_LENGTH - len)) + { + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + return FALSE; + } + } + } + } + return TRUE; +} +#endif /* _WIN32_WINNT >= 0x0600 */ + static char * IsoLocaleName(const char *winlocname) { -#ifdef _MSC_VER - static char iso_lc_messages[32]; - _locale_t loct = NULL; +#if defined(_MSC_VER) || _WIN32_WINNT >= 0x0600 + static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH]; if (pg_strcasecmp("c", winlocname) == 0 || pg_strcasecmp("posix", winlocname) == 0) - { strcpy(iso_lc_messages, "C"); - return iso_lc_messages; - } - - loct = _create_locale(LC_CTYPE, winlocname); - if (loct != NULL) + else { size_t rc; char *hyphen; +#if _WIN32_WINNT >= 0x0600 + wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH]; + wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; + char *period; + int len; + + /* + * Valid locales have the following syntax: + * [_[.]] + * GetLocaleInfoEx can only take locale name without code-page. + */ + period = strchr(winlocname, '.'); + if (period != NULL) + len = period - winlocname; + else + len = pg_mbstrlen(winlocname); + + memset(wc_locale_name, 0, sizeof(wc_locale_name)); + memset(buffer, 0, sizeof(buffer)); + MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name, + LOCALE_NAME_MAX_LENGTH); + /* Is it already a IETF-standardized string, e.g. en-US, en_US */ + if (!GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer, + LOCALE_NAME_MAX_LENGTH)) + { + /* Enumerate all locales supported by the system, until match. */ + wchar_t *argv[2]; + + argv[0] = wc_locale_name; + argv[1] = buffer; + EnumSystemLocalesEx(enum_locales_fn, LOCALE_WINDOWS, (LPARAM) argv, + NULL); + } /* Locale names use only ASCII, any conversion locale suffices. */ - rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE], - sizeof(iso_lc_messages), NULL); - _free_locale(loct); + rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), + NULL); +#else /* _WIN32_WINNT < 0x0600 */ + _locale_t loct; + + loct = _create_locale(LC_CTYPE, winlocname); + if (loct != NULL) + { + rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE], + sizeof(iso_lc_messages), NULL); + _free_locale(loct); + } + else + rc = -1; +#endif /* _WIN32_WINNT >= 0x0600 */ + if (rc == -1 || rc == sizeof(iso_lc_messages)) - return NULL; + iso_lc_messages[0] = '\0'; /* * Since the message catalogs sit on a case-insensitive filesystem, we @@ -1027,9 +1125,10 @@ IsoLocaleName(const char *winlocname) hyphen = strchr(iso_lc_messages, '-'); if (hyphen) *hyphen = '_'; + elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", iso_lc_messages); return iso_lc_messages; } -#endif /* _MSC_VER */ +#endif /* defined(_MSC_VER) || _WIN32_WINNT >= 0x0600 */ return NULL; /* Not supported on this version of msvc/mingw */ } #endif /* WIN32 && LC_MESSAGES */