diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5148095..e298f92 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -6106,8 +6106,8 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
        </row>
        <row>
         <entry><literal>TM</literal> prefix</entry>
-        <entry>translation mode (print localized day and month names based on
-         <xref linkend="guc-lc-time">)</entry>
+        <entry>translation mode (print localized day/month names or read
+        localized month names based on <xref linkend="guc-lc-time">)</entry>
         <entry><literal>TMMonth</literal></entry>
        </row>
        <row>
@@ -6138,8 +6138,6 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
      <listitem>
       <para>
        <literal>TM</literal> does not include trailing blanks.
-       <function>to_timestamp</> and <function>to_date</> ignore
-       the <literal>TM</literal> modifier.
       </para>
      </listitem>
 
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index bbd97dc..ec692cf 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -2359,6 +2359,40 @@ from_char_seq_search(int *dest, char **src, const char *const * array, int type,
 	return len;
 }
 
+/*
+ * Perform a sequential case-insensitive search in 'array' of size 'size' for
+ * text matching the characters in 'src'.
+ *
+ * If a match is found, copy the array index in 'dest', advance 'src' to the
+ * end of the matched string, and return the number of characters consumed.
+ *
+ * If the string doesn't match, throw an error
+ */
+static int
+from_char_case_search(int *dest, char **src, char **array, int size,
+					  FormatNode *node)
+{
+	int			i;
+	size_t		len;
+
+	for (i = 0; i < size; ++i)
+	{
+		len = strlen(array[i]);
+		if (pg_strncasecmp(array[i], *src, len) == 0)
+		{
+			*dest = i;
+			*src += len;
+			return len;
+		}
+	}
+	ereport(ERROR,
+			(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+			 errmsg("invalid value \"%s\" for \"%s\"",
+					*src, node->key->name),
+			 errdetail("The given value did not match any of the allowed "
+					   "values for this field.")));
+}
+
 /* ----------
  * Process a TmToChar struct as denoted by a list of FormatNodes.
  * The formatted data is written to the string pointed to by 'out'.
@@ -2950,6 +2984,9 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				value;
 	bool		fx_mode = false;
 
+	/* cache localized days and months */
+	cache_locale_time();
+
 	for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
 	{
 		if (n->type != NODE_TYPE_ACTION)
@@ -3064,15 +3101,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 			case DCH_MONTH:
 			case DCH_Month:
 			case DCH_month:
-				from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-									 MAX_MONTH_LEN, n);
+				if (S_TM(n->suffix))
+					from_char_case_search(&value, &s, localized_full_months,
+										  12, n);
+				else
+					from_char_seq_search(&value, &s, months_full, ONE_UPPER,
+										 MAX_MONTH_LEN, n);
 				from_char_set_int(&out->mm, value + 1, n);
 				break;
 			case DCH_MON:
 			case DCH_Mon:
 			case DCH_mon:
-				from_char_seq_search(&value, &s, months, ONE_UPPER,
-									 MAX_MON_LEN, n);
+				if (S_TM(n->suffix))
+					from_char_case_search(&value, &s, localized_abbrev_months,
+										  12, n);
+				else
+					from_char_seq_search(&value, &s, months, ONE_UPPER,
+										 MAX_MON_LEN, n);
 				from_char_set_int(&out->mm, value + 1, n);
 				break;
 			case DCH_MM:
