diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d2d23d31fffeeb0a8544630a0d5809da8a5fc582..d4eaa506e8c8250f9ad5404eca2c898d7ebfd565 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -354,21 +354,27 @@ typedef struct
 
 /* ----------
  * Format picture cache
- *	(cache size:
- *		Number part = NUM_CACHE_SIZE * NUM_CACHE_FIELDS
- *		Date-time part	= DCH_CACHE_SIZE * DCH_CACHE_FIELDS
- *	)
+ *
+ * We will cache datetime format pictures up to DCH_CACHE_SIZE bytes long;
+ * likewise number format pictures up to NUM_CACHE_SIZE bytes long.
+ *
+ * For simplicity, the cache entries are fixed-size, so they allow for the
+ * worst case of a FormatNode for each byte in the picture string.
+ *
+ * The max number of entries in the caches is DCH_CACHE_ENTRIES
+ * resp. NUM_CACHE_ENTRIES.
  * ----------
  */
 #define NUM_CACHE_SIZE		64
-#define NUM_CACHE_FIELDS	16
+#define NUM_CACHE_ENTRIES	20
 #define DCH_CACHE_SIZE		128
-#define DCH_CACHE_FIELDS	16
+#define DCH_CACHE_ENTRIES	20
 
 typedef struct
 {
 	FormatNode	format[DCH_CACHE_SIZE + 1];
 	char		str[DCH_CACHE_SIZE + 1];
+	bool		valid;
 	int			age;
 } DCHCacheEntry;
 
@@ -376,22 +382,20 @@ typedef struct
 {
 	FormatNode	format[NUM_CACHE_SIZE + 1];
 	char		str[NUM_CACHE_SIZE + 1];
+	bool		valid;
 	int			age;
 	NUMDesc		Num;
 } NUMCacheEntry;
 
-/* global cache for --- date/time part */
-static DCHCacheEntry DCHCache[DCH_CACHE_FIELDS + 1];
-
-static int	n_DCHCache = 0;		/* number of entries */
-static int	DCHCounter = 0;
-
-/* global cache for --- number part */
-static NUMCacheEntry NUMCache[NUM_CACHE_FIELDS + 1];
+/* global cache for date/time format pictures */
+static DCHCacheEntry DCHCache[DCH_CACHE_ENTRIES];
+static int	n_DCHCache = 0;		/* current number of entries */
+static int	DCHCounter = 0;		/* aging-event counter */
 
-static int	n_NUMCache = 0;		/* number of entries */
-static int	NUMCounter = 0;
-static NUMCacheEntry *last_NUMCacheEntry = NUMCache + 0;
+/* global cache for number format pictures */
+static NUMCacheEntry NUMCache[NUM_CACHE_ENTRIES];
+static int	n_NUMCache = 0;		/* current number of entries */
+static int	NUMCounter = 0;		/* aging-event counter */
 
 /* ----------
  * For char->date/time conversion
@@ -944,11 +948,11 @@ typedef struct NUMProc
  * Functions
  * ----------
  */
-static const KeyWord *index_seq_search(char *str, const KeyWord *kw,
+static const KeyWord *index_seq_search(const char *str, const KeyWord *kw,
 				 const int *index);
-static const KeySuffix *suff_search(char *str, const KeySuffix *suf, int type);
+static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int type);
 static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
-static void parse_format(FormatNode *node, char *str, const KeyWord *kw,
+static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
 
 static void DCH_to_char(FormatNode *node, bool is_interval,
@@ -982,12 +986,12 @@ static void NUM_numpart_to_char(NUMProc *Np, int id);
 static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
 		   char *number, int from_char_input_len, int to_char_out_pre_spaces,
 			  int sign, bool is_to_char, Oid collid);
-static DCHCacheEntry *DCH_cache_search(char *str);
-static DCHCacheEntry *DCH_cache_getnew(char *str);
-
-static NUMCacheEntry *NUM_cache_search(char *str);
-static NUMCacheEntry *NUM_cache_getnew(char *str);
-static void NUM_cache_remove(NUMCacheEntry *ent);
+static DCHCacheEntry *DCH_cache_getnew(const char *str);
+static DCHCacheEntry *DCH_cache_search(const char *str);
+static DCHCacheEntry *DCH_cache_fetch(const char *str);
+static NUMCacheEntry *NUM_cache_getnew(const char *str);
+static NUMCacheEntry *NUM_cache_search(const char *str);
+static NUMCacheEntry *NUM_cache_fetch(const char *str);
 
 
 /* ----------
@@ -997,7 +1001,7 @@ static void NUM_cache_remove(NUMCacheEntry *ent);
  * ----------
  */
 static const KeyWord *
-index_seq_search(char *str, const KeyWord *kw, const int *index)
+index_seq_search(const char *str, const KeyWord *kw, const int *index)
 {
 	int			poz;
 
@@ -1021,7 +1025,7 @@ index_seq_search(char *str, const KeyWord *kw, const int *index)
 }
 
 static const KeySuffix *
-suff_search(char *str, const KeySuffix *suf, int type)
+suff_search(const char *str, const KeySuffix *suf, int type)
 {
 	const KeySuffix *s;
 
@@ -1046,182 +1050,166 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
 	if (n->type != NODE_TYPE_ACTION)
 		return;
 
-	/*
-	 * In case of an error, we need to remove the numeric from the cache.  Use
-	 * a PG_TRY block to ensure that this happens.
-	 */
-	PG_TRY();
-	{
-		if (IS_EEEE(num) && n->key->id != NUM_E)
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("\"EEEE\" must be the last pattern used")));
-
-		switch (n->key->id)
-		{
-			case NUM_9:
-				if (IS_BRACKET(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("\"9\" must be ahead of \"PR\"")));
-				if (IS_MULTI(num))
-				{
-					++num->multi;
-					break;
-				}
-				if (IS_DECIMAL(num))
-					++num->post;
-				else
-					++num->pre;
-				break;
-
-			case NUM_0:
-				if (IS_BRACKET(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("\"0\" must be ahead of \"PR\"")));
-				if (!IS_ZERO(num) && !IS_DECIMAL(num))
-				{
-					num->flag |= NUM_F_ZERO;
-					num->zero_start = num->pre + 1;
-				}
-				if (!IS_DECIMAL(num))
-					++num->pre;
-				else
-					++num->post;
-
-				num->zero_end = num->pre + num->post;
-				break;
+	if (IS_EEEE(num) && n->key->id != NUM_E)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("\"EEEE\" must be the last pattern used")));
 
-			case NUM_B:
-				if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num)))
-					num->flag |= NUM_F_BLANK;
+	switch (n->key->id)
+	{
+		case NUM_9:
+			if (IS_BRACKET(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("\"9\" must be ahead of \"PR\"")));
+			if (IS_MULTI(num))
+			{
+				++num->multi;
 				break;
+			}
+			if (IS_DECIMAL(num))
+				++num->post;
+			else
+				++num->pre;
+			break;
 
-			case NUM_D:
-				num->flag |= NUM_F_LDECIMAL;
-				num->need_locale = TRUE;
-				/* FALLTHROUGH */
-			case NUM_DEC:
-				if (IS_DECIMAL(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("multiple decimal points")));
-				if (IS_MULTI(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
+		case NUM_0:
+			if (IS_BRACKET(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("\"0\" must be ahead of \"PR\"")));
+			if (!IS_ZERO(num) && !IS_DECIMAL(num))
+			{
+				num->flag |= NUM_F_ZERO;
+				num->zero_start = num->pre + 1;
+			}
+			if (!IS_DECIMAL(num))
+				++num->pre;
+			else
+				++num->post;
+
+			num->zero_end = num->pre + num->post;
+			break;
+
+		case NUM_B:
+			if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num)))
+				num->flag |= NUM_F_BLANK;
+			break;
+
+		case NUM_D:
+			num->flag |= NUM_F_LDECIMAL;
+			num->need_locale = TRUE;
+			/* FALLTHROUGH */
+		case NUM_DEC:
+			if (IS_DECIMAL(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("multiple decimal points")));
+			if (IS_MULTI(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("cannot use \"V\" and decimal point together")));
-				num->flag |= NUM_F_DECIMAL;
-				break;
-
-			case NUM_FM:
-				num->flag |= NUM_F_FILLMODE;
-				break;
+			num->flag |= NUM_F_DECIMAL;
+			break;
 
-			case NUM_S:
-				if (IS_LSIGN(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"S\" twice")));
-				if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together")));
-				if (!IS_DECIMAL(num))
-				{
-					num->lsign = NUM_LSIGN_PRE;
-					num->pre_lsign_num = num->pre;
-					num->need_locale = TRUE;
-					num->flag |= NUM_F_LSIGN;
-				}
-				else if (num->lsign == NUM_LSIGN_NONE)
-				{
-					num->lsign = NUM_LSIGN_POST;
-					num->need_locale = TRUE;
-					num->flag |= NUM_F_LSIGN;
-				}
-				break;
-
-			case NUM_MI:
-				if (IS_LSIGN(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"S\" and \"MI\" together")));
-				num->flag |= NUM_F_MINUS;
-				if (IS_DECIMAL(num))
-					num->flag |= NUM_F_MINUS_POST;
-				break;
-
-			case NUM_PL:
-				if (IS_LSIGN(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"S\" and \"PL\" together")));
-				num->flag |= NUM_F_PLUS;
-				if (IS_DECIMAL(num))
-					num->flag |= NUM_F_PLUS_POST;
-				break;
-
-			case NUM_SG:
-				if (IS_LSIGN(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"S\" and \"SG\" together")));
-				num->flag |= NUM_F_MINUS;
-				num->flag |= NUM_F_PLUS;
-				break;
-
-			case NUM_PR:
-				if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
-				num->flag |= NUM_F_BRACKET;
-				break;
-
-			case NUM_rn:
-			case NUM_RN:
-				num->flag |= NUM_F_ROMAN;
-				break;
+		case NUM_FM:
+			num->flag |= NUM_F_FILLMODE;
+			break;
 
-			case NUM_L:
-			case NUM_G:
+		case NUM_S:
+			if (IS_LSIGN(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"S\" twice")));
+			if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together")));
+			if (!IS_DECIMAL(num))
+			{
+				num->lsign = NUM_LSIGN_PRE;
+				num->pre_lsign_num = num->pre;
 				num->need_locale = TRUE;
-				break;
+				num->flag |= NUM_F_LSIGN;
+			}
+			else if (num->lsign == NUM_LSIGN_NONE)
+			{
+				num->lsign = NUM_LSIGN_POST;
+				num->need_locale = TRUE;
+				num->flag |= NUM_F_LSIGN;
+			}
+			break;
 
-			case NUM_V:
-				if (IS_DECIMAL(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
+		case NUM_MI:
+			if (IS_LSIGN(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"S\" and \"MI\" together")));
+			num->flag |= NUM_F_MINUS;
+			if (IS_DECIMAL(num))
+				num->flag |= NUM_F_MINUS_POST;
+			break;
+
+		case NUM_PL:
+			if (IS_LSIGN(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"S\" and \"PL\" together")));
+			num->flag |= NUM_F_PLUS;
+			if (IS_DECIMAL(num))
+				num->flag |= NUM_F_PLUS_POST;
+			break;
+
+		case NUM_SG:
+			if (IS_LSIGN(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"S\" and \"SG\" together")));
+			num->flag |= NUM_F_MINUS;
+			num->flag |= NUM_F_PLUS;
+			break;
+
+		case NUM_PR:
+			if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
+			num->flag |= NUM_F_BRACKET;
+			break;
+
+		case NUM_rn:
+		case NUM_RN:
+			num->flag |= NUM_F_ROMAN;
+			break;
+
+		case NUM_L:
+		case NUM_G:
+			num->need_locale = TRUE;
+			break;
+
+		case NUM_V:
+			if (IS_DECIMAL(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("cannot use \"V\" and decimal point together")));
-				num->flag |= NUM_F_MULTI;
-				break;
+			num->flag |= NUM_F_MULTI;
+			break;
 
-			case NUM_E:
-				if (IS_EEEE(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("cannot use \"EEEE\" twice")));
-				if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
-					IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
-					IS_ROMAN(num) || IS_MULTI(num))
-					ereport(ERROR,
-							(errcode(ERRCODE_SYNTAX_ERROR),
+		case NUM_E:
+			if (IS_EEEE(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("cannot use \"EEEE\" twice")));
+			if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
+				IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
+				IS_ROMAN(num) || IS_MULTI(num))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
 					   errmsg("\"EEEE\" is incompatible with other formats"),
-							 errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
-				num->flag |= NUM_F_EEEE;
-				break;
-		}
-	}
-	PG_CATCH();
-	{
-		NUM_cache_remove(last_NUMCacheEntry);
-		PG_RE_THROW();
+						 errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
+			num->flag |= NUM_F_EEEE;
+			break;
 	}
-	PG_END_TRY();
-
-
-	return;
 }
 
 /* ----------
@@ -1232,7 +1220,7 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
  * ----------
  */
 static void
-parse_format(FormatNode *node, char *str, const KeyWord *kw,
+parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 			 const KeySuffix *suf, const int *index, int ver, NUMDesc *Num)
 {
 	const KeySuffix *s;
@@ -1350,7 +1338,6 @@ parse_format(FormatNode *node, char *str, const KeyWord *kw,
 
 	n->type = NODE_TYPE_END;
 	n->suffix = 0;
-	return;
 }
 
 /* ----------
@@ -3210,41 +3197,51 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 	}
 }
 
+/* select a DCHCacheEntry to hold the given format picture */
 static DCHCacheEntry *
-DCH_cache_getnew(char *str)
+DCH_cache_getnew(const char *str)
 {
 	DCHCacheEntry *ent;
 
 	/* counter overflow check - paranoia? */
-	if (DCHCounter >= (INT_MAX - DCH_CACHE_FIELDS - 1))
+	if (DCHCounter >= (INT_MAX - DCH_CACHE_ENTRIES))
 	{
 		DCHCounter = 0;
 
-		for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
+		for (ent = DCHCache; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
 			ent->age = (++DCHCounter);
 	}
 
 	/*
-	 * If cache is full, remove oldest entry
+	 * If cache is full, remove oldest entry (or recycle first not-valid one)
 	 */
-	if (n_DCHCache > DCH_CACHE_FIELDS)
+	if (n_DCHCache >= DCH_CACHE_ENTRIES)
 	{
 		DCHCacheEntry *old = DCHCache + 0;
 
 #ifdef DEBUG_TO_FROM_CHAR
 		elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
 #endif
-		for (ent = DCHCache + 1; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
+		if (old->valid)
 		{
-			if (ent->age < old->age)
-				old = ent;
+			for (ent = DCHCache + 1; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
+			{
+				if (!ent->valid)
+				{
+					old = ent;
+					break;
+				}
+				if (ent->age < old->age)
+					old = ent;
+			}
 		}
 #ifdef DEBUG_TO_FROM_CHAR
 		elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
 #endif
+		old->valid = false;
 		StrNCpy(old->str, str, DCH_CACHE_SIZE + 1);
-		/* old->format fill parser */
 		old->age = (++DCHCounter);
+		/* caller is expected to fill format, then set valid */
 		return old;
 	}
 	else
@@ -3253,32 +3250,34 @@ DCH_cache_getnew(char *str)
 		elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
 #endif
 		ent = DCHCache + n_DCHCache;
+		ent->valid = false;
 		StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
-		/* ent->format fill parser */
 		ent->age = (++DCHCounter);
+		/* caller is expected to fill format, then set valid */
 		++n_DCHCache;
 		return ent;
 	}
 }
 
+/* look for an existing DCHCacheEntry matching the given format picture */
 static DCHCacheEntry *
-DCH_cache_search(char *str)
+DCH_cache_search(const char *str)
 {
 	int			i;
 	DCHCacheEntry *ent;
 
 	/* counter overflow check - paranoia? */
-	if (DCHCounter >= (INT_MAX - DCH_CACHE_FIELDS - 1))
+	if (DCHCounter >= (INT_MAX - DCH_CACHE_ENTRIES))
 	{
 		DCHCounter = 0;
 
-		for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
+		for (ent = DCHCache; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
 			ent->age = (++DCHCounter);
 	}
 
 	for (i = 0, ent = DCHCache; i < n_DCHCache; i++, ent++)
 	{
-		if (strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0)
 		{
 			ent->age = (++DCHCounter);
 			return ent;
@@ -3288,6 +3287,29 @@ DCH_cache_search(char *str)
 	return NULL;
 }
 
+/* Find or create a DCHCacheEntry for the given format picture */
+static DCHCacheEntry *
+DCH_cache_fetch(const char *str)
+{
+	DCHCacheEntry *ent;
+
+	if ((ent = DCH_cache_search(str)) == NULL)
+	{
+		/*
+		 * Not in the cache, must run parser and save a new format-picture to
+		 * the cache.  Do not mark the cache entry valid until parsing
+		 * succeeds.
+		 */
+		ent = DCH_cache_getnew(str);
+
+		parse_format(ent->format, str, DCH_keywords,
+					 DCH_suff, DCH_index, DCH_TYPE, NULL);
+
+		ent->valid = true;
+	}
+	return ent;
+}
+
 /*
  * Format a date/time or interval into a string according to fmt.
  * We parse fmt into a list of FormatNodes.  This is then passed to DCH_to_char
@@ -3315,47 +3337,27 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid)
 	result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1);
 	*result = '\0';
 
-	/*
-	 * Allocate new memory if format picture is bigger than static cache and
-	 * not use cache (call parser always)
-	 */
 	if (fmt_len > DCH_CACHE_SIZE)
 	{
-		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+		/*
+		 * Allocate new memory if format picture is bigger than static cache
+		 * and do not use cache (call parser always)
+		 */
 		incache = FALSE;
 
+		format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
+
 		parse_format(format, fmt_str, DCH_keywords,
 					 DCH_suff, DCH_index, DCH_TYPE, NULL);
-
-		(format + fmt_len)->type = NODE_TYPE_END;		/* Paranoia? */
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		DCHCacheEntry *ent;
+		DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
 
 		incache = TRUE;
-
-		if ((ent = DCH_cache_search(fmt_str)) == NULL)
-		{
-			ent = DCH_cache_getnew(fmt_str);
-
-			/*
-			 * Not in the cache, must run parser and save a new format-picture
-			 * to the cache.
-			 */
-			parse_format(ent->format, fmt_str, DCH_keywords,
-						 DCH_suff, DCH_index, DCH_TYPE, NULL);
-
-			(ent->format + fmt_len)->type = NODE_TYPE_END;		/* Paranoia? */
-
-#ifdef DEBUG_TO_FROM_CHAR
-			/* dump_node(ent->format, fmt_len); */
-			/* dump_index(DCH_keywords, DCH_index);  */
-#endif
-		}
 		format = ent->format;
 	}
 
@@ -3584,7 +3586,7 @@ do_to_timestamp(text *date_txt, text *fmt,
 		{
 			/*
 			 * Allocate new memory if format picture is bigger than static
-			 * cache and not use cache (call parser always)
+			 * cache and do not use cache (call parser always)
 			 */
 			incache = FALSE;
 
@@ -3592,31 +3594,15 @@ do_to_timestamp(text *date_txt, text *fmt,
 
 			parse_format(format, fmt_str, DCH_keywords,
 						 DCH_suff, DCH_index, DCH_TYPE, NULL);
-
-			(format + fmt_len)->type = NODE_TYPE_END;	/* Paranoia? */
 		}
 		else
 		{
 			/*
 			 * Use cache buffers
 			 */
-			DCHCacheEntry *ent;
+			DCHCacheEntry *ent = DCH_cache_fetch(fmt_str);
 
 			incache = TRUE;
-
-			if ((ent = DCH_cache_search(fmt_str)) == NULL)
-			{
-				/*
-				 * Not in the cache, must run parser and save a new
-				 * format-picture to the cache.
-				 */
-				ent = DCH_cache_getnew(fmt_str);
-
-				parse_format(ent->format, fmt_str, DCH_keywords,
-							 DCH_suff, DCH_index, DCH_TYPE, NULL);
-
-				(ent->format + fmt_len)->type = NODE_TYPE_END;	/* Paranoia? */
-			}
 			format = ent->format;
 		}
 
@@ -3878,51 +3864,52 @@ do { \
 	(_n)->zero_end		= 0;	\
 } while(0)
 
+/* select a NUMCacheEntry to hold the given format picture */
 static NUMCacheEntry *
-NUM_cache_getnew(char *str)
+NUM_cache_getnew(const char *str)
 {
 	NUMCacheEntry *ent;
 
 	/* counter overflow check - paranoia? */
-	if (NUMCounter >= (INT_MAX - NUM_CACHE_FIELDS - 1))
+	if (NUMCounter >= (INT_MAX - NUM_CACHE_ENTRIES))
 	{
 		NUMCounter = 0;
 
-		for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
+		for (ent = NUMCache; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
 			ent->age = (++NUMCounter);
 	}
 
 	/*
-	 * If cache is full, remove oldest entry
+	 * If cache is full, remove oldest entry (or recycle first not-valid one)
 	 */
-	if (n_NUMCache > NUM_CACHE_FIELDS)
+	if (n_NUMCache >= NUM_CACHE_ENTRIES)
 	{
 		NUMCacheEntry *old = NUMCache + 0;
 
 #ifdef DEBUG_TO_FROM_CHAR
 		elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
 #endif
-		for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
+		if (old->valid)
 		{
-			/*
-			 * entry removed via NUM_cache_remove() can be used here, which is
-			 * why it's worth scanning first entry again
-			 */
-			if (ent->str[0] == '\0')
+			for (ent = NUMCache + 1; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
 			{
-				old = ent;
-				break;
+				if (!ent->valid)
+				{
+					old = ent;
+					break;
+				}
+				if (ent->age < old->age)
+					old = ent;
 			}
-			if (ent->age < old->age)
-				old = ent;
 		}
 #ifdef DEBUG_TO_FROM_CHAR
 		elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age);
 #endif
+		old->valid = false;
 		StrNCpy(old->str, str, NUM_CACHE_SIZE + 1);
-		/* old->format fill parser */
 		old->age = (++NUMCounter);
-		ent = old;
+		/* caller is expected to fill format and Num, then set valid */
+		return old;
 	}
 	else
 	{
@@ -3930,39 +3917,36 @@ NUM_cache_getnew(char *str)
 		elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
 #endif
 		ent = NUMCache + n_NUMCache;
+		ent->valid = false;
 		StrNCpy(ent->str, str, NUM_CACHE_SIZE + 1);
-		/* ent->format fill parser */
 		ent->age = (++NUMCounter);
+		/* caller is expected to fill format and Num, then set valid */
 		++n_NUMCache;
+		return ent;
 	}
-
-	zeroize_NUM(&ent->Num);
-
-	last_NUMCacheEntry = ent;
-	return ent;
 }
 
+/* look for an existing NUMCacheEntry matching the given format picture */
 static NUMCacheEntry *
-NUM_cache_search(char *str)
+NUM_cache_search(const char *str)
 {
 	int			i;
 	NUMCacheEntry *ent;
 
 	/* counter overflow check - paranoia? */
-	if (NUMCounter >= (INT_MAX - NUM_CACHE_FIELDS - 1))
+	if (NUMCounter >= (INT_MAX - NUM_CACHE_ENTRIES))
 	{
 		NUMCounter = 0;
 
-		for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
+		for (ent = NUMCache; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
 			ent->age = (++NUMCounter);
 	}
 
 	for (i = 0, ent = NUMCache; i < n_NUMCache; i++, ent++)
 	{
-		if (strcmp(ent->str, str) == 0)
+		if (ent->valid && strcmp(ent->str, str) == 0)
 		{
 			ent->age = (++NUMCounter);
-			last_NUMCacheEntry = ent;
 			return ent;
 		}
 	}
@@ -3970,14 +3954,29 @@ NUM_cache_search(char *str)
 	return NULL;
 }
 
-static void
-NUM_cache_remove(NUMCacheEntry *ent)
+/* Find or create a NUMCacheEntry for the given format picture */
+static NUMCacheEntry *
+NUM_cache_fetch(const char *str)
 {
-#ifdef DEBUG_TO_FROM_CHAR
-	elog(DEBUG_elog_output, "REMOVING ENTRY (%s)", ent->str);
-#endif
-	ent->str[0] = '\0';
-	ent->age = 0;
+	NUMCacheEntry *ent;
+
+	if ((ent = NUM_cache_search(str)) == NULL)
+	{
+		/*
+		 * Not in the cache, must run parser and save a new format-picture to
+		 * the cache.  Do not mark the cache entry valid until parsing
+		 * succeeds.
+		 */
+		ent = NUM_cache_getnew(str);
+
+		zeroize_NUM(&ent->Num);
+
+		parse_format(ent->format, str, NUM_keywords,
+					 NULL, NUM_index, NUM_TYPE, &ent->Num);
+
+		ent->valid = true;
+	}
+	return ent;
 }
 
 /* ----------
@@ -3992,13 +3991,12 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 
 	str = text_to_cstring(pars_str);
 
-	/*
-	 * Allocate new memory if format picture is bigger than static cache and
-	 * not use cache (call parser always). This branches sets shouldFree to
-	 * true, accordingly.
-	 */
 	if (len > NUM_CACHE_SIZE)
 	{
+		/*
+		 * Allocate new memory if format picture is bigger than static cache
+		 * and do not use cache (call parser always)
+		 */
 		format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
 
 		*shouldFree = true;
@@ -4007,32 +4005,16 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
 
 		parse_format(format, str, NUM_keywords,
 					 NULL, NUM_index, NUM_TYPE, Num);
-
-		(format + len)->type = NODE_TYPE_END;	/* Paranoia? */
 	}
 	else
 	{
 		/*
 		 * Use cache buffers
 		 */
-		NUMCacheEntry *ent;
+		NUMCacheEntry *ent = NUM_cache_fetch(str);
 
 		*shouldFree = false;
 
-		if ((ent = NUM_cache_search(str)) == NULL)
-		{
-			ent = NUM_cache_getnew(str);
-
-			/*
-			 * Not in the cache, must run parser and save a new format-picture
-			 * to the cache.
-			 */
-			parse_format(ent->format, str, NUM_keywords,
-						 NULL, NUM_index, NUM_TYPE, &ent->Num);
-
-			(ent->format + len)->type = NODE_TYPE_END;	/* Paranoia? */
-		}
-
 		format = ent->format;
 
 		/*