diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 4956764..6fee7df 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -2955,6 +2955,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				value;
 	bool		fx_mode = false;
 
+#define ERROR_0YEAR(code_node1,txt_node1,txt_node2) \
+	ereport(ERROR, \
+			(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
+			 errmsg("invalid input string for \"%s\"", \
+					n->key->id == (code_node1) ? (txt_node1) : (txt_node2)), \
+			 errdetail("Year cannot be 0.")));
+
 	for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
 	{
 		if (n->type != NODE_TYPE_ACTION)
@@ -3145,6 +3152,11 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_CC:
 				from_char_parse_int(&out->cc, &s, n);
+				if (out->cc == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+							 errmsg("invalid input string for \"CC\""),
+							 errdetail("Century cannot be 0.")));
 				s += SKIP_THth(n->suffix);
 				break;
 			case DCH_Y_YYY:
@@ -3154,19 +3166,30 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 								millenia;
 
 					matched = sscanf(s, "%d,%03d", &millenia, &years);
-					if (matched != 2)
+					if ((matched != 2) || (years < 0))
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
 							  errmsg("invalid input string for \"Y,YYY\"")));
-					years += (millenia * 1000);
+					years += (abs(millenia) * 1000);
+					/* Handle case '-0,001' (parsed as millenia=0, years=1) */
+					if (*s == '-')
+						years = -years;
 					from_char_set_int(&out->year, years, n);
+					if (out->year == 0)
+						ERROR_0YEAR(DCH_Y_YYY, "Y,YYY", NULL);
 					out->yysz = 4;
-					s += strdigits_len(s) + 4 + SKIP_THth(n->suffix);
+					if (*s == '-')
+						/* "s + 1" is to skip leading sign*/
+						s += strdigits_len(s + 1) + 4 + SKIP_THth(n->suffix);
+					else
+						s += strdigits_len(s) + 4 + SKIP_THth(n->suffix);
 				}
 				break;
 			case DCH_YYYY:
 			case DCH_IYYY:
 				from_char_parse_int(&out->year, &s, n);
+				if (out->year == 0)
+					ERROR_0YEAR(DCH_YYYY, "YYYY", "IYYY");
 				out->yysz = 4;
 				s += SKIP_THth(n->suffix);
 				break;
@@ -3174,6 +3197,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			case DCH_IYY:
 				if (from_char_parse_int(&out->year, &s, n) < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
+				if (out->year == 0)
+					ERROR_0YEAR(DCH_YYY, "YYY", "IYY");
 				out->yysz = 3;
 				s += SKIP_THth(n->suffix);
 				break;
@@ -3181,6 +3206,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			case DCH_IY:
 				if (from_char_parse_int(&out->year, &s, n) < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
+				if (out->year == 0)
+					ERROR_0YEAR(DCH_YY, "YY", "IY");
 				out->yysz = 2;
 				s += SKIP_THth(n->suffix);
 				break;
@@ -3188,6 +3215,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			case DCH_I:
 				if (from_char_parse_int(&out->year, &s, n) < 4)
 					out->year = adjust_partial_year_to_2020(out->year);
+				if (out->year == 0)
+					ERROR_0YEAR(DCH_Y, "Y", "I");
 				out->yysz = 1;
 				s += SKIP_THth(n->suffix);
 				break;
@@ -3211,6 +3240,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 		}
 	}
+#undef ERROR_0YEAR
 }
 
 static DCHCacheEntry *
@@ -3680,7 +3710,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		 */
 		if (tmfc.cc && tmfc.yysz <= 2)
 		{
-			if (tmfc.bc)
+			if (tmfc.bc && (tmfc.cc > 0))
 				tmfc.cc = -tmfc.cc;
 			tm->tm_year = tmfc.year % 100;
 			if (tm->tm_year)
@@ -3698,13 +3728,17 @@ do_to_timestamp(text *date_txt, text *fmt,
 			/* If a 4-digit year is provided, we use that and ignore CC. */
 		{
 			tm->tm_year = tmfc.year;
-			if (tmfc.bc && tm->tm_year > 0)
+			if (tm->tm_year < 0)
+				/* "BC" is ignored if year is already negative */
+				tm->tm_year = tm->tm_year + 1;
+			else if (tmfc.bc)
+				/* if year is positive, check for BC era */
 				tm->tm_year = -(tm->tm_year - 1);
 		}
 	}
 	else if (tmfc.cc)			/* use first year of century */
 	{
-		if (tmfc.bc)
+		if (tmfc.bc && (tmfc.cc > 0))
 			tmfc.cc = -tmfc.cc;
 		if (tmfc.cc >= 0)
 			/* +1 because 21st century started in 2001 */
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 1fe02be..193f207 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2982,6 +2982,131 @@ SELECT to_date('2011   12 18', 'YYYY  MM DD');
 (1 row)
 
 --
+-- Check for negative years
+--
+SELECT to_date( '2,008 06 30', 'Y,YYY MM DD');
+  to_date   
+------------
+ 06-30-2008
+(1 row)
+
+SELECT to_date( '0,001 06 30', 'Y,YYY MM DD');
+  to_date   
+------------
+ 06-30-0001
+(1 row)
+
+SELECT to_date( '0,000 06 30', 'Y,YYY MM DD');  -- should fail: there is no "0" year
+ERROR:  invalid input string for "Y,YYY"
+DETAIL:  Year cannot be 0.
+SELECT to_date( '0,-01 06 30', 'Y,YYY MM DD');  -- should fail: "invalid input string"
+ERROR:  invalid input string for "Y,YYY"
+SELECT to_date('-0,001 06 30', 'Y,YYY MM DD');
+    to_date    
+---------------
+ 06-30-0001 BC
+(1 row)
+
+SELECT to_date('-2,008 06 30', 'Y,YYY MM DD');
+    to_date    
+---------------
+ 06-30-2008 BC
+(1 row)
+
+SELECT to_date('-2,008 06 30 BC', 'Y,YYY MM DD BC');  -- "BC" is ignored when year is negative
+    to_date    
+---------------
+ 06-30-2008 BC
+(1 row)
+
+SELECT to_date( '0001 06 30', 'YYYY MM DD');
+  to_date   
+------------
+ 06-30-0001
+(1 row)
+
+SELECT to_date( '0000 06 30', 'YYYY MM DD');  -- should fail: there is no "0" year
+ERROR:  invalid input string for "YYYY"
+DETAIL:  Year cannot be 0.
+SELECT to_date('-0001 06 30', 'YYYY MM DD');
+    to_date    
+---------------
+ 06-30-0001 BC
+(1 row)
+
+SELECT to_date( '000 06 30', 'YYY MM DD'); -- OK, '2000-06-30'
+  to_date   
+------------
+ 06-30-2000
+(1 row)
+
+SELECT to_date('-000 06 30', 'YYY MM DD'); -- should fail, fallbacks to "YYYY", 0 year
+ERROR:  invalid input string for "YYY"
+DETAIL:  Year cannot be 0.
+SELECT to_date('-001 06 30', 'YYY MM DD'); -- OK, fallbacks to "YYYY", 1BC year
+    to_date    
+---------------
+ 06-30-0001 BC
+(1 row)
+
+SELECT to_date( '-01 06 30', 'YYY MM DD');
+  to_date   
+------------
+ 06-30-1999
+(1 row)
+
+SELECT to_date( '-1 06 30 BC', 'CC MM DD BC');  -- "BC" is ignored when year is negative
+    to_date    
+---------------
+ 06-30-0100 BC
+(1 row)
+
+SELECT to_date( '-1 06 30 AD', 'CC MM DD BC');
+    to_date    
+---------------
+ 06-30-0100 BC
+(1 row)
+
+SELECT to_date( '-0001 06 30 BC', 'YYYY MM DD BC');  -- "BC" is ignored when year is negative
+    to_date    
+---------------
+ 06-30-0001 BC
+(1 row)
+
+SELECT to_date( '-0001 06 30 AD', 'YYYY MM DD BC');
+    to_date    
+---------------
+ 06-30-0001 BC
+(1 row)
+
+SELECT to_date('3220 184', 'IYYY IDDD');
+  to_date   
+------------
+ 06-30-3220
+(1 row)
+
+SELECT to_date('3220 184 BC', 'IYYY IDDD BC');
+    to_date    
+---------------
+ 06-30-3220 BC
+(1 row)
+
+SELECT to_date('-3220 184', 'IYYY IDDD');
+    to_date    
+---------------
+ 06-30-3220 BC
+(1 row)
+
+SELECT to_date('-3220 184 BC', 'IYYY IDDD BC');  -- "BC" is ignored when year is negative
+    to_date    
+---------------
+ 06-30-3220 BC
+(1 row)
+
+SELECT to_date('0000 184', 'IYYY IDDD');  -- check for errormsg ("IYYY" in detail)
+ERROR:  invalid input string for "IYYY"
+DETAIL:  Year cannot be 0.
+--
 -- Check errors for some incorrect usages of to_timestamp()
 --
 -- Mixture of date conventions (ISO week and Gregorian):
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index c81437b..6d54fee 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -461,6 +461,39 @@ SELECT to_date('2011  12 18', 'YYYY  MM DD');
 SELECT to_date('2011   12 18', 'YYYY  MM DD');
 
 --
+-- Check for negative years
+--
+SELECT to_date( '2,008 06 30', 'Y,YYY MM DD');
+SELECT to_date( '0,001 06 30', 'Y,YYY MM DD');
+SELECT to_date( '0,000 06 30', 'Y,YYY MM DD');  -- should fail: there is no "0" year
+SELECT to_date( '0,-01 06 30', 'Y,YYY MM DD');  -- should fail: "invalid input string"
+SELECT to_date('-0,001 06 30', 'Y,YYY MM DD');
+SELECT to_date('-2,008 06 30', 'Y,YYY MM DD');
+SELECT to_date('-2,008 06 30 BC', 'Y,YYY MM DD BC');  -- "BC" is ignored when year is negative
+
+SELECT to_date( '0001 06 30', 'YYYY MM DD');
+SELECT to_date( '0000 06 30', 'YYYY MM DD');  -- should fail: there is no "0" year
+SELECT to_date('-0001 06 30', 'YYYY MM DD');
+
+SELECT to_date( '000 06 30', 'YYY MM DD'); -- OK, '2000-06-30'
+SELECT to_date('-000 06 30', 'YYY MM DD'); -- should fail, fallbacks to "YYYY", 0 year
+SELECT to_date('-001 06 30', 'YYY MM DD'); -- OK, fallbacks to "YYYY", 1BC year
+SELECT to_date( '-01 06 30', 'YYY MM DD');
+
+SELECT to_date( '-1 06 30 BC', 'CC MM DD BC');  -- "BC" is ignored when year is negative
+SELECT to_date( '-1 06 30 AD', 'CC MM DD BC');
+
+SELECT to_date( '-0001 06 30 BC', 'YYYY MM DD BC');  -- "BC" is ignored when year is negative
+SELECT to_date( '-0001 06 30 AD', 'YYYY MM DD BC');
+
+SELECT to_date('3220 184', 'IYYY IDDD');
+SELECT to_date('3220 184 BC', 'IYYY IDDD BC');
+SELECT to_date('-3220 184', 'IYYY IDDD');
+SELECT to_date('-3220 184 BC', 'IYYY IDDD BC');  -- "BC" is ignored when year is negative
+
+SELECT to_date('0000 184', 'IYYY IDDD');  -- check for errormsg ("IYYY" in detail)
+
+--
 -- Check errors for some incorrect usages of to_timestamp()
 --
 
