From: Tom Lane Date: Tue, 16 Sep 2008 22:31:21 +0000 (+0000) Subject: Clean up a couple of weird corner cases in interval parsing: make -yyyy-mm be X-Git-Tag: REL8_4_BETA1~979 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b73c0c2a51500d52fcf0eb69100a701cc2273c2e;p=postgresql 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. --- diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index a1cf2b4ed5..9640684cb1 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -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; } diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index a9b5766439..8270516388 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -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... ------------------- diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 06e4705859..732ca026f9 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -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";