]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/pg_locale.c
Update copyright for the year 2010.
[postgresql] / src / backend / utils / adt / pg_locale.c
index 5c2e951be10d7846fc8b05fcaa438c90da6a50ba..a31c24ea44eb2f58a66510c7a7ae0f486c9bab87 100644 (file)
@@ -2,18 +2,18 @@
  *
  * PostgreSQL locale utilities
  *
- * Portions Copyright (c) 2002-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 2002-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.25 2004/01/19 19:04:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.51 2010/01/02 16:57:54 momjian Exp $
  *
  *-----------------------------------------------------------------------
  */
 
 /*----------
  * Here is how the locale stuff is handled: LC_COLLATE and LC_CTYPE
- * are fixed by initdb, stored in pg_control, and cannot be changed.
- * Thus, the effects of strcoll(), strxfrm(), isupper(), toupper(),
- * etc. are always in the same fixed locale.
+ * are fixed at CREATE DATABASE time, stored in pg_database, and cannot
+ * be changed. Thus, the effects of strcoll(), strxfrm(), isupper(),
+ * toupper(), etc. are always in the same fixed locale.
  *
  * LC_MESSAGES is settable at run time and will take effect
  * immediately.
@@ -26,8 +26,7 @@
  * required information obtained from localeconv(), and set them back.
  * The cached information is only used by the formatting functions
  * (to_char, etc.) and the money type. For the user, this should all be
- * transparent.  (Actually, LC_TIME doesn't do anything at all right
- * now.)
+ * transparent.
  *
  * !!! NOW HEAR THIS !!!
  *
 #include "postgres.h"
 
 #include <locale.h>
+#include <time.h>
 
+#include "catalog/pg_control.h"
+#include "mb/pg_wchar.h"
+#include "utils/memutils.h"
 #include "utils/pg_locale.h"
 
+#ifdef WIN32
+#include <shlwapi.h>
+#endif
 
-/* indicated whether locale information cache is valid */
-static bool CurrentLocaleConvValid = false;
-
+#define                MAX_L10N_DATA           80
 
-/* GUC storage area */
 
+/* GUC settings */
 char      *locale_messages;
 char      *locale_monetary;
 char      *locale_numeric;
 char      *locale_time;
 
+/* lc_time localization cache */
+char      *localized_abbrev_days[7];
+char      *localized_full_days[7];
+char      *localized_abbrev_months[12];
+char      *localized_full_months[12];
+
+/* indicates whether locale information cache is valid */
+static bool CurrentLocaleConvValid = false;
+static bool CurrentLCTimeValid = false;
+
+/* Environment variable storage area */
+
+#define LC_ENV_BUFSIZE (NAMEDATALEN + 20)
+
+static char lc_collate_envbuf[LC_ENV_BUFSIZE];
+static char lc_ctype_envbuf[LC_ENV_BUFSIZE];
+
+#ifdef LC_MESSAGES
+static char lc_messages_envbuf[LC_ENV_BUFSIZE];
+#endif
+static char lc_monetary_envbuf[LC_ENV_BUFSIZE];
+static char lc_numeric_envbuf[LC_ENV_BUFSIZE];
+static char lc_time_envbuf[LC_ENV_BUFSIZE];
+
+#if defined(WIN32) && defined(LC_MESSAGES)
+static char *IsoLocaleName(const char *);              /* MSVC specific */
+#endif
 
-/* GUC assign hooks */
 
 /*
- * This is common code for several locale categories.  This doesn't
- * actually set the locale permanently, it only tests if the locale is
- * valid.  (See explanation at the top of this file.)
+ * pg_perm_setlocale
+ *
+ * This is identical to the libc function setlocale(), with the addition
+ * that if the operation is successful, the corresponding LC_XXX environment
+ * variable is set to match.  By setting the environment variable, we ensure
+ * that any subsequent use of setlocale(..., "") will preserve the settings
+ * made through this routine.  Of course, LC_ALL must also be unset to fully
+ * ensure that, but that has to be done elsewhere after all the individual
+ * LC_XXX variables have been set correctly.  (Thank you Perl for making this
+ * kluge necessary.)
  */
-static const char *
-locale_xxx_assign(int category, const char *value, bool doit, GucSource source)
+char *
+pg_perm_setlocale(int category, const char *locale)
+{
+       char       *result;
+       const char *envvar;
+       char       *envbuf;
+
+#ifndef WIN32
+       result = setlocale(category, locale);
+#else
+
+       /*
+        * On Windows, setlocale(LC_MESSAGES) does not work, so just assume that
+        * the given value is good and set it in the environment variables. We
+        * must ignore attempts to set to "", which means "keep using the old
+        * environment value".
+        */
+#ifdef LC_MESSAGES
+       if (category == LC_MESSAGES)
+       {
+               result = (char *) locale;
+               if (locale == NULL || locale[0] == '\0')
+                       return result;
+       }
+       else
+#endif
+               result = setlocale(category, locale);
+#endif   /* WIN32 */
+
+       if (result == NULL)
+               return result;                  /* fall out immediately on failure */
+
+       switch (category)
+       {
+               case LC_COLLATE:
+                       envvar = "LC_COLLATE";
+                       envbuf = lc_collate_envbuf;
+                       break;
+               case LC_CTYPE:
+                       envvar = "LC_CTYPE";
+                       envbuf = lc_ctype_envbuf;
+                       break;
+#ifdef LC_MESSAGES
+               case LC_MESSAGES:
+                       envvar = "LC_MESSAGES";
+                       envbuf = lc_messages_envbuf;
+#ifdef WIN32
+                       result = IsoLocaleName(locale);
+                       if (result == NULL)
+                               result = (char *) locale;
+#endif   /* WIN32 */
+                       break;
+#endif   /* LC_MESSAGES */
+               case LC_MONETARY:
+                       envvar = "LC_MONETARY";
+                       envbuf = lc_monetary_envbuf;
+                       break;
+               case LC_NUMERIC:
+                       envvar = "LC_NUMERIC";
+                       envbuf = lc_numeric_envbuf;
+                       break;
+               case LC_TIME:
+                       envvar = "LC_TIME";
+                       envbuf = lc_time_envbuf;
+                       break;
+               default:
+                       elog(FATAL, "unrecognized LC category: %d", category);
+                       envvar = NULL;          /* keep compiler quiet */
+                       envbuf = NULL;
+                       return NULL;
+       }
+
+       snprintf(envbuf, LC_ENV_BUFSIZE - 1, "%s=%s", envvar, result);
+
+       if (putenv(envbuf))
+               return NULL;
+
+       return result;
+}
+
+
+/*
+ * Is the locale name valid for the locale category?
+ */
+bool
+check_locale(int category, const char *value)
 {
        char       *save;
+       bool            ret;
 
        save = setlocale(category, NULL);
        if (!save)
-               return NULL;                    /* won't happen, we hope */
+               return false;                   /* won't happen, we hope */
 
        /* save may be pointing at a modifiable scratch variable, see above */
        save = pstrdup(save);
 
-       if (!setlocale(category, value))
-               value = NULL;                   /* set failure return marker */
+       /* set the locale with setlocale, to see if it accepts it. */
+       ret = (setlocale(category, value) != NULL);
 
        setlocale(category, save);      /* assume this won't fail */
        pfree(save);
 
+       return ret;
+}
+
+/* GUC assign hooks */
+
+/*
+ * This is common code for several locale categories.  This doesn't
+ * actually set the locale permanently, it only tests if the locale is
+ * valid.  (See explanation at the top of this file.)
+ *
+ * Note: we accept value = "" as selecting the postmaster's environment
+ * value, whatever it was (so long as the environment setting is legal).
+ * This will have been locked down by an earlier call to pg_perm_setlocale.
+ */
+static const char *
+locale_xxx_assign(int category, const char *value, bool doit, GucSource source)
+{
+       if (!check_locale(category, value))
+               value = NULL;                   /* set failure return marker */
+
        /* need to reload cache next time? */
        if (doit && value != NULL)
+       {
                CurrentLocaleConvValid = false;
+               CurrentLCTimeValid = false;
+       }
 
        return value;
 }
@@ -119,23 +264,38 @@ locale_time_assign(const char *value, bool doit, GucSource source)
 
 /*
  * We allow LC_MESSAGES to actually be set globally.
+ *
+ * Note: we normally disallow value = "" because it wouldn't have consistent
+ * semantics (it'd effectively just use the previous value).  However, this
+ * is the value passed for PGC_S_DEFAULT, so don't complain in that case,
+ * not even if the attempted setting fails due to invalid environment value.
+ * The idea there is just to accept the environment setting *if possible*
+ * during startup, until we can read the proper value from postgresql.conf.
  */
 const char *
 locale_messages_assign(const char *value, bool doit, GucSource source)
 {
+       if (*value == '\0' && source != PGC_S_DEFAULT)
+               return NULL;
+
        /*
-        * LC_MESSAGES category does not exist everywhere, but accept it
-        * anyway
+        * LC_MESSAGES category does not exist everywhere, but accept it anyway
+        *
+        * On Windows, we can't even check the value, so the non-doit case is a
+        * no-op
         */
 #ifdef LC_MESSAGES
        if (doit)
        {
-               if (!setlocale(LC_MESSAGES, value))
-                       return NULL;
+               if (!pg_perm_setlocale(LC_MESSAGES, value))
+                       if (source != PGC_S_DEFAULT)
+                               return NULL;
        }
+#ifndef WIN32
        else
                value = locale_xxx_assign(LC_MESSAGES, value, false, source);
-#endif
+#endif   /* WIN32 */
+#endif   /* LC_MESSAGES */
        return value;
 }
 
@@ -167,6 +327,33 @@ lc_collate_is_c(void)
 }
 
 
+/*
+ * We'd like to cache whether LC_CTYPE is C (or POSIX), so we can
+ * optimize a few code paths in various places.
+ */
+bool
+lc_ctype_is_c(void)
+{
+       /* Cache result so we only have to compute it once */
+       static int      result = -1;
+       char       *localeptr;
+
+       if (result >= 0)
+               return (bool) result;
+       localeptr = setlocale(LC_CTYPE, NULL);
+       if (!localeptr)
+               elog(ERROR, "invalid LC_CTYPE setting");
+
+       if (strcmp(localeptr, "C") == 0)
+               result = true;
+       else if (strcmp(localeptr, "POSIX") == 0)
+               result = true;
+       else
+               result = false;
+       return (bool) result;
+}
+
+
 /*
  * Frees the malloced content of a struct lconv.  (But not the struct
  * itself.)
@@ -233,8 +420,8 @@ PGLC_localeconv(void)
        extlconv = localeconv();
 
        /*
-        * Must copy all values since restoring internal settings may
-        * overwrite localeconv()'s results.
+        * Must copy all values since restoring internal settings may overwrite
+        * localeconv()'s results.
         */
        CurrentLocaleConv = *extlconv;
        CurrentLocaleConv.currency_symbol = strdup(extlconv->currency_symbol);
@@ -265,3 +452,207 @@ PGLC_localeconv(void)
        CurrentLocaleConvValid = true;
        return &CurrentLocaleConv;
 }
+
+#ifdef WIN32
+/*
+ * On win32, strftime() returns the encoding in CP_ACP, which is likely
+ * different from SERVER_ENCODING. This is especially important in Japanese
+ * versions of Windows which will use SJIS encoding, which we don't support
+ * as a server encoding.
+ *
+ * Replace strftime() with a version that gets the string in UTF16 and then
+ * converts it to the appropriate encoding as necessary.
+ *
+ * Note that this only affects the calls to strftime() in this file, which are
+ * used to get the locale-aware strings. Other parts of the backend use
+ * pg_strftime(), which isn't locale-aware and does not need to be replaced.
+ */
+static size_t
+strftime_win32(char *dst, size_t dstlen, const wchar_t *format, const struct tm * tm)
+{
+       size_t          len;
+       wchar_t         wbuf[MAX_L10N_DATA];
+       int                     encoding;
+
+       encoding = GetDatabaseEncoding();
+
+       len = wcsftime(wbuf, MAX_L10N_DATA, format, tm);
+       if (len == 0)
+
+               /*
+                * strftime call failed - return 0 with the contents of dst
+                * unspecified
+                */
+               return 0;
+
+       len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, dst, dstlen, NULL, NULL);
+       if (len == 0)
+               elog(ERROR,
+                        "could not convert string to UTF-8:error %lu", GetLastError());
+
+       dst[len] = '\0';
+       if (encoding != PG_UTF8)
+       {
+               char       *convstr = pg_do_encoding_conversion(dst, len, PG_UTF8, encoding);
+
+               if (dst != convstr)
+               {
+                       strlcpy(dst, convstr, dstlen);
+                       len = strlen(dst);
+               }
+       }
+
+       return len;
+}
+
+#define strftime(a,b,c,d) strftime_win32(a,b,L##c,d)
+#endif   /* WIN32 */
+
+
+/*
+ * Update the lc_time localization cache variables if needed.
+ */
+void
+cache_locale_time(void)
+{
+       char       *save_lc_time;
+       time_t          timenow;
+       struct tm  *timeinfo;
+       char            buf[MAX_L10N_DATA];
+       char       *ptr;
+       int                     i;
+
+#ifdef WIN32
+       char       *save_lc_ctype;
+#endif
+
+       /* did we do this already? */
+       if (CurrentLCTimeValid)
+               return;
+
+       elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time);
+
+#ifdef WIN32
+       /* set user's value of ctype locale */
+       save_lc_ctype = setlocale(LC_CTYPE, NULL);
+       if (save_lc_ctype)
+               save_lc_ctype = pstrdup(save_lc_ctype);
+
+       setlocale(LC_CTYPE, locale_time);
+#endif
+
+       /* set user's value of time locale */
+       save_lc_time = setlocale(LC_TIME, NULL);
+       if (save_lc_time)
+               save_lc_time = pstrdup(save_lc_time);
+
+       setlocale(LC_TIME, locale_time);
+
+       timenow = time(NULL);
+       timeinfo = localtime(&timenow);
+
+       /* localized days */
+       for (i = 0; i < 7; i++)
+       {
+               timeinfo->tm_wday = i;
+               strftime(buf, MAX_L10N_DATA, "%a", timeinfo);
+               ptr = MemoryContextStrdup(TopMemoryContext, buf);
+               if (localized_abbrev_days[i])
+                       pfree(localized_abbrev_days[i]);
+               localized_abbrev_days[i] = ptr;
+
+               strftime(buf, MAX_L10N_DATA, "%A", timeinfo);
+               ptr = MemoryContextStrdup(TopMemoryContext, buf);
+               if (localized_full_days[i])
+                       pfree(localized_full_days[i]);
+               localized_full_days[i] = ptr;
+       }
+
+       /* localized months */
+       for (i = 0; i < 12; i++)
+       {
+               timeinfo->tm_mon = i;
+               timeinfo->tm_mday = 1;  /* make sure we don't have invalid date */
+               strftime(buf, MAX_L10N_DATA, "%b", timeinfo);
+               ptr = MemoryContextStrdup(TopMemoryContext, buf);
+               if (localized_abbrev_months[i])
+                       pfree(localized_abbrev_months[i]);
+               localized_abbrev_months[i] = ptr;
+
+               strftime(buf, MAX_L10N_DATA, "%B", timeinfo);
+               ptr = MemoryContextStrdup(TopMemoryContext, buf);
+               if (localized_full_months[i])
+                       pfree(localized_full_months[i]);
+               localized_full_months[i] = ptr;
+       }
+
+       /* try to restore internal settings */
+       if (save_lc_time)
+       {
+               setlocale(LC_TIME, save_lc_time);
+               pfree(save_lc_time);
+       }
+
+#ifdef WIN32
+       /* try to restore internal ctype settings */
+       if (save_lc_ctype)
+       {
+               setlocale(LC_CTYPE, save_lc_ctype);
+               pfree(save_lc_ctype);
+       }
+#endif
+
+       CurrentLCTimeValid = true;
+}
+
+
+#if defined(WIN32) && defined(LC_MESSAGES)
+/*
+ *     Convert Windows locale name to the ISO formatted one
+ *     if possible.
+ *
+ *     This function returns NULL if conversion is impossible,
+ *     otherwise returns the pointer to a static area which
+ *     contains the iso formatted locale name.
+ */
+static
+char *
+IsoLocaleName(const char *winlocname)
+{
+#if (_MSC_VER >= 1400)                 /* VC8.0 or later */
+       static char iso_lc_messages[32];
+       _locale_t       loct = NULL;
+
+       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)
+       {
+               char            isolang[32],
+                                       isocrty[32];
+               LCID            lcid;
+
+               lcid = loct->locinfo->lc_handle[LC_CTYPE];
+               if (lcid == 0)
+                       lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
+               _free_locale(loct);
+
+               if (!GetLocaleInfoA(lcid, LOCALE_SISO639LANGNAME, isolang, sizeof(isolang)))
+                       return NULL;
+               if (!GetLocaleInfoA(lcid, LOCALE_SISO3166CTRYNAME, isocrty, sizeof(isocrty)))
+                       return NULL;
+               snprintf(iso_lc_messages, sizeof(iso_lc_messages) - 1, "%s_%s", isolang, isocrty);
+               return iso_lc_messages;
+       }
+       return NULL;
+#else
+       return NULL;                            /* Not supported on this version of msvc/mingw */
+#endif   /* _MSC_VER >= 1400 */
+}
+
+#endif   /* WIN32 && LC_MESSAGES */