From 3dbbbbf8e98329e1eea9920436defc64af3594d3 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Sat, 23 Jul 2005 14:25:34 +0000
Subject: [PATCH] Andrew pointed out that the current fix didn't handle dates
 that were near daylight savings time boudaries.  This handles it properly,
 e.g.

        test=> select '2005-04-03 04:00:00'::timestamp at time zone
        'America/Los_Angeles';
                timezone
        ------------------------
         2005-04-03 07:00:00-04
        (1 row)
---
 src/backend/utils/adt/date.c       |  6 ++---
 src/backend/utils/adt/datetime.c   | 16 ++++++------
 src/backend/utils/adt/formatting.c |  4 +--
 src/backend/utils/adt/nabstime.c   |  4 +--
 src/backend/utils/adt/timestamp.c  | 39 ++++++++++++++++--------------
 src/include/utils/datetime.h       |  4 +--
 6 files changed, 38 insertions(+), 35 deletions(-)

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index fdb3942f4ff..477d7993e6f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.118 2005/07/22 05:03:09 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.119 2005/07/23 14:25:33 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -301,7 +301,7 @@ date2timestamptz(DateADT dateVal)
 	tm->tm_hour = 0;
 	tm->tm_min = 0;
 	tm->tm_sec = 0;
-	tz = DetermineLocalTimeZone(tm);
+	tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 #ifdef HAVE_INT64_TIMESTAMP
 	result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
@@ -2231,7 +2231,7 @@ time_timetz(PG_FUNCTION_ARGS)
 
 	GetCurrentDateTime(tm);
 	time2tm(time, tm, &fsec);
-	tz = DetermineLocalTimeZone(tm);
+	tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 	result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 04ec7cb216a..9e5be7d4cff 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.156 2005/07/22 03:46:33 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.157 2005/07/23 14:25:33 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1612,7 +1612,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
 			if (fmask & DTK_M(DTZMOD))
 				return DTERR_BAD_FORMAT;
 
-			*tzp = DetermineLocalTimeZone(tm);
+			*tzp = DetermineTimeZoneOffset(tm, global_timezone);
 		}
 	}
 
@@ -1620,10 +1620,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
 }
 
 
-/* DetermineLocalTimeZone()
+/* DetermineTimeZoneOffset()
  *
  * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
- * tm_sec fields are set, attempt to determine the applicable local zone
+ * tm_sec fields are set, attempt to determine the applicable time zone
  * (ie, regular or daylight-savings time) at that time.  Set the struct pg_tm's
  * tm_isdst field accordingly, and return the actual timezone offset.
  *
@@ -1632,7 +1632,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
  * of mktime(), anyway.
  */
 int
-DetermineLocalTimeZone(struct pg_tm *tm)
+DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp)
 {
 	int			date,
 				sec;
@@ -1648,7 +1648,7 @@ DetermineLocalTimeZone(struct pg_tm *tm)
 				after_isdst;
 	int			res;
 
-	if (HasCTZSet)
+	if (tzp == global_timezone && HasCTZSet)
 	{
 		tm->tm_isdst = 0;		/* for lack of a better idea */
 		return CTimeZone;
@@ -1687,7 +1687,7 @@ DetermineLocalTimeZone(struct pg_tm *tm)
 							   &before_gmtoff, &before_isdst,
 							   &boundary,
 							   &after_gmtoff, &after_isdst,
-		                       global_timezone);
+		                       tzp);
 	if (res < 0)
 		goto overflow;			/* failure? */
 
@@ -2282,7 +2282,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 		tmp->tm_hour = tm->tm_hour;
 		tmp->tm_min = tm->tm_min;
 		tmp->tm_sec = tm->tm_sec;
-		*tzp = DetermineLocalTimeZone(tmp);
+		*tzp = DetermineTimeZoneOffset(tmp, global_timezone);
 		tm->tm_isdst = tmp->tm_isdst;
 	}
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 9f1cc1a4768..b473b9b13b4 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1,7 +1,7 @@
 /* -----------------------------------------------------------------------
  * formatting.c
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.92 2005/07/21 03:56:16 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.93 2005/07/23 14:25:33 momjian Exp $
  *
  *
  *	 Portions Copyright (c) 1999-2005, PostgreSQL Global Development Group
@@ -2989,7 +2989,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 
 	do_to_timestamp(date_txt, fmt, &tm, &fsec);
 
-	tz = DetermineLocalTimeZone(&tm);
+	tz = DetermineTimeZoneOffset(&tm, global_timezone);
 
 	if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
 		ereport(ERROR,
diff --git a/src/backend/utils/adt/nabstime.c b/src/backend/utils/adt/nabstime.c
index d4d9d25e48d..739345b7baa 100644
--- a/src/backend/utils/adt/nabstime.c
+++ b/src/backend/utils/adt/nabstime.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.141 2005/07/22 19:55:50 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.142 2005/07/23 14:25:33 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -474,7 +474,7 @@ timestamp_abstime(PG_FUNCTION_ARGS)
 		result = NOEND_ABSTIME;
 	else if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0)
 	{
-		tz = DetermineLocalTimeZone(tm);
+		tz = DetermineTimeZoneOffset(tm, global_timezone);
 		result = tm2abstime(tm, tz);
 	}
 	else
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 999f8fbf522..da7a87a1bb8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.143 2005/07/23 02:02:27 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.144 2005/07/23 14:25:34 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2100,7 +2100,7 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
 			if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
 				tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
 
-			tz = DetermineLocalTimeZone(tm);
+			tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 			if (tm2timestamp(tm, fsec, &tz, &timestamp) != 0)
 				ereport(ERROR,
@@ -2124,7 +2124,7 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
 			julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day;
 			j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
 
-			tz = DetermineLocalTimeZone(tm);
+			tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 			if (tm2timestamp(tm, fsec, &tz, &timestamp) != 0)
 				ereport(ERROR,
@@ -3104,7 +3104,7 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
 		}
 
 		if (redotz)
-			tz = DetermineLocalTimeZone(tm);
+			tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
 			ereport(ERROR,
@@ -3529,7 +3529,7 @@ timestamp_part(PG_FUNCTION_ARGS)
 						   (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 							errmsg("timestamp out of range")));
 
-					tz = DetermineLocalTimeZone(tm);
+					tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 					if (tm2timestamp(tm, fsec, &tz, &timestamptz) != 0)
 						ereport(ERROR,
@@ -3924,12 +3924,11 @@ interval_part(PG_FUNCTION_ARGS)
 
 /* 	timestamp_zone()
  * 	Encode timestamp type with specified time zone.
- * 	Returns timestamp with time zone, with the input
- *	rotated from local time to the specified zone.
- *	This function is tricky because instead of shifting
- *	the time _to_ a new time zone, it sets the time to _be_
- *	the specified timezone.  This requires trickery
- *	of double-subtracting the requested timezone offset.
+ * 	This function is just timestamp2timestamptz() except instead of
+ *	shifting to the global timezone, we shift to the specified timezone.
+ *	This is different from the other AT TIME ZONE cases because instead
+ *	of shifting to a _to_ a new time zone, it sets the time to _be_ the
+ *	specified timezone.
  */
 Datum
 timestamp_zone(PG_FUNCTION_ARGS)
@@ -3943,11 +3942,12 @@ timestamp_zone(PG_FUNCTION_ARGS)
 	int         len;
 	struct pg_tm tm;
 	fsec_t      fsec;
-
+	bool		fail;
+	
 	if (TIMESTAMP_NOT_FINITE(timestamp))
 		PG_RETURN_TIMESTAMPTZ(timestamp);
 
-	/* Find the specified timezone? */
+	/* Find the specified timezone */
 	len = (VARSIZE(zone) - VARHDRSZ>TZ_STRLEN_MAX) ?
 			TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ;
 	memcpy(tzname, VARDATA(zone), len);
@@ -3963,8 +3963,13 @@ timestamp_zone(PG_FUNCTION_ARGS)
 	}
 
 	/* Apply the timezone change */
-	if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0 ||
-	    tm2timestamp(&tm, fsec, &tz, &result) != 0)
+	fail = (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0);
+	if (!fail)
+	{
+		tz = DetermineTimeZoneOffset(&tm, tzp);
+		fail = (tm2timestamp(&tm, fsec, &tz, &result) != 0);
+	}
+	if (fail)
 	{
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -3972,8 +3977,6 @@ timestamp_zone(PG_FUNCTION_ARGS)
 				        tzname)));
 		PG_RETURN_NULL();
 	}
-	/* Must double-adjust for timezone */
-	result = dt2local(result, -tz);
 
 	PG_RETURN_TIMESTAMPTZ(result);
 }
@@ -4039,7 +4042,7 @@ timestamp2timestamptz(Timestamp timestamp)
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("timestamp out of range")));
 
-		tz = DetermineLocalTimeZone(tm);
+		tz = DetermineTimeZoneOffset(tm, global_timezone);
 
 		if (tm2timestamp(tm, fsec, &tz, &result) != 0)
 			ereport(ERROR,
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 2e7155d8030..572a9852f70 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.55 2005/07/22 03:46:34 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.56 2005/07/23 14:25:34 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -291,7 +291,7 @@ extern int DecodeInterval(char **field, int *ftype,
 extern void DateTimeParseError(int dterr, const char *str,
 				   const char *datatype);
 
-extern int	DetermineLocalTimeZone(struct pg_tm *tm);
+extern int	DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp);
 
 extern int	EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern int	EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, int *tzp, int style, char *str);
-- 
GitLab