While writing some unit tests I bumped into the issue of DST gaps. Let’s say I have some timestamps in Europe/London
timezone which have a DST change at 2023-03-26 01:00 UTC which introduces a gap in local time. During this night the local time 01:00 - 01:59 does not exist.
I would like to take arbitrary local timestamp ts
which is inside the DST gap and convert it into a valid timestamp. Let’s say the time is “2023-03-26 01:20” (London):
import datetime as dt
from zoneinfo import ZoneInfo
ts = dt.datetime.strptime("2023-03-26 01:20:00", "%Y-%m-%d %H:%M:%S").replace(
tzinfo=ZoneInfo("Europe/London")
)
Now I would expect that if I convert a timestamp to UTC and back to local time, I would get 02:00 which is what I’m after. As a picture:
That is
a) The original timestamp, which is 01:20 local
b) Original timestamp projected onto UTC axis, getting the value of 01:00 UTC
c) Local timestamp based on the UTC value 01:00, which is 02:00
But what I get instead is
>>> ts.astimezone(ZoneInfo('UTC')).astimezone(ts.tzinfo)
datetime.datetime(2023, 3, 26, 2, 20, tzinfo=zoneinfo.ZoneInfo(key='Europe/London'))
This is because the time zone change for a local time inside a DST gap produces UTC timestamp value of 01:20:
>>> ts.astimezone(ZoneInfo('UTC'))
datetime.datetime(2023, 3, 26, 1, 20, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
In pictures, this conversion logic seems to be:
That is
a) The original timestamp, which is 01:20 local
b) Original timestamp converted to UTC, getting value of 01:20
c) Local timestamp based on the UTC value 01:20, which is 02:20
Questions
- How would I get the value 02:00 in this case? I can check if a local timestamp is within a DST gap, and I could round down and add an hour, but what if some country decides their DST changes will be 30 minutes? I should probably look into some DST changes listing? Or is there some easier way?
- Perhaps the harder one to answer: Why local timestamp converted to UTC is not a projection to the UTC axis but simple 1 hour addition…? Is this a bug or a designed feature with some use case I cannot see?