diff --git a/AUTHORS b/AUTHORS index 97d972fb..1c426f49 100644 --- a/AUTHORS +++ b/AUTHORS @@ -137,6 +137,7 @@ Contributors: * Chris Rose (offbyone/offby1) * Mathieu Dupuy (deronnax) * Chris Novakovic + * Max Smolin (maximsmol) * Josh Lynch (josh-lynch) * Fabio (3ximus) diff --git a/changelog.rst b/changelog.rst index 434afd8e..7a1f205a 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,3 +1,10 @@ +Dev +================== + +Features +-------- +* The session time zone setting is set to the system time zone by default + 4.2.0 (2025-03-06) ================== diff --git a/pgcli/main.py b/pgcli/main.py index d4c6dbf6..663afabe 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1,3 +1,4 @@ +from zoneinfo import ZoneInfoNotFoundError from configobj import ConfigObj, ParseError from pgspecial.namedqueries import NamedQueries from .config import skip_initial_comment @@ -23,6 +24,7 @@ from cli_helpers.utils import strip_ansi from .explain_output_formatter import ExplainOutputFormatter import click +import tzlocal try: import setproctitle @@ -1593,9 +1595,9 @@ def cli( if list_databases or ping_database: database = "postgres" + cfg = load_config(pgclirc, config_full_path) if dsn != "": try: - cfg = load_config(pgclirc, config_full_path) dsn_config = cfg["alias_dsn"][dsn] except KeyError: click.secho( @@ -1624,6 +1626,55 @@ def cli( else: pgcli.connect(database, host, user, port) + if "use_local_timezone" not in cfg["main"] or cfg["main"].as_bool( + "use_local_timezone" + ): + server_tz = pgcli.pgexecute.get_timezone() + + def echo_error(msg: str): + click.secho( + "Failed to determine the local time zone", + err=True, + fg="yellow", + ) + click.secho( + msg, + err=True, + fg="yellow", + ) + click.secho( + f"Continuing with the default time zone as preset by the server ({server_tz})", + err=True, + fg="yellow", + ) + click.secho( + "Set `use_local_timezone = False` in the config to avoid trying to override the server time zone\n", + err=True, + dim=True, + ) + + local_tz = None + try: + local_tz = tzlocal.get_localzone_name() + + if local_tz is None: + echo_error("No local time zone configuration found\n") + else: + click.secho( + f"Using local time zone {local_tz} (server uses {server_tz})", + fg="green", + ) + click.secho( + "Use `set time zone ` to override, or set `use_local_timezone = False` in the config", + dim=True, + ) + + pgcli.pgexecute.set_timezone(local_tz) + except ZoneInfoNotFoundError as e: + # e.args[0] is the pre-formatted message which includes a list + # of conflicting sources + echo_error(e.args[0]) + if list_databases: cur, headers, status = pgcli.pgexecute.full_databases() diff --git a/pgcli/pgclirc b/pgcli/pgclirc index dd8b15f1..67e31530 100644 --- a/pgcli/pgclirc +++ b/pgcli/pgclirc @@ -191,6 +191,10 @@ enable_pager = True # Use keyring to automatically save and load password in a secure manner keyring = True +# Automatically set the session time zone to the local time zone +# If unset, uses the server's time zone, which is the Postgres default +use_local_timezone = True + # Custom colors for the completion menu, toolbar, etc. [colors] completion-menu.completion.current = 'bg:#ffffff #000000' diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py index e0917572..4e7c637d 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -881,3 +881,16 @@ def casing(self): def explain_prefix(self): return "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) " + + def get_timezone(self) -> str: + query = psycopg.sql.SQL("show time zone") + with self.conn.cursor() as cur: + cur.execute(query) + return cur.fetchone()[0] + + def set_timezone(self, timezone: str): + query = psycopg.sql.SQL("set time zone {}").format( + psycopg.sql.Identifier(timezone) + ) + with self.conn.cursor() as cur: + cur.execute(query) diff --git a/pyproject.toml b/pyproject.toml index d714282a..65b8676b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ # task manager. Also setproctitle is a hard dependency to install in Windows, # so we'll only install it if we're not in Windows. "setproctitle >= 1.1.9; sys_platform != 'win32' and 'CYGWIN' not in sys_platform", + "tzlocal >= 5.2", ] dynamic = ["version"]