diff --git a/AUTHORS b/AUTHORS index ca943548e..09ad7bd2d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -137,6 +137,7 @@ Contributors: * Chris Rose (offbyone/offby1) * Mathieu Dupuy (deronnax) * Chris Novakovic + * Doug Harris (dougharris) Creator: -------- diff --git a/changelog.rst b/changelog.rst index 8e7a8aed5..ac9747def 100644 --- a/changelog.rst +++ b/changelog.rst @@ -5,6 +5,7 @@ Features -------- * Add a `--ping` command line option; allows pgcli to replace `pg_isready` * Changed the packaging metadata from setup.py to pyproject.toml +* Added support for per-column date/time formatting using `column_date_formats` in config Bug fixes: ---------- diff --git a/pgcli/main.py b/pgcli/main.py index d4c6dbf6d..e398ef56f 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -19,7 +19,11 @@ from typing import Optional from cli_helpers.tabular_output import TabularOutputFormatter -from cli_helpers.tabular_output.preprocessors import align_decimals, format_numbers +from cli_helpers.tabular_output.preprocessors import ( + align_decimals, + format_numbers, + format_timestamps, +) from cli_helpers.utils import strip_ansi from .explain_output_formatter import ExplainOutputFormatter import click @@ -111,12 +115,13 @@ OutputSettings = namedtuple( "OutputSettings", - "table_format dcmlfmt floatfmt missingval expanded max_width case_function style_output max_field_width", + "table_format dcmlfmt floatfmt column_date_formats missingval expanded max_width case_function style_output max_field_width", ) OutputSettings.__new__.__defaults__ = ( None, None, None, + None, "", False, None, @@ -264,6 +269,7 @@ def __init__( self.on_error = c["main"]["on_error"].upper() self.decimal_format = c["data_formats"]["decimal"] self.float_format = c["data_formats"]["float"] + self.column_date_formats = c["column_date_formats"] auth.keyring_initialize(c["main"].as_bool("keyring"), logger=self.logger) self.show_bottom_toolbar = c["main"].as_bool("show_bottom_toolbar") @@ -1179,6 +1185,7 @@ def _evaluate_command(self, text): table_format=self.table_format, dcmlfmt=self.decimal_format, floatfmt=self.float_format, + column_date_formats=self.column_date_formats, missingval=self.null_string, expanded=expanded, max_width=max_width, @@ -1830,6 +1837,7 @@ def format_status(cur, status): "missing_value": settings.missingval, "integer_format": settings.dcmlfmt, "float_format": settings.floatfmt, + "column_date_formats": settings.column_date_formats, "preprocessors": (format_numbers, format_arrays), "disable_numparse": True, "preserve_whitespace": True, @@ -1839,6 +1847,9 @@ def format_status(cur, status): if not settings.floatfmt: output_kwargs["preprocessors"] = (align_decimals,) + if settings.column_date_formats: + output_kwargs["preprocessors"] += (format_timestamps,) + if table_format == "csv": # The default CSV dialect is "excel" which is not handling newline values correctly # Nevertheless, we want to keep on using "excel" on Windows since it uses '\r\n' diff --git a/pgcli/pgclirc b/pgcli/pgclirc index dd8b15f13..927045b96 100644 --- a/pgcli/pgclirc +++ b/pgcli/pgclirc @@ -240,3 +240,8 @@ output.null = "#808080" [data_formats] decimal = "" float = "" + +# Per column formats for date/timestamp columns +[column_date_formats] +# use strftime format, e.g. +# created = "%Y-%m-%d" diff --git a/tests/test_main.py b/tests/test_main.py index 3683d491f..102ebcd3e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -76,6 +76,57 @@ def test_format_output(): assert list(results) == expected +def test_column_date_formats(): + settings = OutputSettings( + table_format="psql", + column_date_formats={ + "date_col": "%Y-%m-%d", + "datetime_col": "%I:%M:%S %m/%d/%y", + }, + ) + data = [ + ("name1", "2024-12-13T18:32:22", "2024-12-13T19:32:22", "2024-12-13T20:32:22"), + ("name2", "2025-02-13T02:32:22", "2025-02-13T02:32:22", "2025-02-13T02:32:22"), + ] + headers = ["name", "date_col", "datetime_col", "unchanged_col"] + + results = format_output("Title", data, headers, "test status", settings) + expected = [ + "Title", + "+-------+------------+-------------------+---------------------+", + "| name | date_col | datetime_col | unchanged_col |", + "|-------+------------+-------------------+---------------------|", + "| name1 | 2024-12-13 | 07:32:22 12/13/24 | 2024-12-13T20:32:22 |", + "| name2 | 2025-02-13 | 02:32:22 02/13/25 | 2025-02-13T02:32:22 |", + "+-------+------------+-------------------+---------------------+", + "test status", + ] + assert list(results) == expected + + +def test_no_column_date_formats(): + """Test that not setting any column date formats returns unaltered datetime columns""" + settings = OutputSettings(table_format="psql") + data = [ + ("name1", "2024-12-13T18:32:22", "2024-12-13T19:32:22", "2024-12-13T20:32:22"), + ("name2", "2025-02-13T02:32:22", "2025-02-13T02:32:22", "2025-02-13T02:32:22"), + ] + headers = ["name", "date_col", "datetime_col", "unchanged_col"] + + results = format_output("Title", data, headers, "test status", settings) + expected = [ + "Title", + "+-------+---------------------+---------------------+---------------------+", + "| name | date_col | datetime_col | unchanged_col |", + "|-------+---------------------+---------------------+---------------------|", + "| name1 | 2024-12-13T18:32:22 | 2024-12-13T19:32:22 | 2024-12-13T20:32:22 |", + "| name2 | 2025-02-13T02:32:22 | 2025-02-13T02:32:22 | 2025-02-13T02:32:22 |", + "+-------+---------------------+---------------------+---------------------+", + "test status", + ] + assert list(results) == expected + + def test_format_output_truncate_on(): settings = OutputSettings( table_format="psql", dcmlfmt="d", floatfmt="g", max_field_width=10