Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions sqlglot/dialects/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,48 @@ class Postgres(Dialect):
}

TIME_MAPPING = {
"AM": "%p", # AM/PM meridiem indicator
"A.M.": "%p", # AM/PM with periods
"d": "%u", # 1-based day of week
"D": "%u", # 1-based day of week
"day": "%A", # full weekday name (lowercase)
"Day": "%A", # full weekday name (capitalized)
"DAY": "%A", # full weekday name (uppercase)
"dd": "%d", # day of month
"DD": "%d", # day of month
"ddd": "%j", # zero padded day of year
"DDD": "%j", # zero padded day of year
"dy": "%a", # abbreviated weekday name (lowercase)
"Dy": "%a", # abbreviated weekday name (capitalized)
"DY": "%a", # abbreviated weekday name (uppercase)
"FMDD": "%-d", # - is no leading zero for Python; same for FM in postgres
"FMDDD": "%-j", # day of year
"FMHH12": "%-I", # 9
"FMHH24": "%-H", # 9
"FMMI": "%-M", # Minute
"FMMM": "%-m", # 1
"FMSS": "%-S", # Second
"hh": "%I", # 12-hour (HH defaults to 12-hour in PG)
"HH": "%I", # 12-hour (HH defaults to 12-hour in PG)
"HH12": "%I", # 09
"HH24": "%H", # 09
"mi": "%M", # zero padded minute
"MI": "%M", # zero padded minute
"mm": "%m", # 01
"MM": "%m", # 01
"mon": "%b", # abbreviated month name (lowercase)
"Mon": "%b", # abbreviated month name (capitalized)
"MON": "%b", # abbreviated month name (uppercase)
"month": "%B", # full month name (lowercase)
"Month": "%B", # full month name (capitalized)
"MONTH": "%B", # full month name (uppercase)
"OF": "%z", # utc offset
"PM": "%p", # PG treats AM/PM as synonymous; both print actual meridiem
"P.M.": "%p",
"am": "%p",
"a.m.": "%p",
"pm": "%p",
"p.m.": "%p",
"ss": "%S", # zero padded second
"SS": "%S", # zero padded second
"TMDay": "%A", # TM is locale dependent
Expand All @@ -57,6 +79,17 @@ class Postgres(Dialect):
"YYYY": "%Y", # 2015
}

# Prefer bare forms (Mon, Day, Dy, Month) over TM-prefixed forms when
# generating Postgres SQL from the internal strftime representation.
INVERSE_TIME_MAPPING = {
"%A": "Day",
"%a": "Dy",
"%b": "Mon",
"%B": "Month",
"%I": "HH12",
"%p": "AM",
}

class Tokenizer(tokens.Tokenizer):
BIT_STRINGS = [("b'", "'"), ("B'", "'")]
HEX_STRINGS = [("x'", "'"), ("X'", "'")]
Expand Down
2 changes: 1 addition & 1 deletion tests/dialects/test_exasol.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ def test_datetime_functions(self):
write={
"exasol": "SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'DY')",
"oracle": "SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'DY')",
"postgres": "SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'TMDy')",
"postgres": "SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'Dy')",
"databricks": "SELECT DATE_FORMAT(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'EEE')",
},
)
Expand Down
59 changes: 57 additions & 2 deletions tests/dialects/test_postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ def test_postgres(self):
"SELECT TO_TIMESTAMP(1284352323.5), TO_TIMESTAMP('05 Dec 2000', 'DD Mon YYYY')"
)
self.validate_identity(
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 AM', 'DD Mon YYYY HH:MI AM')"
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 AM', 'DD Mon YYYY HH:MI AM')",
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 AM', 'DD Mon YYYY HH12:MI AM')",
)
self.validate_identity(
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 PM', 'DD Mon YYYY HH:MI PM')"
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 PM', 'DD Mon YYYY HH:MI PM')",
"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 PM', 'DD Mon YYYY HH12:MI AM')",
)
self.validate_identity(
"SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) AS ss"
Expand Down Expand Up @@ -1020,6 +1022,59 @@ def test_postgres(self):
"redshift": "SELECT TO_CHAR(foo, bar)",
},
)

# TO_CHAR format token conversions: month names, weekday names, AM/PM, HH
self.validate_all(
"SELECT TO_CHAR(dt, 'Mon YYYY')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%b %Y')",
"postgres": "SELECT TO_CHAR(dt, 'Mon YYYY')",
},
)
self.validate_all(
"SELECT TO_CHAR(dt, 'Month YYYY')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%B %Y')",
"postgres": "SELECT TO_CHAR(dt, 'Month YYYY')",
},
)
self.validate_all(
"SELECT TO_CHAR(dt, 'Day')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%A')",
"postgres": "SELECT TO_CHAR(dt, 'Day')",
},
)
self.validate_all(
"SELECT TO_CHAR(dt, 'Dy')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%a')",
"postgres": "SELECT TO_CHAR(dt, 'Dy')",
},
)
self.validate_all(
"SELECT TO_CHAR(dt, 'HH12:MI AM')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%I:%M %p')",
"postgres": "SELECT TO_CHAR(dt, 'HH12:MI AM')",
},
)
self.validate_all(
"SELECT TO_CHAR(dt, 'DD Mon YYYY HH24:MI')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%d %b %Y %H:%M')",
"postgres": "SELECT TO_CHAR(dt, 'DD Mon YYYY HH24:MI')",
},
)
# Bare HH (no 12/24 suffix) defaults to 12-hour in PostgreSQL
self.validate_all(
"SELECT TO_CHAR(dt, 'HH:MI')",
write={
"clickhouse": "SELECT formatDateTime(dt, '%I:%M')",
"postgres": "SELECT TO_CHAR(dt, 'HH12:MI')",
},
)

self.validate_all(
"CREATE TABLE table1 (a INT, b INT, PRIMARY KEY (a))",
read={
Expand Down
Loading