From f5ba72ea04e71a127031a4436781943008abfa7a Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 3 May 2001 22:53:07 +0000
Subject: [PATCH] Consolidate several near-identical uses of mktime() into a
 single routine DetermineLocalTimeZone().  In that routine, be more wary of
 broken mktime() implementations than the original code was: don't allow
 mktime to change the already-set y/m/d/h/m/s information, and don't use
 tm_gmtoff if mktime failed.  Possibly this will resolve some of the
 complaints we've been hearing from users of Middle Eastern timezones on
 RedHat.

---
 src/backend/utils/adt/datetime.c   | 98 +++++++++++++++++++-----------
 src/backend/utils/adt/formatting.c | 34 +----------
 src/backend/utils/adt/timestamp.c  | 55 +----------------
 src/include/utils/datetime.h       |  4 +-
 4 files changed, 69 insertions(+), 122 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 60346efdcd1..1a13aa4a4af 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.63 2001/04/03 18:05:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.64 2001/05/03 22:53:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -869,37 +869,75 @@ DecodeDateTime(char **field, int *ftype, int nf,
 			if (fmask & DTK_M(DTZMOD))
 				return -1;
 
-			if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
-			{
+			*tzp = DetermineLocalTimeZone(tm);
+		}
+	}
+
+	return 0;
+}	/* DecodeDateTime() */
+
+
+/* DetermineLocalTimeZone()
+ * Given a struct 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
+ * (ie, regular or daylight-savings time) at that time.  Set the struct tm's
+ * tm_isdst field accordingly, and return the actual timezone offset.
+ *
+ * This subroutine exists mainly to centralize uses of mktime() and defend
+ * against mktime() bugs on various platforms...
+ */
+int
+DetermineLocalTimeZone(struct tm * tm)
+{
+	int			tz;
+
+	if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
+	{
 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
-				tm->tm_year -= 1900;
-				tm->tm_mon -= 1;
-				tm->tm_isdst = -1;
-				mktime(tm);
-				tm->tm_year += 1900;
-				tm->tm_mon += 1;
+		/*
+		 * Some buggy mktime() implementations may change the year/month/day
+		 * when given a time right at a DST boundary.  To prevent corruption
+		 * of the caller's data, give mktime() a copy...
+		 */
+		struct tm	tt,
+				   *tmp = &tt;
+
+		*tmp = *tm;
+		/* change to Unix conventions for year/month */
+		tmp->tm_year -= 1900;
+		tmp->tm_mon -= 1;
+
+		/* indicate timezone unknown */
+		tmp->tm_isdst = -1;
+
+		mktime(tmp);
+
+		tm->tm_isdst = tmp->tm_isdst;
 
 #if defined(HAVE_TM_ZONE)
-				*tzp = -(tm->tm_gmtoff);		/* tm_gmtoff is
-												 * Sun/DEC-ism */
+		/* tm_gmtoff is Sun/DEC-ism */
+		if (tmp->tm_isdst >= 0)
+			tz = -(tmp->tm_gmtoff);
+		else
+			tz = 0;				/* assume GMT if mktime failed */
 #elif defined(HAVE_INT_TIMEZONE)
-				*tzp = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
+		tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
 #endif	 /* HAVE_INT_TIMEZONE */
 
 #else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
-				*tzp = CTimeZone;
+		tm->tm_isdst = 0;
+		tz = CTimeZone;
 #endif
-			}
-			else
-			{
-				tm->tm_isdst = 0;
-				*tzp = 0;
-			}
-		}
+	}
+	else
+	{
+		/* Given date is out of range, so assume GMT */
+		tm->tm_isdst = 0;
+		tz = 0;
 	}
 
-	return 0;
-}	/* DecodeDateTime() */
+	return tz;
+}
 
 
 /* DecodeTimeOnly()
@@ -1119,22 +1157,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 		tmp->tm_min = tm->tm_min;
 		tmp->tm_sec = tm->tm_sec;
 
-#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
-		tmp->tm_year -= 1900;
-		tmp->tm_mon -= 1;
-		tmp->tm_isdst = -1;
-		mktime(tmp);
+		*tzp = DetermineLocalTimeZone(tmp);
 		tm->tm_isdst = tmp->tm_isdst;
-
-#if defined(HAVE_TM_ZONE)
-		*tzp = -(tmp->tm_gmtoff);		/* tm_gmtoff is Sun/DEC-ism */
-#elif defined(HAVE_INT_TIMEZONE)
-		*tzp = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
-#endif
-
-#else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
-		*tzp = CTimeZone;
-#endif
 	}
 
 	return 0;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 7e68d7e984e..1ad99d330e5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1,7 +1,7 @@
 /* -----------------------------------------------------------------------
  * formatting.c
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/formatting.c,v 1.36 2001/03/23 04:49:54 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/formatting.c,v 1.37 2001/05/03 22:53:07 tgl Exp $
  *
  *
  *	 Portions Copyright (c) 1999-2000, PostgreSQL Global Development Group
@@ -2936,37 +2936,7 @@ to_timestamp(PG_FUNCTION_ARGS)
 #ifdef DEBUG_TO_FROM_CHAR
 	NOTICE_TM;
 #endif
-	if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
-	{
-
-#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
-		tm->tm_isdst = -1;
-		tm->tm_year -= 1900;
-		tm->tm_mon -= 1;
-
-#ifdef DEBUG_TO_FROM_CHAR
-		elog(DEBUG_elog_output, "TO-FROM_CHAR: Call mktime()");
-		NOTICE_TM;
-#endif
-		mktime(tm);
-		tm->tm_year += 1900;
-		tm->tm_mon += 1;
-
-#if defined(HAVE_TM_ZONE)
-		tz = -(tm->tm_gmtoff);	/* tm_gmtoff is Sun/DEC-ism */
-#elif defined(HAVE_INT_TIMEZONE)
-		tz = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
-#endif
-
-#else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
-		tz = CTimeZone;
-#endif
-	}
-	else
-	{
-		tm->tm_isdst = 0;
-		tz = 0;
-	}
+	tz = DetermineLocalTimeZone(tm);
 #ifdef DEBUG_TO_FROM_CHAR
 	NOTICE_TM;
 #endif
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 186103252b3..68099de8678 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.48 2001/05/03 19:00:36 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.49 2001/05/03 22:53:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -990,32 +990,7 @@ timestamp_pl_span(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]);
 
-				if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
-				{
-#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
-					tm->tm_year -= 1900;
-					tm->tm_mon -= 1;
-					tm->tm_isdst = -1;
-					mktime(tm);
-					tm->tm_year += 1900;
-					tm->tm_mon += 1;
-
-#if defined(HAVE_TM_ZONE)
-					tz = -(tm->tm_gmtoff);		/* tm_gmtoff is
-												 * Sun/DEC-ism */
-#elif defined(HAVE_INT_TIMEZONE)
-					tz = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
-#endif
-
-#else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
-					tz = CTimeZone;
-#endif
-				}
-				else
-				{
-					tm->tm_isdst = 0;
-					tz = 0;
-				}
+				tz = DetermineLocalTimeZone(tm);
 
 				if (tm2timestamp(tm, fsec, &tz, &dt) != 0)
 					elog(ERROR, "Unable to add timestamp and interval");
@@ -1631,31 +1606,7 @@ timestamp_trunc(PG_FUNCTION_ARGS)
 					result = 0;
 			}
 
-			if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
-			{
-#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
-				tm->tm_year -= 1900;
-				tm->tm_mon -= 1;
-				tm->tm_isdst = -1;
-				mktime(tm);
-				tm->tm_year += 1900;
-				tm->tm_mon += 1;
-
-#if defined(HAVE_TM_ZONE)
-				tz = -(tm->tm_gmtoff);	/* tm_gmtoff is Sun/DEC-ism */
-#elif defined(HAVE_INT_TIMEZONE)
-				tz = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
-#endif
-
-#else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
-				tz = CTimeZone;
-#endif
-			}
-			else
-			{
-				tm->tm_isdst = 0;
-				tz = 0;
-			}
+			tz = DetermineLocalTimeZone(tm);
 
 			if (tm2timestamp(tm, fsec, &tz, &result) != 0)
 				elog(ERROR, "Unable to truncate timestamp to '%s'", lowunits);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index af9b7f6ac7e..5922d635dce 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: datetime.h,v 1.17 2001/01/24 19:43:28 momjian Exp $
+ * $Id: datetime.h,v 1.18 2001/05/03 22:53:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -255,6 +255,8 @@ extern int DecodeDateDelta(char **field, int *ftype,
 				int nf, int *dtype,
 				struct tm * tm, double *fsec);
 
+extern int	DetermineLocalTimeZone(struct tm * tm);
+
 extern int	EncodeDateOnly(struct tm * tm, int style, char *str);
 extern int	EncodeTimeOnly(struct tm * tm, double fsec, int *tzp, int style, char *str);
 extern int	EncodeDateTime(struct tm * tm, double fsec, int *tzp, char **tzn, int style, char *str);
-- 
GitLab