From 87de80e95a7c5999dfaf4b769502c97cbe56248b Mon Sep 17 00:00:00 2001 From: Tom Lane 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 1705441329..d40715b7e4 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);