Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 95ab1e0

Browse files
committed
interval: round values when spilling to months
Previously spilled units greater than months were truncated to months. Also document the spill behavior. Reported-by: Bryn Llewelly Discussion: https://postgr.es/m/BDAE4B56-3337-45A2-AC8A-30593849D6C0@yugabyte.com Backpatch-through: master
1 parent e462856 commit 95ab1e0

File tree

3 files changed

+26
-31
lines changed

3 files changed

+26
-31
lines changed

doc/src/sgml/datatype.sgml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2840,15 +2840,18 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
28402840
</para>
28412841

28422842
<para>
2843-
In the verbose input format, and in some fields of the more compact
2844-
input formats, field values can have fractional parts; for example
2845-
<literal>'1.5 week'</literal> or <literal>'01:02:03.45'</literal>. Such input is
2846-
converted to the appropriate number of months, days, and seconds
2847-
for storage. When this would result in a fractional number of
2848-
months or days, the fraction is added to the lower-order fields
2849-
using the conversion factors 1 month = 30 days and 1 day = 24 hours.
2850-
For example, <literal>'1.5 month'</literal> becomes 1 month and 15 days.
2851-
Only seconds will ever be shown as fractional on output.
2843+
Field values can have fractional parts: for example, <literal>'1.5
2844+
weeks'</literal> or <literal>'01:02:03.45'</literal>. However,
2845+
because interval internally stores only three integer units (months,
2846+
days, microseconds), fractional units must be spilled to smaller
2847+
units. Fractional parts of units greater than months is rounded to
2848+
be an integer number of months, e.g. <literal>'1.5 years'</literal>
2849+
becomes <literal>'1 year 6 mons'</literal>. Fractional parts of
2850+
weeks and days are computed to be an integer number of days and
2851+
microseconds, assuming 30 days per month and 24 hours per day, e.g.,
2852+
<literal>'1.75 months'</literal> becomes <literal>1 mon 22 days
2853+
12:00:00</literal>. Only seconds will ever be shown as fractional
2854+
on output.
28522855
</para>
28532856

28542857
<para>
@@ -2892,10 +2895,10 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
28922895

28932896
<para>
28942897
Internally <type>interval</type> values are stored as months, days,
2895-
and seconds. This is done because the number of days in a month
2898+
and microseconds. This is done because the number of days in a month
28962899
varies, and a day can have 23 or 25 hours if a daylight savings
28972900
time adjustment is involved. The months and days fields are integers
2898-
while the seconds field can store fractions. Because intervals are
2901+
while the microseconds field can store fractional seconds. Because intervals are
28992902
usually created from constant strings or <type>timestamp</type> subtraction,
29002903
this storage method works well in most cases, but can cause unexpected
29012904
results:

src/backend/utils/adt/datetime.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3306,29 +3306,25 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
33063306

33073307
case DTK_YEAR:
33083308
tm->tm_year += val;
3309-
if (fval != 0)
3310-
tm->tm_mon += fval * MONTHS_PER_YEAR;
3309+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
33113310
tmask = DTK_M(YEAR);
33123311
break;
33133312

33143313
case DTK_DECADE:
33153314
tm->tm_year += val * 10;
3316-
if (fval != 0)
3317-
tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
3315+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
33183316
tmask = DTK_M(DECADE);
33193317
break;
33203318

33213319
case DTK_CENTURY:
33223320
tm->tm_year += val * 100;
3323-
if (fval != 0)
3324-
tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
3321+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
33253322
tmask = DTK_M(CENTURY);
33263323
break;
33273324

33283325
case DTK_MILLENNIUM:
33293326
tm->tm_year += val * 1000;
3330-
if (fval != 0)
3331-
tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
3327+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
33323328
tmask = DTK_M(MILLENNIUM);
33333329
break;
33343330

@@ -3565,7 +3561,7 @@ DecodeISO8601Interval(char *str,
35653561
{
35663562
case 'Y':
35673563
tm->tm_year += val;
3568-
tm->tm_mon += (fval * MONTHS_PER_YEAR);
3564+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
35693565
break;
35703566
case 'M':
35713567
tm->tm_mon += val;
@@ -3601,7 +3597,7 @@ DecodeISO8601Interval(char *str,
36013597
return DTERR_BAD_FORMAT;
36023598

36033599
tm->tm_year += val;
3604-
tm->tm_mon += (fval * MONTHS_PER_YEAR);
3600+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
36053601
if (unit == '\0')
36063602
return 0;
36073603
if (unit == 'T')

src/interfaces/ecpg/pgtypeslib/interval.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ DecodeISO8601Interval(char *str,
155155
{
156156
case 'Y':
157157
tm->tm_year += val;
158-
tm->tm_mon += (fval * MONTHS_PER_YEAR);
158+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
159159
break;
160160
case 'M':
161161
tm->tm_mon += val;
@@ -191,7 +191,7 @@ DecodeISO8601Interval(char *str,
191191
return DTERR_BAD_FORMAT;
192192

193193
tm->tm_year += val;
194-
tm->tm_mon += (fval * MONTHS_PER_YEAR);
194+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
195195
if (unit == '\0')
196196
return 0;
197197
if (unit == 'T')
@@ -528,29 +528,25 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */
528528

529529
case DTK_YEAR:
530530
tm->tm_year += val;
531-
if (fval != 0)
532-
tm->tm_mon += fval * MONTHS_PER_YEAR;
531+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
533532
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
534533
break;
535534

536535
case DTK_DECADE:
537536
tm->tm_year += val * 10;
538-
if (fval != 0)
539-
tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
537+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
540538
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
541539
break;
542540

543541
case DTK_CENTURY:
544542
tm->tm_year += val * 100;
545-
if (fval != 0)
546-
tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
543+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
547544
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
548545
break;
549546

550547
case DTK_MILLENNIUM:
551548
tm->tm_year += val * 1000;
552-
if (fval != 0)
553-
tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
549+
tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
554550
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
555551
break;
556552

0 commit comments

Comments
 (0)