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;