Reputation: 3022
I am trying to do some testing with time offsets from UTC. I have tried two methods as shown in the code listing below:
timedelta
to UTC datetime
astimezone
method on UTC datetime
In the short test program below the resultant date from astimezone
method is 1 hour behind. I do not understand why??
from datetime import datetime, timedelta, timezone
if __name__ == "__main__":
utc_now = datetime.utcnow()
target_time = int((utc_now + timedelta(hours=10)).timestamp())
timestamp = utc_now.astimezone(timezone(timedelta(hours=10)))
print(f"datetime.utcnow() = {utc_now.isoformat()}")
print(
f"datetime.utcnow() + 10 hours using timedelta = {datetime.fromtimestamp(target_time).isoformat()}"
)
print(f"datetime.utcnow() + 10 hours using astimezone = {timestamp.isoformat()}")
datetime.utcnow() = 2021-09-04T16:12:53.753059
datetime.utcnow() + 10 hours using timedelta = 2021-09-05T02:12:53
datetime.utcnow() + 10 hours using astimezone = 2021-09-05T01:12:53.753059+10:00
Edit - Update - datetime.now (timezone.utc )
from datetime import datetime, timedelta, timezone
if __name__ == "__main__":
utc_now = datetime.now(timezone.utc)
target_time = int((utc_now + timedelta(hours=10)).timestamp())
timestamp = utc_now.astimezone(timezone(timedelta(hours=10)))
print(f"datetime.utcnow() = {utc_now.isoformat()}")
print(
"datetime.utcnow() + 10 hours using timedelta ="
f" {datetime.fromtimestamp(target_time).isoformat()}"
)
print(f"datetime.utcnow() + 10 hours using astimezone = {timestamp.isoformat()}")
Tried to make timezone aware UTC datetime now with the same result.
How do I use astimezone
to get an equivalent result?
Upvotes: 1
Views: 1225
Reputation: 25594
The problem here is the assumption that datetime.utcnow()
gives you UTC. It does not. It gives you a naive datetime object that Python still treats as local time although hours, minutes etc. resemble UTC.
Set tzinfo
adequately to get consistent results (don't use utcnow at all if possible! - unless you know what you're doing):
from datetime import datetime, timedelta, timezone
utc_now = datetime(2021, 9, 4, tzinfo=timezone.utc)
target_time = int((utc_now + timedelta(hours=10)).timestamp())
timestamp = utc_now.astimezone(timezone(timedelta(hours=10)))
print(f"utc_now = {utc_now.isoformat()}")
print(f"utc_now + 10 hours using timedelta = {datetime.fromtimestamp(target_time, tz=timezone.utc).isoformat()}")
print(f"utc_now + 10 hours using astimezone = {timestamp.isoformat()}")
# utc_now = 2021-09-04T00:00:00+00:00
# utc_now + 10 hours using timedelta = 2021-09-04T10:00:00+00:00
# utc_now + 10 hours using astimezone = 2021-09-04T10:00:00+10:00
The key here is understanding the difference between naive datetime (=local time by default) and aware datetime (the time zone you set...). For more time zone handling, see also the zoneinfo lib.
Upvotes: 1
Reputation: 25594
an aspect that is also relevant here in the context of working with time zones in Python: timedelta
arithmetic is wall time arithmetic - i.e. what you would observe on a wall clock that gets adjusted to DST active/inactive. In the following example, I add 24 hours:
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
t0 = datetime(2021, 10, 30, 20, tzinfo=ZoneInfo("Europe/Berlin")) # DST active
t1 = t0 + timedelta(hours=24) # add 24 hours -> DST is inactive
print(t0, t1)
# 2021-10-30 20:00:00+02:00 2021-10-31 20:00:00+01:00
print((t1-t0).total_seconds()/3600)
# 24.0
Now watch how they become 25 hours - not because of magic but because of the wall clock would show local time, not UTC...
t0_utc, t1_utc = t0.astimezone(ZoneInfo("UTC")), t1.astimezone(ZoneInfo("UTC"))
print(t0_utc, t1_utc)
# 2021-10-30 18:00:00+00:00 2021-10-31 19:00:00+00:00
print((t1_utc-t0_utc).total_seconds()/3600)
# 25.0
Note: since Europe/Berlin is the time zone my OS is configured to use, this would also work with t0
and t1
being naive datetime objects!
Upvotes: 1