From 9e7166096e6d01adff88d4a7383ca808f67041b4 Mon Sep 17 00:00:00 2001 From: "Thomas G. Lockhart" Date: Thu, 16 Mar 2000 14:38:42 +0000 Subject: [PATCH] Support full POSIX-style time zone: EST+3, PST-3, etc. We probably support a superset of the spec, but I don't have the spec to confirm this. Update regression tests to include tests for this format. --- src/backend/utils/adt/datetime.c | 226 ++++++++++++++++++++--------- src/test/regress/sql/horology.sql | 28 ++-- src/test/regress/sql/timestamp.sql | 37 +++-- 3 files changed, 195 insertions(+), 96 deletions(-) diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 3c88ac3a3d..6806bb8f4f 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.43 2000/03/14 23:06:36 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.44 2000/03/16 14:36:51 thomas Exp $ * *------------------------------------------------------------------------- */ @@ -35,6 +35,8 @@ #define USE_DATE_CACHE 1 #define ROUND_ALL 0 +static int DecodePosixTimezone(char *str, int *val); + int day_tab[2][13] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}}; @@ -47,15 +49,6 @@ char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL}; -#if 0 - - -static void GetEpochTime(struct tm * tm); - - -#endif - - #define UTIME_MINYEAR (1901) #define UTIME_MINMONTH (12) #define UTIME_MINDAY (14) @@ -399,35 +392,6 @@ j2day(int date) } /* j2day() */ -#if 0 - - -static double -time2t(const int hour, const int min, const double sec) -{ - return (((hour * 60) + min) * 60) + sec; -} /* time2t() */ - -static void -dt2time(Timestamp jd, int *hour, int *min, double *sec) -{ - double time; - - time = jd; - - *hour = (time / 3600); - time -= ((*hour) * 3600); - *min = (time / 60); - time -= ((*min) * 60); - *sec = JROUND(time); - - return; -} /* dt2time() */ - - -#endif - - /* * parse and convert date in timestr (the normal interface) * @@ -493,9 +457,12 @@ ParseDateTime(char *timestr, char *lowstr, while (isalpha(*cp)) *lp++ = tolower(*cp++); - /* full date string with leading text month? */ + /* Full date string with leading text month? + * Could also be a POSIX time zone... + */ if ((*cp == '-') || (*cp == '/') || (*cp == '.')) { +#if 0 /* * special case of Posix timezone "GMT-0800" * Note that other sign (e.g. "GMT+0800" @@ -512,6 +479,7 @@ ParseDateTime(char *timestr, char *lowstr, *cp = '+'; continue; } +#endif ftype[nf] = DTK_DATE; while (isdigit(*cp) || (*cp == '-') || (*cp == '/') || (*cp == '.')) @@ -627,7 +595,21 @@ DecodeDateTime(char **field, int *ftype, int nf, switch (ftype[i]) { case DTK_DATE: - if (DecodeDate(field[i], fmask, &tmask, tm) != 0) + /* Already have a date? + * Then this might be a POSIX time zone + * with an embedded dash (e.g. "PST-3" == "EST") + * - thomas 2000-03-15 + */ + if ((fmask & DTK_DATE_M) == DTK_DATE_M) + { + if ((tzp == NULL) + || (DecodePosixTimezone(field[i], tzp) != 0)) + return -1; + + ftype[i] = DTK_TZ; + tmask = DTK_M(TZ); + } + else if (DecodeDate(field[i], fmask, &tmask, tm) != 0) return -1; break; @@ -646,9 +628,29 @@ DecodeDateTime(char **field, int *ftype, int nf, case DTK_TZ: if (tzp == NULL) return -1; - if (DecodeTimezone(field[i], tzp) != 0) - return -1; - tmask = DTK_M(TZ); + + { + int tz; + + if (DecodeTimezone(field[i], &tz) != 0) + return -1; + + /* Already have a time zone? + * Then maybe this is the second field of a POSIX time: + * EST+3 (equivalent to PST) + */ + if ((i > 0) && ((fmask & DTK_M(TZ)) != 0) + && (ftype[i-1] == DTK_TZ) && (isalpha(*field[i-1]))) + { + *tzp -= tz; + tmask = 0; + } + else + { + *tzp = tz; + tmask = DTK_M(TZ); + } + } break; case DTK_NUMBER: @@ -779,6 +781,7 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tzp == NULL) return -1; *tzp = val * 60; + ftype[i] = DTK_TZ; break; case TZ: @@ -786,25 +789,7 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tzp == NULL) return -1; *tzp = val * 60; - - /* Swallow an immediately succeeding timezone if this is GMT - * This handles the odd case in FreeBSD of "GMT+0800" - * but note that we need to flip the sign on this too. - * Claims to be some sort of POSIX standard format :( - * - thomas 2000-01-20 - */ - if ((i < (nf-1)) && (ftype[i+1] == DTK_TZ) - && (strcmp(field[i], "gmt") == 0)) - { - i++; - if (DecodeTimezone(field[i], tzp) != 0) - return -1; - - /* flip the sign per POSIX standard */ - *tzp = -(*tzp); - } - - + ftype[i] = DTK_TZ; break; case IGNORE: @@ -962,6 +947,19 @@ DecodeTimeOnly(char **field, int *ftype, int nf, { switch (ftype[i]) { + case DTK_DATE: + /* This might be a POSIX time zone + * with an embedded dash (e.g. "PST-3" == "EST") + * - thomas 2000-03-15 + */ + if ((tzp == NULL) + || (DecodePosixTimezone(field[i], tzp) != 0)) + return -1; + + ftype[i] = DTK_TZ; + tmask = DTK_M(TZ); + break; + case DTK_TIME: if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) return -1; @@ -970,9 +968,29 @@ DecodeTimeOnly(char **field, int *ftype, int nf, case DTK_TZ: if (tzp == NULL) return -1; - if (DecodeTimezone(field[i], tzp) != 0) - return -1; - tmask = DTK_M(TZ); + + { + int tz; + + if (DecodeTimezone(field[i], &tz) != 0) + return -1; + + /* Already have a time zone? + * Then maybe this is the second field of a POSIX time: + * EST+3 (equivalent to PST) + */ + if ((i > 0) && ((fmask & DTK_M(TZ)) != 0) + && (ftype[i-1] == DTK_TZ) && (isalpha(*field[i-1]))) + { + *tzp -= tz; + tmask = 0; + } + else + { + *tzp = tz; + tmask = DTK_M(TZ); + } + } break; case DTK_NUMBER: @@ -1015,6 +1033,41 @@ DecodeTimeOnly(char **field, int *ftype, int nf, break; + case DTZMOD: + + /* + * daylight savings time modifier (solves "MET + * DST" syntax) + */ + tmask |= DTK_M(DTZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return -1; + *tzp += val * 60; + break; + + case DTZ: + + /* + * set mask for TZ here _or_ check for DTZ later + * when getting default timezone + */ + tmask |= DTK_M(TZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return -1; + *tzp = val * 60; + ftype[i] = DTK_TZ; + break; + + case TZ: + tm->tm_isdst = 0; + if (tzp == NULL) + return -1; + *tzp = val * 60; + ftype[i] = DTK_TZ; + break; + case IGNORE: break; @@ -1486,6 +1539,47 @@ DecodeTimezone(char *str, int *tzp) } /* DecodeTimezone() */ +/* DecodePosixTimezone() + * Interpret string as a POSIX-compatible timezone: + * PST-hh:mm + * PST+h + * - thomas 2000-03-15 + */ +static int +DecodePosixTimezone(char *str, int *tzp) +{ + int val, tz; + int type; + char *cp; + char delim; + + cp = str; + while ((*cp != '\0') && isalpha(*cp)) + cp++; + + if (DecodeTimezone(cp, &tz) != 0) + return -1; + + delim = *cp; + *cp = '\0'; + type = DecodeSpecial(MAXDATEFIELDS-1, str, &val); + *cp = delim; + + switch(type) + { + case DTZ: + case TZ: + *tzp = (val * 60) - tz; + break; + + default: + return -1; + } + + return 0; +} /* DecodePosixTimezone() */ + + /* DecodeSpecial() * Decode text string using lookup table. * Implement a cache lookup since it is likely that dates diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index 7e4fb99667..f86f2cfd28 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -20,29 +20,29 @@ INSERT INTO TEMP_TIMESTAMP (f1) WHERE d1 BETWEEN '13-jun-1957' AND '1-jan-1997' OR d1 BETWEEN '1-jan-1999' AND '1-jan-2010'; -SELECT '' AS "11", f1 AS timestamp +SELECT '' AS "15", f1 AS timestamp FROM TEMP_TIMESTAMP ORDER BY timestamp; -SELECT '' AS "110", d.f1 AS timestamp, t.f1 AS interval, d.f1 + t.f1 AS plus +SELECT '' AS "150", d.f1 AS timestamp, t.f1 AS interval, d.f1 + t.f1 AS plus FROM TEMP_TIMESTAMP d, INTERVAL_TBL t ORDER BY plus, timestamp, interval; -SELECT '' AS "110", d.f1 AS timestamp, t.f1 AS interval, d.f1 - t.f1 AS minus +SELECT '' AS "150", d.f1 AS timestamp, t.f1 AS interval, d.f1 - t.f1 AS minus FROM TEMP_TIMESTAMP d, INTERVAL_TBL t WHERE isfinite(d.f1) ORDER BY minus, timestamp, interval; -SELECT '' AS "11", d.f1 AS timestamp, timestamp '1980-01-06 00:00 GMT' AS gpstime_zero, +SELECT '' AS "15", d.f1 AS timestamp, timestamp '1980-01-06 00:00 GMT' AS gpstime_zero, d.f1 - timestamp '1980-01-06 00:00 GMT' AS difference FROM TEMP_TIMESTAMP d ORDER BY difference; -SELECT '' AS "121", d1.f1 AS timestamp1, d2.f1 AS timestamp2, d1.f1 - d2.f1 AS difference +SELECT '' AS "225", d1.f1 AS timestamp1, d2.f1 AS timestamp2, d1.f1 - d2.f1 AS difference FROM TEMP_TIMESTAMP d1, TEMP_TIMESTAMP d2 ORDER BY timestamp1, timestamp2, difference; -SELECT '' as fifty, d1 as timestamp, +SELECT '' as "54", d1 as timestamp, date_part('year', d1) AS year, date_part('month', d1) AS month, date_part('day',d1) AS day, date_part('hour', d1) AS hour, date_part('minute', d1) AS minute, date_part('second', d1) AS second @@ -66,12 +66,12 @@ SELECT '' AS four, f1 AS abstime, -- Conversions -- -SELECT '' AS "11", f1 AS timestamp, date( f1) AS date +SELECT '' AS "15", f1 AS timestamp, date( f1) AS date FROM TEMP_TIMESTAMP WHERE f1 <> timestamp 'current' ORDER BY date; -SELECT '' AS "11", f1 AS timestamp, abstime( f1) AS abstime +SELECT '' AS "15", f1 AS timestamp, abstime( f1) AS abstime FROM TEMP_TIMESTAMP ORDER BY abstime; @@ -102,13 +102,13 @@ SET DateStyle TO 'US,Postgres'; SHOW DateStyle; -SELECT '' AS sixty_two, d1 AS us_postgres FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 AS us_postgres FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS us_postgres FROM ABSTIME_TBL; SET DateStyle TO 'US,ISO'; -SELECT '' AS sixty_two, d1 AS us_iso FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 AS us_iso FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS us_iso FROM ABSTIME_TBL; @@ -116,7 +116,7 @@ SET DateStyle TO 'US,SQL'; SHOW DateStyle; -SELECT '' AS sixty_two, d1 AS us_sql FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 AS us_sql FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS us_sql FROM ABSTIME_TBL; @@ -128,7 +128,7 @@ INSERT INTO TIMESTAMP_TBL VALUES('13/06/1957'); SELECT count(*) as one FROM TIMESTAMP_TBL WHERE d1 = 'Jun 13 1957'; -SELECT '' AS sixty_three, d1 AS european_postgres FROM TIMESTAMP_TBL; +SELECT '' AS "67", d1 AS european_postgres FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS european_postgres FROM ABSTIME_TBL; @@ -136,7 +136,7 @@ SET DateStyle TO 'European,ISO'; SHOW DateStyle; -SELECT '' AS sixty_three, d1 AS european_iso FROM TIMESTAMP_TBL; +SELECT '' AS "67", d1 AS european_iso FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS european_iso FROM ABSTIME_TBL; @@ -144,7 +144,7 @@ SET DateStyle TO 'European,SQL'; SHOW DateStyle; -SELECT '' AS sixty_three, d1 AS european_sql FROM TIMESTAMP_TBL; +SELECT '' AS "67", d1 AS european_sql FROM TIMESTAMP_TBL; SELECT '' AS eight, f1 AS european_sql FROM ABSTIME_TBL; diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 984b5c79c6..e4effba4f7 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -72,8 +72,11 @@ INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 -0800'); INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 17:32:01 -07:00'); -- POSIX format -INSERT INTO TIMESTAMP_TBL VALUES ('2000-02-15 23:14:30 GMT+8'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-02-15 23:14:30 EST+3'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 08:14:01 GMT+8'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 13:14:02 GMT-1'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT -2'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 EST+3'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 EST +2:00'); -- Variations for acceptable input formats INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997 -0800'); @@ -129,43 +132,45 @@ INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2001'); INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 -0097'); INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 5097 BC'); -SELECT '' AS sixtythree, d1 FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 FROM TIMESTAMP_TBL; -- Demonstrate functions and operators -SELECT '' AS fortythree, d1 FROM TIMESTAMP_TBL +SELECT '' AS "47", d1 FROM TIMESTAMP_TBL WHERE d1 > timestamp '1997-01-02' and d1 != timestamp 'current'; -SELECT '' AS fifteen, d1 FROM TIMESTAMP_TBL +SELECT '' AS "15", d1 FROM TIMESTAMP_TBL WHERE d1 < timestamp '1997-01-02' and d1 != timestamp 'current'; SELECT '' AS one, d1 FROM TIMESTAMP_TBL WHERE d1 = timestamp '1997-01-02' and d1 != timestamp 'current'; -SELECT '' AS fiftyeight, d1 FROM TIMESTAMP_TBL +SELECT '' AS "62", d1 FROM TIMESTAMP_TBL WHERE d1 != timestamp '1997-01-02' and d1 != timestamp 'current'; -SELECT '' AS sixteen, d1 FROM TIMESTAMP_TBL +SELECT '' AS "16", d1 FROM TIMESTAMP_TBL WHERE d1 <= timestamp '1997-01-02' and d1 != timestamp 'current'; -SELECT '' AS fortyfour, d1 FROM TIMESTAMP_TBL +SELECT '' AS "48", d1 FROM TIMESTAMP_TBL WHERE d1 >= timestamp '1997-01-02' and d1 != timestamp 'current'; -SELECT '' AS sixtythree, d1 + interval '1 year' AS one_year FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 + interval '1 year' AS one_year FROM TIMESTAMP_TBL; -SELECT '' AS sixtythree, d1 - interval '1 year' AS one_year FROM TIMESTAMP_TBL; +SELECT '' AS "66", d1 - interval '1 year' AS one_year FROM TIMESTAMP_TBL; --- Casting within a BETWEEN qualifier should probably be allowed by the parser. - tgl 97/04/26 ---SELECT '' AS fifty, d1 - timestamp '1997-01-02' AS diff --- FROM TIMESTAMP_TBL WHERE d1 BETWEEN timestamp '1902-01-01' AND timestamp '2038-01-01'; -SELECT '' AS fifty, d1 - timestamp '1997-01-02' AS diff +SELECT '' AS "53", d1 - timestamp '1997-01-02' AS diff FROM TIMESTAMP_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01'; -SELECT '' AS fortynine, date_part( 'year', d1) AS year, date_part( 'month', d1) AS month, +-- Test casting within a BETWEEN qualifier +SELECT '' AS "53", d1 - timestamp '1997-01-02' AS diff + FROM TIMESTAMP_TBL + WHERE d1 BETWEEN timestamp '1902-01-01' AND timestamp '2038-01-01'; + +SELECT '' AS "53", date_part( 'year', d1) AS year, date_part( 'month', d1) AS month, date_part( 'day', d1) AS day, date_part( 'hour', d1) AS hour, date_part( 'minute', d1) AS minute, date_part( 'second', d1) AS second FROM TIMESTAMP_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01'; -SELECT '' AS fortynine, date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec, +SELECT '' AS "53", date_part( 'quarter', d1) AS quarter, date_part( 'msec', d1) AS msec, date_part( 'usec', d1) AS usec FROM TIMESTAMP_TBL WHERE d1 BETWEEN '1902-01-01' AND '2038-01-01';