Skip to content

Commit

Permalink
Adding format_timestamps preprocessor
Browse files Browse the repository at this point in the history
  • Loading branch information
dougharris committed Mar 5, 2025
1 parent 3e91c81 commit 14e1fe2
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This project receives help from these awesome contributors:
- Mel Dafert
- Andrii Kohut
- Roland Walker
- Doug Harris

Thanks
------
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

TBD
---

- Added format_timestamps preprocessor for per-column date/time formatting.

## Version 2.3.1

- Don't escape newlines in `ascii` tables, and add `ascii_escaped` table format.
Expand Down
42 changes: 42 additions & 0 deletions cli_helpers/tabular_output/preprocessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""These preprocessor functions are used to process data prior to output."""

import string
from datetime import datetime

from cli_helpers import utils
from cli_helpers.compat import text_type, int_types, float_types, HAS_PYGMENTS, Token
Expand Down Expand Up @@ -353,3 +354,44 @@ def _format_number(field, column_type):
[_format_number(v, column_types[i]) for i, v in enumerate(row)] for row in data
)
return data, headers


def format_timestamps(data, headers, column_date_formats=None, **_):
"""Format timestamps according to user preference.
This allows for per-column formatting for date, time, or datetime like data.
Add a `column_date_formats` section to your config file with separate lines for each column
that you'd like to specify a format using `name=format`. Use standard Python strftime
formatting strings
Example: `signup_date = "%Y-%m-%d"`
:param iterable data: An :term:`iterable` (e.g. list) of rows.
:param iterable headers: The column headers.
:param str column_date_format: The format strings to use for specific columns.
:return: The processed data and headers.
:rtype: tuple
"""
if column_date_formats is None:
return iter(data), headers

def _format_timestamp(value, name, column_date_formats):
if name not in column_date_formats:
return value
try:
dt = datetime.fromisoformat(value)
return dt.strftime(column_date_formats[name])
except (ValueError, TypeError):
# not a date
return value

data = (
[
_format_timestamp(v, headers[i], column_date_formats)
for i, v in enumerate(row)
]
for row in data
)
return data, headers
23 changes: 23 additions & 0 deletions tests/tabular_output/test_preprocessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
override_tab_value,
style_output,
format_numbers,
format_timestamps,
)

if HAS_PYGMENTS:
Expand Down Expand Up @@ -348,3 +349,25 @@ def test_enforce_iterable():
assert False, "{} doesn't return iterable".format(name)
if isinstance(preprocessed[1], types.GeneratorType):
assert False, "{} returns headers as iterator".format(name)


def test_format_timestamps():
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"),
("name3", None, "not-actually-timestamp", "2025-02-13T02:32:22"),
)
headers = ["name", "date_col", "datetime_col", "unchanged_col"]
column_date_formats = {
"date_col": "%Y-%m-%d",
"datetime_col": "%I:%M:%S %m/%d/%y",
}
result_data, result_headers = format_timestamps(data, headers, column_date_formats)

expected = [
["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"],
["name3", None, "not-actually-timestamp", "2025-02-13T02:32:22"],
]
assert expected == list(result_data)
assert headers == result_headers

0 comments on commit 14e1fe2

Please sign in to comment.