1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-26 12:21:12 +03:00

Apply (a somewhat revised version of) Greg Mullane's patch to eliminate

heuristic determination of day vs month in date/time input.  Add the
ability to specify that input is interpreted as yy-mm-dd order (which
formerly worked, but only for yy greater than 31).  DateStyle's input
component now has the preferred spellings DMY, MDY, or YMD; the older
keywords European and US are now aliases for the first two of these.
Per recent discussions on pgsql-general.
This commit is contained in:
Tom Lane
2003-07-29 00:03:19 +00:00
parent 2baf4efe09
commit 9c2a7c2269
26 changed files with 441 additions and 350 deletions

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.84 2003/07/28 00:09:14 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.85 2003/07/29 00:03:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -51,10 +51,10 @@ const char *
assign_datestyle(const char *value, bool doit, bool interactive)
{
int newDateStyle = DateStyle;
bool newEuroDates = EuroDates;
int newDateOrder = DateOrder;
bool ok = true;
int dcnt = 0,
ecnt = 0;
int scnt = 0,
ocnt = 0;
char *rawstring;
char *result;
List *elemlist;
@ -85,38 +85,43 @@ assign_datestyle(const char *value, bool doit, bool interactive)
if (strcasecmp(tok, "ISO") == 0)
{
newDateStyle = USE_ISO_DATES;
dcnt++;
scnt++;
}
else if (strcasecmp(tok, "SQL") == 0)
{
newDateStyle = USE_SQL_DATES;
dcnt++;
scnt++;
}
else if (strncasecmp(tok, "POSTGRES", 8) == 0)
{
newDateStyle = USE_POSTGRES_DATES;
dcnt++;
scnt++;
}
else if (strcasecmp(tok, "GERMAN") == 0)
{
newDateStyle = USE_GERMAN_DATES;
dcnt++;
if ((ecnt > 0) && (!newEuroDates))
ok = false;
newEuroDates = TRUE;
scnt++;
/* GERMAN also sets DMY, unless explicitly overridden */
if (ocnt == 0)
newDateOrder = DATEORDER_DMY;
}
else if (strncasecmp(tok, "EURO", 4) == 0)
else if (strcasecmp(tok, "YMD") == 0)
{
newEuroDates = TRUE;
ecnt++;
newDateOrder = DATEORDER_YMD;
ocnt++;
}
else if (strcasecmp(tok, "US") == 0
|| strncasecmp(tok, "NONEURO", 7) == 0)
else if (strcasecmp(tok, "DMY") == 0 ||
strncasecmp(tok, "EURO", 4) == 0)
{
newEuroDates = FALSE;
ecnt++;
if ((dcnt > 0) && (newDateStyle == USE_GERMAN_DATES))
ok = false;
newDateOrder = DATEORDER_DMY;
ocnt++;
}
else if (strcasecmp(tok, "MDY") == 0 ||
strcasecmp(tok, "US") == 0 ||
strncasecmp(tok, "NONEURO", 7) == 0)
{
newDateOrder = DATEORDER_MDY;
ocnt++;
}
else if (strcasecmp(tok, "DEFAULT") == 0)
{
@ -128,15 +133,17 @@ assign_datestyle(const char *value, bool doit, bool interactive)
* to handle constructs like "DEFAULT, ISO".
*/
int saveDateStyle = DateStyle;
bool saveEuroDates = EuroDates;
int saveDateOrder = DateOrder;
const char *subval;
subval = assign_datestyle(GetConfigOptionResetString("datestyle"),
true, interactive);
newDateStyle = DateStyle;
newEuroDates = EuroDates;
if (scnt == 0)
newDateStyle = DateStyle;
if (ocnt == 0)
newDateOrder = DateOrder;
DateStyle = saveDateStyle;
EuroDates = saveEuroDates;
DateOrder = saveDateOrder;
if (!subval)
{
ok = false;
@ -145,8 +152,6 @@ assign_datestyle(const char *value, bool doit, bool interactive)
/* Here we know that our own return value is always malloc'd */
/* when doit is true */
free((char *) subval);
dcnt++;
ecnt++;
}
else
{
@ -160,7 +165,7 @@ assign_datestyle(const char *value, bool doit, bool interactive)
}
}
if (dcnt > 1 || ecnt > 1)
if (scnt > 1 || ocnt > 1)
ok = false;
pfree(rawstring);
@ -203,14 +208,25 @@ assign_datestyle(const char *value, bool doit, bool interactive)
strcpy(result, "Postgres");
break;
}
strcat(result, newEuroDates ? ", European" : ", US");
switch (newDateOrder)
{
case DATEORDER_YMD:
strcat(result, ", YMD");
break;
case DATEORDER_DMY:
strcat(result, ", DMY");
break;
default:
strcat(result, ", MDY");
break;
}
/*
* Finally, it's safe to assign to the global variables; the
* assignment cannot fail now.
*/
DateStyle = newDateStyle;
EuroDates = newEuroDates;
DateOrder = newDateOrder;
return result;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.352 2003/07/27 21:49:54 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.353 2003/07/29 00:03:18 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -1958,7 +1958,7 @@ usage(char *progname)
printf(" -c NAME=VALUE set run-time parameter\n");
printf(" -d 0-5 debugging level (0 is off)\n");
printf(" -D DATADIR database directory\n");
printf(" -e use European date format\n");
printf(" -e use European date input format (DMY)\n");
printf(" -E echo query before execution\n");
printf(" -F turn fsync off\n");
printf(" -N do not use newline as interactive query delimiter\n");
@ -2155,7 +2155,7 @@ PostgresMain(int argc, char *argv[], const char *username)
case 'e':
/*
* Use european date formats.
* Use European date input format (DMY)
*/
SetConfigOption("datestyle", "euro", ctx, gucsource);
break;
@ -2626,7 +2626,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
puts("$Revision: 1.352 $ $Date: 2003/07/27 21:49:54 $\n");
puts("$Revision: 1.353 $ $Date: 2003/07/29 00:03:18 $\n");
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.107 2003/07/27 04:53:04 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.108 2003/07/29 00:03:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1425,23 +1425,47 @@ DecodeDateTime(char **field, int *ftype, int nf,
fmask |= tmask;
}
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
if (fmask & DTK_M(YEAR))
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("inconsistent use of year %04d and \"BC\"",
tm->tm_year)));
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("inconsistent use of year %04d and \"BC\"",
tm->tm_year)));
}
else if (is2digits)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
}
else if (is2digits)
/* now that we have correct year, decode DOY */
if (fmask & DTK_M(DOY))
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/* check for valid month */
if (fmask & DTK_M(MONTH))
{
if (tm->tm_mon < 1 || tm->tm_mon > 12)
return -1;
}
/* minimal check for valid day */
if (fmask & DTK_M(DAY))
{
if (tm->tm_mday < 1 || tm->tm_mday > 31)
return -1;
}
if ((mer != HR24) && (tm->tm_hour > 12))
@ -1461,13 +1485,11 @@ DecodeDateTime(char **field, int *ftype, int nf,
* check for valid day of month, now that we know for sure the
* month and year...
*/
if ((tm->tm_mday < 1)
|| (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]))
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return -1;
/* timezone not specified? then find local timezone if possible */
if (((fmask & DTK_DATE_M) == DTK_DATE_M)
&& (tzp != NULL) && (!(fmask & DTK_M(TZ))))
if ((tzp != NULL) && (!(fmask & DTK_M(TZ))))
{
/*
* daylight savings time modifier but no standard timezone?
@ -2259,6 +2281,22 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
tm->tm_year += 1900;
}
/* now that we have correct year, decode DOY */
if (fmask & DTK_M(DOY))
{
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/* check for valid month */
if (tm->tm_mon < 1 || tm->tm_mon > 12)
return -1;
/* check for valid day */
if (tm->tm_mday < 1 ||
tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return -1;
return 0;
} /* DecodeDate() */
@ -2356,8 +2394,8 @@ DecodeNumber(int flen, char *str, int fmask,
if (*cp == '.')
{
/*
* More than two digits? Then could be a date or a run-together
* time: 2001.360 20011225 040506.789
* More than two digits before decimal point? Then could be a date
* or a run-together time: 2001.360 20011225 040506.789
*/
if ((cp - str) > 2)
return DecodeNumberField(flen, str, (fmask | DTK_DATE_M),
@ -2370,85 +2408,91 @@ DecodeNumber(int flen, char *str, int fmask,
else if (*cp != '\0')
return -1;
/* Special case day of year? */
if ((flen == 3) && (fmask & DTK_M(YEAR))
&& ((val >= 1) && (val <= 366)))
/* Special case for day of year */
if ((flen == 3) &&
((fmask & DTK_DATE_M) == DTK_M(YEAR)) &&
((val >= 1) && (val <= 366)))
{
*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
tm->tm_yday = val;
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
/* tm_mon and tm_mday can't actually be set yet ... */
return 0;
}
/***
* Enough digits to be unequivocal year? Used to test for 4 digits or
* more, but we now test first for a three-digit doy so anything
* bigger than two digits had better be an explicit year.
* - thomas 1999-01-09
* Back to requiring a 4 digit year. We accept a two digit
* year farther down. - thomas 2000-03-28
***/
else if (flen >= 4)
/* Switch based on what we have so far */
switch (fmask & DTK_DATE_M)
{
*tmask = DTK_M(YEAR);
case 0:
/*
* Nothing so far; make a decision about what we think the
* input is. There used to be lots of heuristics here, but
* the consensus now is to be paranoid. It *must* be either
* YYYY-MM-DD (with a more-than-two-digit year field), or the
* field order defined by DateOrder.
*/
if (flen >= 3 || DateOrder == DATEORDER_YMD)
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
}
else if (DateOrder == DATEORDER_DMY)
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
else
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
break;
/* already have a year? then see if we can substitute... */
if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(DAY)))
&& ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
{
tm->tm_mday = tm->tm_year;
case (DTK_M(YEAR)):
/* Must be at second field of YY-MM-DD */
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
break;
case (DTK_M(YEAR) | DTK_M(MONTH)):
/* Must be at third field of YY-MM-DD */
*tmask = DTK_M(DAY);
}
tm->tm_mday = val;
break;
tm->tm_year = val;
}
case (DTK_M(DAY)):
/* Must be at second field of DD-MM-YY */
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
break;
/* already have year? then could be month */
else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH)))
&& ((val >= 1) && (val <= 12)))
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
/* no year and EuroDates enabled? then could be day */
else if ((EuroDates || (fmask & DTK_M(MONTH)))
&& (!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)))
&& ((val >= 1) && (val <= 31)))
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
else if ((!(fmask & DTK_M(MONTH)))
&& ((val >= 1) && (val <= 12)))
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
else if ((!(fmask & DTK_M(DAY)))
&& ((val >= 1) && (val <= 31)))
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
case (DTK_M(MONTH) | DTK_M(DAY)):
/* Must be at third field of DD-MM-YY or MM-DD-YY */
*tmask = DTK_M(YEAR);
tm->tm_year = val;
break;
case (DTK_M(MONTH)):
/* Must be at second field of MM-DD-YY */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
break;
default:
/* Anything else is bogus input */
return -1;
}
/*
* Check for 2 or 4 or more digits, but currently we reach here only
* if two digits. - thomas 2000-03-28
* When processing a year field, mark it for adjustment if it's
* exactly two digits.
*/
else if (!(fmask & DTK_M(YEAR))
&& ((flen >= 4) || (flen == 2)))
if (*tmask == DTK_M(YEAR))
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
/* adjust ONLY if exactly two digits... */
*is2digits = (flen == 2);
}
else
return -1;
return 0;
} /* DecodeNumber() */
}
/* DecodeNumberField()
@ -2512,18 +2556,6 @@ DecodeNumberField(int len, char *str, int fmask,
tm->tm_year = atoi(str + 0);
*is2digits = TRUE;
return DTK_DATE;
}
/* yyddd? */
else if (len == 5)
{
*tmask = DTK_DATE_M;
tm->tm_mday = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_mon = 1;
tm->tm_year = atoi(str + 0);
*is2digits = TRUE;
return DTK_DATE;
}
}
@ -3152,7 +3184,7 @@ EncodeDateOnly(struct tm * tm, int style, char *str)
case USE_SQL_DATES:
/* compatible with Oracle/Ingres date formats */
if (EuroDates)
if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
@ -3174,7 +3206,7 @@ EncodeDateOnly(struct tm * tm, int style, char *str)
case USE_POSTGRES_DATES:
default:
/* traditional date-only style for Postgres */
if (EuroDates)
if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
@ -3308,7 +3340,7 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
case USE_SQL_DATES:
/* Compatible with Oracle/Ingres date formats */
if (EuroDates)
if (DateOrder == DATEORDER_DMY)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
@ -3410,7 +3442,7 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
strncpy(str, days[tm->tm_wday], 3);
strcpy((str + 3), " ");
if (EuroDates)
if (DateOrder == DATEORDER_DMY)
sprintf((str + 4), "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
else
sprintf((str + 4), "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/init/globals.c,v 1.72 2003/06/27 14:45:30 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/init/globals.c,v 1.73 2003/07/29 00:03:18 tgl Exp $
*
* NOTES
* Globals used all over the place should be declared here and not
@ -62,7 +62,7 @@ bool IsUnderPostmaster = false;
bool ExitOnAnyError = false;
int DateStyle = USE_ISO_DATES;
bool EuroDates = false;
int DateOrder = DATEORDER_MDY;
bool HasCTZSet = false;
int CTimeZone = 0;

View File

@ -1,4 +1,4 @@
$Header: /cvsroot/pgsql/src/backend/utils/misc/README,v 1.1 2002/05/17 01:19:18 tgl Exp $
$Header: /cvsroot/pgsql/src/backend/utils/misc/README,v 1.2 2003/07/29 00:03:18 tgl Exp $
GUC IMPLEMENTATION NOTES
@ -49,8 +49,8 @@ variables, but the return value is handled differently:
malloc'd (not palloc'd!!!) string --- assign that value instead
The third choice is allowed in case the assign_hook wants to return a
"canonical" version of the new value. For example, the assign_hook for
datestyle always returns a string that includes both basic datestyle and
us/euro option, although the input might have specified only one.
datestyle always returns a string that includes both output and input
datestyle options, although the input might have specified only one.
If a show_hook is provided, it points to a function of the signature
const char *show_hook(void)

View File

@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.143 2003/07/28 19:31:32 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.144 2003/07/29 00:03:18 tgl Exp $
*
*--------------------------------------------------------------------
*/
@ -1338,13 +1338,13 @@ static struct config_string ConfigureNamesString[] =
{
{"DateStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("The display format for date and time values"),
gettext_noop("As well as the rules for interpreting ambiguous date "
"input values"),
gettext_noop("The display format for date and time values, "),
gettext_noop("as well as the rules for interpreting ambiguous "
"date input values"),
GUC_LIST_INPUT | GUC_REPORT
},
&datestyle_string,
"ISO, US", assign_datestyle, NULL
"ISO, MDY", assign_datestyle, NULL
},
{

View File

@ -212,7 +212,7 @@
# - Locale and Formatting -
#datestyle = 'iso, us'
#datestyle = 'iso, mdy'
#timezone = unknown # actually, defaults to TZ environment setting
#australian_timezones = false
#extra_float_digits = 0 # min -15, max 2