Skip to content
8 changes: 4 additions & 4 deletions tests/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ def test_happy_strings(self):
{"id": 6, "date": "2022-06-15"},
{"id": 7, "date": "foobar"},
]
result = group_by_year_and_month({"items": input_data}, "date")
result = group_by_year_and_month(input_data, "date")
expected = [
{
"heading": "2021",
Expand Down Expand Up @@ -828,7 +828,7 @@ def test_happy_datetime(self):
{"id": 6, "date": datetime.date(2022, 6, 15)},
{"id": 7, "date": None},
]
result = group_by_year_and_month({"items": input_data}, "date")
result = group_by_year_and_month(input_data, "date")
expected = [
{
"heading": "2021",
Expand Down Expand Up @@ -882,7 +882,7 @@ def test_happy_strings_reverse(self):
{"id": 6, "date": "2022-06-15"},
{"id": 7, "date": "foobar"},
]
result = group_by_year_and_month({"items": input_data}, "date", reverse=True)
result = group_by_year_and_month(input_data, "date", reverse=True)
expected = [
{
"heading": "2022",
Expand Down Expand Up @@ -935,7 +935,7 @@ def test_happy_datetime_reverse(self):
{"id": 6, "date": datetime.date(2022, 6, 15)},
{"id": 7, "date": None},
]
result = group_by_year_and_month({"items": input_data}, "date", reverse=True)
result = group_by_year_and_month(input_data, "date", reverse=True)
expected = [
{
"heading": "2022",
Expand Down
54 changes: 47 additions & 7 deletions tna_utilities/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,24 @@ def pretty_datetime_range( # noqa: C901
return ""


def pretty_age(date, just_now_seconds: int = 5, lowercase_first: bool = False) -> str:
def pretty_age(
date: datetime.datetime, just_now_seconds: int = 5, lowercase_first: bool = False
) -> str:
if not date:
raise ValueError("Date must be provided")

date = date.replace(microsecond=0)
now = datetime.datetime.now().replace(microsecond=0)
# Normalise input date to be timezone-aware in UTC for consistent comparison
if isinstance(date, datetime.datetime):
if date.tzinfo is None:
date = date.replace(tzinfo=datetime.timezone.utc)
date = date.replace(microsecond=0)
else:
# For date objects, convert to a UTC datetime at midnight
date = datetime.datetime.combine(
date, datetime.time.min, tzinfo=datetime.timezone.utc
)

now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)

future = now < date
delta = date - now if future else now - date
Expand Down Expand Up @@ -372,14 +384,14 @@ def is_today_in_date_range(

def group_by_year_and_month(
items: list[dict], date_key: str, reverse: bool = False
) -> dict: # noqa: C901
) -> list[dict]: # noqa: C901
"""
Groups a list of items by year and month based on a date key in each item.
"""

grouped = []

for item in items.get("items", []):
for item in items:
if request_date := item.get(date_key):
if isinstance(request_date, (datetime.datetime, datetime.date)):
request_datetime = request_date
Expand Down Expand Up @@ -438,9 +450,37 @@ def group_by_year_and_month(
for month_group in year_group["items"]:
month_group["items"].sort(
key=lambda x: (
x.get(date_key)
(
x.get(date_key)
if isinstance(
x.get(date_key), (datetime.date, datetime.datetime)
)
else (
get_date_from_string(x.get(date_key))
if x.get(date_key) is not None
else (
datetime.datetime.max
if reverse
else datetime.datetime.min
)
)
)
if isinstance(x.get(date_key), (datetime.date, datetime.datetime))
else get_date_from_string(x.get(date_key))
else (
(
get_date_from_string(x.get(date_key))
if x.get(date_key) is not None
else (
datetime.datetime.max
if reverse
else datetime.datetime.min
)
)
if isinstance(x.get(date_key), str)
else (
datetime.datetime.max if reverse else datetime.datetime.min
)
)
),
reverse=reverse,
)
Expand Down