Deal with Birtish summer time - python

I have a date string 18 May 14:30 which corresponds to the British summertime (WEST or UTC+1). I would like to convert it to central Euopean (summer)time.
Here is my code
# from datetime import datetime
# from pytz import timezone
d = '18 May 14:30'
# Attempt 1
dd=datetime.strptime(d, '%d %b %H:%M').replace(year=datetime.now().year, tzinfo=timezone('WET'))
dd.astimezone(timezone('CET'))
# datetime.datetime(2019, 5, 18, 16, 30, tzinfo=<DstTzInfo 'CET' CEST+2:00:00 DST>)
# It should be 15:30, not 16:30
# Attempt 2
dd=datetime.strptime(d, '%d %b %H:%M').replace(year=datetime.now().year, tzinfo=timezone('WET'))
# Same result as above
# Attempt 3
dd=datetime.strptime(d, '%d %b %H:%M').replace(year=datetime.now().year, tzinfo=timezone('Etc/GMT-1'))
dd.astimezone(timezone('CET'))
# datetime.datetime(2019, 5, 18, 15, 30, tzinfo=<DstTzInfo 'CET' CEST+2:00:00 DST>)
# This works
So my problem in the third attempt I had to manually specify GMT-1 whereas CET automatically transforms to CEST. I hoped this would work identically for WET (to WEST).
Besides, what also confuses me is the fact that according to Wiki British summertime should be UTC +1 but I had to set GMT-1 (as GMT+1 returns 18:30).

In case it interests someone, I did manage to find a workaround
dd=datetime.strptime(d, '%d %b %H:%M').replace(year=datetime.now().year)
timezone('WET').localize(dd).astimezone(timezone('CET'))
# datetime.datetime(2019, 5, 18, 15, 30, tzinfo=<DstTzInfo 'CET' CEST+2:00:00 DST>)
# Correct result without having to specify the shift
# Let's check with another date (non-summertime)
d = '18 Jan 14:30'
dd=datetime.strptime(d, '%d %b %H:%M').replace(year=datetime.now().year)
timezone('WET').localize(dd).astimezone(timezone('CET'))
# datetime.datetime(2019, 1, 18, 15, 30, tzinfo=<DstTzInfo 'CET' CET+1:00:00 STD>)
# Yay!
It's not very elegant but at least it does the job.

Related

Why is this time comparison off by 1?

I want to check if the us/pacific time is after 9am.
However, when I compare the times, it still says 'False' even after 9. At 10am it says 'True'
# get us/pacific time
import pytz
tz = pytz.timezone('US/Pacific')
# get the time now and put it in us/pacific
from datetime import datetime
tmp_date = tz.fromutc(datetime.utcnow())
# make a threshold that is the same but hour = 9
tmp_date_threshold = datetime(year=tmp_date.year,month=tmp_date.month,day=tmp_date.day,hour=9,tzinfo=tz)
# see what the times are
>>> tmp_date
datetime.datetime(2022, 6, 20, 6, 33, 9, 810387, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
>>> tmp_date_threshold
datetime.datetime(2022, 6, 20, 9, 0, tzinfo=<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
# compare (expected)
>>> tmp_date > tmp_date_threshold
False
# add 3 hours to make it after 9am
tmp_date = tmp_date + timedelta(hours=3)
# now after 9am
>>> tmp_date
datetime.datetime(2022, 6, 20, 9, 33, 9, 810387, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
# compare again
# still false
>>> tmp_date > tmp_date_threshold
False
Howcome is my comparison still failing even through tmp_date is after 9?
And howcome is my timezone info different for each object even though I used the same tz object to make each?

datetime.now(tz) and datetime(year, month, day, tzinfo=tz) don't have same UTC offset

Why are the UTC offsets in the 2 datetimes created in the code below different? I'm using pytz==2019.1
from datetime import datetime
import pytz
EASTERN = pytz.timezone('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
results:
dt1: 2020-08-11 18:35:47.984745-04:00
dt2: 2020-08-11 15:30:00-04:56
The first one shows an UTC offset of -04:00 which is correct (for this time of year), but the 2nd one is giving an UTC offset of -04:56.
How can I declare a datetime with the 2nd method and get the UTC offset from the first method.
The issue with the shown code is that datetime.now(tz) can handle a pytz.timezone as tz argument while the default constructor datetime(y,m,d,...) cannot. In this case, you have to use the localize method of the timezone,
from datetime import datetime
import pytz
EASTERN = pytz.timezone('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = EASTERN.localize(datetime(2020, 8, 11, 15, 30))
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
# prints
# dt1: 2020-08-12 10:07:09.738763-04:00
# dt2: 2020-08-11 15:30:00-04:00
dateutil avoids this issue, more info can be found here. That would make the code work "as it is":
from dateutil.tz import gettz
EASTERN = gettz('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
# prints e.g.
# dt1: 2020-08-12 10:20:07.796811-04:00
# dt2: 2020-08-11 15:30:00-04:00
In addition pytz is likely to be deprecated with Python 3.9 as you'll have zoneinfo as part of the standard library for these kinds of jobs.
with regards to dt1:
datetime.now() will return the tzinfo from utc. You can see this if you do the following:
>>> repr(EASTERN.fromutc(datetime.now()))
"datetime.datetime(2020, 8, 11, 12, 33, 28, 849873, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"
the tz.fromutc() is what datetime.now() does behind the scenes.
with regards to dt2:
if you check the repr of EASTERN you will see the following:
>>> repr(EASTERN)
"<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>"
>>> repr(dt2)
"datetime.datetime(2020, 8, 11, 15, 30, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)"
I'm not very comfortable with timezone differences so I don't want to steer you in the wrong direction, but from what I can tell both of these two options yield the same result:
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
dt2_eastern_corrected = EASTERN.fromutc(dt2)
dt3 = datetime(2020, 8, 11, 15, 30)
dt3_eastern_corrected = EASTERN.fromutc(dt3
>>> repr(dt2_eastern_corrected)
"datetime.datetime(2020, 8, 11, 11, 30, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"
>>> repr(dt3_eastern_corrected)
"datetime.datetime(2020, 8, 11, 11, 30, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"

How to compare same time in different time zone?

I have 2 timestamps with different time zones:
date_1_str = '20200520090000' # GMT+2
date_2_str = '20 May 2020, 07:00' # UTC
I convert them to a datetime:
from datetime import datetime
from dateutil import tz
date_1 = datetime.strptime(date_1_str, '%Y%m%d%H%M%S').replace(tzinfo=tz.tzoffset('GMT+2', 0))
date_2 = datetime.strptime(date_2_str, '%d %b %Y, %H:%M').replace(tzinfo=tz.tzoffset('UTC', 0))
After that, I convert them to the local time zone to compare:
date_1_ = date_1.astimezone(tz.tzlocal())
# datetime.datetime(2020, 5, 20, 12, 0, tzinfo=tzlocal())
date_2_ = date_2.astimezone(tz.tzlocal())
# datetime.datetime(2020, 5, 20, 10, 0, tzinfo=tzlocal())
As you can see, it turned out not what I wanted, although in the case of UTC the conversion is correct. After conversion, both objects should be datetime.datetime(2020, 5, 20, 10, 0, tzinfo=tzlocal()) because my time zone is gmt+3 so 7.00 utc == 10.00 gmt+3 and 9.00 gmt+2 must be == 10.00 gmt+3
Where am I wrong?
both tzoffsets are 0 in your code; if you set them accordingly, you should get the correct output:
from datetime import datetime
from dateutil import tz
date_1_str = '20200520090000' # GMT+2
date_2_str = '20 May 2020, 07:00' # UTC
date_1 = datetime.strptime(date_1_str, '%Y%m%d%H%M%S').replace(tzinfo=tz.tzoffset('GMT+2', 2*3600))
date_2 = datetime.strptime(date_2_str, '%d %b %Y, %H:%M').replace(tzinfo=tz.tzoffset('UTC', 0))
# I'm on UTC+2, so the hour should stay the same:
date_1_ = date_1.astimezone(tz.tzlocal())
# datetime.datetime(2020, 5, 20, 9, 0, tzinfo=tzlocal())
# UTC hour should be +2:
date_2_ = date_2.astimezone(tz.tzlocal())
# datetime.datetime(2020, 5, 20, 9, 0, tzinfo=tzlocal())

Saving a local datetime offset the time by 4 minutes

I'm trying to modify a datetime based on a timezone on save and on load the following way:
An input datetime, along with a input timezone are sent to the server and the server should update the datetime to reflect the timezone. So when it saves in the database (PostregSQL), the UTC time is saved (after the offset caused by the timezone, of course).
To reflect this here's a simpler example that fails in the same way:
Some imports:
>>> import datetime
>>> import pytz
>>> from apps.myapp.models import Project
Creating the two inputs:
>>> input_date = timezone.now()
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz = pytz.timezone('America/New_York')
>>> current_tz
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
As you can see, the timezone is not 5h (24 - 19 = 5), but 4h56. At this stage I'm thinking that's OK, it may be related to the Daylight Saving Time.
Now I'm replacing the timezone on the input date:
>>> input_date = input_date.replace(tzinfo=current_tz)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
As expected, the time hasn't changed, but the timezone has, which is fine.
I'll assign this value to a project (the launch_date is a DateTimeField without any specific option):
>>> project = Project.objects.get(pk=1)
>>> project.launch_date
datetime.datetime(2017, 1, 14, 8, 53, 57, 241718, tzinfo=<UTC>)
>>> project.launch_date = input_date
>>> project.launch_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
Now I'll save this into (and refresh from) the database, leaving Django/PostgreSQL do the maths:
>>> project.save()
>>> project.refresh_from_db()
>>> project.launch_date
datetime.datetime(2017, 2, 7, 21, 3, 14, 377429, tzinfo=<UTC>)
As expected the date is now 4h56 ahead of the previous date. I'm trying now to get back the local time:
>>> project.launch_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 16, 3, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
This time, the offset is perfectly 5h. And I'm missing 4 minutes.
3 questions here:
Where is this 4 minutes coming from?
Why is astimezone not using the 4 minutes as well?
How can a datetime be converted to UTC, saved, loaded and converted back to local?
pytz time zones are a little weird, as you can see by multiple questions on StackOverflow. They often won't show the correct offset or timezone name unless they are allowed to adjust themselves to the datetime they are being paired with. Here is what the documentation has to say:
This library only supports two ways of building a localized time. The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):
The second way of building a localized time is by converting an existing localized time using the standard astimezone() method:
Unfortunately using the tzinfo argument of the standard datetime constructors “does not work” with pytz for many timezones.
It does not say so explicitly, but using replace has the same problem as using the datetime constructor.
To accomplish what your code was doing without the 4-minute discrepancy, you can use localize():
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz.localize(input_date.replace(tzinfo=None))
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
I suspect that's a bug though, and you really want to do a timezone conversion from UTC:
>>> input_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 11, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)

inconsistency when switching between timezones in python

I have a datetime object created like this:
tm = datetime.datetime.strptime('2010 Aug 04 14:15:16', '%Y %b %d %H:%M:%S')
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16)
I then set the timezone like this:
tm.replace(tzinfo=pytz.timezone('UTC'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<UTC>)
Eventually, I change the timezone to US/Pacific like this:
>>> tm = tm.astimezone(pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 7, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
This is correct. But if I were to go into the other direction:
>>> tm = tm.replace(tzinfo =pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>)
As you can see, using replace() with the PST timezone incorrectly assumes it is not daylight savings time. Therefore, when I convert to UTC, it adds 8 hours instead of 7.
Why is it doing this and how do I change it so it does what I want (correctly realize DST or not)?
Yes, this is why you use .astimezone and not .replace when you have a datetime with a timezone. Using .astimezone gives the timezone a chance to adjust for things like daylight savings. Only use .replace to give a naïve datetime a tzinfo object.

Categories

Resources