From 4172d5015cb2aa15fa48da9b3dfdefcdfa3af8f7 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:52:52 -0700 Subject: [PATCH 1/8] Set session timezone to local timezone on startup Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/main.py | 7 +++++++ pgcli/pgexecute.py | 5 +++++ setup.py | 1 + 3 files changed, 13 insertions(+) diff --git a/pgcli/main.py b/pgcli/main.py index d4c6dbf6d..066a3fb6e 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1624,6 +1624,13 @@ def cli( else: pgcli.connect(database, host, user, port) + try: + import tzlocal + + pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) + except ImportError: + ... + if list_databases: cur, headers, status = pgcli.pgexecute.full_databases() diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py index e09175728..65cda1757 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -881,3 +881,8 @@ def casing(self): def explain_prefix(self): return "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) " + + 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/setup.py b/setup.py index f4606c2ba..40f50ae73 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ "sqlparse >=0.3.0,<0.6", "configobj >= 5.0.6", "cli_helpers[styles] >= 2.2.1", + "tzlocal >= 5.2" ] From ca450d23305e0674449a4ee4026bb8981092bd41 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:02:39 -0700 Subject: [PATCH 2/8] Update changelog Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.rst b/changelog.rst index 47e45b88f..6d3fde1aa 100644 --- a/changelog.rst +++ b/changelog.rst @@ -4,6 +4,7 @@ Dev Features -------- * Add a `--ping` command line option; allows pgcli to replace `pg_isready` +* The session time zone setting is set to the system time zone by default Bug fixes: ---------- From d8bd63961141c0f0980138a353e93b8e29fdba96 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:02:46 -0700 Subject: [PATCH 3/8] Unconditionally import tzlocal Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/main.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pgcli/main.py b/pgcli/main.py index 066a3fb6e..1b09d8d83 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -23,6 +23,7 @@ from cli_helpers.utils import strip_ansi from .explain_output_formatter import ExplainOutputFormatter import click +import tzlocal try: import setproctitle @@ -1624,12 +1625,7 @@ def cli( else: pgcli.connect(database, host, user, port) - try: - import tzlocal - - pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) - except ImportError: - ... + pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) if list_databases: cur, headers, status = pgcli.pgexecute.full_databases() From 4a60eaf3fe39a22a6a705844054491882d8deee3 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:03:18 -0700 Subject: [PATCH 4/8] Update authors Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index cac2b38dc..7a7fc6944 100644 --- a/AUTHORS +++ b/AUTHORS @@ -136,6 +136,7 @@ Contributors: * saucoide * Chris Rose (offbyone/offby1) * Chris Novakovic + * Max Smolin (maximsmol) Creator: -------- From 62a5b9dee617a67baa6b42a0c67523b89b521d27 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:04:44 -0700 Subject: [PATCH 5/8] Format Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/pgexecute.py | 4 +++- setup.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py index 65cda1757..a25902501 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -883,6 +883,8 @@ def explain_prefix(self): return "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) " def set_timezone(self, timezone: str): - query = psycopg.sql.SQL("set time zone {}").format(psycopg.sql.Identifier(timezone)) + query = psycopg.sql.SQL("set time zone {}").format( + psycopg.sql.Identifier(timezone) + ) with self.conn.cursor() as cur: cur.execute(query) diff --git a/setup.py b/setup.py index 40f50ae73..89b24e1b7 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ "sqlparse >=0.3.0,<0.6", "configobj >= 5.0.6", "cli_helpers[styles] >= 2.2.1", - "tzlocal >= 5.2" + "tzlocal >= 5.2", ] From 1003b3aacce671773ecce95f407182f266907069 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:41:48 -0800 Subject: [PATCH 6/8] add warning message for conflicting timezone configs Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pgcli/main.py b/pgcli/main.py index 1b09d8d83..09ad039c0 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 @@ -1625,7 +1626,13 @@ def cli( else: pgcli.connect(database, host, user, port) - pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) + try: + pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) + except ZoneInfoNotFoundError as e: + click.secho(e.args[0], err=True, fg="yellow") + click.secho( + "Continuing with the default server timezone", err=True, fg="yellow" + ) if list_databases: cur, headers, status = pgcli.pgexecute.full_databases() From ffed6bd6a589cddf4a4177574077e0d71e894b4c Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:48:47 -0800 Subject: [PATCH 7/8] handle None when setting timezone Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/main.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pgcli/main.py b/pgcli/main.py index 09ad039c0..63a93ce79 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1626,12 +1626,28 @@ def cli( else: pgcli.connect(database, host, user, port) + local_tz = None try: - pgcli.pgexecute.set_timezone(tzlocal.get_localzone_name()) + local_tz = tzlocal.get_localzone_name() + + if local_tz is None: + click.secho( + "No local time zone configuration found", + err=True, + fg="yellow", + ) except ZoneInfoNotFoundError as e: + # e.args[0] is the pre-formatted message which includes a list + # of conflicting sources click.secho(e.args[0], err=True, fg="yellow") + + if local_tz is not None: + pgcli.pgexecute.set_timezone(local_tz) + else: click.secho( - "Continuing with the default server timezone", err=True, fg="yellow" + "Continuing with the default server time zone", + err=True, + fg="yellow", ) if list_databases: From ddbba715fa848ae4713e392395cadf5b937bd09d Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:34:16 -0700 Subject: [PATCH 8/8] feat: add config for use_local_timezone, update error messages Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- pgcli/main.py | 61 ++++++++++++++++++++++++++++++++-------------- pgcli/pgclirc | 4 +++ pgcli/pgexecute.py | 6 +++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/pgcli/main.py b/pgcli/main.py index 63a93ce79..663afabe9 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1595,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( @@ -1626,29 +1626,54 @@ def cli( else: pgcli.connect(database, host, user, port) - local_tz = None - try: - local_tz = tzlocal.get_localzone_name() + if "use_local_timezone" not in cfg["main"] or cfg["main"].as_bool( + "use_local_timezone" + ): + server_tz = pgcli.pgexecute.get_timezone() - if local_tz is None: + def echo_error(msg: str): + click.secho( + "Failed to determine the local time zone", + err=True, + fg="yellow", + ) click.secho( - "No local time zone configuration found", + msg, err=True, fg="yellow", ) - except ZoneInfoNotFoundError as e: - # e.args[0] is the pre-formatted message which includes a list - # of conflicting sources - click.secho(e.args[0], 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, + ) - if local_tz is not None: - pgcli.pgexecute.set_timezone(local_tz) - else: - click.secho( - "Continuing with the default server time zone", - err=True, - fg="yellow", - ) + 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 dd8b15f13..67e315303 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 a25902501..4e7c637d3 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -882,6 +882,12 @@ 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)