Hacker News new | past | comments | ask | show | jobs | submit login

Until now I never took a close look at how Python deals with timezones. Now I did, and it turns out the way Python handles arithmetic on timezone-aware datetimes is not what I expected. An example to illustrate:

    from zoneinfo import ZoneInfo
    from datetime import datetime, timedelta, timezone

    localtz = ZoneInfo('Europe/Brussels')
    utctz = timezone.utc

    a_local = datetime(2023, 3, 26, 1, 59, 59, tzinfo=localtz)  # right before change to DST
    b_local = a_local + timedelta(seconds=2)   # 2 seconds later; should be right after change to DST
    c_local = a_local + timedelta(hours=2)     # 2 hours later

    a_utc = a_local.astimezone(utctz)
    b_utc = b_local.astimezone(utctz)
    c_utc = c_local.astimezone(utctz)

    print('datetimes:')
    print(f'(A)  {a_local}  (utc: {a_local.astimezone(utctz)})')
    print(f'(B)  {b_local}  (utc: {b_local.astimezone(utctz)})')
    print(f'(C)  {c_local}  (utc: {c_local.astimezone(utctz)})')

    print('deltas:')
    print(f'(B-A)  local: {b_local-a_local}  utc: {b_utc-a_utc}')
    print(f'(C-A)  local: {c_local-a_local}  utc: {c_utc-a_utc}')
This prints:

    datetimes:
    (A)  2023-03-26 01:59:59+01:00  (utc: 2023-03-26 00:59:59+00:00)
    (B)  2023-03-26 02:00:01+01:00  (utc: 2023-03-26 01:00:01+00:00)
    (C)  2023-03-26 03:59:59+02:00  (utc: 2023-03-26 01:59:59+00:00)
    deltas:
    (B-A)  local: 0:00:02  utc: 0:00:02
    (C-A)  local: 2:00:00  utc: 1:00:00
For (B), I would expect 2023-03-26 03:00:01+02:00: that is the point in time 2 seconds after (A). The datetime Python produced doesn't even exist (because of the start of DST, time jumped from 2:00 to 3:00).

For (C), I would expect 2023-03-26 04:59:59+02:00: that is the point in time 2 hours after (A). As you can see by the times in UTC, (C) is only 1 hour later then (A) instead of 2.

Both feel really wrong.

The article compares pointer arithmetic using Python's time zone model vs pytz's time zone model, and concludes that pytz is worse because you have to normalize the result for the time zone to be adjusted. But the pytz result is correct (according to my expectations, at least), while Python's standard approach does adjust the time zone but without properly adjusting the time (and hence is incorrect, according to my expectations). It seems the author only looked at the time zone, without checking the time itself.

I can work around it by converting to UTC and back, fortunately. I had a quick look at Pendulum and it seems it has functions to do arithmetic properly.




I've run into this at work and I handle by always add timedeltas in UTC or just design everything to work in UTC.

Unfortunately, some people dislike the former because it's 3 lines of code (convert to UTC, add delta, convert to local). I've had the same data scientist reject 3 MRs fixing this sort of issue in his code. At least it only causes a once per year error or a blip that no one notices.

Always using UTC doesn't work always depending on requirements. The worst was interacting with an API that required precisely every hour of the following day to be sent without good documentation.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: