Day and month name localization uses wrong locale category

Started by Peter Eisentrautabout 19 years ago26 messages
#1Peter Eisentraut
peter_e@gmx.net

In 8.2, utils/adt/formatting.c uses our NLS mechanism to localize day and
month names (I assume for use by to_char). But since this necessarily ties
the outcome to the LC_MESSAGES setting, this comes out inconsistently with
Unix locale behavior, e.g.,

pei@bell:~$ locale
LANG=
LC_CTYPE="POSIX"
LC_NUMERIC="POSIX"
LC_TIME="POSIX"
LC_COLLATE="POSIX"
LC_MONETARY="POSIX"
LC_MESSAGES="POSIX"
LC_PAPER="POSIX"
LC_NAME="POSIX"
LC_ADDRESS="POSIX"
LC_TELEPHONE="POSIX"
LC_MEASUREMENT="POSIX"
LC_IDENTIFICATION="POSIX"
LC_ALL=

pei@bell:~$ date +%A
Friday

pei@bell:~$ LC_MESSAGES=de_DE@euro date +%A
Friday

pei@bell:~$ LC_TIME=de_DE@euro date +%A
Freitag

Is there no API to get the localized names from the C library so that LC_TIME
takes effect?

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

In reply to: Peter Eisentraut (#1)
Re: Day and month name localization uses wrong locale category

Peter Eisentraut wrote:

pei@bell:~$ date +%A
Friday

pei@bell:~$ LC_MESSAGES=de_DE@euro date +%A
Friday

pei@bell:~$ LC_TIME=de_DE@euro date +%A
Freitag

Is there no API to get the localized names from the C library so that LC_TIME
takes effect?

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

PS> I was thinking about changing this for the same reasons Peter
pointed out; I didn't have the time to do it before.

--
Euler Taveira de Oliveira
http://www.timbira.com/

#3Peter Eisentraut
peter_e@gmx.net
In reply to: Euler Taveira de Oliveira (#2)
Re: Day and month name localization uses wrong locale category

Euler Taveira de Oliveira wrote:

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

How is this going?

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

In reply to: Peter Eisentraut (#3)
1 attachment(s)
Re: Day and month name localization uses wrong locale category

Peter Eisentraut wrote:

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

How is this going?

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

template1=# show lc_time;
lc_time
---------
pt_BR
(1 registro)

template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
---------------------------
Segunda, 20 Novembro 2006
(1 registro)

template1=# set lc_time to 'C';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Monday, 20 November 2006
(1 registro)

template1=# set lc_time to 'de_DE';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Montag, 20 November 2006
(1 registro)

template1=#

Comments?

--
Euler Taveira de Oliveira
http://www.timbira.com/

Attachments:

tm.difftext/plain; charset=us-asciiDownload
*** ./src/backend/utils/adt/formatting.c.orig	2006-11-18 16:22:59.000000000 -0200
--- ./src/backend/utils/adt/formatting.c	2006-11-20 21:50:02.000000000 -0200
***************
*** 73,79 ****
  #include <unistd.h>
  #include <math.h>
  #include <float.h>
! #include <locale.h>
  
  #include "utils/builtins.h"
  #include "utils/date.h"
--- 73,79 ----
  #include <unistd.h>
  #include <math.h>
  #include <float.h>
! #include <time.h>
  
  #include "utils/builtins.h"
  #include "utils/date.h"
***************
*** 83,90 ****
  #include "utils/numeric.h"
  #include "utils/pg_locale.h"
  
- #define _(x)	gettext((x))
- 
  /* ----------
   * Routines type
   * ----------
--- 83,88 ----
***************
*** 163,169 ****
  
  /* ----------
   * Full months
-  *	This needs to be NLS-localized someday.
   * ----------
   */
  static char *months_full[] = {
--- 161,166 ----
***************
*** 942,951 ****
  static NUMCacheEntry *NUM_cache_getnew(char *str);
  static void NUM_cache_remove(NUMCacheEntry *ent);
  
- static char *localize_month_full(int index);
- static char *localize_month(int index);
- static char *localize_day_full(int index);
- static char *localize_day(int index);
  
  /* ----------
   * Fast sequential search, use index for data selection which
--- 939,944 ----
***************
*** 2074,2079 ****
--- 2067,2085 ----
  	struct pg_tm *tm = NULL;
  	TmFromChar *tmfc = NULL;
  	TmToChar   *tmtc = NULL;
+ 	char		*save_loc = NULL;
+ 	char		*lct = NULL;
+ 
+ 	/*
+ 	 * Set the LC_TIME only to do some operation (strftime) and then 
+ 	 * set it back. See pg_locale.c for explanations.
+ 	 */
+ 	if (S_TM(suf))
+ 	{
+ 		save_loc = setlocale(LC_TIME, NULL);
+ 		lct = pg_get_lc_time();
+ 		setlocale(LC_TIME, lct);
+ 	}
  
  	if (is_to_char)
  	{
***************
*** 2189,2197 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
  			else
  				strcpy(workbuff, months_full[tm->tm_mon - 1]);
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
--- 2195,2214 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(workbuff, sizeof(workbuff), "%B", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(workbuff, months_full[tm->tm_mon - 1]);
+ 			}
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
***************
*** 2200,2208 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
  			return strlen(p_inout);
  
  		case DCH_month:
--- 2217,2238 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%B", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_month:
***************
*** 2210,2218 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2240,2259 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%B", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2221,2229 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			str_toupper(inout);
  			return strlen(p_inout);
  
--- 2262,2281 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			str_toupper(inout);
  			return strlen(p_inout);
  
***************
*** 2232,2240 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			return strlen(p_inout);
  
  		case DCH_mon:
--- 2284,2305 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_mon:
***************
*** 2242,2250 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2307,2326 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2273,2324 ****
  		case DCH_DAY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(workbuff, localize_day_full(tm->tm_wday));
  			else
  				strcpy(workbuff, days[tm->tm_wday]);
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
  		case DCH_Day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
  			return strlen(p_inout);
  
  		case DCH_day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
  		case DCH_DY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			str_toupper(inout);
  			return strlen(p_inout);
  
  		case DCH_Dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			return strlen(p_inout);
  
  		case DCH_dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2349,2440 ----
  		case DCH_DAY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(workbuff, sizeof(workbuff), "%A", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(workbuff, days[tm->tm_wday]);
+ 			}
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
  		case DCH_Day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%A", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%A", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
  		case DCH_DY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			str_toupper(inout);
  			return strlen(p_inout);
  
  		case DCH_Dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2860,3026 ****
  	return res;
  }
  
- static char *
- localize_month_full(int index)
- {
- 	char	   *m = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			m = _("January");
- 			break;
- 		case 1:
- 			m = _("February");
- 			break;
- 		case 2:
- 			m = _("March");
- 			break;
- 		case 3:
- 			m = _("April");
- 			break;
- 		case 4:
- 			m = _("May");
- 			break;
- 		case 5:
- 			m = _("June");
- 			break;
- 		case 6:
- 			m = _("July");
- 			break;
- 		case 7:
- 			m = _("August");
- 			break;
- 		case 8:
- 			m = _("September");
- 			break;
- 		case 9:
- 			m = _("October");
- 			break;
- 		case 10:
- 			m = _("November");
- 			break;
- 		case 11:
- 			m = _("December");
- 			break;
- 	}
- 
- 	return m;
- }
- 
- static char *
- localize_month(int index)
- {
- 	char	   *m = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			m = _("Jan");
- 			break;
- 		case 1:
- 			m = _("Feb");
- 			break;
- 		case 2:
- 			m = _("Mar");
- 			break;
- 		case 3:
- 			m = _("Apr");
- 			break;
- 		case 4:
- 			m = _("May");
- 			break;
- 		case 5:
- 			m = _("Jun");
- 			break;
- 		case 6:
- 			m = _("Jul");
- 			break;
- 		case 7:
- 			m = _("Aug");
- 			break;
- 		case 8:
- 			m = _("Sep");
- 			break;
- 		case 9:
- 			m = _("Oct");
- 			break;
- 		case 10:
- 			m = _("Nov");
- 			break;
- 		case 11:
- 			m = _("Dec");
- 			break;
- 	}
- 
- 	return m;
- }
- 
- static char *
- localize_day_full(int index)
- {
- 	char	   *d = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			d = _("Sunday");
- 			break;
- 		case 1:
- 			d = _("Monday");
- 			break;
- 		case 2:
- 			d = _("Tuesday");
- 			break;
- 		case 3:
- 			d = _("Wednesday");
- 			break;
- 		case 4:
- 			d = _("Thursday");
- 			break;
- 		case 5:
- 			d = _("Friday");
- 			break;
- 		case 6:
- 			d = _("Saturday");
- 			break;
- 	}
- 
- 	return d;
- }
- 
- static char *
- localize_day(int index)
- {
- 	char	   *d = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			d = _("Sun");
- 			break;
- 		case 1:
- 			d = _("Mon");
- 			break;
- 		case 2:
- 			d = _("Tue");
- 			break;
- 		case 3:
- 			d = _("Wed");
- 			break;
- 		case 4:
- 			d = _("Thu");
- 			break;
- 		case 5:
- 			d = _("Fri");
- 			break;
- 		case 6:
- 			d = _("Sat");
- 			break;
- 	}
- 
- 	return d;
- }
  
  /****************************************************************************
   *				Public routines
--- 2976,2981 ----
*** ./src/backend/utils/adt/pg_locale.c.orig	2006-11-19 21:13:10.000000000 -0200
--- ./src/backend/utils/adt/pg_locale.c	2006-11-20 21:33:34.000000000 -0200
***************
*** 26,33 ****
   * 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.)
   *
   * !!! NOW HEAR THIS !!!
   *
--- 26,32 ----
   * 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.
   *
   * !!! NOW HEAR THIS !!!
   *
***************
*** 425,427 ****
--- 424,435 ----
  	CurrentLocaleConvValid = true;
  	return &CurrentLocaleConv;
  }
+ 
+ /*
+  * Return the LC_TIME information
+  */
+ char *
+ pg_get_lc_time(void)
+ {
+ 	return locale_time;
+ }
*** ./src/include/utils/pg_locale.h.orig	2006-11-19 22:07:21.000000000 -0200
--- ./src/include/utils/pg_locale.h	2006-11-19 22:08:22.000000000 -0200
***************
*** 42,45 ****
--- 42,47 ----
   */
  extern struct lconv *PGLC_localeconv(void);
  
+ extern char *pg_get_lc_time(void);
+ 
  #endif   /* _PG_LOCALE_ */
#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira de Oliveira (#4)
Re: Day and month name localization uses wrong locale category

Euler Taveira de Oliveira <euler@timbira.com> writes:

+ /*
+  * Return the LC_TIME information
+  */
+ char *
+ pg_get_lc_time(void)
+ {
+ 	return locale_time;
+ }

locale_time is a global GUC variable, so there is surely no point in the
above function. I have not looked at the rest of the patch.

regards, tom lane

In reply to: Tom Lane (#5)
Re: Day and month name localization uses wrong locale category

Tom Lane wrote:

+ /*
+  * Return the LC_TIME information
+  */
+ char *
+ pg_get_lc_time(void)
+ {
+ 	return locale_time;
+ }

locale_time is a global GUC variable, so there is surely no point in the
above function. I have not looked at the rest of the patch.

I know that. If I didn't use it how could i know what is the current
LC_TIME setting? The LC_TIME in backend is always C so I need to change
it to xx_XX briefly, do the job (strftime) and then get it back to C. Am
I wrong? That's what I do.

--
Euler Taveira de Oliveira
http://www.timbira.com/

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira de Oliveira (#6)
Re: Day and month name localization uses wrong locale category

Euler Taveira de Oliveira <euler@timbira.com> writes:

Tom Lane wrote:

locale_time is a global GUC variable, so there is surely no point in the
above function. I have not looked at the rest of the patch.

I know that. If I didn't use it how could i know what is the current
LC_TIME setting?

You just look at the variable directly. While there's sometimes value
in an encapsulation function, I fail to see any here.

regards, tom lane

In reply to: Tom Lane (#7)
1 attachment(s)
Re: Day and month name localization uses wrong locale category

Tom Lane wrote:

You just look at the variable directly. While there's sometimes value
in an encapsulation function, I fail to see any here.

Oh, my :-) That's the consequence to not sleep at least a little at
night.
The attached patch, corrects what was pointed out by Tom (thanks).

PS> going to bed right now :-)

--
Euler Taveira de Oliveira
http://www.timbira.com/

Attachments:

tm2.difftext/plain; charset=us-asciiDownload
*** ./src/backend/utils/adt/formatting.c.orig	2006-11-20 23:47:32.000000000 -0200
--- ./src/backend/utils/adt/formatting.c	2006-11-21 00:05:53.000000000 -0200
***************
*** 73,79 ****
  #include <unistd.h>
  #include <math.h>
  #include <float.h>
! #include <locale.h>
  
  #include "utils/builtins.h"
  #include "utils/date.h"
--- 73,79 ----
  #include <unistd.h>
  #include <math.h>
  #include <float.h>
! #include <time.h>
  
  #include "utils/builtins.h"
  #include "utils/date.h"
***************
*** 83,90 ****
  #include "utils/numeric.h"
  #include "utils/pg_locale.h"
  
- #define _(x)	gettext((x))
- 
  /* ----------
   * Routines type
   * ----------
--- 83,88 ----
***************
*** 163,169 ****
  
  /* ----------
   * Full months
-  *	This needs to be NLS-localized someday.
   * ----------
   */
  static char *months_full[] = {
--- 161,166 ----
***************
*** 942,951 ****
  static NUMCacheEntry *NUM_cache_getnew(char *str);
  static void NUM_cache_remove(NUMCacheEntry *ent);
  
- static char *localize_month_full(int index);
- static char *localize_month(int index);
- static char *localize_day_full(int index);
- static char *localize_day(int index);
  
  /* ----------
   * Fast sequential search, use index for data selection which
--- 939,944 ----
***************
*** 2074,2079 ****
--- 2067,2083 ----
  	struct pg_tm *tm = NULL;
  	TmFromChar *tmfc = NULL;
  	TmToChar   *tmtc = NULL;
+ 	char       *save_loc = NULL;
+ 
+ 	/*
+ 	 * Set the LC_TIME only to do some operation (strftime) and then 
+ 	 * set it back. See pg_locale.c for explanations.
+ 	 */
+ 	if (S_TM(suf))
+ 	{
+ 		save_loc = setlocale(LC_TIME, NULL);
+ 		setlocale(LC_TIME, locale_time);
+ 	}
  
  	if (is_to_char)
  	{
***************
*** 2189,2197 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
  			else
  				strcpy(workbuff, months_full[tm->tm_mon - 1]);
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
--- 2193,2212 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(workbuff, sizeof(workbuff), "%B", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(workbuff, months_full[tm->tm_mon - 1]);
+ 			}
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
***************
*** 2200,2208 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
  			return strlen(p_inout);
  
  		case DCH_month:
--- 2215,2236 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%B", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_month:
***************
*** 2210,2218 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2238,2257 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%B", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2221,2229 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			str_toupper(inout);
  			return strlen(p_inout);
  
--- 2260,2279 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			str_toupper(inout);
  			return strlen(p_inout);
  
***************
*** 2232,2240 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			return strlen(p_inout);
  
  		case DCH_mon:
--- 2282,2303 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_mon:
***************
*** 2242,2250 ****
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
  			else
  				strcpy(inout, months[tm->tm_mon - 1]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2305,2324 ----
  			if (!tm->tm_mon)
  				return -1;
  			if (S_TM(suf))
! 			{
! 				/*
! 				 * tm_mon in 'pg_tm struct' based on one, but rather POSIX 'tm struct' based on zero.
! 				 * See notes at the top of this file.
! 				 */
! 				tm->tm_mon = tm->tm_mon - 1;
! 				strftime(inout, 32, "%b", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, months[tm->tm_mon - 1]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2273,2324 ****
  		case DCH_DAY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(workbuff, localize_day_full(tm->tm_wday));
  			else
  				strcpy(workbuff, days[tm->tm_wday]);
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
  		case DCH_Day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
  			return strlen(p_inout);
  
  		case DCH_day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));
  			else
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
  		case DCH_DY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			str_toupper(inout);
  			return strlen(p_inout);
  
  		case DCH_Dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			return strlen(p_inout);
  
  		case DCH_dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
  			else
  				strcpy(inout, days_short[tm->tm_wday]);
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
--- 2347,2438 ----
  		case DCH_DAY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(workbuff, sizeof(workbuff), "%A", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(workbuff, days[tm->tm_wday]);
+ 			}
  			sprintf(inout, "%*s", (S_FM(suf) || S_TM(suf)) ? 0 : -9, str_toupper(workbuff));
  			return strlen(p_inout);
  
  		case DCH_Day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%A", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_day:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%A", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
  		case DCH_DY:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			str_toupper(inout);
  			return strlen(p_inout);
  
  		case DCH_Dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* capitalize output */
! 				inout[0] = pg_toupper((unsigned char) inout[0]);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			return strlen(p_inout);
  
  		case DCH_dy:
  			INVALID_FOR_INTERVAL;
  			if (S_TM(suf))
! 			{
! 				strftime(inout, 32, "%a", (struct tm *) tm);
! 				/* set it back; see comments in pg_locale.c */
! 				setlocale(LC_TIME, save_loc);
! 			}
  			else
+ 			{
  				strcpy(inout, days_short[tm->tm_wday]);
+ 			}
  			*inout = pg_tolower((unsigned char) *inout);
  			return strlen(p_inout);
  
***************
*** 2860,3026 ****
  	return res;
  }
  
- static char *
- localize_month_full(int index)
- {
- 	char	   *m = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			m = _("January");
- 			break;
- 		case 1:
- 			m = _("February");
- 			break;
- 		case 2:
- 			m = _("March");
- 			break;
- 		case 3:
- 			m = _("April");
- 			break;
- 		case 4:
- 			m = _("May");
- 			break;
- 		case 5:
- 			m = _("June");
- 			break;
- 		case 6:
- 			m = _("July");
- 			break;
- 		case 7:
- 			m = _("August");
- 			break;
- 		case 8:
- 			m = _("September");
- 			break;
- 		case 9:
- 			m = _("October");
- 			break;
- 		case 10:
- 			m = _("November");
- 			break;
- 		case 11:
- 			m = _("December");
- 			break;
- 	}
- 
- 	return m;
- }
- 
- static char *
- localize_month(int index)
- {
- 	char	   *m = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			m = _("Jan");
- 			break;
- 		case 1:
- 			m = _("Feb");
- 			break;
- 		case 2:
- 			m = _("Mar");
- 			break;
- 		case 3:
- 			m = _("Apr");
- 			break;
- 		case 4:
- 			m = _("May");
- 			break;
- 		case 5:
- 			m = _("Jun");
- 			break;
- 		case 6:
- 			m = _("Jul");
- 			break;
- 		case 7:
- 			m = _("Aug");
- 			break;
- 		case 8:
- 			m = _("Sep");
- 			break;
- 		case 9:
- 			m = _("Oct");
- 			break;
- 		case 10:
- 			m = _("Nov");
- 			break;
- 		case 11:
- 			m = _("Dec");
- 			break;
- 	}
- 
- 	return m;
- }
- 
- static char *
- localize_day_full(int index)
- {
- 	char	   *d = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			d = _("Sunday");
- 			break;
- 		case 1:
- 			d = _("Monday");
- 			break;
- 		case 2:
- 			d = _("Tuesday");
- 			break;
- 		case 3:
- 			d = _("Wednesday");
- 			break;
- 		case 4:
- 			d = _("Thursday");
- 			break;
- 		case 5:
- 			d = _("Friday");
- 			break;
- 		case 6:
- 			d = _("Saturday");
- 			break;
- 	}
- 
- 	return d;
- }
- 
- static char *
- localize_day(int index)
- {
- 	char	   *d = NULL;
- 
- 	switch (index)
- 	{
- 		case 0:
- 			d = _("Sun");
- 			break;
- 		case 1:
- 			d = _("Mon");
- 			break;
- 		case 2:
- 			d = _("Tue");
- 			break;
- 		case 3:
- 			d = _("Wed");
- 			break;
- 		case 4:
- 			d = _("Thu");
- 			break;
- 		case 5:
- 			d = _("Fri");
- 			break;
- 		case 6:
- 			d = _("Sat");
- 			break;
- 	}
- 
- 	return d;
- }
  
  /****************************************************************************
   *				Public routines
--- 2974,2979 ----
*** ./src/backend/utils/adt/pg_locale.c.orig	2006-11-21 00:07:30.000000000 -0200
--- ./src/backend/utils/adt/pg_locale.c	2006-11-21 00:07:57.000000000 -0200
***************
*** 26,33 ****
   * 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.)
   *
   * !!! NOW HEAR THIS !!!
   *
--- 26,32 ----
   * 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.
   *
   * !!! NOW HEAR THIS !!!
   *
#9Bruce Momjian
bruce@momjian.us
In reply to: Euler Taveira de Oliveira (#8)
Re: Day and month name localization uses wrong

Is this for 8.2?

---------------------------------------------------------------------------

Euler Taveira de Oliveira wrote:

Tom Lane wrote:

You just look at the variable directly. While there's sometimes value
in an encapsulation function, I fail to see any here.

Oh, my :-) That's the consequence to not sleep at least a little at
night.
The attached patch, corrects what was pointed out by Tom (thanks).

PS> going to bed right now :-)

--
Euler Taveira de Oliveira
http://www.timbira.com/

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

In reply to: Bruce Momjian (#9)
Re: Day and month name localization uses wrong locale category

Bruce Momjian wrote:

Is this for 8.2?

This patch "fixes" (reimplements) a feature that was written for 8.2. So
I think it's a must-fix. That patch is not so huge or invasive.
Comments?

--
Euler Taveira de Oliveira
http://www.timbira.com/

#11Bruce Momjian
bruce@momjian.us
In reply to: Euler Taveira de Oliveira (#10)
Re: Day and month name localization uses wrong

Euler Taveira de Oliveira wrote:

Bruce Momjian wrote:

Is this for 8.2?

This patch "fixes" (reimplements) a feature that was written for 8.2. So
I think it's a must-fix. That patch is not so huge or invasive.
Comments?

Agreed, patch applied:

Fix to_char() locale handling to honor LC_TIME, not LC_MESSAGES.

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Euler Taveira de Oliveira (#10)
Re: Day and month name localization uses wrong locale category

Euler Taveira de Oliveira <euler@timbira.com> writes:

Bruce Momjian wrote:

Is this for 8.2?

This patch "fixes" (reimplements) a feature that was written for 8.2. So
I think it's a must-fix. That patch is not so huge or invasive.
Comments?

Exactly how bad could the consequences get if someone sets LC_TIME to a
value not encoding-compatible with the database encoding? One of the
reasons LC_MESSAGES is superuser-only is that you can PANIC the backend
by choosing an incompatible value --- will that happen now for LC_TIME
too?

I think it might be OK, because the reason for the PANIC in the bogus
message case is that the encoding-violation error happens recursively
inside error processing, and that shouldn't need to happen here. But
one thing we'll need to be damn sure of is that control can't get into
elog.c while we've got LC_TIME set to a non-C value, else the same
recursion scenario could occur due to log_line_prefix expansion.

regards, tom lane

#13Peter Eisentraut
peter_e@gmx.net
In reply to: Euler Taveira de Oliveira (#4)
Re: Day and month name localization uses wrong locale category

Am Dienstag, 21. November 2006 00:52 schrieb Euler Taveira de Oliveira:

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

What's concerning me about the way this is written is that it calls
setlocale() for each formatting instance, which will be very slow.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#13)
Re: Day and month name localization uses wrong locale category

Peter Eisentraut <peter_e@gmx.net> writes:

What's concerning me about the way this is written is that it calls
setlocale() for each formatting instance, which will be very slow.

Perhaps, the first time the info is needed, do setlocale(), ask strftime
for the 12+7 strings we need and save them away, then revert to C locale
and proceed from there.

regards, tom lane

#15Bruce Momjian
bruce@momjian.us
In reply to: Peter Eisentraut (#13)
Re: Day and month name localization uses wrong

Peter Eisentraut wrote:

Am Dienstag, 21. November 2006 00:52 schrieb Euler Taveira de Oliveira:

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

What's concerning me about the way this is written is that it calls
setlocale() for each formatting instance, which will be very slow.

Should we have it set from a guc hook on lc_time?

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#16Bruce Momjian
bruce@momjian.us
In reply to: Euler Taveira de Oliveira (#4)
2 attachment(s)
Re: Day and month name localization uses wrong

It is too close to the RC1 release to apply this patch. I have added
documentation that "TM"'s locale is controlled by "lc_messages".

This has been saved for the 8.3 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Euler Taveira de Oliveira wrote:

Peter Eisentraut wrote:

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

How is this going?

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

template1=# show lc_time;
lc_time
---------
pt_BR
(1 registro)

template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
---------------------------
Segunda, 20 Novembro 2006
(1 registro)

template1=# set lc_time to 'C';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Monday, 20 November 2006
(1 registro)

template1=# set lc_time to 'de_DE';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Montag, 20 November 2006
(1 registro)

template1=#

Comments?

--
Euler Taveira de Oliveira
http://www.timbira.com/

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

Attachments:

/bjm/difftext/x-diffDownload
Index: doc/src/sgml/func.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/func.sgml,v
retrieving revision 1.346
diff -c -c -r1.346 func.sgml
*** doc/src/sgml/func.sgml	20 Nov 2006 20:20:18 -0000	1.346
--- doc/src/sgml/func.sgml	24 Nov 2006 23:18:59 -0000
***************
*** 4689,4695 ****
         </row>   
         <row>
          <entry><literal>TM</literal> prefix</entry>
!         <entry>translation mode (print localized day and month names)</entry>
          <entry><literal>TMMonth</literal></entry>
         </row>       
         <row>
--- 4689,4695 ----
         </row>   
         <row>
          <entry><literal>TM</literal> prefix</entry>
!         <entry>translation mode (print localized day and month names based on <varname>lc_messages</>)</entry>
          <entry><literal>TMMonth</literal></entry>
         </row>       
         <row>
/bjm/difftext/x-diffDownload
Index: doc/src/sgml/func.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/func.sgml,v
retrieving revision 1.346
diff -c -c -r1.346 func.sgml
*** doc/src/sgml/func.sgml	20 Nov 2006 20:20:18 -0000	1.346
--- doc/src/sgml/func.sgml	24 Nov 2006 23:18:59 -0000
***************
*** 4689,4695 ****
         </row>   
         <row>
          <entry><literal>TM</literal> prefix</entry>
!         <entry>translation mode (print localized day and month names)</entry>
          <entry><literal>TMMonth</literal></entry>
         </row>       
         <row>
--- 4689,4695 ----
         </row>   
         <row>
          <entry><literal>TM</literal> prefix</entry>
!         <entry>translation mode (print localized day and month names based on <varname>lc_messages</>)</entry>
          <entry><literal>TMMonth</literal></entry>
         </row>       
         <row>
#17Bruce Momjian
bruce@momjian.us
In reply to: Euler Taveira de Oliveira (#4)
Re: Day and month name localization uses wrong

I now remember a new problem with this feature, irregardless of whether
we use 'lc_messages' or 'lc_time'.

The problem is having a function's output affected by a GUC variable.
If you create an expression index using the function, and later query
the index with a different GUC value, or you do inserts with different
GUC values, the index will not work.

I know we have had this problem in the past, but I can't remember if or
how we addressed it.

---------------------------------------------------------------------------

Euler Taveira de Oliveira wrote:

Peter Eisentraut wrote:

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

How is this going?

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

template1=# show lc_time;
lc_time
---------
pt_BR
(1 registro)

template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
---------------------------
Segunda, 20 Novembro 2006
(1 registro)

template1=# set lc_time to 'C';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Monday, 20 November 2006
(1 registro)

template1=# set lc_time to 'de_DE';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Montag, 20 November 2006
(1 registro)

template1=#

Comments?

--
Euler Taveira de Oliveira
http://www.timbira.com/

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#18Alvaro Herrera
alvherre@commandprompt.com
In reply to: Bruce Momjian (#17)
Re: Day and month name localization uses wrong

Bruce Momjian wrote:

I now remember a new problem with this feature, irregardless of whether
we use 'lc_messages' or 'lc_time'.

The problem is having a function's output affected by a GUC variable.
If you create an expression index using the function, and later query
the index with a different GUC value, or you do inserts with different
GUC values, the index will not work.

I know we have had this problem in the past, but I can't remember if or
how we addressed it.

Mark the function as volatile, which precludes from using it in a
functional index?

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#18)
Re: Day and month name localization uses wrong

Alvaro Herrera <alvherre@commandprompt.com> writes:

Bruce Momjian wrote:

The problem is having a function's output affected by a GUC variable.

Mark the function as volatile, which precludes from using it in a
functional index?

Stable, not volatile.

It looks like we have some of the variants marked stable already for
their dependence on TimeZone, but the dependence on lc_messages is new.

regards, tom lane

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#19)
Re: Day and month name localization uses wrong

I wrote:

It looks like we have some of the variants marked stable already for
their dependence on TimeZone, but the dependence on lc_messages is new.

Actually, now that I look at it, *most* of the variants of to_char,
to_number, and friends have been broken on this score since day one.
There's been a dependency on LC_NUMERIC for the numeric variants all
along, but they're marked immutable :-(

regards, tom lane

#21Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#20)
Re: Day and month name localization uses wrong

Tom Lane wrote:

I wrote:

It looks like we have some of the variants marked stable already for
their dependence on TimeZone, but the dependence on lc_messages is new.

Actually, now that I look at it, *most* of the variants of to_char,
to_number, and friends have been broken on this score since day one.
There's been a dependency on LC_NUMERIC for the numeric variants all
along, but they're marked immutable :-(

Where are we on this? Should I mark to_char as stable? Seems this
would break pre-8.2 to_char expression indexes. Do we need something in
the release notes for this?

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#21)
Re: Day and month name localization uses wrong

Bruce Momjian <bruce@momjian.us> writes:

Tom Lane wrote:

Actually, now that I look at it, *most* of the variants of to_char,
to_number, and friends have been broken on this score since day one.
There's been a dependency on LC_NUMERIC for the numeric variants all
along, but they're marked immutable :-(

Where are we on this?

In the past our practice has been to go ahead and back-patch such
changes, eg
http://archives.postgresql.org/pgsql-committers/2003-04/msg00095.php
Obviously we can't force initdb in the back branches, but we can and
should get the entries right for future installations.

I think the only interesting question is whether we should force initdb
in HEAD. I'd prefer not to since we're past RC1. This is a pretty
minor bug really, and I don't think it justifies forcing a dump/reload
cycle on people testing RC1.

regards, tom lane

#23Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#22)
Re: Day and month name localization uses wrong

Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

Tom Lane wrote:

Actually, now that I look at it, *most* of the variants of to_char,
to_number, and friends have been broken on this score since day one.
There's been a dependency on LC_NUMERIC for the numeric variants all
along, but they're marked immutable :-(

Where are we on this?

In the past our practice has been to go ahead and back-patch such
changes, eg
http://archives.postgresql.org/pgsql-committers/2003-04/msg00095.php
Obviously we can't force initdb in the back branches, but we can and
should get the entries right for future installations.

I think the only interesting question is whether we should force initdb
in HEAD. I'd prefer not to since we're past RC1. This is a pretty
minor bug really, and I don't think it justifies forcing a dump/reload
cycle on people testing RC1.

Agreed. There is no reported failure in the field, and it is a
preventative fix.

--
Bruce Momjian bruce@momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#22)
Re: Day and month name localization uses wrong

I wrote:

I think the only interesting question is whether we should force initdb
in HEAD.

I take that back; there's something possibly worth discussing in the
markings themselves. What I find after experimentation is:

* These functions pay attention to LC_NUMERIC, and have since at least
7.3, and therefore should be marked STABLE in all branches:

to_char(numeric,text) | i | numeric_to_char WRONG
to_char(integer,text) | i | int4_to_char WRONG
to_char(bigint,text) | i | int8_to_char WRONG
to_char(real,text) | i | float4_to_char WRONG
to_char(double precision,text) | i | float8_to_char WRONG
to_number(text,text) | i | numeric_to_number WRONG

* These functions are already correctly marked STABLE, because they have
depended on TimeZone all along:

to_char(timestamp with time zone,text) | s | timestamptz_to_char OK
to_timestamp(text,text) | s | to_timestamp OK

* This function is clearly mis-marked as of HEAD, because of its new
dependence on LC_MESSAGES (but shouldn't be changed in back branches):

to_char(timestamp without time zone,text) | i | timestamp_to_char

* These functions appear to still not depend on any GUC variable:

to_date(text,text) | i | to_date
to_char(interval,text) | i | interval_to_char

It's the last two that are bothering me. It seems likely that somebody
will soon fix to_date() to support input as well as output using the
localizable format items. Should we mark it stable now, rather than
risk missing this again? How about to_char for intervals --- it seems
we currently have INVALID_FOR_INTERVAL on all localizable format items,
but is that going to be true forevermore?

I'm much tempted to mark the last two STABLE as well, and just have a
consistent rule that all the formatting functions are stable. Thoughts?

regards, tom lane

#25Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#14)
Re: Day and month name localization uses wrong locale category

Would someone update this patch with the optimization below. The patch
is at?

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

What's concerning me about the way this is written is that it calls
setlocale() for each formatting instance, which will be very slow.

Perhaps, the first time the info is needed, do setlocale(), ask strftime
for the 12+7 strings we need and save them away, then revert to C locale
and proceed from there.

regards, tom lane

---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faq

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#26Bruce Momjian
bruce@momjian.us
In reply to: Euler Taveira de Oliveira (#4)
Re: Day and month name localization uses wrong locale category

Added to TODO:

o Use LC_TIME for localized weekday/month names, rather than
LC_MESSAGES

http://archives.postgresql.org/pgsql-hackers/2006-11/msg00390.php

---------------------------------------------------------------------------

Euler Taveira de Oliveira wrote:

Peter Eisentraut wrote:

What about using strftime()? So we couldn't worry about gettext
translations; "all" is localized.
Why didn't I think it before? :-)

I'll try to code a patch today later if noone objects.

How is this going?

Finished. Sorry for the delay I had some trouble understanding how
backend treats the locale stuff (Neil pointed out the path).
Now TM mode is returning strftime() output. It would be nice if in the
future we change this to pg_strftime() but unfortunately the last one is
not i18n. :(

template1=# show lc_time;
lc_time
---------
pt_BR
(1 registro)

template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
---------------------------
Segunda, 20 Novembro 2006
(1 registro)

template1=# set lc_time to 'C';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Monday, 20 November 2006
(1 registro)

template1=# set lc_time to 'de_DE';
SET
template1=# select to_char(now(), 'TMDay, DD TMMonth YYYY');
to_char
--------------------------
Montag, 20 November 2006
(1 registro)

template1=#

Comments?

--
Euler Taveira de Oliveira
http://www.timbira.com/

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +