#34483 closed Bug (fixed)

Negative result of django.utils.timesince.timesince

Reported by: Lorenzo Peña Owned by: Natalia Bidart
Component: Utilities Version: 4.2
Severity: Release blocker Keywords: timesince
Cc: GianpaoloBranca, Natalia Bidart Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Reproducible example:

import zoneinfo
import datetime as _dt
from django.utils.timesince import timesince
import django.utils.timezone as _timezone

timesince(_timezone.now().astimezone(zoneinfo.ZoneInfo(key='Europe/Berlin'))-_dt.timedelta(seconds=1))

>>> '-1\xa0Year, 11\xa0Months'

Change History (17)

comment:1 by Mariusz Felisiak, 21 months ago

Cc: GianpaoloBranca added
Severity: NormalRelease blocker
Triage Stage: UnreviewedAccepted

Thanks for the report.

Regression in 8d67e16493c903adc9d049141028bc0fff43f8c8.
Reproduced at 3b4728310a7a64f8fcc548163b0aa5f98a5c78f5.

comment:2 by Mariusz Felisiak, 21 months ago

Cc: Natalia Bidart added

comment:3 by Natalia Bidart, 21 months ago

Owner: changed from nobody to Natalia Bidart
Status: newassigned

comment:4 by Natalia Bidart, 21 months ago

Has patch: set
Version 0, edited 21 months ago by Natalia Bidart (next)

comment:5 by Mariusz Felisiak, 21 months ago

Needs tests: set
Patch needs improvement: set

comment:6 by Mariusz Felisiak, 21 months ago

Needs tests: unset
Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:7 by GitHub <noreply@…>, 21 months ago

Resolution: fixed
Status: assignedclosed

In 813015d:

Fixed #34483 -- Fixed timesince()/timeuntil() with timezone-aware dates and interval less than 1 day.

Regression in 8d67e16493c903adc9d049141028bc0fff43f8c8.

Thanks Lorenzo Peña for the report.

comment:8 by Natalia <124304+nessita@…>, 21 months ago

In a3c14ea:

[4.2.x] Fixed #34483 -- Fixed timesince()/timeuntil() with timezone-aware dates and interval less than 1 day.

Regression in 8d67e16493c903adc9d049141028bc0fff43f8c8.

Thanks Lorenzo Peña for the report.

Backport of 813015d67e2557fa859a07930a9becec4e5f64a0 from main

comment:9 by Mariusz Felisiak, 20 months ago

Has patch: unset
Resolution: fixed
Status: closednew
Triage Stage: Ready for checkinAccepted

It seems that there is still an issue when a time difference is less than 1 day but days are different, see logs.

comment:10 by Mariusz Felisiak, 20 months ago

Status: newassigned

comment:11 by GitHub <noreply@…>, 20 months ago

In cd464fbc:

[4.2.x] Refs #34483 -- Fixed utils_tests.test_timesince crash on Python 3.8.

comment:12 by Mariusz Felisiak, 20 months ago

It seems that now must be in the same time zone (as you suggested at the beginning), however we also have to change time zone when now is passed, e.g.

  • django/utils/timesince.py

    diff --git a/django/utils/timesince.py b/django/utils/timesince.py
    index 94ba24d48a..9b9dd45af9 100644
    a b def timesince(d, now=None, reversed=False, time_strings=None, depth=2):  
    6363    if now and not isinstance(now, datetime.datetime):
    6464        now = datetime.datetime(now.year, now.month, now.day)
    6565
    66     now = now or datetime.datetime.now(datetime.timezone.utc if is_aware(d) else None)
     66    if is_aware(d):
     67        now = now.astimezone(d.tzinfo) if now else datetime.datetime.now(tz=d.tzinfo)
     68    else:
     69        now = now or datetime.datetime.now()
    6770
    6871    if reversed:
    6972        d, now = now, d
    def timesince(d, now=None, reversed=False, time_strings=None, depth=2):  
    7780
    7881    # Get years and months.
    7982    total_months = (now.year - d.year) * 12 + (now.month - d.month)
    80     time_delta = delta - datetime.timedelta(days=delta.days)
    81     if d.day > now.day or (d.day == now.day and time_delta.total_seconds() < 0):
     83    if d.day > now.day or (d.day == now.day and d.time() > now.time()):
    8284        total_months -= 1
    8385    years, months = divmod(total_months, 12)
    8486

see a regression test:

  • tests/utils_tests/test_timesince.py

    diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py
    index d54fce2be6..e023e95148 100644
    a b class TimesinceTests(TestCase):  
    258258            with self.subTest(value):
    259259                self.assertEqual(timesince(value), expected)
    260260
     261    @requires_tz_support
     262    def test_less_than_a_day_cross_day_with_zoneinfo(self):
     263        now_with_zoneinfo = timezone.make_aware(
     264            datetime.datetime(2023, 4, 14, 1, 30, 30),
     265            zoneinfo.ZoneInfo(key="Asia/Kathmandu")  # UTC+05:45
     266        )
     267        now_utc = now_with_zoneinfo.astimezone(datetime.timezone.utc)
     268        tests = [
     269            (now_with_zoneinfo, "0\xa0minutes"),
     270            (now_with_zoneinfo - self.onemicrosecond, "0\xa0minutes"),
     271            (now_with_zoneinfo - self.onesecond, "0\xa0minutes"),
     272            (now_with_zoneinfo - self.oneminute, "1\xa0minute"),
     273            (now_with_zoneinfo - self.onehour, "1\xa0hour"),
     274        ]
     275        for value, expected in tests:
     276            with self.subTest(value):
     277                self.assertEqual(timesince(value, now_utc), expected)
     278
    261279
    262280@requires_tz_support
    263281@override_settings(USE_TZ=True)

What do you think?

comment:13 by Mariusz Felisiak, 20 months ago

comment:14 by Mariusz Felisiak, 20 months ago

Has patch: set

comment:15 by GitHub <noreply@…>, 20 months ago

In 198a19b6:

Refs #34483 -- Fixed timesince()/timeuntil() with timezone-aware dates on different days and interval less than 1 day.

Follow up to 813015d67e2557fa859a07930a9becec4e5f64a0.
Regression in 8d67e16493c903adc9d049141028bc0fff43f8c8.

comment:16 by Mariusz Felisiak <felisiak.mariusz@…>, 20 months ago

In f75a6977:

[4.2.x] Refs #34483 -- Fixed timesince()/timeuntil() with timezone-aware dates on different days and interval less than 1 day.

Follow up to 813015d67e2557fa859a07930a9becec4e5f64a0.
Regression in 8d67e16493c903adc9d049141028bc0fff43f8c8.
Backport of 198a19b692699ad3940373d9ed797fe9155f3f4a from main

comment:17 by Mariusz Felisiak, 20 months ago

Resolution: fixed
Status: assignedclosed
Note: See TracTickets for help on using tickets.
Back to Top