1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-17 17:02:08 +03:00

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
This commit is contained in:
Bruce Momjian
2021-08-03 12:10:29 -04:00
parent e462856a7a
commit 95ab1e0a9d
3 changed files with 26 additions and 31 deletions

View File

@ -2840,15 +2840,18 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
</para> </para>
<para> <para>
In the verbose input format, and in some fields of the more compact Field values can have fractional parts: for example, <literal>'1.5
input formats, field values can have fractional parts; for example weeks'</literal> or <literal>'01:02:03.45'</literal>. However,
<literal>'1.5 week'</literal> or <literal>'01:02:03.45'</literal>. Such input is because interval internally stores only three integer units (months,
converted to the appropriate number of months, days, and seconds days, microseconds), fractional units must be spilled to smaller
for storage. When this would result in a fractional number of units. Fractional parts of units greater than months is rounded to
months or days, the fraction is added to the lower-order fields be an integer number of months, e.g. <literal>'1.5 years'</literal>
using the conversion factors 1 month = 30 days and 1 day = 24 hours. becomes <literal>'1 year 6 mons'</literal>. Fractional parts of
For example, <literal>'1.5 month'</literal> becomes 1 month and 15 days. weeks and days are computed to be an integer number of days and
Only seconds will ever be shown as fractional on output. microseconds, assuming 30 days per month and 24 hours per day, e.g.,
<literal>'1.75 months'</literal> becomes <literal>1 mon 22 days
12:00:00</literal>. Only seconds will ever be shown as fractional
on output.
</para> </para>
<para> <para>
@ -2892,10 +2895,10 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
<para> <para>
Internally <type>interval</type> values are stored as months, days, Internally <type>interval</type> values are stored as months, days,
and seconds. This is done because the number of days in a month and microseconds. This is done because the number of days in a month
varies, and a day can have 23 or 25 hours if a daylight savings varies, and a day can have 23 or 25 hours if a daylight savings
time adjustment is involved. The months and days fields are integers time adjustment is involved. The months and days fields are integers
while the seconds field can store fractions. Because intervals are while the microseconds field can store fractional seconds. Because intervals are
usually created from constant strings or <type>timestamp</type> subtraction, usually created from constant strings or <type>timestamp</type> subtraction,
this storage method works well in most cases, but can cause unexpected this storage method works well in most cases, but can cause unexpected
results: results:

View File

@ -3306,29 +3306,25 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
case DTK_YEAR: case DTK_YEAR:
tm->tm_year += val; tm->tm_year += val;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
tm->tm_mon += fval * MONTHS_PER_YEAR;
tmask = DTK_M(YEAR); tmask = DTK_M(YEAR);
break; break;
case DTK_DECADE: case DTK_DECADE:
tm->tm_year += val * 10; tm->tm_year += val * 10;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
tmask = DTK_M(DECADE); tmask = DTK_M(DECADE);
break; break;
case DTK_CENTURY: case DTK_CENTURY:
tm->tm_year += val * 100; tm->tm_year += val * 100;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
tmask = DTK_M(CENTURY); tmask = DTK_M(CENTURY);
break; break;
case DTK_MILLENNIUM: case DTK_MILLENNIUM:
tm->tm_year += val * 1000; tm->tm_year += val * 1000;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
tmask = DTK_M(MILLENNIUM); tmask = DTK_M(MILLENNIUM);
break; break;
@ -3565,7 +3561,7 @@ DecodeISO8601Interval(char *str,
{ {
case 'Y': case 'Y':
tm->tm_year += val; tm->tm_year += val;
tm->tm_mon += (fval * MONTHS_PER_YEAR); tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
break; break;
case 'M': case 'M':
tm->tm_mon += val; tm->tm_mon += val;
@ -3601,7 +3597,7 @@ DecodeISO8601Interval(char *str,
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
tm->tm_year += val; tm->tm_year += val;
tm->tm_mon += (fval * MONTHS_PER_YEAR); tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
if (unit == '\0') if (unit == '\0')
return 0; return 0;
if (unit == 'T') if (unit == 'T')

View File

@ -155,7 +155,7 @@ DecodeISO8601Interval(char *str,
{ {
case 'Y': case 'Y':
tm->tm_year += val; tm->tm_year += val;
tm->tm_mon += (fval * MONTHS_PER_YEAR); tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
break; break;
case 'M': case 'M':
tm->tm_mon += val; tm->tm_mon += val;
@ -191,7 +191,7 @@ DecodeISO8601Interval(char *str,
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
tm->tm_year += val; tm->tm_year += val;
tm->tm_mon += (fval * MONTHS_PER_YEAR); tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
if (unit == '\0') if (unit == '\0')
return 0; return 0;
if (unit == 'T') if (unit == 'T')
@ -528,29 +528,25 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */
case DTK_YEAR: case DTK_YEAR:
tm->tm_year += val; tm->tm_year += val;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
tm->tm_mon += fval * MONTHS_PER_YEAR;
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
break; break;
case DTK_DECADE: case DTK_DECADE:
tm->tm_year += val * 10; tm->tm_year += val * 10;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
break; break;
case DTK_CENTURY: case DTK_CENTURY:
tm->tm_year += val * 100; tm->tm_year += val * 100;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
break; break;
case DTK_MILLENNIUM: case DTK_MILLENNIUM:
tm->tm_year += val * 1000; tm->tm_year += val * 1000;
if (fval != 0) tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
break; break;