Skip to content

Commit 87de80e

Browse files
committed
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.
1 parent d534b9e commit 87de80e

File tree

1 file changed

+17
-13
lines changed

1 file changed

+17
-13
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.106 2004/05/21 05:08:02 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.107 2004/05/31 18:31:51 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -933,22 +933,18 @@ dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
933933
* local time zone. If out of this range, leave as GMT. - tgl 97/05/27
934934
*/
935935
int
936-
timestamp2tm(Timestamp dt, int *tzp, struct pg_tm * tm, fsec_t *fsec, char **tzn)
936+
timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn)
937937
{
938938
#ifdef HAVE_INT64_TIMESTAMP
939-
int date,
940-
date0;
939+
int date;
941940
int64 time;
942941
#else
943-
double date,
944-
date0;
942+
double date;
945943
double time;
946944
#endif
947945
time_t utime;
948946
struct pg_tm *tx;
949947

950-
date0 = POSTGRES_EPOCH_JDATE;
951-
952948
/*
953949
* If HasCTZSet is true then we have a brute force time zone
954950
* 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
983979
#endif
984980

985981
/* Julian day routine does not work for negative Julian days */
986-
if (date < -date0)
982+
if (date < -POSTGRES_EPOCH_JDATE)
987983
return -1;
988984

989985
/* add offset to go from J2000 back to standard Julian date */
990-
date += date0;
986+
date += POSTGRES_EPOCH_JDATE;
991987

992988
j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
993989
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
10141010
*/
10151011
else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
10161012
{
1013+
/*
1014+
* Convert to integer, avoiding platform-specific
1015+
* roundoff-in-wrong-direction errors, and adjust to
1016+
* Unix epoch. Note we have to do this in one step
1017+
* because the intermediate result before adjustment
1018+
* won't necessarily fit in an int32.
1019+
*/
10171020
#ifdef HAVE_INT64_TIMESTAMP
1018-
utime = ((dt / INT64CONST(1000000))
1019-
+ ((date0 - UNIX_EPOCH_JDATE) * INT64CONST(86400)));
1021+
utime = (dt - *fsec) / INT64CONST(1000000) +
1022+
(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * 86400;
10201023
#else
1021-
utime = (dt + ((date0 - UNIX_EPOCH_JDATE) * 86400));
1024+
utime = rint(dt - *fsec +
1025+
(POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * 86400);
10221026
#endif
10231027

10241028
tx = pg_localtime(&utime);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy