From 0e1e899955dbedbadc909c6ca54c9ddcfb545b73 Mon Sep 17 00:00:00 2001
From: Haibo Yan <tristan.yim@gmail.com>
Date: Sun, 28 Jun 2026 13:28:19 -0700
Subject: [PATCH 4/4] Add built-in format casts for CAST ... FORMAT

Add built-in format cast functions and pg_format_cast bootstrap rows for the
initial datetime/string formatted casts:

    text/varchar/bpchar <-> date
    text/varchar/bpchar <-> timestamp
    text/varchar/bpchar <-> timestamptz

The wrappers reuse PostgreSQL's existing formatting code and are reached
through pg_format_cast, not by parser or analyzer special cases.  They are
STRICT, so NULL input or NULL FORMAT returns NULL.

This provides SQL-standard-style CAST ... FORMAT support for the common
datetime/string cases, while keeping PostgreSQL extensions from the
generic infrastructure: FORMAT may be any expression coercible to text,
and format cast lookup remains catalog-driven.  The patch does not claim
complete SQL-standard datetime-template conformance; time, timetz,
numeric, interval, and fuller template coverage are left for future work.

Built-in format cast rows are treated as system objects and are not dumped
by pg_dump.
---
 src/backend/utils/adt/formatting.c         | 317 +++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  25 +-
 src/include/catalog/pg_format_cast.dat     |  71 +++++
 src/include/catalog/pg_proc.dat            |  75 +++++
 src/test/regress/expected/expressions.out  |  25 +-
 src/test/regress/expected/format_casts.out | 279 +++++++++++++++++-
 src/test/regress/sql/expressions.sql       |  17 +-
 src/test/regress/sql/format_casts.sql      | 128 ++++++++-
 8 files changed, 914 insertions(+), 23 deletions(-)
 create mode 100644 src/include/catalog/pg_format_cast.dat

diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d52d71b0a8c..d42dea166b6 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4138,6 +4138,323 @@ to_date(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+/* ----------
+ * Built-in format casts for CAST ( <operand> AS <type> FORMAT <template> )
+ *
+ * These wrappers are registered in pg_format_cast (see pg_format_cast.dat) and
+ * reached through a CoerceViaFormatCast node.  They reuse PostgreSQL's existing
+ * formatting code: to_date()/to_timestamp()/parse_datetime() for the
+ * string -> datetime direction and timestamp_to_char()/timestamptz_to_char()
+ * for the datetime -> string direction, so the accepted template tokens are
+ * those of to_char()/to_date().
+ *
+ * There is a separate wrapper per exact source/target type pair, because
+ * pg_format_cast lookup matches the exact source and target types and text,
+ * varchar and bpchar are distinct on each side.  bpchar sources are
+ * right-trimmed first.  The returned text varlena is a valid varchar/bpchar
+ * value; any declared length/typmod is enforced by the ordinary coercion
+ * layered above the CoerceViaFormatCast node.
+ *
+ * All of these functions are STRICT, so a NULL source value or NULL FORMAT
+ * expression yields NULL without the C code being called.
+ * ----------
+ */
+
+/*
+ * Strip blank padding from a bpchar argument, returning a plain text value.
+ * Standard-style character casts must not fail merely because a fixed-length
+ * CHAR(n) source carries trailing spaces, so bpchar sources are right-trimmed
+ * before the format template is applied.
+ *
+ * bpchar padding uses ordinary ASCII space bytes, so we trim only trailing
+ * ' ' bytes here.  This is deliberately not a general Unicode whitespace trim.
+ */
+static text *
+formatcast_rtrim_blanks(text *src)
+{
+	char	   *data = VARDATA_ANY(src);
+	int			len = VARSIZE_ANY_EXHDR(src);
+	int			orig_len = len;
+
+	while (len > 0 && data[len - 1] == ' ')
+		len--;
+
+	/* Avoid a copy when there was no trailing padding to trim. */
+	if (len == orig_len)
+		return src;
+
+	return cstring_to_text_with_len(data, len);
+}
+
+/*
+ * string -> date: reuse to_date() verbatim, so behavior matches the
+ * SQL-callable to_date(text, text).
+ */
+static Datum
+formatcast_in_date(text *src, text *fmt, Oid collid)
+{
+	return DirectFunctionCall2Coll(to_date, collid,
+								   PointerGetDatum(src), PointerGetDatum(fmt));
+}
+
+/*
+ * string -> timestamp without time zone.  There is no SQL-callable function
+ * for this (to_timestamp() returns timestamptz), so we parse with
+ * parse_datetime() and build the result from the local datetime fields without
+ * consulting the session time zone.  A date-only template is widened to
+ * timestamp; a zoned template is rejected, since the result type carries no
+ * zone and we must not silently fold a time zone through the session setting.
+ */
+static Datum
+formatcast_in_timestamp(text *src, text *fmt, Oid collid)
+{
+	Oid			typid;
+	int32		typmod;
+	int			tz;
+	Datum		d;
+
+	d = parse_datetime(src, fmt, collid, false, &typid, &typmod, &tz, NULL);
+
+	switch (typid)
+	{
+		case TIMESTAMPOID:
+			return d;
+		case DATEOID:
+			return TimestampGetDatum(date2timestamp_safe(DatumGetDateADT(d),
+														 NULL));
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+					 errmsg("FORMAT template for a cast to timestamp without time zone must not specify a time zone")));
+			return (Datum) 0;	/* keep compiler quiet */
+	}
+}
+
+/*
+ * string -> timestamp with time zone: reuse to_timestamp() verbatim, matching
+ * the SQL-callable to_timestamp(text, text).
+ */
+static Datum
+formatcast_in_timestamptz(text *src, text *fmt, Oid collid)
+{
+	return DirectFunctionCall2Coll(to_timestamp, collid,
+								   PointerGetDatum(src), PointerGetDatum(fmt));
+}
+
+/* string -> date (text and varchar sources) */
+Datum
+formatcast_str_to_date(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_date(PG_GETARG_TEXT_PP(0), PG_GETARG_TEXT_PP(1),
+							  PG_GET_COLLATION());
+}
+
+/* string -> date (bpchar source; trailing blanks trimmed) */
+Datum
+formatcast_bpchar_to_date(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_date(formatcast_rtrim_blanks(PG_GETARG_TEXT_PP(0)),
+							  PG_GETARG_TEXT_PP(1), PG_GET_COLLATION());
+}
+
+/* string -> timestamp (text and varchar sources) */
+Datum
+formatcast_str_to_timestamp(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_timestamp(PG_GETARG_TEXT_PP(0), PG_GETARG_TEXT_PP(1),
+								   PG_GET_COLLATION());
+}
+
+/* string -> timestamp (bpchar source; trailing blanks trimmed) */
+Datum
+formatcast_bpchar_to_timestamp(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_timestamp(formatcast_rtrim_blanks(PG_GETARG_TEXT_PP(0)),
+								   PG_GETARG_TEXT_PP(1), PG_GET_COLLATION());
+}
+
+/* string -> timestamptz (text and varchar sources) */
+Datum
+formatcast_str_to_timestamptz(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_timestamptz(PG_GETARG_TEXT_PP(0), PG_GETARG_TEXT_PP(1),
+									 PG_GET_COLLATION());
+}
+
+/* string -> timestamptz (bpchar source; trailing blanks trimmed) */
+Datum
+formatcast_bpchar_to_timestamptz(PG_FUNCTION_ARGS)
+{
+	return formatcast_in_timestamptz(formatcast_rtrim_blanks(PG_GETARG_TEXT_PP(0)),
+									 PG_GETARG_TEXT_PP(1), PG_GET_COLLATION());
+}
+
+/*
+ * Call a collation-aware (datetime, text) -> text formatting function,
+ * propagating a SQL NULL result.  DirectFunctionCall cannot handle a SQL NULL
+ * result, so build a local FunctionCallInfo.  Pass the caller's flinfo to
+ * provide a normal fmgr context; the callees used here do not depend on
+ * function-specific flinfo state.
+ */
+static Datum
+formatcast_to_char_call(FunctionCallInfo caller_fcinfo, PGFunction fn,
+						Oid collid, Datum value, Datum fmt, bool *resultnull)
+{
+	LOCAL_FCINFO(fcinfo, 2);
+	Datum		result;
+
+	InitFunctionCallInfoData(*fcinfo, caller_fcinfo->flinfo, 2, collid,
+							 NULL, NULL);
+	fcinfo->args[0].value = value;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = fmt;
+	fcinfo->args[1].isnull = false;
+
+	result = (*fn) (fcinfo);
+	*resultnull = fcinfo->isnull;
+	return result;
+}
+
+/*
+ * Per-source-type helpers shared by the datetime -> string wrappers below.
+ * Each formats the source value with the given template, returning the result
+ * text (with *resultnull set on an empty template, as for to_char()).
+ */
+static Datum
+formatcast_date_out(FunctionCallInfo fcinfo, bool *resultnull)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	text	   *fmt = PG_GETARG_TEXT_PP(1);
+	Timestamp	ts = date2timestamp_safe(dateVal, NULL);
+
+	return formatcast_to_char_call(fcinfo, timestamp_to_char, PG_GET_COLLATION(),
+								   TimestampGetDatum(ts), PointerGetDatum(fmt),
+								   resultnull);
+}
+
+static Datum
+formatcast_timestamp_out(FunctionCallInfo fcinfo, bool *resultnull)
+{
+	return formatcast_to_char_call(fcinfo, timestamp_to_char, PG_GET_COLLATION(),
+								   PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
+								   resultnull);
+}
+
+static Datum
+formatcast_timestamptz_out(FunctionCallInfo fcinfo, bool *resultnull)
+{
+	return formatcast_to_char_call(fcinfo, timestamptz_to_char, PG_GET_COLLATION(),
+								   PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
+								   resultnull);
+}
+
+/*
+ * Exported datetime -> string wrappers.  The text, varchar and bpchar targets
+ * each get a distinct C symbol so that no two built-in functions share a
+ * prosrc with a different return type; they all delegate to the per-source
+ * helper above, so there is no duplicated logic.  The returned text varlena is
+ * a valid varchar/bpchar value; any declared length/typmod is enforced by the
+ * ordinary coercion layered above the CoerceViaFormatCast node.
+ */
+Datum
+formatcast_date_to_text(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_date_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_date_to_varchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_date_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_date_to_bpchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_date_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamp_to_text(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamp_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamp_to_varchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamp_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamp_to_bpchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamp_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamptz_to_text(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamptz_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamptz_to_varchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamptz_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+formatcast_timestamptz_to_bpchar(PG_FUNCTION_ARGS)
+{
+	bool		resultnull;
+	Datum		result = formatcast_timestamptz_out(fcinfo, &resultnull);
+
+	if (resultnull)
+		PG_RETURN_NULL();
+	PG_RETURN_DATUM(result);
+}
+
 /*
  * Convert the 'date_txt' input to a datetime type using argument 'fmt'
  * as a format string.  The collation 'collid' may be used for case-folding
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6517a450daa..3c0387ab347 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2188,6 +2188,29 @@ selectDumpableCast(CastInfo *cast, Archive *fout)
 			DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
 }
 
+/*
+ * selectDumpableFormatCast: policy-setting subroutine
+ *		Mark a format cast as to be dumped or not
+ *
+ * Like casts, format_casts have no namespace and no identifiable owner, so we
+ * distinguish the built-in (initdb-created) format_casts from user-defined ones
+ * by checking whether the format cast's OID is in the range reserved for initdb.
+ * Built-in format casts are part of the system catalogs and must not be dumped
+ * as CREATE FORMAT CAST commands.
+ */
+static void
+selectDumpableFormatCast(FormatCastInfo *formatcast, Archive *fout)
+{
+	if (checkExtensionMembership(&formatcast->dobj, fout))
+		return;					/* extension membership overrides all else */
+
+	if (formatcast->dobj.catId.oid <= g_last_builtin_oid)
+		formatcast->dobj.dump = DUMP_COMPONENT_NONE;
+	else
+		formatcast->dobj.dump = fout->dopt->include_everything ?
+			DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
+}
+
 /*
  * selectDumpableProcLang: policy-setting subroutine
  *		Mark a procedural language as to be dumped or not
@@ -9390,7 +9413,7 @@ getFormatCasts(Archive *fout)
 		formatcastinfo[i].dobj.name = namebuf.data;
 
 		/* Decide whether we want to dump it */
-		selectDumpableObject(&(formatcastinfo[i].dobj), fout);
+		selectDumpableFormatCast(&(formatcastinfo[i]), fout);
 	}
 
 	PQclear(res);
diff --git a/src/include/catalog/pg_format_cast.dat b/src/include/catalog/pg_format_cast.dat
new file mode 100644
index 00000000000..39d3ee523ad
--- /dev/null
+++ b/src/include/catalog/pg_format_cast.dat
@@ -0,0 +1,71 @@
+#----------------------------------------------------------------------
+#
+# pg_format_cast.dat
+#    Initial contents of the pg_format_cast system catalog.
+#
+# These are the built-in format casts for CAST ( <operand> AS <type>
+# FORMAT <template> ).  Each row maps an exact (source type, target type) pair
+# to a wrapper function with the signature
+#     function(source_type, text) returns target_type
+# Lookup is by exact source and target type, so text, varchar and bpchar are
+# registered separately on each side.  The wrapper functions live in
+# formatting.c and reuse PostgreSQL's existing formatting code.
+#
+# Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/include/catalog/pg_format_cast.dat
+#
+#----------------------------------------------------------------------
+
+[
+
+# string -> date
+{ oid => '8668', fmtsource => 'text', fmttarget => 'date',
+  fmtfunc => 'pg_format_text_to_date(text,text)' },
+{ oid => '8669', fmtsource => 'varchar', fmttarget => 'date',
+  fmtfunc => 'pg_format_varchar_to_date(varchar,text)' },
+{ oid => '8670', fmtsource => 'bpchar', fmttarget => 'date',
+  fmtfunc => 'pg_format_bpchar_to_date(bpchar,text)' },
+
+# string -> timestamp without time zone
+{ oid => '8671', fmtsource => 'text', fmttarget => 'timestamp',
+  fmtfunc => 'pg_format_text_to_timestamp(text,text)' },
+{ oid => '8672', fmtsource => 'varchar', fmttarget => 'timestamp',
+  fmtfunc => 'pg_format_varchar_to_timestamp(varchar,text)' },
+{ oid => '8673', fmtsource => 'bpchar', fmttarget => 'timestamp',
+  fmtfunc => 'pg_format_bpchar_to_timestamp(bpchar,text)' },
+
+# string -> timestamp with time zone
+{ oid => '8674', fmtsource => 'text', fmttarget => 'timestamptz',
+  fmtfunc => 'pg_format_text_to_timestamptz(text,text)' },
+{ oid => '8675', fmtsource => 'varchar', fmttarget => 'timestamptz',
+  fmtfunc => 'pg_format_varchar_to_timestamptz(varchar,text)' },
+{ oid => '8676', fmtsource => 'bpchar', fmttarget => 'timestamptz',
+  fmtfunc => 'pg_format_bpchar_to_timestamptz(bpchar,text)' },
+
+# date -> string
+{ oid => '8677', fmtsource => 'date', fmttarget => 'text',
+  fmtfunc => 'pg_format_date_to_text(date,text)' },
+{ oid => '8678', fmtsource => 'date', fmttarget => 'varchar',
+  fmtfunc => 'pg_format_date_to_varchar(date,text)' },
+{ oid => '8679', fmtsource => 'date', fmttarget => 'bpchar',
+  fmtfunc => 'pg_format_date_to_bpchar(date,text)' },
+
+# timestamp without time zone -> string
+{ oid => '8680', fmtsource => 'timestamp', fmttarget => 'text',
+  fmtfunc => 'pg_format_timestamp_to_text(timestamp,text)' },
+{ oid => '8681', fmtsource => 'timestamp', fmttarget => 'varchar',
+  fmtfunc => 'pg_format_timestamp_to_varchar(timestamp,text)' },
+{ oid => '8682', fmtsource => 'timestamp', fmttarget => 'bpchar',
+  fmtfunc => 'pg_format_timestamp_to_bpchar(timestamp,text)' },
+
+# timestamp with time zone -> string
+{ oid => '8683', fmtsource => 'timestamptz', fmttarget => 'text',
+  fmtfunc => 'pg_format_timestamptz_to_text(timestamptz,text)' },
+{ oid => '8684', fmtsource => 'timestamptz', fmttarget => 'varchar',
+  fmtfunc => 'pg_format_timestamptz_to_varchar(timestamptz,text)' },
+{ oid => '8685', fmtsource => 'timestamptz', fmttarget => 'bpchar',
+  fmtfunc => 'pg_format_timestamptz_to_bpchar(timestamptz,text)' },
+
+]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 402d869710b..4c0c7590939 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4847,6 +4847,81 @@
 { oid => '1780', descr => 'convert text to date',
   proname => 'to_date', provolatile => 's', prorettype => 'date',
   proargtypes => 'text text', prosrc => 'to_date' },
+
+# Built-in format cast support functions.  These are registered in
+# pg_format_cast and called through CoerceViaFormatCast.
+{ oid => '8650', descr => 'formatted cast: text to date',
+  proname => 'pg_format_text_to_date', proisstrict => 't', provolatile => 's',
+  prorettype => 'date', proargtypes => 'text text',
+  prosrc => 'formatcast_str_to_date' },
+{ oid => '8651', descr => 'formatted cast: varchar to date',
+  proname => 'pg_format_varchar_to_date', proisstrict => 't', provolatile => 's',
+  prorettype => 'date', proargtypes => 'varchar text',
+  prosrc => 'formatcast_str_to_date' },
+{ oid => '8652', descr => 'formatted cast: bpchar to date',
+  proname => 'pg_format_bpchar_to_date', proisstrict => 't', provolatile => 's',
+  prorettype => 'date', proargtypes => 'bpchar text',
+  prosrc => 'formatcast_bpchar_to_date' },
+{ oid => '8653', descr => 'formatted cast: text to timestamp',
+  proname => 'pg_format_text_to_timestamp', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamp', proargtypes => 'text text',
+  prosrc => 'formatcast_str_to_timestamp' },
+{ oid => '8654', descr => 'formatted cast: varchar to timestamp',
+  proname => 'pg_format_varchar_to_timestamp', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamp', proargtypes => 'varchar text',
+  prosrc => 'formatcast_str_to_timestamp' },
+{ oid => '8655', descr => 'formatted cast: bpchar to timestamp',
+  proname => 'pg_format_bpchar_to_timestamp', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamp', proargtypes => 'bpchar text',
+  prosrc => 'formatcast_bpchar_to_timestamp' },
+{ oid => '8656', descr => 'formatted cast: text to timestamptz',
+  proname => 'pg_format_text_to_timestamptz', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamptz', proargtypes => 'text text',
+  prosrc => 'formatcast_str_to_timestamptz' },
+{ oid => '8657', descr => 'formatted cast: varchar to timestamptz',
+  proname => 'pg_format_varchar_to_timestamptz', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamptz', proargtypes => 'varchar text',
+  prosrc => 'formatcast_str_to_timestamptz' },
+{ oid => '8658', descr => 'formatted cast: bpchar to timestamptz',
+  proname => 'pg_format_bpchar_to_timestamptz', proisstrict => 't', provolatile => 's',
+  prorettype => 'timestamptz', proargtypes => 'bpchar text',
+  prosrc => 'formatcast_bpchar_to_timestamptz' },
+{ oid => '8659', descr => 'formatted cast: date to text',
+  proname => 'pg_format_date_to_text', proisstrict => 't', provolatile => 's',
+  prorettype => 'text', proargtypes => 'date text',
+  prosrc => 'formatcast_date_to_text' },
+{ oid => '8660', descr => 'formatted cast: date to varchar',
+  proname => 'pg_format_date_to_varchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'varchar', proargtypes => 'date text',
+  prosrc => 'formatcast_date_to_varchar' },
+{ oid => '8661', descr => 'formatted cast: date to bpchar',
+  proname => 'pg_format_date_to_bpchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'bpchar', proargtypes => 'date text',
+  prosrc => 'formatcast_date_to_bpchar' },
+{ oid => '8662', descr => 'formatted cast: timestamp to text',
+  proname => 'pg_format_timestamp_to_text', proisstrict => 't', provolatile => 's',
+  prorettype => 'text', proargtypes => 'timestamp text',
+  prosrc => 'formatcast_timestamp_to_text' },
+{ oid => '8663', descr => 'formatted cast: timestamp to varchar',
+  proname => 'pg_format_timestamp_to_varchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'varchar', proargtypes => 'timestamp text',
+  prosrc => 'formatcast_timestamp_to_varchar' },
+{ oid => '8664', descr => 'formatted cast: timestamp to bpchar',
+  proname => 'pg_format_timestamp_to_bpchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'bpchar', proargtypes => 'timestamp text',
+  prosrc => 'formatcast_timestamp_to_bpchar' },
+{ oid => '8665', descr => 'formatted cast: timestamptz to text',
+  proname => 'pg_format_timestamptz_to_text', proisstrict => 't', provolatile => 's',
+  prorettype => 'text', proargtypes => 'timestamptz text',
+  prosrc => 'formatcast_timestamptz_to_text' },
+{ oid => '8666', descr => 'formatted cast: timestamptz to varchar',
+  proname => 'pg_format_timestamptz_to_varchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'varchar', proargtypes => 'timestamptz text',
+  prosrc => 'formatcast_timestamptz_to_varchar' },
+{ oid => '8667', descr => 'formatted cast: timestamptz to bpchar',
+  proname => 'pg_format_timestamptz_to_bpchar', proisstrict => 't', provolatile => 's',
+  prorettype => 'bpchar', proargtypes => 'timestamptz text',
+  prosrc => 'formatcast_timestamptz_to_bpchar' },
 { oid => '1768', descr => 'format interval to text',
   proname => 'to_char', provolatile => 's', prorettype => 'text',
   proargtypes => 'interval text', prosrc => 'interval_to_char' },
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 1cbd05135dd..06b1c98ac36 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -565,21 +565,22 @@ rollback;
 -- CAST(expr AS type FORMAT format_expr)
 --
 -- A FORMAT clause is resolved through a registered format cast (see CREATE
--- FORMAT CAST) and never falls back to an ordinary cast.  No format_casts are
--- defined in this test, so these casts fail with a missing-format cast error;
--- this confirms the FORMAT clause is neither ignored nor treated as an
--- ordinary cast.  An unknown-type source literal is coerced to text first,
--- so the lookup key is (text, target).
--- basic form (looks up a format cast for (text, date))
-SELECT CAST('2026-06-24' AS date FORMAT 'YYYY-MM-DD');
-ERROR:  format cast from type text to type date does not exist
-LINE 1: SELECT CAST('2026-06-24' AS date FORMAT 'YYYY-MM-DD');
+-- FORMAT CAST) and never falls back to an ordinary cast.  The type pairs used
+-- here have no format cast (the built-in format casts cover datetime/string pairs,
+-- not these), so these casts fail with a missing-format cast error even though an
+-- ordinary cast would succeed; this confirms the FORMAT clause is neither
+-- ignored nor treated as an ordinary cast.  An unknown-type source literal is
+-- coerced to text first, so the lookup key is (text, target).
+-- basic form (looks up a format cast for (text, integer); none exists)
+SELECT CAST('42' AS integer FORMAT 'whatever');
+ERROR:  format cast from type text to type integer does not exist
+LINE 1: SELECT CAST('42' AS integer FORMAT 'whatever');
                ^
 HINT:  Use CREATE FORMAT CAST to define a format cast for this type pair.
 -- the format may be a general expression, not just a string literal
-SELECT CAST('2026-06-24' AS date FORMAT 'YYYY' || '-MM-DD');
-ERROR:  format cast from type text to type date does not exist
-LINE 1: SELECT CAST('2026-06-24' AS date FORMAT 'YYYY' || '-MM-DD');
+SELECT CAST('42' AS integer FORMAT 'what' || 'ever');
+ERROR:  format cast from type text to type integer does not exist
+LINE 1: SELECT CAST('42' AS integer FORMAT 'what' || 'ever');
                ^
 HINT:  Use CREATE FORMAT CAST to define a format cast for this type pair.
 -- a no-op-looking cast must not be relabeled away; it needs a (text, text) format cast
diff --git a/src/test/regress/expected/format_casts.out b/src/test/regress/expected/format_casts.out
index cb8684080ef..54897eef4d7 100644
--- a/src/test/regress/expected/format_casts.out
+++ b/src/test/regress/expected/format_casts.out
@@ -99,7 +99,9 @@ CREATE FUNCTION fmt_bad_set(integer, text) RETURNS SETOF text
 CREATE FORMAT CAST (integer AS text)
 	WITH FUNCTION fmt_bad_set(integer, text);		-- set-returning rejected
 ERROR:  format cast function must not return a set
--- No pseudo-types
+-- No pseudo-types: a format cast's source/target may not be a pseudo-type, so
+-- CREATE FORMAT CAST on a polymorphic (anyelement) source is intentionally
+-- rejected.
 CREATE FUNCTION fmt_anyel(anyelement, text) RETURNS text
 	LANGUAGE sql IMMUTABLE AS $$ SELECT $2 $$;
 CREATE FORMAT CAST (anyelement AS text)
@@ -397,3 +399,278 @@ SELECT count(*) FROM pg_class WHERE relname = 'format_cast_chain_view';
      0
 (1 row)
 
+-- ====================================================================
+-- Built-in datetime/string format casts.
+-- These rows are registered in pg_format_cast and reuse PostgreSQL's
+-- existing formatting template behavior.
+-- This initial set covers date, timestamp and timestamptz with
+-- text/varchar/bpchar.
+-- ====================================================================
+-- Pin the time zone and date style so results are stable across machines.
+SET TimeZone = 'UTC';
+SET DateStyle = 'ISO, YMD';
+-- text -> date.  The unknown-type literal is coerced to text before format
+-- cast lookup.
+SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+-- varchar source -> date
+SELECT CAST('2026-06-28'::varchar AS date FORMAT 'YYYY-MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+-- bpchar source -> date: a CHAR(10) holds the value exactly, and a wider
+-- CHAR(20) pads with blanks that the format cast trims before parsing.
+SELECT CAST('2026-06-28'::char(10) AS date FORMAT 'YYYY-MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+SELECT CAST('2026-06-28'::char(20) AS date FORMAT 'YYYY-MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+-- The built-in format cast functions are STRICT: a NULL source or a NULL FORMAT
+-- expression yields NULL without invoking the format cast.
+SELECT CAST(NULL::text AS date FORMAT 'YYYY-MM-DD') IS NULL AS null_src;
+ null_src 
+----------
+ t
+(1 row)
+
+SELECT CAST('2026-06-28' AS date FORMAT NULL::text) IS NULL AS null_fmt;
+ null_fmt 
+----------
+ t
+(1 row)
+
+SELECT CAST(NULL::date AS text FORMAT 'YYYY-MM-DD') IS NULL AS null_src_out;
+ null_src_out 
+--------------
+ t
+(1 row)
+
+-- date -> text / varchar
+SELECT CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD');
+    text    
+------------
+ 2026-06-28
+(1 row)
+
+SELECT CAST(date '2026-06-28' AS varchar FORMAT 'YYYY-MM-DD');
+  varchar   
+------------
+ 2026-06-28
+(1 row)
+
+-- date -> varchar with a typmod: the declared length is enforced by the
+-- ordinary coercion layered above the format cast (here it truncates to 7).
+SELECT CAST(date '2026-06-28' AS varchar(7) FORMAT 'YYYY-MM-DD');
+ varchar 
+---------
+ 2026-06
+(1 row)
+
+-- date -> bpchar: AS char(12) pads to the declared length; bare AS char is
+-- char(1), so the ordinary coercion truncates to one character.  The wrapper
+-- returns a text varlena that is a valid bpchar value.
+SELECT CAST(date '2026-06-28' AS char(12) FORMAT 'YYYY-MM-DD');
+    bpchar    
+--------------
+ 2026-06-28  
+(1 row)
+
+SELECT CAST(date '2026-06-28' AS char FORMAT 'YYYY-MM-DD');
+ bpchar 
+--------
+ 2
+(1 row)
+
+-- Collation: a formatted cast is collated like the function call it executes.
+-- A collatable result type (text) must get a real result collation, not
+-- InvalidOid; "collation for" reports "default" (it would be NULL if the
+-- result collation were unset), and an explicit COLLATE on the result works.
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD'));
+ pg_collation_for 
+------------------
+ "default"
+(1 row)
+
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD') COLLATE "C");
+ pg_collation_for 
+------------------
+ "C"
+(1 row)
+
+-- An explicit COLLATE on the FORMAT argument flows in as the input collation.
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text
+                             FORMAT 'YYYY-MM-DD'::text COLLATE "C"));
+ pg_collation_for 
+------------------
+ "C"
+(1 row)
+
+-- timestamp without time zone, both directions
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+      timestamp      
+---------------------
+ 2026-06-28 13:45:30
+(1 row)
+
+SELECT CAST(timestamp '2026-06-28 13:45:30' AS text FORMAT 'YYYY-MM-DD HH24:MI:SS');
+        text         
+---------------------
+ 2026-06-28 13:45:30
+(1 row)
+
+-- a date-only template is widened to timestamp (midnight)
+SELECT CAST('2026-06-28' AS timestamp FORMAT 'YYYY-MM-DD');
+      timestamp      
+---------------------
+ 2026-06-28 00:00:00
+(1 row)
+
+-- The built-ins reuse PostgreSQL's existing formatting parser: a template with
+-- no time-zone field consumes only the fields it names, so a trailing time zone
+-- in the input is simply ignored (existing to_timestamp() behavior, unchanged).
+SELECT CAST('2026-06-28 13:45:30+05' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+      timestamp      
+---------------------
+ 2026-06-28 13:45:30
+(1 row)
+
+-- But a template that explicitly contains a time-zone field is rejected for a
+-- timestamp-without-time-zone target (we must not fold a zone into a zoneless
+-- result via the session TimeZone).
+SELECT CAST('2026-06-28 13:45:30+05' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+ERROR:  FORMAT template for a cast to timestamp without time zone must not specify a time zone
+-- timestamp WITHOUT time zone must not depend on the session TimeZone: the same
+-- (unzoned) input/template yields the same wall-clock value under any zone.
+SET TimeZone = 'America/Los_Angeles';
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+      timestamp      
+---------------------
+ 2026-06-28 13:45:30
+(1 row)
+
+SET TimeZone = 'UTC';
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+      timestamp      
+---------------------
+ 2026-06-28 13:45:30
+(1 row)
+
+-- timestamp with time zone, both directions (TimeZone is UTC, set above)
+SELECT CAST('2026-06-28 13:45:30 +00' AS timestamptz FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+      timestamptz       
+------------------------
+ 2026-06-28 13:45:30+00
+(1 row)
+
+SELECT CAST(timestamptz '2026-06-28 13:45:30+00' AS text FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+          text           
+-------------------------
+ 2026-06-28 13:45:30 +00
+(1 row)
+
+-- A sampling of datetime template tokens, exercised through the existing
+-- to_char/to_timestamp engine.  TZM and SSSSS are supported here.
+SELECT CAST(timestamptz '2026-06-28 13:45:30+00' AS text
+            FORMAT 'YYYY-MM-DD"T"HH24:MI:SS TZH:TZM');
+            text            
+----------------------------
+ 2026-06-28T13:45:30 +00:00
+(1 row)
+
+SELECT CAST(timestamp '2026-06-28 13:45:30' AS text FORMAT 'SSSSS');
+ text  
+-------
+ 49530
+(1 row)
+
+-- Fractional seconds (FFn) are accepted by to_char in the output direction.
+SELECT CAST(timestamp '2026-06-28 13:45:30.123456' AS text
+            FORMAT 'HH24:MI:SS.FF6');
+      text       
+-----------------
+ 13:45:30.123456
+(1 row)
+
+-- Empty FORMAT template behavior follows the underlying formatting functions.
+-- In the parse direction the underlying to_date() engine parses no fields and
+-- returns the all-defaults date; in the format direction the wrappers mirror
+-- to_char(value, ''), which is NULL.
+SELECT CAST('2026-06-28' AS date FORMAT '');
+     date      
+---------------
+ 0001-01-01 BC
+(1 row)
+
+SELECT CAST(date '2026-06-28' AS text FORMAT '') IS NULL AS empty_fmt_out;
+ empty_fmt_out 
+---------------
+ t
+(1 row)
+
+-- The FORMAT clause remains a PostgreSQL extension: it may be any expression
+-- coercible to text, not just a string literal.
+SELECT CAST('2026-' || '06-28' AS date FORMAT 'YYYY-' || 'MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+-- A FORMAT clause never falls back to an ordinary cast: an integer -> date
+-- pair has no format cast (and no ordinary cast), so it errors.
+SELECT CAST(5 AS date FORMAT 'YYYY');
+ERROR:  format cast from type integer to type date does not exist
+LINE 1: SELECT CAST(5 AS date FORMAT 'YYYY');
+               ^
+HINT:  Use CREATE FORMAT CAST to define a format cast for this type pair.
+-- A built-in format cast occupies its (source, target) pair, so a user
+-- CREATE FORMAT CAST for the same pair fails with the usual duplicate error.
+CREATE FUNCTION my_text_to_date(text, text) RETURNS date
+	LANGUAGE sql IMMUTABLE RETURN to_date($1, $2);
+CREATE FORMAT CAST (text AS date)
+	WITH FUNCTION my_text_to_date(text, text);		-- fails: already exists
+ERROR:  format cast from type text to type date already exists
+DROP FUNCTION my_text_to_date(text, text);
+-- Built-in format cast rows are system objects (their OIDs are in the pinned
+-- range), so they cannot be dropped.
+DROP FORMAT CAST (text AS date);				-- fails: pinned system object
+ERROR:  cannot drop format cast from text to date because it is required by the database system
+-- A view over a built-in formatted cast deparses back to CAST(... FORMAT ...).
+CREATE VIEW builtin_format_cast_view AS
+	SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD') AS d;
+SELECT pg_get_viewdef('builtin_format_cast_view'::regclass, true);
+                              pg_get_viewdef                              
+--------------------------------------------------------------------------
+  SELECT CAST('2026-06-28'::text AS date FORMAT 'YYYY-MM-DD'::text) AS d;
+(1 row)
+
+SELECT * FROM builtin_format_cast_view;
+     d      
+------------
+ 2026-06-28
+(1 row)
+
+DROP VIEW builtin_format_cast_view;
+-- Make sure query jumbling handles CoerceViaFormatCast.
+SET compute_query_id = on;
+SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD');
+    date    
+------------
+ 2026-06-28
+(1 row)
+
+RESET compute_query_id;
+RESET TimeZone;
+RESET DateStyle;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index df4f5b20466..60ca2d49d06 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -306,17 +306,18 @@ rollback;
 -- CAST(expr AS type FORMAT format_expr)
 --
 -- A FORMAT clause is resolved through a registered format cast (see CREATE
--- FORMAT CAST) and never falls back to an ordinary cast.  No format_casts are
--- defined in this test, so these casts fail with a missing-format cast error;
--- this confirms the FORMAT clause is neither ignored nor treated as an
--- ordinary cast.  An unknown-type source literal is coerced to text first,
--- so the lookup key is (text, target).
+-- FORMAT CAST) and never falls back to an ordinary cast.  The type pairs used
+-- here have no format cast (the built-in format casts cover datetime/string pairs,
+-- not these), so these casts fail with a missing-format cast error even though an
+-- ordinary cast would succeed; this confirms the FORMAT clause is neither
+-- ignored nor treated as an ordinary cast.  An unknown-type source literal is
+-- coerced to text first, so the lookup key is (text, target).
 
--- basic form (looks up a format cast for (text, date))
-SELECT CAST('2026-06-24' AS date FORMAT 'YYYY-MM-DD');
+-- basic form (looks up a format cast for (text, integer); none exists)
+SELECT CAST('42' AS integer FORMAT 'whatever');
 
 -- the format may be a general expression, not just a string literal
-SELECT CAST('2026-06-24' AS date FORMAT 'YYYY' || '-MM-DD');
+SELECT CAST('42' AS integer FORMAT 'what' || 'ever');
 
 -- a no-op-looking cast must not be relabeled away; it needs a (text, text) format cast
 SELECT CAST('abc'::text AS text FORMAT 'whatever');
diff --git a/src/test/regress/sql/format_casts.sql b/src/test/regress/sql/format_casts.sql
index 05d3236b5ac..a8c7314cfbb 100644
--- a/src/test/regress/sql/format_casts.sql
+++ b/src/test/regress/sql/format_casts.sql
@@ -81,7 +81,9 @@ CREATE FUNCTION fmt_bad_set(integer, text) RETURNS SETOF text
 CREATE FORMAT CAST (integer AS text)
 	WITH FUNCTION fmt_bad_set(integer, text);		-- set-returning rejected
 
--- No pseudo-types
+-- No pseudo-types: a format cast's source/target may not be a pseudo-type, so
+-- CREATE FORMAT CAST on a polymorphic (anyelement) source is intentionally
+-- rejected.
 CREATE FUNCTION fmt_anyel(anyelement, text) RETURNS text
 	LANGUAGE sql IMMUTABLE AS $$ SELECT $2 $$;
 CREATE FORMAT CAST (anyelement AS text)
@@ -263,3 +265,127 @@ DROP FUNCTION i2t_chain(integer, text) CASCADE;			-- drops format cast and view
 SELECT count(*) FROM pg_format_cast
 	WHERE fmtsource = 'integer'::regtype AND fmttarget = 'text'::regtype;
 SELECT count(*) FROM pg_class WHERE relname = 'format_cast_chain_view';
+
+-- ====================================================================
+-- Built-in datetime/string format casts.
+-- These rows are registered in pg_format_cast and reuse PostgreSQL's
+-- existing formatting template behavior.
+-- This initial set covers date, timestamp and timestamptz with
+-- text/varchar/bpchar.
+-- ====================================================================
+-- Pin the time zone and date style so results are stable across machines.
+SET TimeZone = 'UTC';
+SET DateStyle = 'ISO, YMD';
+
+-- text -> date.  The unknown-type literal is coerced to text before format
+-- cast lookup.
+SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD');
+-- varchar source -> date
+SELECT CAST('2026-06-28'::varchar AS date FORMAT 'YYYY-MM-DD');
+-- bpchar source -> date: a CHAR(10) holds the value exactly, and a wider
+-- CHAR(20) pads with blanks that the format cast trims before parsing.
+SELECT CAST('2026-06-28'::char(10) AS date FORMAT 'YYYY-MM-DD');
+SELECT CAST('2026-06-28'::char(20) AS date FORMAT 'YYYY-MM-DD');
+
+-- The built-in format cast functions are STRICT: a NULL source or a NULL FORMAT
+-- expression yields NULL without invoking the format cast.
+SELECT CAST(NULL::text AS date FORMAT 'YYYY-MM-DD') IS NULL AS null_src;
+SELECT CAST('2026-06-28' AS date FORMAT NULL::text) IS NULL AS null_fmt;
+SELECT CAST(NULL::date AS text FORMAT 'YYYY-MM-DD') IS NULL AS null_src_out;
+
+-- date -> text / varchar
+SELECT CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD');
+SELECT CAST(date '2026-06-28' AS varchar FORMAT 'YYYY-MM-DD');
+-- date -> varchar with a typmod: the declared length is enforced by the
+-- ordinary coercion layered above the format cast (here it truncates to 7).
+SELECT CAST(date '2026-06-28' AS varchar(7) FORMAT 'YYYY-MM-DD');
+-- date -> bpchar: AS char(12) pads to the declared length; bare AS char is
+-- char(1), so the ordinary coercion truncates to one character.  The wrapper
+-- returns a text varlena that is a valid bpchar value.
+SELECT CAST(date '2026-06-28' AS char(12) FORMAT 'YYYY-MM-DD');
+SELECT CAST(date '2026-06-28' AS char FORMAT 'YYYY-MM-DD');
+
+-- Collation: a formatted cast is collated like the function call it executes.
+-- A collatable result type (text) must get a real result collation, not
+-- InvalidOid; "collation for" reports "default" (it would be NULL if the
+-- result collation were unset), and an explicit COLLATE on the result works.
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD'));
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text FORMAT 'YYYY-MM-DD') COLLATE "C");
+-- An explicit COLLATE on the FORMAT argument flows in as the input collation.
+SELECT pg_collation_for(CAST(date '2026-06-28' AS text
+                             FORMAT 'YYYY-MM-DD'::text COLLATE "C"));
+
+-- timestamp without time zone, both directions
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+SELECT CAST(timestamp '2026-06-28 13:45:30' AS text FORMAT 'YYYY-MM-DD HH24:MI:SS');
+-- a date-only template is widened to timestamp (midnight)
+SELECT CAST('2026-06-28' AS timestamp FORMAT 'YYYY-MM-DD');
+-- The built-ins reuse PostgreSQL's existing formatting parser: a template with
+-- no time-zone field consumes only the fields it names, so a trailing time zone
+-- in the input is simply ignored (existing to_timestamp() behavior, unchanged).
+SELECT CAST('2026-06-28 13:45:30+05' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+-- But a template that explicitly contains a time-zone field is rejected for a
+-- timestamp-without-time-zone target (we must not fold a zone into a zoneless
+-- result via the session TimeZone).
+SELECT CAST('2026-06-28 13:45:30+05' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+-- timestamp WITHOUT time zone must not depend on the session TimeZone: the same
+-- (unzoned) input/template yields the same wall-clock value under any zone.
+SET TimeZone = 'America/Los_Angeles';
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+SET TimeZone = 'UTC';
+SELECT CAST('2026-06-28 13:45:30' AS timestamp FORMAT 'YYYY-MM-DD HH24:MI:SS');
+
+-- timestamp with time zone, both directions (TimeZone is UTC, set above)
+SELECT CAST('2026-06-28 13:45:30 +00' AS timestamptz FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+SELECT CAST(timestamptz '2026-06-28 13:45:30+00' AS text FORMAT 'YYYY-MM-DD HH24:MI:SS TZH');
+
+-- A sampling of datetime template tokens, exercised through the existing
+-- to_char/to_timestamp engine.  TZM and SSSSS are supported here.
+SELECT CAST(timestamptz '2026-06-28 13:45:30+00' AS text
+            FORMAT 'YYYY-MM-DD"T"HH24:MI:SS TZH:TZM');
+SELECT CAST(timestamp '2026-06-28 13:45:30' AS text FORMAT 'SSSSS');
+-- Fractional seconds (FFn) are accepted by to_char in the output direction.
+SELECT CAST(timestamp '2026-06-28 13:45:30.123456' AS text
+            FORMAT 'HH24:MI:SS.FF6');
+
+-- Empty FORMAT template behavior follows the underlying formatting functions.
+-- In the parse direction the underlying to_date() engine parses no fields and
+-- returns the all-defaults date; in the format direction the wrappers mirror
+-- to_char(value, ''), which is NULL.
+SELECT CAST('2026-06-28' AS date FORMAT '');
+SELECT CAST(date '2026-06-28' AS text FORMAT '') IS NULL AS empty_fmt_out;
+
+-- The FORMAT clause remains a PostgreSQL extension: it may be any expression
+-- coercible to text, not just a string literal.
+SELECT CAST('2026-' || '06-28' AS date FORMAT 'YYYY-' || 'MM-DD');
+
+-- A FORMAT clause never falls back to an ordinary cast: an integer -> date
+-- pair has no format cast (and no ordinary cast), so it errors.
+SELECT CAST(5 AS date FORMAT 'YYYY');
+
+-- A built-in format cast occupies its (source, target) pair, so a user
+-- CREATE FORMAT CAST for the same pair fails with the usual duplicate error.
+CREATE FUNCTION my_text_to_date(text, text) RETURNS date
+	LANGUAGE sql IMMUTABLE RETURN to_date($1, $2);
+CREATE FORMAT CAST (text AS date)
+	WITH FUNCTION my_text_to_date(text, text);		-- fails: already exists
+DROP FUNCTION my_text_to_date(text, text);
+
+-- Built-in format cast rows are system objects (their OIDs are in the pinned
+-- range), so they cannot be dropped.
+DROP FORMAT CAST (text AS date);				-- fails: pinned system object
+
+-- A view over a built-in formatted cast deparses back to CAST(... FORMAT ...).
+CREATE VIEW builtin_format_cast_view AS
+	SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD') AS d;
+SELECT pg_get_viewdef('builtin_format_cast_view'::regclass, true);
+SELECT * FROM builtin_format_cast_view;
+DROP VIEW builtin_format_cast_view;
+
+-- Make sure query jumbling handles CoerceViaFormatCast.
+SET compute_query_id = on;
+SELECT CAST('2026-06-28' AS date FORMAT 'YYYY-MM-DD');
+RESET compute_query_id;
+
+RESET TimeZone;
+RESET DateStyle;
-- 
2.54.0

