aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/timestamp.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-05-31 18:31:51 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-05-31 18:31:51 +0000
commit87de80e95a7c5999dfaf4b769502c97cbe56248b (patch)
tree3bd50b67729b185646616a4b03a6ad6111faaf1c /src/backend/utils/adt/timestamp.c
parentd534b9ee9e8fec0b4d295e7450f6654b08490690 (diff)
downloadpostgresql-87de80e95a7c5999dfaf4b769502c97cbe56248b.tar.gz
postgresql-87de80e95a7c5999dfaf4b769502c97cbe56248b.zip
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.
Diffstat (limited to 'src/backend/utils/adt/timestamp.c')
-rw-r--r--src/backend/utils/adt/timestamp.c30
1 files 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);