Clean up a couple of weird corner cases in interval parsing: make -yyyy-mm be

interpreted as expected (the sign should affect months too), and get rid of
hard-wired assumption that unmarked signed values must be hours (if integers)
or seconds (if floats).  The former was just a bug in my previous patch,
while the latter may have made sense at one time but seems illogical now
that we support determination of the units from typmod information.
Ron Mayer and myself.
This commit is contained in:
Tom Lane 2008-09-16 22:31:21 +00:00
parent 30df79a70b
commit b73c0c2a51
3 changed files with 13 additions and 38 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.192 2008/09/11 15:27:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.193 2008/09/16 22:31:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -448,7 +448,7 @@ TrimTrailingZeros(char *str)
* DTK_TIME - digits, colon delimiters, and possibly a decimal point
* DTK_STRING - text (no digits or punctuation)
* DTK_SPECIAL - leading "+" or "-" followed by text
* DTK_TZ - leading "+" or "-" followed by digits (also eats ':' or '.')
* DTK_TZ - leading "+" or "-" followed by digits (also eats ':', '.', '-')
*
* Note that some field types can hold unexpected items:
* DTK_NUMBER can hold date fields (yy.ddd)
@ -610,12 +610,13 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
while (isspace((unsigned char) *cp))
cp++;
/* numeric timezone? */
/* note that "DTK_TZ" could also be a signed float or yyyy-mm */
if (isdigit((unsigned char) *cp))
{
ftype[nf] = DTK_TZ;
APPEND_CHAR(bufp, bufend, *cp++);
while (isdigit((unsigned char) *cp) ||
*cp == ':' || *cp == '.')
*cp == ':' || *cp == '.' || *cp == '-')
APPEND_CHAR(bufp, bufend, *cp++);
}
/* special? */
@ -2774,19 +2775,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
/*
* Timezone is a token with a leading sign character and
* otherwise the same as a non-signed time field
* at least one digit; there could be ':', '.', '-'
* embedded in it as well.
*/
Assert(*field[i] == '-' || *field[i] == '+');
/*
* A single signed number ends up here, but will be rejected
* by DecodeTime(). So, work this out to drop through to
* DTK_NUMBER, which *can* tolerate this.
* Try for hh:mm or hh:mm:ss. If not, fall through to
* DTK_NUMBER case, which can handle signed float numbers
* and signed year-month values.
*/
cp = field[i] + 1;
while (*cp != '\0' && *cp != ':' && *cp != '.')
cp++;
if (*cp == ':' &&
if (strchr(field[i] + 1, ':') != NULL &&
DecodeTime(field[i] + 1, fmask, INTERVAL_FULL_RANGE,
&tmask, tm, fsec) == 0)
{
@ -2808,32 +2807,13 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
tmask = DTK_M(TZ);
break;
}
else if (type == IGNORE_DTF)
{
if (*cp == '.')
{
/*
* Got a decimal point? Then assume some sort of
* seconds specification
*/
type = DTK_SECOND;
}
else if (*cp == '\0')
{
/*
* Only a signed integer? Then must assume a
* timezone-like usage
*/
type = DTK_HOUR;
}
}
/* FALL THROUGH */
case DTK_DATE:
case DTK_NUMBER:
if (type == IGNORE_DTF)
{
/* use typmod to decide what rightmost integer field is */
/* use typmod to decide what rightmost field is */
switch (range)
{
case INTERVAL_MASK(YEAR):
@ -2883,6 +2863,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (*cp != '\0')
return DTERR_BAD_FORMAT;
type = DTK_MONTH;
if (val < 0)
val2 = -val2;
val = val * MONTHS_PER_YEAR + val2;
fval = 0;
}

View File

@ -21,12 +21,6 @@ SELECT INTERVAL '-08:00' AS "Eight hours";
-08:00:00
(1 row)
SELECT INTERVAL '-05' AS "Five hours";
Five hours
------------
-05:00:00
(1 row)
SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
22 hours ago...
-------------------

View File

@ -8,7 +8,6 @@ SET DATESTYLE = 'ISO';
SELECT INTERVAL '01:00' AS "One hour";
SELECT INTERVAL '+02:00' AS "Two hours";
SELECT INTERVAL '-08:00' AS "Eight hours";
SELECT INTERVAL '-05' AS "Five hours";
SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";