简体   繁体   中英

Python datetime: unexpected dependency between timedelta and daylight saving time

I want to know if there's a daylight saving time change within the next hours. Thereby I recognized an unexpected dependency between datetime.timedelta, astimezone and daylight saving time. During the daylight saving time change the timedelta calculation seems no to work. Here's a simple example:

import datetime
import pytz

format = '%Y-%m-%d %H:%M:%S %Z%z'
local_tz = pytz.timezone('Europe/Berlin')
time_diff = datetime.timedelta(hours = 1)

print((datetime.datetime(2023, 2, 26, 1, 0, 0) + time_diff).astimezone(local_tz).strftime(format))
# => 2023-02-26 02:00:00 CET+0100

print((datetime.datetime(2023, 3, 26, 1, 0, 0) + time_diff).astimezone(local_tz).strftime(format))
# => 2023-03-26 01:00:00 CET+0100

I would expect that the last print would result "2023-03-26 02:00:00 CET+0100" or "2023-03-26 03:00:00 CEST+0200". Does anyone can explain this behaviour?

After different tries I found a solution by adding the time delta after adding the timezone to the timestamp.

print((datetime.datetime(2023, 3, 26, 1, 0, 0).astimezone(local_tz) + time_diff).strftime(format))

But I still don't understand the error in my first used code.

My versions:
- Python 3.10.2
- pytz 2022.7

See also this answer by Paul Ganssle - with native Python's timedelta combined with time zones, non-existing datetimes (like 2023-02-26 02:00:00 CET+0100) have to be expected.

Here's a slightly extended comparison for reference, + comments in the code. pytz is deprecated since the release of Python 3.9's zoneinfo.

from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
import pandas as pd
import pytz

def absolute_add(dt: datetime, td: timedelta) -> datetime:
    utc_in = dt.astimezone(timezone.utc)  # Convert input to UTC
    utc_out = utc_in + td  # Do addition in UTC
    civil_out = utc_out.astimezone(dt.tzinfo)  # Back to original tzinfo
    return civil_out

# -----
# in vanilla Python, we can create non-existent datetime...

# I) tz already set
t = datetime(2023, 3, 26, 1, tzinfo=ZoneInfo("Europe/Berlin"))
print(t + timedelta(hours=1))
# 2023-03-26 02:00:00+01:00
# this datetime should not exist in that time zone since there is a DST transtion,
# 1 am UTC+1 plus one hour gives 3 am UTC+2

# II) tz set after timedelta addition
print(datetime(2023, 3, 26, 1) + timedelta(hours=1))
# 2023-03-26 02:00:00
# this is ok since no tz specified
print((datetime(2023, 3, 26, 1) + timedelta(hours=1)).replace(tzinfo=ZoneInfo("Europe/Berlin")))
# 2023-03-26 02:00:00+01:00
# again, we have a non-existent datetime
print((datetime(2023, 3, 26, 1) + timedelta(hours=1)).astimezone(ZoneInfo("Europe/Berlin")))
# 2023-03-26 01:00:00+01:00
# also a bit confusing; 2 am would be non-existing, so the hour is "corrected"
# backwards before setting the time zone
# -----
# adding the timedelta in UTC as "absolute duration" works as expected:
print(absolute_add(t, timedelta(hours=1)))
# 2023-03-26 03:00:00+02:00

# with pytz timezones, you can normalize to correct the non-existent datetime:
tz = pytz.timezone("Europe/Berlin")
t = tz.localize(datetime(2023, 3, 26, 1))
print(tz.normalize(t + timedelta(hours=1)))
# 2023-03-26 03:00:00+02:00

# this is correctly implemented in pandas for instance:
t = pd.Timestamp(2023, 3, 26, 1).tz_localize("Europe/Berlin")
print(t + pd.Timedelta(hours=1))
# 2023-03-26 03:00:00+02:00

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM