Why is only one date under DST influence? - python

I've been reading the pytz and datetime module documentation but I can't figure out why one date is under DST and the other is not.
import pytz
import datetime
mytz = pytz.timezone('America/New_York')
od = datetime.datetime(2021, 7, 1, 4, 0)
mytz.localize(od)
# Out: datetime.datetime(2021, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
mytz.localize(od).dst()
# Out: datetime.timedelta(0, 3600)
dt = datetime.datetime(2089, 7, 1, 4, 0)
mytz.localize(dt)
# Out: datetime.datetime(2089, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
mytz.localize(dt).dst()
# Out: datetime.timedelta(0)

If you look at the source of the time zone rules, you find that they can have a keyword "max" specified that "is used to extend a rule’s application into the indefinite future" ref. For the US, you can find that here. Unless otherwise specified, DST just continues to be applied during the specified period of the year. But keep in mind that this does not mean that it will actually be the case in the future, since time zones are subject to political decisions.
As an addition to #balmy 's comment suggesting this is a deficiency of pytz, Python 3.9's zoneinfo gives the result to be expected from the above:
import datetime
from zoneinfo import ZoneInfo
od = datetime.datetime(2021, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(od.dst())
# 1:00:00
dt = datetime.datetime(2089, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(dt.dst())
# 1:00:00

Related

python datetime .timestamp() doesnt give accurate timestamp. It gets behind by 7 minutes when I convert it back

I am using python 3.6
I converted midnight of "US/Pacific" timezone to unix timestamp.
When I convert the timestamp back to "US/Pacific" local time, I see its 7 minutes before the midnight.
Can anyone shade light on what is happening?
>>> (
datetime(2021, 5, 5, 0, 0)
.replace(tzinfo=pytz.timezone("US/Pacific"))
.timestamp()
)
>>> 1620201180.0
>>> datetime.utcfromtimestamp(1620201180.0)
>>> datetime.datetime(2021, 5, 5, 7, 53)
>>> datetime.fromtimestamp(1620201180.0, tz=pytz.timezone("US/Pacific"))
>>> datetime.datetime(2021, 5, 5, 0, 53, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
FYI I am located at "Asia/Kathmandu" timezone.

How to create a datetime object with tzinfo set as 'UTC'?

I have a list of datetimes objects :
time_range = [datetime.datetime(2019, 7, 9, 0, 0, tzinfo=tzutc()),
datetime.datetime(2019, 7, 8, 0, 0, tzinfo=tzutc()),
datetime.datetime(2019, 7, 7, 0, 0, tzinfo=tzutc()),
datetime.datetime(2019, 7, 6, 0, 0, tzinfo=tzutc())
... ]
And I have an other datetime object :
time = datetime(2019, 7, 7)
I have to test if time is in time_range.
But each time I test :
time in time_range
I get the output False, because I don't have the tzinfo.
Here's what I've tried :
I tried to add the tzinfo :
time = datetime(2019, 7, 7, tzinfo=tzuct())
but I can't find where the tzutc() function is.
I also tried tu use pandas :
import pandas as pd
pd.to_datetime(str(time) + '+00:00')
I get the UTC :
Timestamp('2019-07-05 00:00:00+0000', tz='UTC')
But this is not a datetime.datetime object...
Do you have an idea how I could do ?
(Note : i'm compelled to use the form time in time_range, because of the rest of my program)
In datetime constructor, tzinfo parameter expects a type of timezone. It's not the clearest documentation. Try this:
from datetime import datetime, timezone
dt = datetime(2019, 7, 7, tzinfo=timezone.utc)
>>> from datetime import datetime, timezone
>>> time = datetime(2019, 7, 7, tzinfo=timezone.utc)
>>> print(time)
2019-07-07 00:00:00+00:00
>>> print(time.tzinfo)
UTC
After some research, I found an other solution, using pandas :
utc_time = pd.to_datetime(str(time) + '+00:00').to_pydatetime()
returns a datetime.datetime object :
datetime.datetime(2019, 7, 7, 0, 0, tzinfo=<UTC>)
However, to avoid importing pandas library, here's the solution I used :
from datetime import datetime, timezone
new_time = time.replace(tzinfo=timezone.utc)
new_time in time_range.
>>> True

Python astimezone() unexpected result

Given a variable containing the datetime of 2000-01-01 00:01 in Paris timezone (UTC+2 in winter afaik):
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
I expected the conversion to UTC to result in a datetime of 1999-12-31 22:01, but got instead:
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris')).astimezone(pytz.utc)
datetime.datetime(1999, 12, 31, 23, 52, tzinfo=<UTC>)
What am I missing ?
Thanks
Unfortunately using the tzinfo argument of the standard datetime
constructors ‘’does not work’’ with pytz for many timezones.
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt)
'2002-10-27 12:00:00 LMT+0020'
It is safe for timezones without daylight saving transitions though, such as UTC:
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt)
'2002-10-27 12:00:00 UTC+0000'
As you'll notice:
>>> datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=<DstTzInfo 'Europe/Paris' LMT+0:09:00 STD>)
"LMT+0:09:00 STD"…?! That's a historical offset, not a current standard.
The timezone bundles (containing all historical offsets since forever) returned by pytz aren't handled correctly by datetime, and it chooses some random (well, the first probably) offset instead of the offset pertinent to the actual time. Arguably, since it needs to interpret the time correctly first it cannot choose the right offset by time from the timezone bundle.
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):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
The second way of building a localized time is by converting an existing
localized time using the standard astimezone() method:
>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET+0100'
http://pytz.sourceforge.net

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>)

Difference of two timedelta objects, with timezones

I would like to calculate how many hours there are in a date interval: for example "2014.03.29-30" should give 47, because of the daylight savings.
My method is making two datetime objects, in the example the following:
datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
datetime.datetime(2014, 3, 30, 23, 59, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
return (date2-date1) + timedelta(minutes=1)
However, it gives "2 days, 0:00:00", which is not correct. How could I make a timedelta object which takes timezones and dst into account? Also, if there's a simpler solution for the whole problem, I'm open to it.
Thank you!
Before 1901-12-13 20:45:52 UTC, the 'Europe/Budapest' timezone was LMT+1:16:00 STD.
Currently, as of 2016-05-05, the 'Europe/Budapest' timezone is CET+2:00:00 DST.
If you use pytz's localize method, then pytz will choose the timezone (utcoffset and dstoffset) for 'Europe/Budapest' which is appropriate for the given naive datetime:
import datetime as DT
import pytz
tzone = pytz.timezone('Europe/Budapest')
date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
# datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' CET+1:00:00 STD>)
In contrast, if you supply tzinfo=tzone directly to datetime.datetime, as in:
wrong_date1 = datetime.datetime(2014, 3, 29, 0, 0, tzinfo=tzone)
# datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
then the datetime.datetime incorrectly chooses the very first timezone associated with 'Europe/Budapest' regardless of whether or not that was the timezone in effect on 2014-3-29.
Therefore, when using pytz, always use tzone.localize to make naive datetimes timezone-aware:
import datetime as DT
import pytz
tzone = pytz.timezone('Europe/Budapest')
date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
date2 = tzone.localize(DT.datetime(2014, 3, 30, 23, 59), is_dst=None)
print(((date2-date1) + DT.timedelta(minutes=1)).total_seconds()/3600.)
# 47.0
Do not use tzinfo=tzone unless tzone is pytz.utc (or a timezone which is alway the same throughout its history.)
Where did the date 1901-12-13 20:45:52 UTC come from?
You can peek at a pytz timezone's utc transition times (and associated transition info) using its tzone._utc_transition_times and tzone._transition_info private attributes:
In [43]: [(utcdate, utcoffset, dstoffset, tzabbrev) for utcdate, (utcoffset, dstoffset, tzabbrev) in zip(tzone._utc_transition_times, tzone._transition_info)][:2]
Out[43]:
[(datetime.datetime(1, 1, 1, 0, 0),
datetime.timedelta(0, 4560),
datetime.timedelta(0),
'LMT'),
(datetime.datetime(1901, 12, 13, 20, 45, 52),
datetime.timedelta(0, 3600),
datetime.timedelta(0),
'CET')]
This shows that from the date 1-1-1 UTC to 1901-12-13 20:45:52 UTC the timezone abbreviation was LMT and the utcoffset was 4560 seconds, which equals 1 hour and 16 minutes:
In [47]: print(DT.timedelta(0, 4560))
1:16:00
Hence the first timezone associated with 'Europe/Budapest' is LMT+1:16:00 STD.

Categories

Resources