MySQL DATE_FORMAT() → PostgreSQL TO_CHAR()

Translating MySQL's %-style DATE_FORMAT() specifiers to PostgreSQL's to_char() format-token vocabulary, token by token.

MEDIUM

Quick answer

to_char(expr, format) is PostgreSQL's equivalent of DATE_FORMAT(expr, format), but the format string itself uses a completely different token vocabulary (YYYY/MM/DD instead of %Y/%m/%d) — every format string needs re-translating, not just the function name.

Why it breaks

MySQL's DATE_FORMAT() format specifiers are %-prefixed (%Y, %m, %d, %H, %i, %s). PostgreSQL's to_char() format tokens are bare words (YYYY, MM, DD, HH24, MI, SS) with no % prefix at all — renaming just the function call while keeping the MySQL-style format string produces either an error or, worse, a string that to_char() interprets as a mix of literal characters and unintended tokens.

Real examples

Basic date formatting

MySQL
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders;
PostgreSQL
SELECT to_char(created_at, 'YYYY-MM-DD') FROM orders;
A direct token-by-token translation for a common ISO-style date format.

12-hour time with AM/PM

MySQL
SELECT DATE_FORMAT(created_at, '%h:%i %p') FROM orders;
PostgreSQL
SELECT to_char(created_at, 'HH12:MI AM') FROM orders;
MySQL's %h/%i/%p map to PostgreSQL's HH12/MI/AM — note PostgreSQL's AM token in the format string controls both AM and PM output, it isn't literal.

Safe migration options

  • Translate common tokens directly: %Y → YYYY, %y → YY, %m → MM, %d → DD, %H → HH24, %h → HH12, %i → MI, %s → SS, %p → AM/PM.
  • Wrap literal characters (anything not meant as a token) in double quotes inside the to_char() format string — PostgreSQL treats unquoted letters as format tokens even if MySQL treated them as literals.
  • Double-check month/day name tokens specifically: %M (full month name) → Month, %a (abbreviated weekday) → Dy — these are easy to mis-map since the letter casing carries meaning in to_char (Month vs MONTH vs month all format differently).

PostgreSQL solution

to_char(timestamp_expr, 'YYYY-MM-DD') replaces DATE_FORMAT(datetime_expr, '%Y-%m-%d')

Validation

Before cutover

Run SELECT DATE_FORMAT(NOW(), '<mysql format>') and SELECT to_char(now(), '<translated format>') side by side and confirm the output strings match exactly, including padding, casing, and separators — for every distinct format string your application actually uses, not just the common ones.

Scan your own schema

This page documents detector rule DATE_FORMAT in the DevEquiv analyzer — paste or upload your real MySQL/MariaDB schema to find every occurrence of this (and 19 other documented issues) automatically, with a prioritized readiness report.

Analyze a schema free

References

  • MySQL Manual — Date and Time Functions (v8.0) — checked 2026-07-30
  • PostgreSQL Manual — Data Type Formatting Functions (v17) — checked 2026-07-30

Last verified 2026-07-30.