Wrong time delta calculation in python datetime package - python

What value do you expect to be
26-Mar-2023 8AM Amsterdam time - 25-Mar-2023 8AM Amsterdam time?
Day-light-saving is on since 26-Mar-2023 in Amsterdam, so 25-Mar is in UTC+1 and 26-Mar is in UTC+2, intuitively either 23 hours or 25 hours, but not 24 hours because of the DST shift...
But the below calculation shows 24 hours, why is that?
My python version is 3.9.6
from datetime import datetime, timedelta
import pendulum
p1 = datetime(year=2023, month=3, day=25, hour=8, minute=0, tzinfo=pendulum.timezone('Europe/Amsterdam')) # No DST
p2 = datetime(year=2023, month=3, day=26, hour=8, minute=0, tzinfo=pendulum.timezone('Europe/Amsterdam')) # DST
print((p2 - p1).total_seconds()/3600)
I tried to use pytz or pendulum, results are the same, 24 hours.
I convert those two time points to UTC, or just use the unix timestamp, I got 23 hours, as I expected.
(p2.astimezone(pendulum.timezone('UTC')) - p1.astimezone(pendulum.timezone('UTC'))).total_seconds()/3600
Or
(p2.timestamp() - p1.timestamp())/3600

Specifying tzinfo doesn't correctly create the timezone-aware datetime object for timezones that have daylight-saving transformationsSee docs. Printing the repr of your p1 and p2 shows that both p1 and p2 have the same timezone:
print(repr(p1))
# datetime.datetime(2023, 3, 25, 8, 0, tzinfo=<DstTzInfo 'Europe/Amsterdam' LMT+0:18:00 STD>)
print(repr(p2))
# datetime.datetime(2023, 3, 26, 8, 0, tzinfo=<DstTzInfo 'Europe/Amsterdam' LMT+0:18:00 STD>)
You need to pass the timezone-unaware datetime object through timezone.localize.
import pytz
tz = pytz.timezone('Europe/Amsterdam')
u1 = datetime(year=2023, month=3, day=25, hour=8, minute=0)
u2 = datetime(year=2023, month=3, day=26, hour=8, minute=0)
a1 = tz.localize(u1)
a2 = tz.localize(u2)
print(repr(a1))
# datetime.datetime(2023, 3, 25, 8, 0, tzinfo=<DstTzInfo 'Europe/Amsterdam' CET+1:00:00 STD>)
print(repr(a2))
# datetime.datetime(2023, 3, 26, 8, 0, tzinfo=<DstTzInfo 'Europe/Amsterdam' CEST+2:00:00 DST>)
print((a2 - a1).total_seconds() / 3600)
# 23.0
Alternatively, create pendulum.datetime objects:
p1 = pendulum.datetime(year=2023, month=3, day=25, hour=8, minute=0, tz=pendulum.timezone('Europe/Amsterdam')) # No DST
p2 = pendulum.datetime(year=2023, month=3, day=26, hour=8, minute=0, tz=pendulum.timezone('Europe/Amsterdam')) # DST
print((p2 - p1).total_seconds()/3600)
# 23.0

Related

convert timestamp in utc timezone to a given timezone

python version: 3.7
I am trying to convert utc timestamps to a given timezone using pytz and astimezone.
(I have timestamps stored in my db in utc timezone and I amtrynig to convert them)
For example:
converting '2022-07-18 19:43:26.164345' timestamp to 'US/Pacific' timezone will result '2022-07-18 12:43:26.164345-07:00'
converting '2022-07-18 19:43:26.164345' timestamp to 'UTC' timezone will not affect any change - will result '2022-07-18 19:43:26.164345'
here is what I tried:
import pytz
def convert_utc_timestamp_to_timezone(utc_timestamp, dt_timezone):
return utc_timestamp.astimezone(pytz.timezone(dt_timezone))
expected:
> dt = datetime.datetime.now()
> convert_utc_timestamp_to_timezone(dt, 'UTC') // expected result: dt
but no.., actual result::
> dt = datetime.datetime.now()
> converted = convert_utc_timestamp_to_timezone(dt, 'UTC')
> print(dt) # 2022-07-18 23:10:34.061169
> print(converted) # 2022-07-18 20:10:34.061169+00:00
# dt != converted not as I expect
This works when my local timezone is set to utc - for example when running on online browser
import datetime
import pytz
def convert_utc_timestamp_to_timezone(utc_timestamp, dt_timezone):
return utc_timestamp.astimezone(pytz.timezone(dt_timezone))
dt = datetime.datetime.utcnow()
a = convert_utc_timestamp_to_timezone(dt, 'US/Pacific')
print('in utc timezone',dt)
print('after convert to US/Pacific timezone: ',a)
# results:
#in utc timezone 2022-07-18 20:18:37.253062
# after convert to US/Pacific timezone: 2022-07-18 13:18:37.253062-07:00
dt2 = datetime.datetime.utcnow()
a2 = convert_utc_timestamp_to_timezone(dt, 'UTC')
print('in utc timezone', dt2)
print('after convert to UTC timezone: ',a2)
# results:
# in utc timezone 2022-07-18 20:18:37.264363
# after convert to UTC timezone: 2022-07-18 20:18:37.253062+00:00
But does not work well when running locally - since my pc timezone isn't utc
(the doc actually states that astimezone “converts to local time”)
import pytz
def convert_utc_timestamp_to_timezone(utc_timestamp, dt_timezone):
return utc_timestamp.astimezone(pytz.timezone(dt_timezone))
dt = datetime.datetime.utcnow()
a = convert_utc_timestamp_to_timezone(dt, 'US/Pacific')
print('in utc timezone', dt)
print('after convert to US/Pacific timezone: ', a)
# result:
# in utc timezone 2022-07-18 20:22:05.526588
# after convert to US/Pacific timezone: 2022-07-18 10:22:05.526588-07:00
dt2 = datetime.datetime.utcnow()
a2 = convert_utc_timestamp_to_timezone(dt, 'UTC')
print('in utc timezone', dt2)
print('after convert to UTC timezone: ', a2)
# result:
# in utc timezone 2022-07-18 20:22:05.576213
# after convert to UTC timezone: 2022-07-18 17:22:05.526588+00:00
what is the correct way to convert timestamp in utc timezone to a given timezone?
I tried pytz localize , but also doesn't give expected result
This is related to a common gotcha with Python datetime types, kinda due to legacy code reasons. The short answer is that you're mixing timezone aware and timezone un-aware types.
Here's the right way to get a utc timestamp:
In [85]: bad_utc = dt.datetime.utcnow() # no timezone info
...: (bad_utc, bad_utc.astimezone(pytz.utc))
...:
Out[85]:
(
datetime.datetime(2022, 7, 18, 20, 23, 44, 583377),
datetime.datetime(2022, 7, 19, 3, 23, 44, 583377, tzinfo=<UTC>),
)
In [86]: good_utc = dt.datetime.now(datetime.timezone.utc) # has timezone info
...: (good_utc, good_utc.astimezone(pytz.utc))
...:
Out[86]:
(
datetime.datetime(2022, 7, 18, 20, 23, 47, 323579, tzinfo=datetime.timezone.utc),
datetime.datetime(2022, 7, 18, 20, 23, 47, 323579, tzinfo=<UTC>),
)
For this reason, there are libraries like pendulum that "do the right thing" and avoid these gotchas, e.g.:
In [79]: dt.datetime.utcnow() # wrong
Out[79]: datetime.datetime(2022, 7, 18, 20, 20, 41, 681750)
In [80]: dt.datetime.now(datetime.timezone.utc) # right
Out[80]: datetime.datetime(2022, 7, 18, 20, 20, 45, 947941, tzinfo=datetime.timezone.utc)
In [81]: pendulum.now().utcnow() # without gotchas
Out[81]: DateTime(2022, 7, 18, 20, 20, 48, 501773, tzinfo=Timezone('UTC'))
Fetch a timezone-aware datetime. Here's an example:
>>> from datetime import datetime
>>> datetime.now() # timezone unaware, shows my local time
datetime.datetime(2022, 7, 18, 13, 16, 50, 249012)
>>> from datetime import timezone
>>> datetime.now(tz=timezone.utc) # timezone aware, UTC
datetime.datetime(2022, 7, 18, 20, 17, 28, 760889, tzinfo=datetime.timezone.utc)
Converting from UTC to a particular time zone:
>>> import pytz
>>> now = datetime.now(tz=timezone.utc)
>>> now.astimezone(tz=pytz.timezone('US/Pacific'))
datetime.datetime(2022, 7, 18, 13, 18, 6, 929968, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)

Convert UTC date string to PST and then subtract 12 hours from it

I have incoming strings that are UTC dates in ISO format like,
2021-11-21T12:16:42Z
I wish to convert these dates into PST and then subtract 12 hours from it.
import datetime
time_list_utc=2021-11-21T12:16:42Z
time_list_pst=time_list_utc.convert(pst)
print(time_list_pst-8hours)
I am new to datetime manipulation so any input is greatly appreciated
Use datetime, tzinfo and timedelta modules since Python 3.9:
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
s = '2021-11-21T12:16:42Z'
# Convert to datetime (UTC)
dt_utc = datetime.strptime(utc, "%Y-%m-%dT%H:%M:%S%z")
# Convert to PST
dt_pst = dt_utc.astimezone(ZoneInfo('US/Pacific'))
# Subtract 12 hours
dt = dt_pst - timedelta(hours=12)
Output:
>>> dt
datetime.datetime(2021, 11, 20, 16, 16, 42, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific'))
>>> dt_pst
datetime.datetime(2021, 11, 21, 4, 16, 42, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific'))
>>> dt_utc
datetime.datetime(2021, 11, 21, 12, 16, 42, tzinfo=datetime.timezone.utc)

Time localized wrongly in Python

Using python Im trying to localize a datetime value to timezone "America/Chicago" which is -06:00 currently.
I get the timezone the following way:
>>> import pytz
>>> tz = pytz.timezone("America/Chicago")
<DstTzInfo 'America/Chicago' CST-1 day, 18:00:00 STD>
when I localize a date:
>>> my_date = tz.localize(datetime.now())
datetime.datetime(2016, 9, 24, 17, 4, 43, 439824, tzinfo=<DstTzInfo 'America/Chicago' CDT-1 day, 19:00:00 DST>)
Notice it is the wrong timezone after localize:
<DstTzInfo 'America/Chicago' CDT-1 day, 19:00:00 DST>
And later when i ask for the offset, you can see it is confirmed it has the wrong offset:
>>> my_date.strftime("%z")
'-0500'
Exactly the same happend if I use astimezone instead:
>>>my_date
datetime.datetime(2016, 9, 24, 22, 15, 1, 620364, tzinfo=<UTC>)
>>>my_date.astimezone(tz)
datetime.datetime(2016, 9, 24, 17, 15, 1, 620364, tzinfo=<DstTzInfo 'America/Chicago' CDT-1 day, 19:00:00 DST>)
Btw Chicago is observing DST now. So -05.00 is the right offset. Pytz timezone by default has the standard time, but when localized can account for day light saving based on the date(as in your case).

Incorrectly converting between Eastern and GMT time

I am having a strange issue in converting the following time from eastern to UTC/GMT. Can someone advise?
>>> import datetime
>>> import pytz
>>>
>>> ept_time = datetime.datetime(2014,03,21,7) # March 21st at 7am
>>> ept_time = ept_time.replace(tzinfo=pytz.timezone('US/Eastern'))
>>> print ept_time
2014-03-21 07:00:00-05:00
>>>
>>> gmt_time = pytz.utc.normalize(ept_time)
>>> print gmt_time
2014-03-21 12:00:00+00:00
>>>
However, according to Wolfram Alpha, the results should be 11am, not 12.
>>> gmt = pytz.timezone('GMT')
>>> eastern = pytz.timezone('US/Eastern')
>>> d = datetime.datetime(2014,03,21,7)
>>> dateeastern = eastern.localize(d)
>>> dateeastern
datetime.datetime(2014, 3, 21, 7, 0, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
>>> dategmt = dateeastern.astimezone(gmt)
>>> dategmt
datetime.datetime(2014, 3, 21, 11, 0, tzinfo=<StaticTzInfo 'GMT'>)
Replace GMT with UTC:
>>> eastern = pytz.timezone('US/Eastern')
>>> d = datetime.datetime(2014,03,21,7)
>>> dateeastern = eastern.localize(d)
>>> dateeastern
datetime.datetime(2014, 3, 21, 7, 0, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
>>> dateutc = dateeastern.astimezone(pytz.utc)
>>> dateutc
datetime.datetime(2014, 3, 21, 11, 0, tzinfo=<UTC>)
Ref: How to convert GMT time to EST time using python

timezone conversion in Python

I'm probably missing something about timezones:
>>> import datetime, pytz
>>> date = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.timezone("Europe/Paris"))
>>> date.astimezone(pytz.UTC)
datetime.datetime(2013, 9, 3, 15, 51, tzinfo=<UTC>)
I was expecting
datetime.datetime(2013, 9, 3, 15, 00, tzinfo=<UTC>)
Can anyone explain me where these 51 minutes come from?
Thanks,
Jean-Philippe
The UTC offset gives (date.tzinfo.utcoffset(date)):
datetime.timedelta(0, 540)
This is 540 seconds or 9 minutes.
In France the switch to UTC was made on March 11, 1911 and the clocks were turned back 9 minutes and 21 seconds (source 1, source 2):
Until 1911, Paris was 9 minutes and 21 seconds off UTC.
You can also see it here (Paris time in 1911) where the time goes from March 11, 12:01:00 AM to March 10, 11:51:39 PM.
Read the note at the very begining of pytz documentation ; use .localize() method to create timezone-aware datetime object:
import datetime
import pytz
naive_dt = datetime.datetime(2013,9,3,16,0)
dt = pytz.timezone("Europe/Paris").localize(naive_dt, is_dst=None)
to_s = lambda d: d.strftime('%Y-%m-%d %H:%M:%S %Z%z')
print(to_s(dt))
print(to_s(dt.astimezone(pytz.utc)))
Output
2013-09-03 16:00:00 CEST+0200
2013-09-03 14:00:00 UTC+0000
I don't know why you are expecting 15:00 UTC here.
Thanks Simeon for your answer. It made me realize how shallow is my understanding of all of this. The following experimentations lost me a little more...
>>> import datetime, pytz
>>> date_paris = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.timezone("Europe/Paris"))
>>> date_utc = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.utc)
>>> date_paris.astimezone(pytz.utc)
datetime.datetime(2013, 9, 3, 15, 51, tzinfo=<UTC>)
>>> date_utc.astimezone(pytz.timezone("Europe/Paris"))
datetime.datetime(2013, 9, 3, 18, 0, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)
Why this 9 minutes offset shows up when converting in one direction but not the other? The following piece of code concentrate all disappointment:
>>> date_paris
datetime.datetime(2013, 9, 3, 16, 0, tzinfo=<DstTzInfo 'Europe/Paris' PMT+0:09:00 STD>)
>>> date_paris.astimezone(pytz.utc).astimezone(pytz.timezone("Europe/Paris"))
datetime.datetime(2013, 9, 3, 17, 51, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)

Categories