From 57bfb27e60055c10e42b87e68a894720c2f78e70 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 4 Sep 2006 01:26:28 +0000
Subject: [PATCH] Fix interval input parser so that fractional weeks and months
 are cascaded first to days and only what is leftover into seconds.  This
 seems to satisfy the principle of least surprise given the general conversion
 to three-part interval values --- it was an oversight that these cases
 weren't dealt with in 8.1.  Michael Glaesemann

---
 src/backend/utils/adt/datetime.c          | 44 +++++++++++++++--------
 src/interfaces/ecpg/pgtypeslib/interval.c | 44 +++++++++++++++--------
 src/test/regress/expected/interval.out    | 12 +++++++
 src/test/regress/sql/interval.sql         |  2 ++
 4 files changed, 72 insertions(+), 30 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index f4aceed0aa3..ab0a3b4e89e 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.169 2006/07/25 03:51:21 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.170 2006/09/04 01:26:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2920,16 +2920,23 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
 						tm->tm_mday += val * 7;
 						if (fval != 0)
 						{
-							int			sec;
-
-							fval *= 7 * SECS_PER_DAY;
-							sec = fval;
-							tm->tm_sec += sec;
+							int extra_days;
+							fval *= 7;
+							extra_days = (int32) fval;
+							tm->tm_mday += extra_days;
+							fval -= extra_days;
+							if (fval != 0)
+							{
+								int			sec;
+								fval *= SECS_PER_DAY;
+								sec = fval;
+								tm->tm_sec += sec;
 #ifdef HAVE_INT64_TIMESTAMP
-							*fsec += (fval - sec) * 1000000;
+								*fsec += (fval - sec) * 1000000;
 #else
-							*fsec += fval - sec;
+								*fsec += fval - sec;
 #endif
+							}
 						}
 						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
 						break;
@@ -2938,16 +2945,23 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
 						tm->tm_mon += val;
 						if (fval != 0)
 						{
-							int			sec;
-
-							fval *= DAYS_PER_MONTH * SECS_PER_DAY;
-							sec = fval;
-							tm->tm_sec += sec;
+							int         day;
+							fval *= DAYS_PER_MONTH;
+							day = fval;
+							tm->tm_mday += day;
+							fval -= day;
+							if (fval != 0)
+							{
+								int			sec;
+								fval *= SECS_PER_DAY;
+								sec = fval;
+								tm->tm_sec += sec;
 #ifdef HAVE_INT64_TIMESTAMP
-							*fsec += (fval - sec) * 1000000;
+								*fsec += (fval - sec) * 1000000;
 #else
-							*fsec += fval - sec;
+								*fsec += fval - sec;
 #endif
+							}
 						}
 						tmask = DTK_M(MONTH);
 						break;
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
index b6d9b19b645..22643dbd966 100644
--- a/src/interfaces/ecpg/pgtypeslib/interval.c
+++ b/src/interfaces/ecpg/pgtypeslib/interval.c
@@ -1,4 +1,4 @@
-/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/interval.c,v 1.32 2006/06/06 11:31:55 meskes Exp $ */
+/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/interval.c,v 1.33 2006/09/04 01:26:28 tgl Exp $ */
 
 #include "postgres_fe.h"
 #include <time.h>
@@ -307,16 +307,23 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
 						tm->tm_mday += val * 7;
 						if (fval != 0)
 						{
-							int			sec;
-
-							fval *= 7 * SECS_PER_DAY;
-							sec = fval;
-							tm->tm_sec += sec;
+							int extra_days;
+							fval *= 7;
+							extra_days = (int32) fval;
+							tm->tm_mday += extra_days;
+							fval -= extra_days;
+							if (fval != 0)
+							{
+								int			sec;
+								fval *= SECS_PER_DAY;
+								sec = fval;
+								tm->tm_sec += sec;
 #ifdef HAVE_INT64_TIMESTAMP
-							*fsec += (fval - sec) * 1000000;
+								*fsec += (fval - sec) * 1000000;
 #else
-							*fsec += fval - sec;
+								*fsec += fval - sec;
 #endif
+							}
 						}
 						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
 						break;
@@ -325,16 +332,23 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
 						tm->tm_mon += val;
 						if (fval != 0)
 						{
-							int			sec;
-
-							fval *= DAYS_PER_MONTH * SECS_PER_DAY;
-							sec = fval;
-							tm->tm_sec += sec;
+							int         day;
+							fval *= DAYS_PER_MONTH;
+							day = fval;
+							tm->tm_mday += day;
+							fval -= day;
+							if (fval != 0)
+							{
+								int			sec;
+								fval *= SECS_PER_DAY;
+								sec = fval;
+								tm->tm_sec += sec;
 #ifdef HAVE_INT64_TIMESTAMP
-							*fsec += (fval - sec) * 1000000;
+								*fsec += (fval - sec) * 1000000;
 #else
-							*fsec += fval - sec;
+								*fsec += fval - sec;
 #endif
+							}
 						}
 						tmask = DTK_M(MONTH);
 						break;
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 0adda4a981d..3a5fb12cbc3 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -39,6 +39,18 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
  -1 days +02:03:00
 (1 row)
 
+SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+ Ten days twelve hours 
+-----------------------
+ 10 days 12:00:00
+(1 row)
+
+SELECT INTERVAL '1.5 months' AS "One month 15 days";
+ One month 15 days 
+-------------------
+ 1 mon 15 days
+(1 row)
+
 SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
             9 years...            
 ----------------------------------
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc384d01212..f38b01e45dc 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -11,6 +11,8 @@ SELECT INTERVAL '-08:00' AS "Eight hours";
 SELECT INTERVAL '-05' AS "Five hours";
 SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
 SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
+SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+SELECT INTERVAL '1.5 months' AS "One month 15 days";
 SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
 
 CREATE TABLE INTERVAL_TBL (f1 interval);
-- 
GitLab