diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eff243d73aa70493b5c58e8f132ef3136b4ca445..4574105b05eb18aa6a3bab2cfa1de50c3ec10669 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.309 2006/03/06 04:53:50 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.310 2006/03/06 22:49:15 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -5287,11 +5287,14 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
    <indexterm>
     <primary>isfinite</primary>
    </indexterm>
+   <indexterm>
+    <primary>justify_days</primary>
+   </indexterm>
    <indexterm>
     <primary>justify_hours</primary>
    </indexterm>
    <indexterm>
-    <primary>justify_days</primary>
+    <primary>justify_interval</primary>
    </indexterm>
    <indexterm>
     <primary>localtime</primary>
@@ -5429,6 +5432,14 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
         <entry><literal>true</literal></entry>
        </row>
 
+       <row>
+        <entry><literal><function>justify_days</function>(<type>interval</type>)</literal></entry>
+        <entry><type>interval</type></entry>
+        <entry>Adjust interval so 30-day time periods are represented as months</entry>
+        <entry><literal>justify_days(interval '30 days')</literal></entry>
+        <entry><literal>1 month</literal></entry>
+       </row>
+
        <row>
         <entry><literal><function>justify_hours</function>(<type>interval</type>)</literal></entry>
         <entry><type>interval</type></entry>
@@ -5438,11 +5449,11 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
        </row>
 
        <row>
-        <entry><literal><function>justify_days</function>(<type>interval</type>)</literal></entry>
+        <entry><literal><function>justify_interval</function>(<type>interval</type>)</literal></entry>
         <entry><type>interval</type></entry>
-        <entry>Adjust interval so 30-day time periods are represented as months</entry>
-        <entry><literal>justify_days(interval '30 days')</literal></entry>
-        <entry><literal>1 month</literal></entry>
+        <entry>Adjust interval using <function>justify_days</> and <function>justify_hours</>, with additional sign adjustments</></entry>
+        <entry><literal>justify_interval(interval '1 mon -1 hour')</literal></entry>
+        <entry><literal>29 days 23:00:00</literal></entry>
        </row>
 
        <row>
@@ -5486,13 +5497,6 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
      </tgroup>
     </table>
 
-   <para>
-    If you are using both <function>justify_hours</> and
-    <function>justify_days</>, it is best to use <function>justify_hours</>
-    first so any additional days will be included in the
-    <function>justify_days</> calculation.
-   </para>
-
    <para>
     In addition to these functions, the SQL <literal>OVERLAPS</> operator is
     supported:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ce73f714f8bbfa49645c0fd776ec3d28f4e8fcb4..0524bf123944b0e947cc4c84e07d880a521c93c5 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.161 2006/03/05 15:58:44 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.162 2006/03/06 22:49:16 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1974,6 +1974,82 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+/*
+ *  interval_justify_interval()
+ *
+ *  Adjust interval so 'month', 'day', and 'time' portions are within
+ *  customary bounds.  Specifically:
+ *
+ *  	0 <= abs(time) < 24 hours
+ *  	0 <= abs(day)  < 30 days
+ *
+ *  Also, the sign bit on all three fields is made equal, so either
+ *  all three fields are negative or all are positive.
+ */
+Datum
+interval_justify_interval(PG_FUNCTION_ARGS)
+{
+	Interval   *span = PG_GETARG_INTERVAL_P(0);
+	Interval   *result;
+	
+#ifdef HAVE_INT64_TIMESTAMP
+	int64		wholeday;
+#else
+	double		wholeday;
+#endif
+	int32		wholemonth;
+
+	result = (Interval *) palloc(sizeof(Interval));
+	result->month = span->month;
+	result->day = span->day;
+	result->time = span->time;
+
+#ifdef HAVE_INT64_TIMESTAMP
+	TMODULO(result->time, wholeday, USECS_PER_DAY);
+#else
+	TMODULO(result->time, wholeday, (double) SECS_PER_DAY);
+#endif
+	result->day += wholeday;	/* could overflow... */
+
+	wholemonth = result->day / DAYS_PER_MONTH;
+	result->day -= wholemonth * DAYS_PER_MONTH;
+	result->month += wholemonth;
+
+	if (result->month > 0 &&
+		(result->day < 0 || (result->day == 0 && result->time < 0)))
+	{
+		result->day += DAYS_PER_MONTH;
+		result->month--;
+	}
+	else if (result->month < 0 &&
+		(result->day > 0 || (result->day == 0 && result->time > 0)))
+	{
+		result->day -= DAYS_PER_MONTH;
+		result->month++;
+	}
+
+	if (result->day > 0 && result->time < 0)
+	{
+#ifdef HAVE_INT64_TIMESTAMP
+		result->time += USECS_PER_DAY;
+#else
+		result->time += (double) SECS_PER_DAY;
+#endif
+		result->day--;
+	}
+	else if (result->day < 0 && result->time > 0)
+ 	{
+#ifdef HAVE_INT64_TIMESTAMP
+		result->time -= USECS_PER_DAY;
+#else
+		result->time -= (double) SECS_PER_DAY;
+#endif
+		result->day++;
+	}
+
+	PG_RETURN_INTERVAL_P(result);
+}
+
 /*
  *	interval_justify_hours()
  *
@@ -2006,6 +2082,25 @@ interval_justify_hours(PG_FUNCTION_ARGS)
 #endif
 	result->day += wholeday;	/* could overflow... */
 
+	if (result->day > 0 && result->time < 0)
+	{
+#ifdef HAVE_INT64_TIMESTAMP
+		result->time += USECS_PER_DAY;
+#else
+		result->time += (double) SECS_PER_DAY;
+#endif
+		result->day--;
+	}
+	else if (result->day < 0 && result->time > 0)
+	{
+#ifdef HAVE_INT64_TIMESTAMP
+		result->time -= USECS_PER_DAY;
+#else
+		result->time -= (double) SECS_PER_DAY;
+#endif
+		result->day++;
+	}
+
 	PG_RETURN_INTERVAL_P(result);
 }
 
@@ -2031,6 +2126,17 @@ interval_justify_days(PG_FUNCTION_ARGS)
 	result->day -= wholemonth * DAYS_PER_MONTH;
 	result->month += wholemonth;
 
+	if (result->month > 0 && result->day < 0)
+	{
+		result->day += DAYS_PER_MONTH;
+		result->month--;
+	}
+	else if (result->month < 0 && result->day > 0)
+	{
+		result->day -= DAYS_PER_MONTH;
+		result->month++;
+	}
+
 	PG_RETURN_INTERVAL_P(result);
 }
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index f99eba80ba3a1387578724d6f43e9ee54086d2cc..978e35af8e0f647d5b291b50ccb7094ea4b3e6e4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.318 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.319 2006/03/06 22:49:16 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200602281
+#define CATALOG_VERSION_NO	200603061
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index b183960602699068393936a3a7b2925f5a3969b0..5121991c58a5f7936d719378a6777d27219da260 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.400 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.401 2006/03/06 22:49:16 momjian Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -1462,6 +1462,8 @@ DATA(insert OID = 1173 (  timestamptz	   PGNSP PGUID 12 f f t f i 1 1184 "702" _
 DESCR("convert abstime to timestamp with time zone");
 DATA(insert OID = 1174 (  timestamptz	   PGNSP PGUID 12 f f t f s 1 1184 "1082" _null_ _null_ _null_	date_timestamptz - _null_ ));
 DESCR("convert date to timestamp with time zone");
+DATA(insert OID = 2711 (  justify_interval PGNSP PGUID 12 f f t f i 1 1186 "1186" _null_ _null_ _null_	interval_justify_interval - _null_ ));
+DESCR("promote groups of 24 hours to numbers of days and promote groups of 30 days to numbers of months");
 DATA(insert OID = 1175 (  justify_hours    PGNSP PGUID 12 f f t f i 1 1186 "1186" _null_ _null_ _null_	interval_justify_hours - _null_ ));
 DESCR("promote groups of 24 hours to numbers of days");
 DATA(insert OID = 1295 (  justify_days	   PGNSP PGUID 12 f f t f i 1 1186 "1186" _null_ _null_ _null_	interval_justify_days - _null_ ));
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 7469f40723a1ce3cebc036e64fa76cef8cfdbbf9..9747b300b6c27105b16c529cb8327a2328c85b46 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.58 2006/03/05 15:59:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/timestamp.h,v 1.59 2006/03/06 22:49:17 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -234,6 +234,7 @@ extern Datum interval_cmp(PG_FUNCTION_ARGS);
 extern Datum interval_hash(PG_FUNCTION_ARGS);
 extern Datum interval_smaller(PG_FUNCTION_ARGS);
 extern Datum interval_larger(PG_FUNCTION_ARGS);
+extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
 extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
 extern Datum interval_justify_days(PG_FUNCTION_ARGS);
 
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index d07dd3013dc4ffa746e58ea4695a0033c1d39b26..7a43e90d5dbc34329623b6796b182b3a8506c0c9 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -241,3 +241,10 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
  @ 7 mons 6 days 5 hours 4 mins 3 secs
 (1 row)
 
+-- test justify_interval()
+SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
+  1 month -1 hour   
+--------------------
+ @ 29 days 23 hours
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 25e58659e7dd5f6f0b43f7db5ff5346c30495f7f..bc384d012120441a155b6b8c4363e89849a3f50d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -76,3 +76,6 @@ select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31
 SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as "6 mons 5 days 4 hours 3 mins 2 seconds";
 SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
 
+-- test justify_interval()
+
+SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";