diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d8b27903593..7fd6b49d1f2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -8085,6 +8085,10 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
MM
month number (01–12)
+
+ MZ
+ month number (zero–based) (00–11)
+
DAY
full upper case day name (blank-padded to 9 chars)
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 5f4b37abad2..484fbc6e17f 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -641,6 +641,7 @@ typedef enum
DCH_I,
DCH_J,
DCH_MI,
+ DCH_MZ,
DCH_MM,
DCH_MONTH,
DCH_MON,
@@ -813,6 +814,7 @@ static const KeyWord DCH_keywords[] = {
{"MS", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
{"Month", 5, DCH_Month, false, FROM_CHAR_DATE_GREGORIAN},
{"Mon", 3, DCH_Mon, false, FROM_CHAR_DATE_GREGORIAN},
+ {"MZ", 2, DCH_MZ, true, FROM_CHAR_DATE_GREGORIAN},
{"OF", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* O */
{"P.M.", 4, DCH_P_M, false, FROM_CHAR_DATE_NONE}, /* P */
{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
@@ -867,6 +869,7 @@ static const KeyWord DCH_keywords[] = {
{"month", 5, DCH_month, false, FROM_CHAR_DATE_GREGORIAN},
{"mon", 3, DCH_mon, false, FROM_CHAR_DATE_GREGORIAN},
{"ms", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
+ {"mz", 2, DCH_MZ, true, FROM_CHAR_DATE_GREGORIAN},
{"of", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* o */
{"p.m.", 4, DCH_p_m, false, FROM_CHAR_DATE_NONE}, /* p */
{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
@@ -3076,6 +3079,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
+ case DCH_MZ:
+ sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_mon >= 0) ? 2 : 3,
+ tm->tm_mon - 1);
+ if (S_THth(n->suffix))
+ str_numth(s, s, S_TH_TYPE(n->suffix));
+ s += strlen(s);
+ break;
case DCH_DAY:
INVALID_FOR_INTERVAL;
if (S_TM(n->suffix))
@@ -3760,6 +3770,18 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
return;
SKIP_THth(s, n->suffix);
break;
+ case DCH_MZ:
+ if (from_char_parse_int(&out->mm, &s, n, escontext) < 0)
+ return;
+ if (out->mm >= 0 && out->mm <= 11) {
+ out->mm += 1; // Convert (0-11) to (1-12)
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("Invalid month value for MZ: must be between 0 and 11")));
+ SKIP_THth(s, n->suffix);
+ break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
@@ -4032,6 +4054,7 @@ DCH_datetime_type(FormatNode *node)
case DCH_MON:
case DCH_Mon:
case DCH_mon:
+ case DCH_MZ:
case DCH_MM:
case DCH_DAY:
case DCH_Day:
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 12eefb09d4d..a6a5d3bef63 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3482,6 +3482,14 @@ SELECT to_date('2458872', 'J');
01-23-2020
(1 row)
+SELECT to_date('01012025', 'DDMZYYYY');
+ to_date
+------------
+ 02-01-2025
+(1 row)
+
+SELECT to_date('01122025', 'DDMZYYYY'); -- error
+ERROR: Invalid month value for MZ: must be between 0 and 11
--
-- Check handling of BC dates
--
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 86481637223..1a37c004882 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -564,6 +564,9 @@ SELECT to_date('1 4 1902', 'Q MM YYYY'); -- Q is ignored
SELECT to_date('3 4 21 01', 'W MM CC YY');
SELECT to_date('2458872', 'J');
+SELECT to_date('01012025', 'DDMZYYYY');
+SELECT to_date('01122025', 'DDMZYYYY'); -- error
+
--
-- Check handling of BC dates
--