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

Commit 3ad319b

Browse files
committed
Doc: improve explanation of type interval, especially extract().
The explanation of interval's behavior in datatype.sgml wasn't wrong exactly, but it was unclear, partly because it buried the lede about there being three internal fields. Rearrange and wordsmith for more clarity. The discussion of extract() claimed that input of type date was handled by casting, but actually there's been a separate SQL function taking date for a very long time. Also, it was mostly silent about how interval inputs are handled, but there are several field types for which it seems useful to be specific. Improve discussion of justify_days()/justify_hours() too. In passing, remove vertical space in some groups of examples, as there was little consistency about whether to have such space or not. (I only did this within the datetime functions section; there are some related inconsistencies elsewhere.) Per discussion of bug #18348 from Michael Bondarenko. There may be some code changes coming out of that discussion too, but we likely won't back-patch them. This docs-only patch seems useful to back-patch, though I only carried it back to v13 because it didn't apply easily in v12. Discussion: https://postgr.es/m/18348-b097a3587dfde8a4@postgresql.org
1 parent 5ad6c72 commit 3ad319b

File tree

2 files changed

+96
-84
lines changed

2 files changed

+96
-84
lines changed

doc/src/sgml/datatype.sgml

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2869,10 +2869,31 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
28692869
</para>
28702870

28712871
<para>
2872-
Field values can have fractional parts: for example, <literal>'1.5
2872+
Internally, <type>interval</type> values are stored as three integral
2873+
fields: months, days, and microseconds. These fields are kept
2874+
separate because the number of days in a month varies, while a day
2875+
can have 23 or 25 hours if a daylight savings time transition is
2876+
involved. An interval input string that uses other units is
2877+
normalized into this format, and then reconstructed in a standardized
2878+
way for output, for example:
2879+
2880+
<programlisting>
2881+
SELECT '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::interval;
2882+
interval
2883+
---------------------------------------
2884+
3 years 3 mons 700 days 133:17:36.789
2885+
</programlisting>
2886+
2887+
Here weeks, which are understood as <quote>7 days</quote>, have been
2888+
kept separate, while the smaller and larger time units were
2889+
combined and normalized.
2890+
</para>
2891+
2892+
<para>
2893+
Input field values can have fractional parts, for example <literal>'1.5
28732894
weeks'</literal> or <literal>'01:02:03.45'</literal>. However,
2874-
because interval internally stores only three integer units (months,
2875-
days, microseconds), fractional units must be spilled to smaller
2895+
because <type>interval</type> internally stores only integral fields,
2896+
fractional values must be converted into smaller
28762897
units. Fractional parts of units greater than months are rounded to
28772898
be an integer number of months, e.g. <literal>'1.5 years'</literal>
28782899
becomes <literal>'1 year 6 mons'</literal>. Fractional parts of
@@ -2922,33 +2943,6 @@ P <optional> <replaceable>years</replaceable>-<replaceable>months</replaceable>-
29222943
</tgroup>
29232944
</table>
29242945

2925-
<para>
2926-
Internally <type>interval</type> values are stored as months, days,
2927-
and microseconds. This is done because the number of days in a month
2928-
varies, and a day can have 23 or 25 hours if a daylight savings
2929-
time adjustment is involved. The months and days fields are integers
2930-
while the microseconds field can store fractional seconds. Because intervals are
2931-
usually created from constant strings or <type>timestamp</type> subtraction,
2932-
this storage method works well in most cases, but can cause unexpected
2933-
results:
2934-
2935-
<programlisting>
2936-
SELECT EXTRACT(hours from '80 minutes'::interval);
2937-
date_part
2938-
-----------
2939-
1
2940-
2941-
SELECT EXTRACT(days from '80 hours'::interval);
2942-
date_part
2943-
-----------
2944-
0
2945-
</programlisting>
2946-
2947-
Functions <function>justify_days</function> and
2948-
<function>justify_hours</function> are available for adjusting days
2949-
and hours that overflow their normal ranges.
2950-
</para>
2951-
29522946
</sect2>
29532947

29542948
<sect2 id="datatype-interval-output">
@@ -2960,6 +2954,23 @@ SELECT EXTRACT(days from '80 hours'::interval);
29602954
<seealso>formatting</seealso>
29612955
</indexterm>
29622956

2957+
<para>
2958+
As previously explained, <productname>PostgreSQL</productname>
2959+
stores <type>interval</type> values as months, days, and
2960+
microseconds. For output, the months field is converted to years and
2961+
months by dividing by 12. The days field is shown as-is. The
2962+
microseconds field is converted to hours, minutes, seconds, and
2963+
fractional seconds. Thus months, minutes, and seconds will never be
2964+
shown as exceeding the ranges 0&ndash;11, 0&ndash;59, and 0&ndash;59
2965+
respectively, while the displayed years, days, and hours fields can
2966+
be quite large. (The <link
2967+
linkend="function-justify-days"><function>justify_days</function></link>
2968+
and <link
2969+
linkend="function-justify-hours"><function>justify_hours</function></link>
2970+
functions can be used if it is desirable to transpose large days or
2971+
hours values into the next higher field.)
2972+
</para>
2973+
29632974
<para>
29642975
The output format of the interval type can be set to one of the
29652976
four styles <literal>sql_standard</literal>, <literal>postgres</literal>,

doc/src/sgml/func.sgml

Lines changed: 55 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9077,7 +9077,8 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
90779077
</para>
90789078
<para>
90799079
Subtract timestamps (converting 24-hour intervals into days,
9080-
similarly to <function>justify_hours()</function>)
9080+
similarly to <link
9081+
linkend="function-justify-hours"><function>justify_hours()</function></link>)
90819082
</para>
90829083
<para>
90839084
<literal>timestamp '2001-09-29 03:00' - timestamp '2001-07-27 12:00'</literal>
@@ -9490,35 +9491,35 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
94909491

94919492
<row>
94929493
<entry role="func_table_entry"><para role="func_signature">
9493-
<indexterm>
9494+
<indexterm id="function-justify-days">
94949495
<primary>justify_days</primary>
94959496
</indexterm>
94969497
<function>justify_days</function> ( <type>interval</type> )
94979498
<returnvalue>interval</returnvalue>
94989499
</para>
94999500
<para>
9500-
Adjust interval so 30-day time periods are represented as months
9501+
Adjust interval, converting 30-day time periods to months
95019502
</para>
95029503
<para>
9503-
<literal>justify_days(interval '35 days')</literal>
9504-
<returnvalue>1 mon 5 days</returnvalue>
9504+
<literal>justify_days(interval '1 year 65 days')</literal>
9505+
<returnvalue>1 year 2 mons 5 days</returnvalue>
95059506
</para></entry>
95069507
</row>
95079508

95089509
<row>
95099510
<entry role="func_table_entry"><para role="func_signature">
9510-
<indexterm>
9511+
<indexterm id="function-justify-hours">
95119512
<primary>justify_hours</primary>
95129513
</indexterm>
95139514
<function>justify_hours</function> ( <type>interval</type> )
95149515
<returnvalue>interval</returnvalue>
95159516
</para>
95169517
<para>
9517-
Adjust interval so 24-hour time periods are represented as days
9518+
Adjust interval, converting 24-hour time periods to days
95189519
</para>
95199520
<para>
9520-
<literal>justify_hours(interval '27 hours')</literal>
9521-
<returnvalue>1 day 03:00:00</returnvalue>
9521+
<literal>justify_hours(interval '50 hours 10 minutes')</literal>
9522+
<returnvalue>2 days 02:10:00</returnvalue>
95229523
</para></entry>
95239524
</row>
95249525

@@ -9951,13 +9952,19 @@ EXTRACT(<replaceable>field</replaceable> FROM <replaceable>source</replaceable>)
99519952
The <function>extract</function> function retrieves subfields
99529953
such as year or hour from date/time values.
99539954
<replaceable>source</replaceable> must be a value expression of
9954-
type <type>timestamp</type>, <type>time</type>, or <type>interval</type>.
9955-
(Expressions of type <type>date</type> are
9956-
cast to <type>timestamp</type> and can therefore be used as
9957-
well.) <replaceable>field</replaceable> is an identifier or
9955+
type <type>timestamp</type>, <type>date</type>, <type>time</type>,
9956+
or <type>interval</type>. (Timestamps and times can be with or
9957+
without time zone.)
9958+
<replaceable>field</replaceable> is an identifier or
99589959
string that selects what field to extract from the source value.
9960+
Not all fields are valid for every input data type; for example, fields
9961+
smaller than a day cannot be extracted from a <type>date</type>, while
9962+
fields of a day or more cannot be extracted from a <type>time</type>.
99599963
The <function>extract</function> function returns values of type
99609964
<type>numeric</type>.
9965+
</para>
9966+
9967+
<para>
99619968
The following are valid field names:
99629969

99639970
<!-- alphabetical -->
@@ -9966,40 +9973,36 @@ EXTRACT(<replaceable>field</replaceable> FROM <replaceable>source</replaceable>)
99669973
<term><literal>century</literal></term>
99679974
<listitem>
99689975
<para>
9969-
The century
9976+
The century; for <type>interval</type> values, the year field
9977+
divided by 100
99709978
</para>
99719979

99729980
<screen>
99739981
SELECT EXTRACT(CENTURY FROM TIMESTAMP '2000-12-16 12:21:13');
99749982
<lineannotation>Result: </lineannotation><computeroutput>20</computeroutput>
99759983
SELECT EXTRACT(CENTURY FROM TIMESTAMP '2001-02-16 20:38:40');
99769984
<lineannotation>Result: </lineannotation><computeroutput>21</computeroutput>
9985+
SELECT EXTRACT(CENTURY FROM DATE '0001-01-01 AD');
9986+
<lineannotation>Result: </lineannotation><computeroutput>1</computeroutput>
9987+
SELECT EXTRACT(CENTURY FROM DATE '0001-12-31 BC');
9988+
<lineannotation>Result: </lineannotation><computeroutput>-1</computeroutput>
9989+
SELECT EXTRACT(CENTURY FROM INTERVAL '2001 years');
9990+
<lineannotation>Result: </lineannotation><computeroutput>20</computeroutput>
99779991
</screen>
9978-
9979-
<para>
9980-
The first century starts at 0001-01-01 00:00:00 AD, although
9981-
they did not know it at the time. This definition applies to all
9982-
Gregorian calendar countries. There is no century number 0,
9983-
you go from -1 century to 1 century.
9984-
9985-
If you disagree with this, please write your complaint to:
9986-
Pope, Cathedral Saint-Peter of Roma, Vatican.
9987-
</para>
99889992
</listitem>
99899993
</varlistentry>
99909994

99919995
<varlistentry>
99929996
<term><literal>day</literal></term>
99939997
<listitem>
99949998
<para>
9995-
For <type>timestamp</type> values, the day (of the month) field
9996-
(1&ndash;31) ; for <type>interval</type> values, the number of days
9999+
The day of the month (1&ndash;31); for <type>interval</type>
10000+
values, the number of days
999710001
</para>
999810002

999910003
<screen>
1000010004
SELECT EXTRACT(DAY FROM TIMESTAMP '2001-02-16 20:38:40');
1000110005
<lineannotation>Result: </lineannotation><computeroutput>16</computeroutput>
10002-
1000310006
SELECT EXTRACT(DAY FROM INTERVAL '40 days 1 minute');
1000410007
<lineannotation>Result: </lineannotation><computeroutput>40</computeroutput>
1000510008
</screen>
@@ -10073,10 +10076,8 @@ SELECT EXTRACT(DOY FROM TIMESTAMP '2001-02-16 20:38:40');
1007310076
<screen>
1007410077
SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40.12-08');
1007510078
<lineannotation>Result: </lineannotation><computeroutput>982384720.120000</computeroutput>
10076-
1007710079
SELECT EXTRACT(EPOCH FROM TIMESTAMP '2001-02-16 20:38:40.12');
1007810080
<lineannotation>Result: </lineannotation><computeroutput>982355920.120000</computeroutput>
10079-
1008010081
SELECT EXTRACT(EPOCH FROM INTERVAL '5 days 3 hours');
1008110082
<lineannotation>Result: </lineannotation><computeroutput>442800.000000</computeroutput>
1008210083
</screen>
@@ -10104,7 +10105,8 @@ SELECT to_timestamp(982384720.12);
1010410105
<term><literal>hour</literal></term>
1010510106
<listitem>
1010610107
<para>
10107-
The hour field (0&ndash;23)
10108+
The hour field (0&ndash;23 in timestamps, unrestricted in
10109+
intervals)
1010810110
</para>
1010910111

1011010112
<screen>
@@ -10139,7 +10141,7 @@ SELECT EXTRACT(ISODOW FROM TIMESTAMP '2001-02-18 20:38:40');
1013910141
<listitem>
1014010142
<para>
1014110143
The <acronym>ISO</acronym> 8601 week-numbering year that the date
10142-
falls in (not applicable to intervals)
10144+
falls in
1014310145
</para>
1014410146

1014510147
<screen>
@@ -10156,9 +10158,6 @@ SELECT EXTRACT(ISOYEAR FROM DATE '2006-01-02');
1015610158
different from the Gregorian year. See the <literal>week</literal>
1015710159
field for more information.
1015810160
</para>
10159-
<para>
10160-
This field is not available in PostgreSQL releases prior to 8.3.
10161-
</para>
1016210161
</listitem>
1016310162
</varlistentry>
1016410163

@@ -10167,7 +10166,7 @@ SELECT EXTRACT(ISOYEAR FROM DATE '2006-01-02');
1016710166
<listitem>
1016810167
<para>
1016910168
The <firstterm>Julian Date</firstterm> corresponding to the
10170-
date or timestamp (not applicable to intervals). Timestamps
10169+
date or timestamp. Timestamps
1017110170
that are not local midnight result in a fractional value. See
1017210171
<xref linkend="datetime-julian-dates"/> for more information.
1017310172
</para>
@@ -10200,12 +10199,15 @@ SELECT EXTRACT(MICROSECONDS FROM TIME '17:12:28.5');
1020010199
<term><literal>millennium</literal></term>
1020110200
<listitem>
1020210201
<para>
10203-
The millennium
10202+
The millennium; for <type>interval</type> values, the year field
10203+
divided by 1000
1020410204
</para>
1020510205

1020610206
<screen>
1020710207
SELECT EXTRACT(MILLENNIUM FROM TIMESTAMP '2001-02-16 20:38:40');
1020810208
<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
10209+
SELECT EXTRACT(MILLENNIUM FROM INTERVAL '2001 years');
10210+
<lineannotation>Result: </lineannotation><computeroutput>2</computeroutput>
1020910211
</screen>
1021010212

1021110213
<para>
@@ -10248,18 +10250,16 @@ SELECT EXTRACT(MINUTE FROM TIMESTAMP '2001-02-16 20:38:40');
1024810250
<term><literal>month</literal></term>
1024910251
<listitem>
1025010252
<para>
10251-
For <type>timestamp</type> values, the number of the month
10252-
within the year (1&ndash;12) ; for <type>interval</type> values,
10253-
the number of months, modulo 12 (0&ndash;11)
10253+
The number of the month within the year (1&ndash;12);
10254+
for <type>interval</type> values, the number of months modulo 12
10255+
(0&ndash;11)
1025410256
</para>
1025510257

1025610258
<screen>
1025710259
SELECT EXTRACT(MONTH FROM TIMESTAMP '2001-02-16 20:38:40');
1025810260
<lineannotation>Result: </lineannotation><computeroutput>2</computeroutput>
10259-
1026010261
SELECT EXTRACT(MONTH FROM INTERVAL '2 years 3 months');
1026110262
<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
10262-
1026310263
SELECT EXTRACT(MONTH FROM INTERVAL '2 years 13 months');
1026410264
<lineannotation>Result: </lineannotation><computeroutput>1</computeroutput>
1026510265
</screen>
@@ -10290,7 +10290,6 @@ SELECT EXTRACT(QUARTER FROM TIMESTAMP '2001-02-16 20:38:40');
1029010290
<screen>
1029110291
SELECT EXTRACT(SECOND FROM TIMESTAMP '2001-02-16 20:38:40');
1029210292
<lineannotation>Result: </lineannotation><computeroutput>40.000000</computeroutput>
10293-
1029410293
SELECT EXTRACT(SECOND FROM TIME '17:12:28.5');
1029510294
<lineannotation>Result: </lineannotation><computeroutput>28.500000</computeroutput>
1029610295
</screen>
@@ -10372,6 +10371,20 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
1037210371
</variablelist>
1037310372
</para>
1037410373

10374+
<para>
10375+
When processing an <type>interval</type> value,
10376+
the <function>extract</function> function produces field values that
10377+
match the interpretation used by the interval output function. This
10378+
can produce surprising results if one starts with a non-normalized
10379+
interval representation, for example:
10380+
<screen>
10381+
SELECT INTERVAL '80 minutes';
10382+
<lineannotation>Result: </lineannotation><computeroutput>01:20:00</computeroutput>
10383+
SELECT EXTRACT(MINUTES FROM INTERVAL '80 minutes');
10384+
<lineannotation>Result: </lineannotation><computeroutput>20</computeroutput>
10385+
</screen>
10386+
</para>
10387+
1037510388
<note>
1037610389
<para>
1037710390
When the input value is +/-Infinity, <function>extract</function> returns
@@ -10409,7 +10422,6 @@ date_part('<replaceable>field</replaceable>', <replaceable>source</replaceable>)
1040910422
<screen>
1041010423
SELECT date_part('day', TIMESTAMP '2001-02-16 20:38:40');
1041110424
<lineannotation>Result: </lineannotation><computeroutput>16</computeroutput>
10412-
1041310425
SELECT date_part('hour', INTERVAL '4 hours 3 minutes');
1041410426
<lineannotation>Result: </lineannotation><computeroutput>4</computeroutput>
1041510427
</screen>
@@ -10487,16 +10499,12 @@ date_trunc(<replaceable>field</replaceable>, <replaceable>source</replaceable> [
1048710499
<screen>
1048810500
SELECT date_trunc('hour', TIMESTAMP '2001-02-16 20:38:40');
1048910501
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 20:00:00</computeroutput>
10490-
1049110502
SELECT date_trunc('year', TIMESTAMP '2001-02-16 20:38:40');
1049210503
<lineannotation>Result: </lineannotation><computeroutput>2001-01-01 00:00:00</computeroutput>
10493-
1049410504
SELECT date_trunc('day', TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40+00');
1049510505
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 00:00:00-05</computeroutput>
10496-
1049710506
SELECT date_trunc('day', TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40+00', 'Australia/Sydney');
1049810507
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 08:00:00-05</computeroutput>
10499-
1050010508
SELECT date_trunc('hour', INTERVAL '3 days 02:47:33');
1050110509
<lineannotation>Result: </lineannotation><computeroutput>3 days 02:00:00</computeroutput>
1050210510
</screen>
@@ -10535,7 +10543,6 @@ date_bin(<replaceable>stride</replaceable>, <replaceable>source</replaceable>, <
1053510543
<screen>
1053610544
SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-01-01');
1053710545
<lineannotation>Result: </lineannotation><computeroutput>2020-02-11 15:30:00</computeroutput>
10538-
1053910546
SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-01-01 00:02:30');
1054010547
<lineannotation>Result: </lineannotation><computeroutput>2020-02-11 15:32:30</computeroutput>
1054110548
</screen>
@@ -10659,10 +10666,8 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0
1065910666
<screen>
1066010667
SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'America/Denver';
1066110668
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 19:38:40-08</computeroutput>
10662-
1066310669
SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT TIME ZONE 'America/Denver';
1066410670
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 18:38:40</computeroutput>
10665-
1066610671
SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'America/Chicago';
1066710672
<lineannotation>Result: </lineannotation><computeroutput>2001-02-16 05:38:40</computeroutput>
1066810673
</screen>
@@ -10737,16 +10742,12 @@ LOCALTIMESTAMP(<replaceable>precision</replaceable>)
1073710742
<screen>
1073810743
SELECT CURRENT_TIME;
1073910744
<lineannotation>Result: </lineannotation><computeroutput>14:39:53.662522-05</computeroutput>
10740-
1074110745
SELECT CURRENT_DATE;
1074210746
<lineannotation>Result: </lineannotation><computeroutput>2019-12-23</computeroutput>
10743-
1074410747
SELECT CURRENT_TIMESTAMP;
1074510748
<lineannotation>Result: </lineannotation><computeroutput>2019-12-23 14:39:53.662522-05</computeroutput>
10746-
1074710749
SELECT CURRENT_TIMESTAMP(2);
1074810750
<lineannotation>Result: </lineannotation><computeroutput>2019-12-23 14:39:53.66-05</computeroutput>
10749-
1075010751
SELECT LOCALTIMESTAMP;
1075110752
<lineannotation>Result: </lineannotation><computeroutput>2019-12-23 14:39:53.662522</computeroutput>
1075210753
</screen>

0 commit comments

Comments
 (0)