diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3f6d431523062c9fa93672387e7242257e09f83c..a38bbda9af1e5d0e0e5e0c30ed3499a899ea2ffa 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.109 2004/06/03 17:57:09 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.110 2004/08/20 03:45:13 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2727,11 +2727,26 @@ timestamp_trunc(PG_FUNCTION_ARGS)
 				fsec = 0;
 				break;
 			case DTK_MILLENNIUM:
-				tm->tm_year = (tm->tm_year / 1000) * 1000;
+				/* see comments in timestamptz_trunc */
+				if (tm->tm_year > 0)
+					tm->tm_year = ((tm->tm_year+999) / 1000) * 1000 - 999;
+				else
+					tm->tm_year = - ((999 - (tm->tm_year-1))/1000) * 1000 + 1;
 			case DTK_CENTURY:
-				tm->tm_year = (tm->tm_year / 100) * 100;
+				/* see comments in timestamptz_trunc */
+				if (tm->tm_year > 0)
+					tm->tm_year = ((tm->tm_year+99) / 100) * 100 - 99;
+				else
+					tm->tm_year = - ((99 - (tm->tm_year-1))/100) * 100 + 1;
 			case DTK_DECADE:
-				tm->tm_year = (tm->tm_year / 10) * 10;
+				/* see comments in timestamptz_trunc */
+				if (val != DTK_MILLENNIUM && val != DTK_CENTURY)
+				{
+					if (tm->tm_year > 0)
+						tm->tm_year = (tm->tm_year / 10) * 10;
+					else
+						tm->tm_year = - ((8-(tm->tm_year-1)) / 10) * 10;
+				}
 			case DTK_YEAR:
 				tm->tm_mon = 1;
 			case DTK_QUARTER:
@@ -2830,12 +2845,33 @@ timestamptz_trunc(PG_FUNCTION_ARGS)
 				tm->tm_sec = 0;
 				fsec = 0;
 				break;
+				/* one may consider DTK_THOUSAND and DTK_HUNDRED... */
 			case DTK_MILLENNIUM:
-				tm->tm_year = (tm->tm_year / 1000) * 1000;
+				/* truncating to the millennium? what is this supposed to mean?
+				 * let us put the first year of the millennium... 
+				 * i.e. -1000, 1, 1001, 2001...
+				 */
+				if (tm->tm_year > 0)
+					tm->tm_year = ((tm->tm_year+999) / 1000) * 1000 - 999;
+				else
+					tm->tm_year = - ((999 - (tm->tm_year-1))/1000) * 1000 + 1;
 			case DTK_CENTURY:
-				tm->tm_year = (tm->tm_year / 100) * 100;
+				/* truncating to the century? as above: -100, 1, 101... */
+				if (tm->tm_year > 0)
+					tm->tm_year = ((tm->tm_year+99) / 100) * 100 - 99 ;
+				else
+					tm->tm_year = - ((99 - (tm->tm_year-1))/100) * 100 + 1;
 			case DTK_DECADE:
-				tm->tm_year = (tm->tm_year / 10) * 10;
+				/* truncating to the decade? first year of the decade.
+				 * must not be applied if year was truncated before!
+				 */
+				if (val != DTK_MILLENNIUM && val != DTK_CENTURY)
+				{
+					if (tm->tm_year > 0)
+						tm->tm_year = (tm->tm_year / 10) * 10;
+					else
+						tm->tm_year = - ((8-(tm->tm_year-1)) / 10) * 10;
+				}
 			case DTK_YEAR:
 				tm->tm_mon = 1;
 			case DTK_QUARTER:
@@ -2923,10 +2959,13 @@ interval_trunc(PG_FUNCTION_ARGS)
 			switch (val)
 			{
 				case DTK_MILLENNIUM:
+					/* caution: C division may have negative remainder */
 					tm->tm_year = (tm->tm_year / 1000) * 1000;
 				case DTK_CENTURY:
+					/* caution: C division may have negative remainder */
 					tm->tm_year = (tm->tm_year / 100) * 100;
 				case DTK_DECADE:
+					/* caution: C division may have negative remainder */
 					tm->tm_year = (tm->tm_year / 10) * 10;
 				case DTK_YEAR:
 					tm->tm_mon = 0;
@@ -3221,7 +3260,14 @@ timestamp_part(PG_FUNCTION_ARGS)
 				break;
 
 			case DTK_DECADE:
-				result = (tm->tm_year / 10);
+				/* what is a decade wrt dates?
+				 * let us assume that decade 199 is 1990 thru 1999...
+				 * decade 0 starts on year 1 BC, and -1 is 11 BC thru 2 BC...
+				 */
+				if (tm->tm_year>=0)
+					result = (tm->tm_year / 10);
+				else
+					result = -((8-(tm->tm_year-1)) / 10);
 				break;
 
 			case DTK_CENTURY:
@@ -3232,7 +3278,7 @@ timestamp_part(PG_FUNCTION_ARGS)
 				if (tm->tm_year > 0)
 					result = ((tm->tm_year+99) / 100);
 				else
-					/* caution: C division may yave negative remainder */
+					/* caution: C division may have negative remainder */
 					result = - ((99 - (tm->tm_year-1))/100);
 				break;
 
@@ -3445,15 +3491,27 @@ timestamptz_part(PG_FUNCTION_ARGS)
 				break;
 
 			case DTK_DECADE:
-				result = (tm->tm_year / 10);
+				/* see comments in timestamp_part */
+				if (tm->tm_year>0)
+					result = (tm->tm_year / 10);
+				else
+					result = - ((8-(tm->tm_year-1)) / 10);
 				break;
 
 			case DTK_CENTURY:
-				result = (tm->tm_year / 100);
+				/* see comments in timestamp_part */
+				if (tm->tm_year > 0)
+					result = ((tm->tm_year+99) / 100);
+				else
+					result = - ((99 - (tm->tm_year-1))/100);
 				break;
 
 			case DTK_MILLENNIUM:
-				result = (tm->tm_year / 1000);
+				/* see comments in timestamp_part */
+				if (tm->tm_year > 0)
+					result = ((tm->tm_year+999) / 1000);
+				else
+					result = - ((999 - (tm->tm_year-1))/1000);
 				break;
 
 			case DTK_JULIAN:
@@ -3606,14 +3664,17 @@ interval_part(PG_FUNCTION_ARGS)
 					break;
 
 				case DTK_DECADE:
+					/* caution: C division may have negative remainder */
 					result = (tm->tm_year / 10);
 					break;
 
 				case DTK_CENTURY:
+					/* caution: C division may have negative remainder */
 					result = (tm->tm_year / 100);
 					break;
 
 				case DTK_MILLENNIUM:
+					/* caution: C division may have negative remainder */
 					result = (tm->tm_year / 1000);
 					break;
 
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 77bbdb967624ef745babdb83f95250fed8822dd2..24018de56060e165f5796d6946eb7a8d63c9d99f 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -930,3 +930,152 @@ SELECT EXTRACT(MILLENNIUM FROM CURRENT_DATE);         --  3
          3
 (1 row)
 
+--
+-- decade
+--
+SELECT EXTRACT(DECADE FROM DATE '1994-12-25');    -- 199
+ date_part 
+-----------
+       199
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0010-01-01');    --   1
+ date_part 
+-----------
+         1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0009-12-31');    --   0
+ date_part 
+-----------
+         0
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0001-01-01 BC'); --   0
+ date_part 
+-----------
+         0
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0002-12-31 BC'); --  -1
+ date_part 
+-----------
+        -1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0011-01-01 BC'); --  -1
+ date_part 
+-----------
+        -1
+(1 row)
+
+SELECT EXTRACT(DECADE FROM DATE '0012-12-31 BC'); --  -2
+ date_part 
+-----------
+        -2
+(1 row)
+
+--
+-- some other types:
+--
+-- on a timestamp.
+SELECT EXTRACT(CENTURY FROM NOW())>=21 AS True;       -- true
+ true 
+------
+ t
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM TIMESTAMP '1970-03-20 04:30:00.00000'); -- 20
+ date_part 
+-----------
+        20
+(1 row)
+
+-- on an interval
+SELECT EXTRACT(CENTURY FROM INTERVAL '100 y');  -- 1
+ date_part 
+-----------
+         1
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '99 y');   -- 0
+ date_part 
+-----------
+         0
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '-99 y');  -- 0
+ date_part 
+-----------
+         0
+(1 row)
+
+SELECT EXTRACT(CENTURY FROM INTERVAL '-100 y'); -- -1
+ date_part 
+-----------
+        -1
+(1 row)
+
+--
+-- test trunc function!
+--
+SELECT DATE_TRUNC('MILLENNIUM', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1001
+        date_trunc        
+--------------------------
+ Thu Jan 01 00:00:00 1001
+(1 row)
+
+SELECT DATE_TRUNC('MILLENNIUM', DATE '1970-03-20'); -- 1001-01-01
+          date_trunc          
+------------------------------
+ Thu Jan 01 00:00:00 1001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1901
+        date_trunc        
+--------------------------
+ Tue Jan 01 00:00:00 1901
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '1970-03-20'); -- 1901
+          date_trunc          
+------------------------------
+ Tue Jan 01 00:00:00 1901 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '2004-08-10'); -- 2001-01-01
+          date_trunc          
+------------------------------
+ Mon Jan 01 00:00:00 2001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '0002-02-04'); -- 0001-01-01
+          date_trunc          
+------------------------------
+ Mon Jan 01 00:00:00 0001 PST
+(1 row)
+
+SELECT DATE_TRUNC('CENTURY', DATE '0055-08-10 BC'); -- 0100-01-01 BC
+           date_trunc            
+---------------------------------
+ Tue Jan 01 00:00:00 0100 PST BC
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '1993-12-25'); -- 1990-01-01
+          date_trunc          
+------------------------------
+ Mon Jan 01 00:00:00 1990 PST
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '0004-12-25'); -- 0001-01-01 BC
+           date_trunc            
+---------------------------------
+ Sat Jan 01 00:00:00 0001 PST BC
+(1 row)
+
+SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC
+           date_trunc            
+---------------------------------
+ Mon Jan 01 00:00:00 0011 PST BC
+(1 row)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index a3cad66ed2e16d95f8ee41be25ff8fa383d8e8e6..97ddbe9e45aca8f398a08895cb14f624107cafa4 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -235,3 +235,37 @@ SELECT EXTRACT(MILLENNIUM FROM DATE '2000-12-31');    --  2
 SELECT EXTRACT(MILLENNIUM FROM DATE '2001-01-01');    --  3
 -- next test to be fixed on the turn of the next millennium;-)
 SELECT EXTRACT(MILLENNIUM FROM CURRENT_DATE);         --  3
+--
+-- decade
+--
+SELECT EXTRACT(DECADE FROM DATE '1994-12-25');    -- 199
+SELECT EXTRACT(DECADE FROM DATE '0010-01-01');    --   1
+SELECT EXTRACT(DECADE FROM DATE '0009-12-31');    --   0
+SELECT EXTRACT(DECADE FROM DATE '0001-01-01 BC'); --   0
+SELECT EXTRACT(DECADE FROM DATE '0002-12-31 BC'); --  -1
+SELECT EXTRACT(DECADE FROM DATE '0011-01-01 BC'); --  -1
+SELECT EXTRACT(DECADE FROM DATE '0012-12-31 BC'); --  -2
+--
+-- some other types:
+--
+-- on a timestamp.
+SELECT EXTRACT(CENTURY FROM NOW())>=21 AS True;       -- true
+SELECT EXTRACT(CENTURY FROM TIMESTAMP '1970-03-20 04:30:00.00000'); -- 20
+-- on an interval
+SELECT EXTRACT(CENTURY FROM INTERVAL '100 y');  -- 1
+SELECT EXTRACT(CENTURY FROM INTERVAL '99 y');   -- 0
+SELECT EXTRACT(CENTURY FROM INTERVAL '-99 y');  -- 0
+SELECT EXTRACT(CENTURY FROM INTERVAL '-100 y'); -- -1
+--
+-- test trunc function!
+--
+SELECT DATE_TRUNC('MILLENNIUM', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1001
+SELECT DATE_TRUNC('MILLENNIUM', DATE '1970-03-20'); -- 1001-01-01
+SELECT DATE_TRUNC('CENTURY', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1901
+SELECT DATE_TRUNC('CENTURY', DATE '1970-03-20'); -- 1901
+SELECT DATE_TRUNC('CENTURY', DATE '2004-08-10'); -- 2001-01-01
+SELECT DATE_TRUNC('CENTURY', DATE '0002-02-04'); -- 0001-01-01
+SELECT DATE_TRUNC('CENTURY', DATE '0055-08-10 BC'); -- 0100-01-01 BC
+SELECT DATE_TRUNC('DECADE', DATE '1993-12-25'); -- 1990-01-01
+SELECT DATE_TRUNC('DECADE', DATE '0004-12-25'); -- 0001-01-01 BC
+SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC