From 87de80e95a7c5999dfaf4b769502c97cbe56248b Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 31 May 2004 18:31:51 +0000
Subject: [PATCH] I think I've finally identified the cause of the
 off-by-one-second issue in timestamp conversion that we hacked around for so
 long by ignoring the seconds field from localtime().  It's simple: you have
 to watch out for platform-specific roundoff error when reducing a
 possibly-fractional timestamp to integral time_t form.  In particular we
 should subtract off the already-determined fractional fsec field. This should
 be enough to get an exact answer with int64 timestamps; with float
 timestamps, throw in a rint() call just to be sure.

---
 src/backend/utils/adt/timestamp.c | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 1705441329f..d40715b7e44 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.106 2004/05/21 05:08:02 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.107 2004/05/31 18:31:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -933,22 +933,18 @@ dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
  *	local time zone. If out of this range, leave as GMT. - tgl 97/05/27
  */
 int
-timestamp2tm(Timestamp dt, int *tzp, struct pg_tm * tm, fsec_t *fsec, char **tzn)
+timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn)
 {
 #ifdef HAVE_INT64_TIMESTAMP
-	int			date,
-				date0;
+	int			date;
 	int64		time;
 #else
-	double		date,
-				date0;
+	double		date;
 	double		time;
 #endif
 	time_t		utime;
 	struct pg_tm  *tx;
 
-	date0 = POSTGRES_EPOCH_JDATE;
-
 	/*
 	 * If HasCTZSet is true then we have a brute force time zone
 	 * specified. Go ahead and rotate to the local time zone since we will
@@ -983,11 +979,11 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm * tm, fsec_t *fsec, char **tzn
 #endif
 
 	/* Julian day routine does not work for negative Julian days */
-	if (date < -date0)
+	if (date < -POSTGRES_EPOCH_JDATE)
 		return -1;
 
 	/* add offset to go from J2000 back to standard Julian date */
-	date += date0;
+	date += POSTGRES_EPOCH_JDATE;
 
 	j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
 	dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
@@ -1014,11 +1010,19 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm * tm, fsec_t *fsec, char **tzn
 		 */
 		else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
 		{
+			/*
+			 * Convert to integer, avoiding platform-specific
+			 * roundoff-in-wrong-direction errors, and adjust to
+			 * Unix epoch.  Note we have to do this in one step
+			 * because the intermediate result before adjustment
+			 * won't necessarily fit in an int32.
+			 */
 #ifdef HAVE_INT64_TIMESTAMP
-			utime = ((dt / INT64CONST(1000000))
-					 + ((date0 - UNIX_EPOCH_JDATE) * INT64CONST(86400)));
+			utime = (dt - *fsec) / INT64CONST(1000000) +
+				(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * 86400;
 #else
-			utime = (dt + ((date0 - UNIX_EPOCH_JDATE) * 86400));
+			utime = rint(dt - *fsec +
+						 (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * 86400);
 #endif
 
 			tx = pg_localtime(&utime);
-- 
GitLab