Skip to content

Commit 313ed1e

Browse files
committed
Fix (hopefully for the last time) problems with datetime values displaying
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.
1 parent 7754f76 commit 313ed1e

File tree

10 files changed

+125
-56
lines changed

10 files changed

+125
-56
lines changed

contrib/btree_gist/btree_ts.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,7 @@ tstz_to_ts_gmt(Timestamp *gmt, TimestampTz *ts)
122122
*gmt -= (tz * INT64CONST(1000000));
123123
#else
124124
*gmt -= tz;
125-
*gmt = JROUND(*gmt);
126125
#endif
127-
128126
}
129127
return gmt;
130128
}

src/backend/utils/adt/date.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.120 2005/09/09 02:31:49 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.121 2005/10/09 17:21:46 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -944,10 +944,18 @@ time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec)
944944
#else
945945
double trem;
946946

947+
recalc:
947948
trem = time;
948949
TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
949950
TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
950951
TMODULO(trem, tm->tm_sec, 1.0);
952+
trem = TIMEROUND(trem);
953+
/* roundoff may need to propagate to higher-order fields */
954+
if (trem >= 1.0)
955+
{
956+
time = ceil(time);
957+
goto recalc;
958+
}
951959
*fsec = trem;
952960
#endif
953961

@@ -1837,9 +1845,17 @@ timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp)
18371845
#else
18381846
double trem = time->time;
18391847

1848+
recalc:
18401849
TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
18411850
TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
18421851
TMODULO(trem, tm->tm_sec, 1.0);
1852+
trem = TIMEROUND(trem);
1853+
/* roundoff may need to propagate to higher-order fields */
1854+
if (trem >= 1.0)
1855+
{
1856+
trem = ceil(time->time);
1857+
goto recalc;
1858+
}
18431859
*fsec = trem;
18441860
#endif
18451861

src/backend/utils/adt/datetime.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.157 2005/07/23 14:25:33 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.158 2005/10/09 17:21:46 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -3488,16 +3488,16 @@ EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, int *tzp, int style, char *str)
34883488
sprintf(str, "%02d:%02d", tm->tm_hour, tm->tm_min);
34893489

34903490
/*
3491-
* Print fractional seconds if any. The field widths here should be
3492-
* at least equal to the larger of MAX_TIME_PRECISION and
3491+
* Print fractional seconds if any. The fractional field widths
3492+
* here should be equal to the larger of MAX_TIME_PRECISION and
34933493
* MAX_TIMESTAMP_PRECISION.
34943494
*/
34953495
if (fsec != 0)
34963496
{
34973497
#ifdef HAVE_INT64_TIMESTAMP
34983498
sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
34993499
#else
3500-
sprintf(str + strlen(str), ":%012.9f", tm->tm_sec + fsec);
3500+
sprintf(str + strlen(str), ":%013.10f", tm->tm_sec + fsec);
35013501
#endif
35023502
TrimTrailingZeros(str);
35033503
}

src/backend/utils/adt/timestamp.c

Lines changed: 43 additions & 25 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.153 2005/09/09 06:46:14 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.154 2005/10/09 17:21:46 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -998,10 +998,8 @@ dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec)
998998
*min = time / SECS_PER_MINUTE;
999999
time -= (*min) * SECS_PER_MINUTE;
10001000
*sec = time;
1001-
*fsec = JROUND(time - *sec);
1001+
*fsec = time - *sec;
10021002
#endif
1003-
1004-
return;
10051003
} /* dt2time() */
10061004

10071005

@@ -1038,35 +1036,62 @@ timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, char **tzn,
10381036
#endif
10391037
}
10401038

1041-
time = dt;
10421039
#ifdef HAVE_INT64_TIMESTAMP
1040+
time = dt;
10431041
TMODULO(time, date, USECS_PER_DAY);
10441042

10451043
if (time < INT64CONST(0))
10461044
{
10471045
time += USECS_PER_DAY;
10481046
date -= 1;
10491047
}
1048+
1049+
/* add offset to go from J2000 back to standard Julian date */
1050+
date += POSTGRES_EPOCH_JDATE;
1051+
1052+
/* Julian day routine does not work for negative Julian days */
1053+
if (date < 0 || date > (Timestamp) INT_MAX)
1054+
return -1;
1055+
1056+
j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1057+
dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
10501058
#else
1059+
time = dt;
10511060
TMODULO(time, date, (double)SECS_PER_DAY);
10521061

10531062
if (time < 0)
10541063
{
10551064
time += SECS_PER_DAY;
1056-
date -=1;
1065+
date -= 1;
10571066
}
1058-
#endif
10591067

10601068
/* add offset to go from J2000 back to standard Julian date */
10611069
date += POSTGRES_EPOCH_JDATE;
10621070

1071+
recalc_d:
10631072
/* Julian day routine does not work for negative Julian days */
1064-
if (date <0 || date >(Timestamp) INT_MAX)
1073+
if (date < 0 || date > (Timestamp) INT_MAX)
10651074
return -1;
10661075

10671076
j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1077+
recalc_t:
10681078
dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
10691079

1080+
*fsec = TSROUND(*fsec);
1081+
/* roundoff may need to propagate to higher-order fields */
1082+
if (*fsec >= 1.0)
1083+
{
1084+
time = ceil(time);
1085+
if (time >= (double)SECS_PER_DAY)
1086+
{
1087+
time = 0;
1088+
date += 1;
1089+
goto recalc_d;
1090+
}
1091+
goto recalc_t;
1092+
}
1093+
#endif
1094+
10701095
/* Done if no TZ conversion wanted */
10711096
if (tzp == NULL)
10721097
{
@@ -1216,9 +1241,17 @@ interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
12161241
tm->tm_sec = time / USECS_PER_SEC;
12171242
*fsec = time - (tm->tm_sec * USECS_PER_SEC);
12181243
#else
1244+
recalc:
12191245
TMODULO(time, tm->tm_hour, (double)SECS_PER_HOUR);
12201246
TMODULO(time, tm->tm_min, (double)SECS_PER_MINUTE);
12211247
TMODULO(time, tm->tm_sec, 1.0);
1248+
time = TSROUND(time);
1249+
/* roundoff may need to propagate to higher-order fields */
1250+
if (time >= 1.0)
1251+
{
1252+
time = ceil(span.time);
1253+
goto recalc;
1254+
}
12221255
*fsec = time;
12231256
#endif
12241257

@@ -1237,8 +1270,7 @@ tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
12371270
#else
12381271
span->time = (((tm->tm_hour * (double)MINS_PER_HOUR) +
12391272
tm->tm_min) * (double)SECS_PER_MINUTE) +
1240-
tm->tm_sec;
1241-
span->time = JROUND(span->time + fsec);
1273+
tm->tm_sec + fsec;
12421274
#endif
12431275

12441276
return 0;
@@ -1266,7 +1298,6 @@ dt2local(Timestamp dt, int tz)
12661298
dt -= (tz * USECS_PER_SEC);
12671299
#else
12681300
dt -= tz;
1269-
dt = JROUND(dt);
12701301
#endif
12711302
return dt;
12721303
} /* dt2local() */
@@ -1901,11 +1932,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
19011932
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
19021933
errmsg("cannot subtract infinite timestamps")));
19031934

1904-
#ifdef HAVE_INT64_TIMESTAMP
19051935
result->time = dt1 - dt2;
1906-
#else
1907-
result->time = JROUND(dt1 - dt2);
1908-
#endif
19091936

19101937
result->month = 0;
19111938
result->day = 0;
@@ -2224,11 +2251,7 @@ interval_pl(PG_FUNCTION_ARGS)
22242251

22252252
result->month = span1->month + span2->month;
22262253
result->day = span1->day + span2->day;
2227-
#ifdef HAVE_INT64_TIMESTAMP
22282254
result->time = span1->time + span2->time;
2229-
#else
2230-
result->time = JROUND(span1->time + span2->time);
2231-
#endif
22322255

22332256
PG_RETURN_INTERVAL_P(result);
22342257
}
@@ -2244,11 +2267,7 @@ interval_mi(PG_FUNCTION_ARGS)
22442267

22452268
result->month = span1->month - span2->month;
22462269
result->day = span1->day - span2->day;
2247-
#ifdef HAVE_INT64_TIMESTAMP
22482270
result->time = span1->time - span2->time;
2249-
#else
2250-
result->time = JROUND(span1->time - span2->time);
2251-
#endif
22522271

22532272
PG_RETURN_INTERVAL_P(result);
22542273
}
@@ -2280,7 +2299,7 @@ interval_mul(PG_FUNCTION_ARGS)
22802299
#ifdef HAVE_INT64_TIMESTAMP
22812300
result->time = rint(span->time * factor + day_remainder * USECS_PER_DAY);
22822301
#else
2283-
result->time = JROUND(span->time * factor + day_remainder * SECS_PER_DAY);
2302+
result->time = span->time * factor + day_remainder * SECS_PER_DAY;
22842303
#endif
22852304

22862305
result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
@@ -2332,7 +2351,6 @@ interval_div(PG_FUNCTION_ARGS)
23322351
result->time += rint(day_remainder * USECS_PER_DAY);
23332352
#else
23342353
result->time += day_remainder * SECS_PER_DAY;
2335-
result->time = JROUND(result->time);
23362354
#endif
23372355

23382356
result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,

src/include/utils/date.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $PostgreSQL: pgsql/src/include/utils/date.h,v 1.30 2005/02/25 16:13:29 teodor Exp $
10+
* $PostgreSQL: pgsql/src/include/utils/date.h,v 1.31 2005/10/09 17:21:47 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -60,6 +60,10 @@ typedef struct
6060

6161
#define MAX_TIME_PRECISION 10
6262

63+
/* round off to MAX_TIME_PRECISION decimal places */
64+
#define TIME_PREC_INV 10000000000.0
65+
#define TIMEROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
66+
6367
#define DatumGetDateADT(X) ((DateADT) DatumGetInt32(X))
6468
#define DatumGetTimeADT(X) ((TimeADT) DatumGetFloat8(X))
6569
#define DatumGetTimeTzADTP(X) ((TimeTzADT *) DatumGetPointer(X))

src/include/utils/timestamp.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
77
* Portions Copyright (c) 1994, Regents of the University of California
88
*
9-
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.55 2005/10/07 20:13:16 momjian Exp $
9+
* $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.56 2005/10/09 17:21:47 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -163,8 +163,11 @@ typedef int32 fsec_t;
163163

164164
typedef double fsec_t;
165165

166-
#define TIME_PREC_INV 1000000.0
167-
#define JROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
166+
/* round off to MAX_TIMESTAMP_PRECISION decimal places */
167+
/* note: this is also used for rounding off intervals */
168+
#define TS_PREC_INV 1000000.0
169+
#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
170+
168171
#endif
169172

170173
#define TIMESTAMP_MASK(b) (1 << (b))

src/interfaces/ecpg/pgtypeslib/dt.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ typedef int32 fsec_t;
1313

1414
typedef double fsec_t;
1515

16-
#define TIME_PREC_INV 1000000.0
17-
#define JROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV)
16+
/* round off to MAX_TIMESTAMP_PRECISION decimal places */
17+
/* note: this is also used for rounding off intervals */
18+
#define TS_PREC_INV 1000000.0
19+
#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV)
20+
1821
#endif
1922

2023
#define USE_POSTGRES_DATES 0

src/interfaces/ecpg/pgtypeslib/dt_common.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,9 +1255,8 @@ dt2time(double jd, int *hour, int *min, int *sec, fsec_t *fsec)
12551255
*min = time / SECS_PER_MINUTE;
12561256
time -= (*min) * SECS_PER_MINUTE;
12571257
*sec = time;
1258-
*fsec = JROUND(time - *sec);
1258+
*fsec = time - *sec;
12591259
#endif
1260-
return;
12611260
} /* dt2time() */
12621261

12631262

src/interfaces/ecpg/pgtypeslib/interval.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -702,10 +702,18 @@ interval2tm(interval span, struct tm *tm, fsec_t *fsec)
702702
tm->tm_sec = time / USECS_PER_SEC;
703703
*fsec = time - (tm->tm_sec * USECS_PER_SEC);
704704
#else
705+
recalc:
705706
TMODULO(time, tm->tm_mday, (double)SECS_PER_DAY);
706707
TMODULO(time, tm->tm_hour, (double)SECS_PER_HOUR);
707708
TMODULO(time, tm->tm_min, (double)SECS_PER_MINUTE);
708709
TMODULO(time, tm->tm_sec, 1.0);
710+
time = TSROUND(time);
711+
/* roundoff may need to propagate to higher-order fields */
712+
if (time >= 1.0)
713+
{
714+
time = ceil(span.time);
715+
goto recalc;
716+
}
709717
*fsec = time;
710718
#endif
711719

@@ -725,8 +733,7 @@ tm2interval(struct tm *tm, fsec_t fsec, interval *span)
725733
span->time = (((((tm->tm_mday * (double)HOURS_PER_DAY) +
726734
tm->tm_hour) * (double)MINS_PER_HOUR) +
727735
tm->tm_min) * (double)SECS_PER_MINUTE) +
728-
tm->tm_sec;
729-
span->time = JROUND(span->time + fsec);
736+
tm->tm_sec + fsec;
730737
#endif
731738

732739
return 0;

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