Re: BUG #18348: Inconsistency with EXTRACT([field] from INTERVAL); - Mailing list pgsql-bugs

From Tom Lane
Subject Re: BUG #18348: Inconsistency with EXTRACT([field] from INTERVAL);
Date
Msg-id 1079670.1720458208@sss.pgh.pa.us
Whole thread Raw
In response to Re: BUG #18348: Inconsistency with EXTRACT([field] from INTERVAL);  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: BUG #18348: Inconsistency with EXTRACT([field] from INTERVAL);
List pgsql-bugs
I took another look at this issue and got annoyed by the fact that the
proposed coding for "quarter" still doesn't satisfy the rule that
the output for a negative interval should be the negative of the
output for the sign-reversed interval.  Specifically, if the month
field is zero, the v2 patch always emits 1:

regression=# select extract(quarter from interval '1 day');
 extract 
---------
       1
(1 row)

regression=# select extract(quarter from interval '-1 day');
 extract 
---------
       1
(1 row)

We could fix that by examining the sign of the lower-order fields
when month is zero, as in the v3 patch attached.  However, I'm not
at all sure this is really better than v2.  Notably, it makes the
documentation's statement that the result is "the month field
divided by 3 plus 1" even more incomplete.  I still don't really
want to go into details about the behavior for negative intervals.
OTOH if we did do that, I'd rather write a blanket statement
about the result being the negative of the result for a positive
interval.

Thoughts?

            regards, tom lane

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 93ee3d4b60..bd430e712d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10392,12 +10392,16 @@ SELECT EXTRACT(MONTH FROM INTERVAL '2 years 13 months');
       <term><literal>quarter</literal></term>
       <listitem>
        <para>
-        The quarter of the year (1–4) that the date is in
+        The quarter of the year (1–4) that the date is in;
+        for <type>interval</type> values, the month field divided by 3
+        plus 1
        </para>

 <screen>
 SELECT EXTRACT(QUARTER FROM TIMESTAMP '2001-02-16 20:38:40');
 <lineannotation>Result: </lineannotation><computeroutput>1</computeroutput>
+SELECT EXTRACT(QUARTER FROM INTERVAL '1 year 6 months');
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
 </screen>
       </listitem>
      </varlistentry>
@@ -10468,9 +10472,16 @@ SELECT EXTRACT(SECOND FROM TIME '17:12:28.5');
         <literal>week</literal> to get consistent results.
        </para>

+       <para>
+        For <type>interval</type> values, the week field is simply the number
+        of integral days divided by 7.
+       </para>
+
 <screen>
 SELECT EXTRACT(WEEK FROM TIMESTAMP '2001-02-16 20:38:40');
 <lineannotation>Result: </lineannotation><computeroutput>7</computeroutput>
+SELECT EXTRACT(WEEK FROM INTERVAL '13 days 24 hours');
+<lineannotation>Result: </lineannotation><computeroutput>1</computeroutput>
 </screen>
       </listitem>
      </varlistentry>
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index cdc7e43b93..13ddba4674 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -5918,6 +5918,7 @@ NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
         case DTK_MILLISEC:
         case DTK_SECOND:
         case DTK_MINUTE:
+        case DTK_WEEK:
         case DTK_MONTH:
         case DTK_QUARTER:
             return 0.0;
@@ -6037,12 +6038,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                 intresult = tm->tm_mday;
                 break;

+            case DTK_WEEK:
+                intresult = tm->tm_mday / 7;
+                break;
+
             case DTK_MONTH:
                 intresult = tm->tm_mon;
                 break;

             case DTK_QUARTER:
-                intresult = (tm->tm_mon / 3) + 1;
+
+                /*
+                 * We want to maintain the rule that a field extracted from a
+                 * negative interval is the negative of the field's value for
+                 * the sign-reversed interval.  The broken-down tm_year and
+                 * tm_mon aren't very helpful for that, so work from
+                 * interval->month.  If the month field is zero, use the
+                 * lower-order fields to decide the sign.
+                 */
+                if (interval->month > 0)
+                    intresult = (tm->tm_mon / 3) + 1;
+                else if (interval->month < 0)
+                    intresult = -(((-interval->month % MONTHS_PER_YEAR) / 3) + 1);
+                else if (interval->day > 0 ||
+                         (interval->day == 0 && interval->time >= 0))
+                    intresult = 1;
+                else
+                    intresult = -1;
                 break;

             case DTK_YEAR:
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 51ae010c7b..a6009f8e6a 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1834,6 +1834,7 @@ SELECT f1,
     EXTRACT(MINUTE FROM f1) AS MINUTE,
     EXTRACT(HOUR FROM f1) AS HOUR,
     EXTRACT(DAY FROM f1) AS DAY,
+    EXTRACT(WEEK FROM f1) AS WEEK,
     EXTRACT(MONTH FROM f1) AS MONTH,
     EXTRACT(QUARTER FROM f1) AS QUARTER,
     EXTRACT(YEAR FROM f1) AS YEAR,
@@ -1842,20 +1843,52 @@ SELECT f1,
     EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
     EXTRACT(EPOCH FROM f1) AS EPOCH
     FROM INTERVAL_TBL;
-              f1               | microsecond | millisecond |   second   | minute |   hour    |    day    | month |
quarter|   year    |  decade   |  century  | millennium |       epoch        

--------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
- @ 1 min                       |           0 |       0.000 |   0.000000 |      1 |         0 |         0 |     0 |
 1 |         0 |         0 |         0 |          0 |         60.000000 
- @ 5 hours                     |           0 |       0.000 |   0.000000 |      0 |         5 |         0 |     0 |
 1 |         0 |         0 |         0 |          0 |      18000.000000 
- @ 10 days                     |           0 |       0.000 |   0.000000 |      0 |         0 |        10 |     0 |
 1 |         0 |         0 |         0 |          0 |     864000.000000 
- @ 34 years                    |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     0 |
 1 |        34 |         3 |         0 |          0 | 1072958400.000000 
- @ 3 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     3 |
 2 |         0 |         0 |         0 |          0 |    7776000.000000 
- @ 14 secs ago                 |   -14000000 |  -14000.000 | -14.000000 |      0 |         0 |         0 |     0 |
 1 |         0 |         0 |         0 |          0 |        -14.000000 
- @ 1 day 2 hours 3 mins 4 secs |     4000000 |    4000.000 |   4.000000 |      3 |         2 |         1 |     0 |
 1 |         0 |         0 |         0 |          0 |      93784.000000 
- @ 6 years                     |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     0 |
 1 |         6 |         0 |         0 |          0 |  189345600.000000 
- @ 5 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     5 |
 2 |         0 |         0 |         0 |          0 |   12960000.000000 
- @ 5 mons 12 hours             |           0 |       0.000 |   0.000000 |      0 |        12 |         0 |     5 |
 2 |         0 |         0 |         0 |          0 |   13003200.000000 
- infinity                      |             |             |            |        |  Infinity |  Infinity |       |
   |  Infinity |  Infinity |  Infinity |   Infinity |          Infinity 
- -infinity                     |             |             |            |        | -Infinity | -Infinity |       |
   | -Infinity | -Infinity | -Infinity |  -Infinity |         -Infinity 
+              f1               | microsecond | millisecond |   second   | minute |   hour    |    day    | week |
month| quarter |   year    |  decade   |  century  | millennium |       epoch        

+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min                       |           0 |       0.000 |   0.000000 |      1 |         0 |         0 |    0 |
0|       1 |         0 |         0 |         0 |          0 |         60.000000 
+ @ 5 hours                     |           0 |       0.000 |   0.000000 |      0 |         5 |         0 |    0 |
0|       1 |         0 |         0 |         0 |          0 |      18000.000000 
+ @ 10 days                     |           0 |       0.000 |   0.000000 |      0 |         0 |        10 |    1 |
0|       1 |         0 |         0 |         0 |          0 |     864000.000000 
+ @ 34 years                    |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |    0 |
0|       1 |        34 |         3 |         0 |          0 | 1072958400.000000 
+ @ 3 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |    0 |
3|       2 |         0 |         0 |         0 |          0 |    7776000.000000 
+ @ 14 secs ago                 |   -14000000 |  -14000.000 | -14.000000 |      0 |         0 |         0 |    0 |
0|      -1 |         0 |         0 |         0 |          0 |        -14.000000 
+ @ 1 day 2 hours 3 mins 4 secs |     4000000 |    4000.000 |   4.000000 |      3 |         2 |         1 |    0 |
0|       1 |         0 |         0 |         0 |          0 |      93784.000000 
+ @ 6 years                     |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |    0 |
0|       1 |         6 |         0 |         0 |          0 |  189345600.000000 
+ @ 5 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |    0 |
5|       2 |         0 |         0 |         0 |          0 |   12960000.000000 
+ @ 5 mons 12 hours             |           0 |       0.000 |   0.000000 |      0 |        12 |         0 |    0 |
5|       2 |         0 |         0 |         0 |          0 |   13003200.000000 
+ infinity                      |             |             |            |        |  Infinity |  Infinity |      |
|         |  Infinity |  Infinity |  Infinity |   Infinity |          Infinity 
+ -infinity                     |             |             |            |        | -Infinity | -Infinity |      |
|         | -Infinity | -Infinity | -Infinity |  -Infinity |         -Infinity 
+(12 rows)
+
+SELECT -f1,
+    EXTRACT(MICROSECOND FROM -f1) AS MICROSECOND,
+    EXTRACT(MILLISECOND FROM -f1) AS MILLISECOND,
+    EXTRACT(SECOND FROM -f1) AS SECOND,
+    EXTRACT(MINUTE FROM -f1) AS MINUTE,
+    EXTRACT(HOUR FROM -f1) AS HOUR,
+    EXTRACT(DAY FROM -f1) AS DAY,
+    EXTRACT(WEEK FROM -f1) AS WEEK,
+    EXTRACT(MONTH FROM -f1) AS MONTH,
+    EXTRACT(QUARTER FROM -f1) AS QUARTER,
+    EXTRACT(YEAR FROM -f1) AS YEAR,
+    EXTRACT(DECADE FROM -f1) AS DECADE,
+    EXTRACT(CENTURY FROM -f1) AS CENTURY,
+    EXTRACT(MILLENNIUM FROM -f1) AS MILLENNIUM,
+    EXTRACT(EPOCH FROM -f1) AS EPOCH
+    FROM INTERVAL_TBL;
+             ?column?              | microsecond | millisecond |  second   | minute |   hour    |    day    | week |
month| quarter |   year    |  decade   |  century  | millennium |       epoch         

+-----------------------------------+-------------+-------------+-----------+--------+-----------+-----------+------+-------+---------+-----------+-----------+-----------+------------+--------------------
+ @ 1 min ago                       |           0 |       0.000 |  0.000000 |     -1 |         0 |         0 |    0 |
 0 |      -1 |         0 |         0 |         0 |          0 |         -60.000000 
+ @ 5 hours ago                     |           0 |       0.000 |  0.000000 |      0 |        -5 |         0 |    0 |
 0 |      -1 |         0 |         0 |         0 |          0 |      -18000.000000 
+ @ 10 days ago                     |           0 |       0.000 |  0.000000 |      0 |         0 |       -10 |   -1 |
 0 |      -1 |         0 |         0 |         0 |          0 |     -864000.000000 
+ @ 34 years ago                    |           0 |       0.000 |  0.000000 |      0 |         0 |         0 |    0 |
 0 |      -1 |       -34 |        -3 |         0 |          0 | -1072958400.000000 
+ @ 3 mons ago                      |           0 |       0.000 |  0.000000 |      0 |         0 |         0 |    0 |
-3 |      -2 |         0 |         0 |         0 |          0 |    -7776000.000000 
+ @ 14 secs                         |    14000000 |   14000.000 | 14.000000 |      0 |         0 |         0 |    0 |
 0 |       1 |         0 |         0 |         0 |          0 |          14.000000 
+ @ 1 day 2 hours 3 mins 4 secs ago |    -4000000 |   -4000.000 | -4.000000 |     -3 |        -2 |        -1 |    0 |
 0 |      -1 |         0 |         0 |         0 |          0 |      -93784.000000 
+ @ 6 years ago                     |           0 |       0.000 |  0.000000 |      0 |         0 |         0 |    0 |
 0 |      -1 |        -6 |         0 |         0 |          0 |  -189345600.000000 
+ @ 5 mons ago                      |           0 |       0.000 |  0.000000 |      0 |         0 |         0 |    0 |
-5 |      -2 |         0 |         0 |         0 |          0 |   -12960000.000000 
+ @ 5 mons 12 hours ago             |           0 |       0.000 |  0.000000 |      0 |       -12 |         0 |    0 |
-5 |      -2 |         0 |         0 |         0 |          0 |   -13003200.000000 
+ -infinity                         |             |             |           |        | -Infinity | -Infinity |      |
   |         | -Infinity | -Infinity | -Infinity |  -Infinity |          -Infinity 
+ infinity                          |             |             |           |        |  Infinity |  Infinity |      |
   |         |  Infinity |  Infinity |  Infinity |   Infinity |           Infinity 
 (12 rows)

 SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days');  -- error
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index fbf6e064d6..55054ae65d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -592,6 +592,7 @@ SELECT f1,
     EXTRACT(MINUTE FROM f1) AS MINUTE,
     EXTRACT(HOUR FROM f1) AS HOUR,
     EXTRACT(DAY FROM f1) AS DAY,
+    EXTRACT(WEEK FROM f1) AS WEEK,
     EXTRACT(MONTH FROM f1) AS MONTH,
     EXTRACT(QUARTER FROM f1) AS QUARTER,
     EXTRACT(YEAR FROM f1) AS YEAR,
@@ -601,6 +602,23 @@ SELECT f1,
     EXTRACT(EPOCH FROM f1) AS EPOCH
     FROM INTERVAL_TBL;

+SELECT -f1,
+    EXTRACT(MICROSECOND FROM -f1) AS MICROSECOND,
+    EXTRACT(MILLISECOND FROM -f1) AS MILLISECOND,
+    EXTRACT(SECOND FROM -f1) AS SECOND,
+    EXTRACT(MINUTE FROM -f1) AS MINUTE,
+    EXTRACT(HOUR FROM -f1) AS HOUR,
+    EXTRACT(DAY FROM -f1) AS DAY,
+    EXTRACT(WEEK FROM -f1) AS WEEK,
+    EXTRACT(MONTH FROM -f1) AS MONTH,
+    EXTRACT(QUARTER FROM -f1) AS QUARTER,
+    EXTRACT(YEAR FROM -f1) AS YEAR,
+    EXTRACT(DECADE FROM -f1) AS DECADE,
+    EXTRACT(CENTURY FROM -f1) AS CENTURY,
+    EXTRACT(MILLENNIUM FROM -f1) AS MILLENNIUM,
+    EXTRACT(EPOCH FROM -f1) AS EPOCH
+    FROM INTERVAL_TBL;
+
 SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days');  -- error
 SELECT EXTRACT(TIMEZONE FROM INTERVAL '2 days');  -- error


pgsql-bugs by date:

Previous
From: "Haifang Wang (Centific Technologies Inc)"
Date:
Subject: RE: [EXTERNAL] Re: Windows Application Issues | PostgreSQL | REF # 51961374
Next
From: Melanie Plageman
Date:
Subject: Re: relfrozenxid may disagree with row XIDs after 1ccc1e05ae