Commit 1a908a00 authored by Tom Lane's avatar Tom Lane

Fix datetime input parsing to accept YYYY-MONTHNAME-DD and related syntaxes,

which had been unintentionally broken by recent changes to tighten up the
DateStyle rules for all-numeric date input.  Add documentation and
regression tests for this, too.
parent 9ad53b04
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/datatype.sgml,v 1.130 2003/11/06 22:21:47 tgl Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/datatype.sgml,v 1.131 2003/11/16 20:29:16 tgl Exp $
--> -->
<chapter id="datatype"> <chapter id="datatype">
...@@ -1464,7 +1464,7 @@ SELECT b, char_length(b) FROM test2; ...@@ -1464,7 +1464,7 @@ SELECT b, char_length(b) FROM test2;
</row> </row>
<row> <row>
<entry>1999-01-08</entry> <entry>1999-01-08</entry>
<entry>ISO 8601, January 8 in any mode <entry>ISO 8601; January 8 in any mode
(recommended format)</entry> (recommended format)</entry>
</row> </row>
<row> <row>
...@@ -1484,6 +1484,30 @@ SELECT b, char_length(b) FROM test2; ...@@ -1484,6 +1484,30 @@ SELECT b, char_length(b) FROM test2;
February 3, 2001 in <literal>YMD</> mode February 3, 2001 in <literal>YMD</> mode
</entry> </entry>
</row> </row>
<row>
<entry>1999-Jan-08</entry>
<entry>January 8 in any mode</entry>
</row>
<row>
<entry>Jan-08-1999</entry>
<entry>January 8 in any mode</entry>
</row>
<row>
<entry>08-Jan-1999</entry>
<entry>January 8 in any mode</entry>
</row>
<row>
<entry>99-Jan-08</entry>
<entry>January 8 in <literal>YMD</> mode, else error</entry>
</row>
<row>
<entry>08-Jan-99</entry>
<entry>January 8, except error in <literal>YMD</> mode</entry>
</row>
<row>
<entry>Jan-08-99</entry>
<entry>January 8, except error in <literal>YMD</> mode</entry>
</row>
<row> <row>
<entry>19990108</entry> <entry>19990108</entry>
<entry>ISO 8601; January 8, 1999 in any mode</entry> <entry>ISO 8601; January 8, 1999 in any mode</entry>
...@@ -1625,7 +1649,7 @@ SELECT b, char_length(b) FROM test2; ...@@ -1625,7 +1649,7 @@ SELECT b, char_length(b) FROM test2;
</row> </row>
<row> <row>
<entry><literal>zulu</literal></entry> <entry><literal>zulu</literal></entry>
<entry>Military abbreviation for GMT</entry> <entry>Military abbreviation for UTC</entry>
</row> </row>
<row> <row>
<entry><literal>z</literal></entry> <entry><literal>z</literal></entry>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.118 2003/09/25 06:58:03 petere Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.119 2003/11/16 20:29:16 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#include "utils/guc.h" #include "utils/guc.h"
static int DecodeNumber(int flen, char *field, static int DecodeNumber(int flen, char *field, bool haveTextMonth,
int fmask, int *tmask, int fmask, int *tmask,
struct tm * tm, fsec_t *fsec, int *is2digits); struct tm * tm, fsec_t *fsec, int *is2digits);
static int DecodeNumberField(int len, char *str, static int DecodeNumberField(int len, char *str,
...@@ -924,7 +924,7 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -924,7 +924,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
int val; int val;
int dterr; int dterr;
int mer = HR24; int mer = HR24;
int haveTextMonth = FALSE; bool haveTextMonth = FALSE;
int is2digits = FALSE; int is2digits = FALSE;
int bc = FALSE; int bc = FALSE;
...@@ -1281,7 +1281,8 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -1281,7 +1281,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
/* otherwise it is a single date/time field... */ /* otherwise it is a single date/time field... */
else else
{ {
dterr = DecodeNumber(flen, field[i], fmask, dterr = DecodeNumber(flen, field[i],
haveTextMonth, fmask,
&tmask, tm, &tmask, tm,
fsec, &is2digits); fsec, &is2digits);
if (dterr) if (dterr)
...@@ -2032,6 +2033,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -2032,6 +2033,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
else else
{ {
dterr = DecodeNumber(flen, field[i], dterr = DecodeNumber(flen, field[i],
FALSE,
(fmask | DTK_DATE_M), (fmask | DTK_DATE_M),
&tmask, tm, &tmask, tm,
fsec, &is2digits); fsec, &is2digits);
...@@ -2229,6 +2231,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm) ...@@ -2229,6 +2231,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
int i, int i,
len; len;
int dterr; int dterr;
bool haveTextMonth = FALSE;
int bc = FALSE; int bc = FALSE;
int is2digits = FALSE; int is2digits = FALSE;
int type, int type,
...@@ -2283,6 +2286,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm) ...@@ -2283,6 +2286,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
{ {
case MONTH: case MONTH:
tm->tm_mon = val; tm->tm_mon = val;
haveTextMonth = TRUE;
break; break;
case ADBC: case ADBC:
...@@ -2312,7 +2316,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm) ...@@ -2312,7 +2316,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
if ((len = strlen(field[i])) <= 0) if ((len = strlen(field[i])) <= 0)
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
dterr = DecodeNumber(len, field[i], fmask, dterr = DecodeNumber(len, field[i], haveTextMonth, fmask,
&dmask, tm, &dmask, tm,
&fsec, &is2digits); &fsec, &is2digits);
if (dterr) if (dterr)
...@@ -2444,7 +2448,7 @@ DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, fsec_t *fsec) ...@@ -2444,7 +2448,7 @@ DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, fsec_t *fsec)
* Return 0 if okay, a DTERR code if not. * Return 0 if okay, a DTERR code if not.
*/ */
static int static int
DecodeNumber(int flen, char *str, int fmask, DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
int *tmask, struct tm * tm, fsec_t *fsec, int *is2digits) int *tmask, struct tm * tm, fsec_t *fsec, int *is2digits)
{ {
int val; int val;
...@@ -2534,10 +2538,59 @@ DecodeNumber(int flen, char *str, int fmask, ...@@ -2534,10 +2538,59 @@ DecodeNumber(int flen, char *str, int fmask,
tm->tm_mon = val; tm->tm_mon = val;
break; break;
case (DTK_M(MONTH)):
if (haveTextMonth)
{
/*
* We are at the first numeric field of a date that included
* a textual month name. We want to support the variants
* MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous
* inputs. We will also accept MON-DD-YY or DD-MON-YY in
* either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
*/
if (flen >= 3 || DateOrder == DATEORDER_YMD)
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
}
else
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
}
else
{
/* Must be at second field of MM-DD-YY */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
break;
case (DTK_M(YEAR) | DTK_M(MONTH)): case (DTK_M(YEAR) | DTK_M(MONTH)):
/* Must be at third field of YY-MM-DD */ if (haveTextMonth)
*tmask = DTK_M(DAY); {
tm->tm_mday = val; /* Need to accept DD-MON-YYYY even in YMD mode */
if (flen >= 3 && *is2digits)
{
/* Guess that first numeric field is day was wrong */
*tmask = DTK_M(DAY); /* YEAR is already set */
tm->tm_mday = tm->tm_year;
tm->tm_year = val;
*is2digits = FALSE;
}
else
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
}
else
{
/* Must be at third field of YY-MM-DD */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
break; break;
case (DTK_M(DAY)): case (DTK_M(DAY)):
...@@ -2552,12 +2605,6 @@ DecodeNumber(int flen, char *str, int fmask, ...@@ -2552,12 +2605,6 @@ DecodeNumber(int flen, char *str, int fmask,
tm->tm_year = val; tm->tm_year = val;
break; break;
case (DTK_M(MONTH)):
/* Must be at second field of MM-DD-YY */
*tmask = DTK_M(DAY);
tm->tm_mday = val;
break;
case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)): case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)):
/* we have all the date, so it must be a time field */ /* we have all the date, so it must be a time field */
dterr = DecodeNumberField(flen, str, fmask, dterr = DecodeNumberField(flen, str, fmask,
...@@ -2574,10 +2621,10 @@ DecodeNumber(int flen, char *str, int fmask, ...@@ -2574,10 +2621,10 @@ DecodeNumber(int flen, char *str, int fmask,
/* /*
* When processing a year field, mark it for adjustment if it's * When processing a year field, mark it for adjustment if it's
* exactly two digits. * only one or two digits.
*/ */
if (*tmask == DTK_M(YEAR)) if (*tmask == DTK_M(YEAR))
*is2digits = (flen == 2); *is2digits = (flen <= 2);
return 0; return 0;
} }
......
This diff is collapsed.
...@@ -28,6 +28,166 @@ SELECT f1 AS "Nine" FROM DATE_TBL WHERE f1 < '2000-01-01'; ...@@ -28,6 +28,166 @@ SELECT f1 AS "Nine" FROM DATE_TBL WHERE f1 < '2000-01-01';
SELECT f1 AS "Three" FROM DATE_TBL SELECT f1 AS "Three" FROM DATE_TBL
WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01'; WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01';
--
-- Check all the documented input formats
--
SET datestyle TO iso; -- display results in ISO
SET datestyle TO ymd;
SELECT date 'January 8, 1999';
SELECT date '1999-01-08';
SELECT date '1999-01-18';
SELECT date '1/8/1999';
SELECT date '1/18/1999';
SELECT date '18/1/1999';
SELECT date '01/02/03';
SELECT date '19990108';
SELECT date '990108';
SELECT date '1999.008';
SELECT date 'J2451187';
SELECT date 'January 8, 99 BC';
SELECT date '99-Jan-08';
SELECT date '1999-Jan-08';
SELECT date '08-Jan-99';
SELECT date '08-Jan-1999';
SELECT date 'Jan-08-99';
SELECT date 'Jan-08-1999';
SELECT date '99-08-Jan';
SELECT date '1999-08-Jan';
SELECT date '99 Jan 08';
SELECT date '1999 Jan 08';
SELECT date '08 Jan 99';
SELECT date '08 Jan 1999';
SELECT date 'Jan 08 99';
SELECT date 'Jan 08 1999';
SELECT date '99 08 Jan';
SELECT date '1999 08 Jan';
SELECT date '99-01-08';
SELECT date '1999-01-08';
SELECT date '08-01-99';
SELECT date '08-01-1999';
SELECT date '01-08-99';
SELECT date '01-08-1999';
SELECT date '99-08-01';
SELECT date '1999-08-01';
SELECT date '99 01 08';
SELECT date '1999 01 08';
SELECT date '08 01 99';
SELECT date '08 01 1999';
SELECT date '01 08 99';
SELECT date '01 08 1999';
SELECT date '99 08 01';
SELECT date '1999 08 01';
SET datestyle TO dmy;
SELECT date 'January 8, 1999';
SELECT date '1999-01-08';
SELECT date '1999-01-18';
SELECT date '1/8/1999';
SELECT date '1/18/1999';
SELECT date '18/1/1999';
SELECT date '01/02/03';
SELECT date '19990108';
SELECT date '990108';
SELECT date '1999.008';
SELECT date 'J2451187';
SELECT date 'January 8, 99 BC';
SELECT date '99-Jan-08';
SELECT date '1999-Jan-08';
SELECT date '08-Jan-99';
SELECT date '08-Jan-1999';
SELECT date 'Jan-08-99';
SELECT date 'Jan-08-1999';
SELECT date '99-08-Jan';
SELECT date '1999-08-Jan';
SELECT date '99 Jan 08';
SELECT date '1999 Jan 08';
SELECT date '08 Jan 99';
SELECT date '08 Jan 1999';
SELECT date 'Jan 08 99';
SELECT date 'Jan 08 1999';
SELECT date '99 08 Jan';
SELECT date '1999 08 Jan';
SELECT date '99-01-08';
SELECT date '1999-01-08';
SELECT date '08-01-99';
SELECT date '08-01-1999';
SELECT date '01-08-99';
SELECT date '01-08-1999';
SELECT date '99-08-01';
SELECT date '1999-08-01';
SELECT date '99 01 08';
SELECT date '1999 01 08';
SELECT date '08 01 99';
SELECT date '08 01 1999';
SELECT date '01 08 99';
SELECT date '01 08 1999';
SELECT date '99 08 01';
SELECT date '1999 08 01';
SET datestyle TO mdy;
SELECT date 'January 8, 1999';
SELECT date '1999-01-08';
SELECT date '1999-01-18';
SELECT date '1/8/1999';
SELECT date '1/18/1999';
SELECT date '18/1/1999';
SELECT date '01/02/03';
SELECT date '19990108';
SELECT date '990108';
SELECT date '1999.008';
SELECT date 'J2451187';
SELECT date 'January 8, 99 BC';
SELECT date '99-Jan-08';
SELECT date '1999-Jan-08';
SELECT date '08-Jan-99';
SELECT date '08-Jan-1999';
SELECT date 'Jan-08-99';
SELECT date 'Jan-08-1999';
SELECT date '99-08-Jan';
SELECT date '1999-08-Jan';
SELECT date '99 Jan 08';
SELECT date '1999 Jan 08';
SELECT date '08 Jan 99';
SELECT date '08 Jan 1999';
SELECT date 'Jan 08 99';
SELECT date 'Jan 08 1999';
SELECT date '99 08 Jan';
SELECT date '1999 08 Jan';
SELECT date '99-01-08';
SELECT date '1999-01-08';
SELECT date '08-01-99';
SELECT date '08-01-1999';
SELECT date '01-08-99';
SELECT date '01-08-1999';
SELECT date '99-08-01';
SELECT date '1999-08-01';
SELECT date '99 01 08';
SELECT date '1999 01 08';
SELECT date '08 01 99';
SELECT date '08 01 1999';
SELECT date '01 08 99';
SELECT date '01 08 1999';
SELECT date '99 08 01';
SELECT date '1999 08 01';
RESET datestyle;
-- --
-- Simple math -- Simple math
-- Leave most of it for the horology tests -- Leave most of it for the horology tests
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment