From 9e7166096e6d01adff88d4a7383ca808f67041b4 Mon Sep 17 00:00:00 2001
From: "Thomas G. Lockhart" <lockhart@fourpalms.org>
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 3c88ac3a3d1..6806bb8f4f9 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 7e4fb996678..f86f2cfd28e 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 984b5c79c6e..e4effba4f7a 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';
 
-- 
GitLab