]> granicus.if.org Git - postgresql/commitdiff
Fix (hopefully for the last time) problems with datetime values displaying
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 9 Oct 2005 17:21:47 +0000 (17:21 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 9 Oct 2005 17:21:47 +0000 (17:21 +0000)
like '23:59:60' because of fractional-second roundoff problems.  Trying
to control this upstream of the actual display code was hopeless; the right
way is to explicitly round fractional seconds in the display code and then
refigure the results if the fraction rounds up to 1.  Per bug #1927.

contrib/btree_gist/btree_ts.c
src/backend/utils/adt/date.c
src/backend/utils/adt/datetime.c
src/backend/utils/adt/timestamp.c
src/include/utils/date.h
src/include/utils/timestamp.h
src/interfaces/ecpg/pgtypeslib/dt.h
src/interfaces/ecpg/pgtypeslib/dt_common.c
src/interfaces/ecpg/pgtypeslib/interval.c
src/interfaces/ecpg/pgtypeslib/timestamp.c

index 6c9481b4b2ee63d9724b4c7ba3f92be26140e917..119c45093eb947c58026e6708006bfba711da49c 100644 (file)
@@ -122,9 +122,7 @@ tstz_to_ts_gmt(Timestamp *gmt, TimestampTz *ts)
                *gmt -= (tz * INT64CONST(1000000));
 #else
                *gmt -= tz;
-               *gmt = JROUND(*gmt);
 #endif
-
        }
        return gmt;
 }
index b36ee180929b628e0dcf87f216cb9a74bc1e5d45..ec1d808544bcab82b42dacbe615e827313ef4080 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.120 2005/09/09 02:31:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.121 2005/10/09 17:21:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -944,10 +944,18 @@ time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec)
 #else
        double          trem;
 
+recalc:
        trem = time;
        TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
        TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
        TMODULO(trem, tm->tm_sec, 1.0);
+       trem = TIMEROUND(trem);
+       /* roundoff may need to propagate to higher-order fields */
+       if (trem >= 1.0)
+       {
+               time = ceil(time);
+               goto recalc;
+       }
        *fsec = trem;
 #endif
 
@@ -1837,9 +1845,17 @@ timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp)
 #else
        double          trem = time->time;
 
+recalc:
        TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
        TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
        TMODULO(trem, tm->tm_sec, 1.0);
+       trem = TIMEROUND(trem);
+       /* roundoff may need to propagate to higher-order fields */
+       if (trem >= 1.0)
+       {
+               trem = ceil(time->time);
+               goto recalc;
+       }
        *fsec = trem;
 #endif
 
index 9e5be7d4cff90b3b93881334b954bc7cdfc3f06d..74dda5441f1684b896e00fad03fd5456551ebce5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.157 2005/07/23 14:25:33 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.158 2005/10/09 17:21:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3488,8 +3488,8 @@ EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, int *tzp, int style, char *str)
        sprintf(str, "%02d:%02d", tm->tm_hour, tm->tm_min);
 
        /*
-        * Print fractional seconds if any.  The field widths here should be
-        * at least equal to the larger of MAX_TIME_PRECISION and
+        * Print fractional seconds if any.  The fractional field widths
+        * here should be equal to the larger of MAX_TIME_PRECISION and
         * MAX_TIMESTAMP_PRECISION.
         */
        if (fsec != 0)
@@ -3497,7 +3497,7 @@ EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, int *tzp, int style, char *str)
 #ifdef HAVE_INT64_TIMESTAMP
                sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
 #else
-               sprintf(str + strlen(str), ":%012.9f", tm->tm_sec + fsec);
+               sprintf(str + strlen(str), ":%013.10f", tm->tm_sec + fsec);
 #endif
                TrimTrailingZeros(str);
        }
index 8f18b870b5e51d1158d9f23ecd63a0eef72cafe2..73e7bb8ea8ae4f6bac8557271f4046e3dfd340f8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.153 2005/09/09 06:46:14 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.154 2005/10/09 17:21:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -998,10 +998,8 @@ dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
        *min = time / SECS_PER_MINUTE;
        time -= (*min) * SECS_PER_MINUTE;
        *sec = time;
-       *fsec = JROUND(time - *sec);
+       *fsec = time - *sec;
 #endif
-
-       return;
 }      /* dt2time() */
 
 
@@ -1038,8 +1036,8 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn,
 #endif
        }
 
-       time = dt;
 #ifdef HAVE_INT64_TIMESTAMP
+       time = dt;
        TMODULO(time, date, USECS_PER_DAY);
 
        if (time < INT64CONST(0))
@@ -1047,26 +1045,53 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn,
                time += USECS_PER_DAY;
                date -= 1;
        }
+
+       /* add offset to go from J2000 back to standard Julian date */
+       date += POSTGRES_EPOCH_JDATE;
+
+       /* Julian day routine does not work for negative Julian days */
+       if (date < 0 || date > (Timestamp) INT_MAX)
+               return -1;
+
+       j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+       dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
 #else
+       time = dt;
        TMODULO(time, date, (double)SECS_PER_DAY);
 
        if (time < 0)
        {
                time += SECS_PER_DAY;
-               date -=1;
+               date -= 1;
        }
-#endif
 
        /* add offset to go from J2000 back to standard Julian date */
        date += POSTGRES_EPOCH_JDATE;
 
+recalc_d:
        /* Julian day routine does not work for negative Julian days */
-       if (date <0 || date >(Timestamp) INT_MAX)
+       if (date < 0 || date > (Timestamp) INT_MAX)
                return -1;
 
        j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+recalc_t:
        dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
 
+       *fsec = TSROUND(*fsec);
+       /* roundoff may need to propagate to higher-order fields */
+       if (*fsec >= 1.0)
+       {
+               time = ceil(time);
+               if (time >= (double)SECS_PER_DAY)
+               {
+                       time = 0;
+                       date += 1;
+                       goto recalc_d;
+               }
+               goto recalc_t;
+       }
+#endif
+
        /* Done if no TZ conversion wanted */
        if (tzp == NULL)
        {
@@ -1216,9 +1241,17 @@ interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
        tm->tm_sec = time / USECS_PER_SEC;
        *fsec = time - (tm->tm_sec * USECS_PER_SEC);
 #else
+recalc:
        TMODULO(time, tm->tm_hour, (double)SECS_PER_HOUR);
        TMODULO(time, tm->tm_min, (double)SECS_PER_MINUTE);
        TMODULO(time, tm->tm_sec, 1.0);
+       time = TSROUND(time);
+       /* roundoff may need to propagate to higher-order fields */
+       if (time >= 1.0)
+       {
+               time = ceil(span.time);
+               goto recalc;
+       }
        *fsec = time;
 #endif
 
@@ -1237,8 +1270,7 @@ tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
 #else
        span->time = (((tm->tm_hour * (double)MINS_PER_HOUR) +
                                                tm->tm_min) * (double)SECS_PER_MINUTE) +
-                                               tm->tm_sec;
-       span->time = JROUND(span->time + fsec);
+                                               tm->tm_sec + fsec;
 #endif
 
        return 0;
@@ -1266,7 +1298,6 @@ dt2local(Timestamp dt, int tz)
        dt -= (tz * USECS_PER_SEC);
 #else
        dt -= tz;
-       dt = JROUND(dt);
 #endif
        return dt;
 }      /* dt2local() */
@@ -1901,11 +1932,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("cannot subtract infinite timestamps")));
 
-#ifdef HAVE_INT64_TIMESTAMP
        result->time = dt1 - dt2;
-#else
-       result->time = JROUND(dt1 - dt2);
-#endif
 
        result->month = 0;
        result->day = 0;
@@ -2224,11 +2251,7 @@ interval_pl(PG_FUNCTION_ARGS)
 
        result->month = span1->month + span2->month;
        result->day = span1->day + span2->day;
-#ifdef HAVE_INT64_TIMESTAMP
        result->time = span1->time + span2->time;
-#else
-       result->time = JROUND(span1->time + span2->time);
-#endif
 
        PG_RETURN_INTERVAL_P(result);
 }
@@ -2244,11 +2267,7 @@ interval_mi(PG_FUNCTION_ARGS)
 
        result->month = span1->month - span2->month;
        result->day = span1->day - span2->day;
-#ifdef HAVE_INT64_TIMESTAMP
        result->time = span1->time - span2->time;
-#else
-       result->time = JROUND(span1->time - span2->time);
-#endif
 
        PG_RETURN_INTERVAL_P(result);
 }
@@ -2280,7 +2299,7 @@ interval_mul(PG_FUNCTION_ARGS)
 #ifdef HAVE_INT64_TIMESTAMP
        result->time = rint(span->time * factor + day_remainder * USECS_PER_DAY);
 #else
-       result->time = JROUND(span->time * factor + day_remainder * SECS_PER_DAY);
+       result->time = span->time * factor + day_remainder * SECS_PER_DAY;
 #endif
 
        result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
@@ -2332,7 +2351,6 @@ interval_div(PG_FUNCTION_ARGS)
        result->time += rint(day_remainder * USECS_PER_DAY);
 #else
        result->time += day_remainder * SECS_PER_DAY;
-       result->time = JROUND(result->time);
 #endif
 
        result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
index c3c4a06d87193fba02262a414cbbefd648de5e02..869e2ade29bb3f650a5f391b0fe29ff09a70ebae 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/date.h,v 1.30 2005/02/25 16:13:29 teodor Exp $
+ * $PostgreSQL: pgsql/src/include/utils/date.h,v 1.31 2005/10/09 17:21:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,6 +60,10 @@ typedef struct
 
 #define MAX_TIME_PRECISION 10
 
+/* round off to MAX_TIME_PRECISION decimal places */
+#define TIME_PREC_INV 10000000000.0
+#define TIMEROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
+
 #define DatumGetDateADT(X)       ((DateADT) DatumGetInt32(X))
 #define DatumGetTimeADT(X)       ((TimeADT) DatumGetFloat8(X))
 #define DatumGetTimeTzADTP(X) ((TimeTzADT *) DatumGetPointer(X))
index 14c8f6c91b9c0e7e680d39995840237257c0d5b0..dc218f3b28fbae6e3dd5212192d482cbfe79be41 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.55 2005/10/07 20:13:16 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.56 2005/10/09 17:21:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -163,8 +163,11 @@ typedef int32 fsec_t;
 
 typedef double fsec_t;
 
-#define TIME_PREC_INV 1000000.0
-#define JROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
+/* round off to MAX_TIMESTAMP_PRECISION decimal places */
+/* note: this is also used for rounding off intervals */
+#define TS_PREC_INV 1000000.0
+#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
+
 #endif
 
 #define TIMESTAMP_MASK(b) (1 << (b))
index 3c9dd4e01863b1647a6a7fbfec7e243bb66c4354..d7ca2d5bf2fe86799b950b509b00e545ac06307d 100644 (file)
@@ -13,8 +13,11 @@ typedef int32 fsec_t;
 
 typedef double fsec_t;
 
-#define TIME_PREC_INV 1000000.0
-#define JROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
+/* round off to MAX_TIMESTAMP_PRECISION decimal places */
+/* note: this is also used for rounding off intervals */
+#define TS_PREC_INV 1000000.0
+#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
+
 #endif
 
 #define USE_POSTGRES_DATES                             0
index 7cb65f7e962210c5db5fb65e68e84060981ef6c2..305f192a7bdc6094f6acb1699b57903b70533e77 100644 (file)
@@ -1255,9 +1255,8 @@ dt2time(double jd, int *hour, int *min, int *sec, fsec_t *fsec)
        *min = time / SECS_PER_MINUTE;
        time -= (*min) * SECS_PER_MINUTE;
        *sec = time;
-       *fsec = JROUND(time - *sec);
+       *fsec = time - *sec;
 #endif
-       return;
 }      /* dt2time() */
 
 
index a9ed260e70a60f9508068c7bf80d9858e220eff1..93a9d3b45e196033b88742815a4d6820134b2798 100644 (file)
@@ -702,10 +702,18 @@ interval2tm(interval span, struct tm *tm, fsec_t *fsec)
        tm->tm_sec = time / USECS_PER_SEC;
        *fsec = time - (tm->tm_sec * USECS_PER_SEC);
 #else
+recalc:
        TMODULO(time, tm->tm_mday, (double)SECS_PER_DAY);
        TMODULO(time, tm->tm_hour, (double)SECS_PER_HOUR);
        TMODULO(time, tm->tm_min, (double)SECS_PER_MINUTE);
        TMODULO(time, tm->tm_sec, 1.0);
+       time = TSROUND(time);
+       /* roundoff may need to propagate to higher-order fields */
+       if (time >= 1.0)
+       {
+               time = ceil(span.time);
+               goto recalc;
+       }
        *fsec = time;
 #endif
 
@@ -725,8 +733,7 @@ tm2interval(struct tm *tm, fsec_t fsec, interval *span)
        span->time = (((((tm->tm_mday * (double)HOURS_PER_DAY) +
                                                tm->tm_hour) * (double)MINS_PER_HOUR) +
                                                tm->tm_min) * (double)SECS_PER_MINUTE) +
-                                               tm->tm_sec;
-       span->time = JROUND(span->time + fsec);
+                                               tm->tm_sec + fsec;
 #endif
 
        return 0;
index 74b024d0a9b6ab25ff85a438454acaecb443016a..5b7928b182eb528c808618c535049911328f081b 100644 (file)
@@ -38,7 +38,6 @@ dt2local(timestamp dt, int tz)
        dt -= (tz * USECS_PER_SEC);
 #else
        dt -= tz;
-       dt = JROUND(dt);
 #endif
        return dt;
 }      /* dt2local() */
@@ -124,9 +123,8 @@ dt2time(timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
        *min = time / SECS_PER_MINUTE;
        time -= (*min) * SECS_PER_MINUTE;
        *sec = time;
-       *fsec = JROUND(time - *sec);
+       *fsec = time - *sec;
 #endif
-       return;
 }      /* dt2time() */
 
 /* timestamp2tm()
@@ -144,7 +142,7 @@ static int
 timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, char **tzn)
 {
 #ifdef HAVE_INT64_TIMESTAMP
-       int                     dDate,
+       int64           dDate,
                                date0;
        int64           time;
 #else
@@ -160,8 +158,8 @@ timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, char **tzn)
 
        date0 = date2j(2000, 1, 1);
 
-       time = dt;
 #ifdef HAVE_INT64_TIMESTAMP
+       time = dt;
        TMODULO(time, dDate, USECS_PER_DAY);
 
        if (time < INT64CONST(0))
@@ -169,7 +167,18 @@ timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, char **tzn)
                time += USECS_PER_DAY;
                dDate -= 1;
        }
+
+       /* add offset to go from J2000 back to standard Julian date */
+       dDate += date0;
+
+       /* Julian day routine does not work for negative Julian days */
+       if (dDate < 0 || dDate > (timestamp) INT_MAX)
+               return -1;
+
+       j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+       dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
 #else
+       time = dt;
        TMODULO(time, dDate, (double)SECS_PER_DAY);
 
        if (time < 0)
@@ -177,18 +186,34 @@ timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, char **tzn)
                time += SECS_PER_DAY;
                dDate -= 1;
        }
-#endif
-
-       /* Julian day routine does not work for negative Julian days */
-       if (dDate < -date0)
-               return -1;
 
        /* add offset to go from J2000 back to standard Julian date */
        dDate += date0;
 
+recalc_d:
+       /* Julian day routine does not work for negative Julian days */
+       if (dDate < 0 || dDate > (timestamp) INT_MAX)
+               return -1;
+
        j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+recalc_t:
        dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
 
+       *fsec = TSROUND(*fsec);
+       /* roundoff may need to propagate to higher-order fields */
+       if (*fsec >= 1.0)
+       {
+               time = ceil(time);
+               if (time >= (double)SECS_PER_DAY)
+               {
+                       time = 0;
+                       dDate += 1;
+                       goto recalc_d;
+               }
+               goto recalc_t;
+       }
+#endif
+
        if (tzp != NULL)
        {
                /*
@@ -791,11 +816,7 @@ PGTYPEStimestamp_sub(timestamp *ts1, timestamp *ts2, interval *iv)
        if (TIMESTAMP_NOT_FINITE(*ts1) || TIMESTAMP_NOT_FINITE(*ts2))
                return PGTYPES_TS_ERR_EINFTIME;
        else
-#ifdef HAVE_INT64_TIMESTAMP
                iv->time = (ts1 - ts2);
-#else
-               iv->time = JROUND(ts1 - ts2);
-#endif
 
        iv->month = 0;