Daylight savings time in Python - python

I am writing a program which deals a lot with timezones and crossing them. The two things I deal with most are creating a datetime object from "now" and then localizing a naive datetime object.
To create a datetime object from now in the pacific timezone, I am currently doing this (python 2.7.2+)
from datetime import datetime
import pytz
la = pytz.timezone("America/Los_Angeles")
now = datetime.now(la)
Is this correct with regards to DST? If not, I suppose I should be doing:
now2 = la.localize(datetime.now())
My question is why? Can anyone show me a case where the first is wrong and the seconds is right?
As for my seconds question, suppose I had a naive date and time from some user input for 9/1/2012 at 8:00am in Los Angeles, CA. Is the right way to make the datetime like this:
la.localize(datetime(2012, 9, 1, 8, 0))
If not, how should I be building these datetimes?

From the pytz documentation:
The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans.
So ideally you should be using utcnow instead of now.
Assuming for some reason that your hands are tied and you need to work with local times, you can still run into a problem with trying to localize the current time if you're doing it during the daylight saving transition window. The same datetime might occur twice, once during daylight time and again during standard time, and the localize method doesn't know how to settle the conflict unless you tell it explicitly with the is_dst parameter.
So to get the current UTC time:
utc = pytz.timezone('UTC')
now = utc.localize(datetime.datetime.utcnow())
And to convert it to your local time (but only when you must):
la = pytz.timezone('America/Los_Angeles')
local_time = now.astimezone(la)
Edit: as pointed out in the comments by #J.F. Sebastian, your first example using datetime.now(tz) will work in all cases. Your second example fails during the fall transition as I outlined above. I still advocate using UTC instead of local time for everything except display.

The first solution is correct with regards to DST, and the second solution is bad.
I'll give an example. Here in Europe, when running this code:
from datetime import datetime
import pytz # $ pip install pytz
la = pytz.timezone("America/Los_Angeles")
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
now = datetime.now(la)
now2 = la.localize(datetime.now())
now3 = datetime.now()
print(now.strftime(fmt))
print(now2.strftime(fmt))
print(now3.strftime(fmt))
I get the following:
2012-08-30 12:34:06 PDT-0700
2012-08-30 21:34:06 PDT-0700
2012-08-30 21:34:06
datetime.now(la) creates a datetime with the current time in LA, plus the timezone information for LA.
la.localize(datetime.now()) adds timezone information to the naive datetime, but does no timezone conversion; it just assumes the time was already in this timezone.
datetime.now() creates a naive datetime (without timezone information) with the local time.
As long as you are in LA, you will not see the difference, but if your code ever runs somewhere else, it will probably not do what you wanted.
Apart from that, if you ever need to deal seriously with timezones, it is better to have all your times in UTC, saving yourself a lot of trouble with DST.

This works:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# Convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# show as in LA time zone (not converting here)
d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we get Pacific Daylight Time: PDT
# add 1 day to UTC date
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>)
d.astimezone(pst) # now cast to LA time zone
>>> datetime.datetime(2016, 11, 6, 8, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
# Daylight saving is applied -> we get Pacific Standard Time PST
This DOES NOT work:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# convert to 'America/Los_Angeles' timezone: DON'T DO THIS
d = d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we are in Pacific Daylight Time PDT
# add 1 day to LA local date: DON'T DO THAT
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# Daylight Saving is NOT respected, we are still in PDT time, not PST
Conclusion:
datetime.timedelta() DOES NOT account for daylight saving.
Do your time add/subtract in UTC timezone ALWAYS.
Cast to local time only for output / display.

The pytz website says:
Unfortunately using the tzinfo argument of the standard datetime
constructors ‘’does not work’’ with pytz for many timezones.
So you should not use datetime.now(la). I don't know the specifics, but some timezones operate on more exotic rules then we are used to, and python's datetime code can't handle them. By using pytz's code, they should be handled correctly since that's pytz's purpose. It may also have issues for the times that occur twice thanks to jumping times in daylight savings.
As for the second question, that's exactly what the documentation shows, so you should be good.

Related

Convert numeric offset to a timezone?

There is a website that provides timezone information in the form of e.g. -07:00. Is there a way I can use this to localize a timestamp in pytz?
Normally I am doing this:
EASTERN_TIMEZONE = pytz.timezone("US/Eastern")
date_in_pacific_time = EASTERN_TIMEZONE.localize(my_date)
where my_date is some datetime date. But I don't know how to take a number and get the timezone from it so I can apply it to the date and get a timestamp from it.
This may be an XY question though because what I want to do is take a date string like "2021-04-02T18:30:04-07:00" and convert it to a Unix UTC timestamp.
Edit:
Like this?
listing_date_string = "2021-04-02T18:30:04-07:00"
listing_date_string_datepart = listing_date_string[:19]
listing_date_string_timezone = int(listing_date_string[19:].replace(":00", ""))
d = datetime.datetime.strptime(listing_date_string_datepart, '%Y-%m-%dT%H:%M:%S')
d -= datetime.timedelta(hours=listing_date_string_timezone)
print(int(d.timestamp())) # outputs 1617427804
Python < 3.7 - from the docs:
Changed in version 3.7: When the %z directive is provided to the
strptime() method, the UTC offsets can have a colon as a separator
between hours, minutes and seconds.
Vice versa that means that with older Python versions, %z won't parse a UTC offset with a colon as hours/minutes separator. You can work around like
from datetime import datetime
# your input
s = "2021-04-02T18:30:04-07:00"
# parse separately, colon removed from UTC offset
tz = datetime.strptime(s[19:].replace(':', ''), '%z').tzinfo
dtobj = datetime.strptime(s[:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=tz)
print(repr(dtobj))
datetime.datetime(2021, 4, 2, 18, 30, 4, tzinfo=datetime.timezone(datetime.timedelta(-1, 61200)))
If you know the time zone, you can skip parsing the UTC offset:
import pytz
tz = pytz.timezone('US/Pacific')
dtobj = tz.localize(datetime.strptime(s[:19], "%Y-%m-%dT%H:%M:%S"))
print(repr(dtobj))
datetime.datetime(2021, 4, 2, 18, 30, 4, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
Note: pytz is deprecated with the release of Python 3.9 - you now have zoneinfo in the standard lib.
On Python 3.7+ you can just use fromisoformat;
dtobj = datetime.fromisoformat(s)
and change from UTC offset to an actual time zone like
dtobj.astimezone(tz) # tz is a timezone object from pytz, dateutil, zoneinfo...
Out[9]: datetime.datetime(2021, 4, 2, 18, 30, 4, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
For question X, it is not in general feasible.
But you have some candidate timezones in mind, like
US/Pacific
America/Phoenix
US/Mountain (same as America/Denver)
so you could try each one to see if it matches -7 hours.
Some days Denver is at -7 hours,
on other days Los Angeles is at -7 hours.
And Phoenix is at -7 hours all the time.
Note that a timezone, like US/Mountain, is quite different from a zone offset, like -7 hours.
For question Y, well, that's easy!
Just treat the first part as UTC,
then apply the remaining -7 hour correction.
does this work on your side?
import datetime
dt = datetime.datetime.strptime(
"2021-04-02T18:30:04-07:00",
"%Y-%m-%dT%H:%M:%S%z"
)
print(
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.tzinfo,
)
2021 4 2 18 30 4 UTC-07:00

datetime with pytz timezone. Different offset depending on how tzinfo is set [duplicate]

This question already has answers here:
How to add timezone into a naive datetime instance in python [duplicate]
(2 answers)
Closed 7 years ago.
I ran across an interesting situation today. Can anyone explain why the offsets for ts1 and ts2 are different? ts1 is a datetime object that is timezone-aware right off the bat. ts2 is a datetime object that starts off timezone-naive and has its tzinfo replaced. However, they end up with different offsets.
>>> from pytz import timezone
>>> EST = timezone('America/New_York')
>>> ts1 = datetime.datetime.now(tz=EST)
>>> ts2 = datetime.datetime.now()
>>> ts2 = ts2.replace(tzinfo=EST)
>>> print ts1
2014-05-16 11:25:16.749748-04:00
>>> print ts2
2014-05-16 11:25:19.581710-05:00
When you call ts2.replace(tzinfo=EST), the tzinfo object you're getting doesn't match the one you get with ts1:
>>> ts1
datetime.datetime(2014, 5, 16, 11, 51, 7, 916090, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> ts2
datetime.datetime(2014, 5, 16, 11, 51, 30, 922692, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
You end up with LMT instead of EDT.
The pytz documentation actually notes that using pytz with the tzinfo argument of standard datetime objects simply doesn't work for many timezones:
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'
I'm not exactly sure why the first one works; perhaps because it doesn't actually have to convert anything when the object is initially constructed with the tzinfo object.
Edit:
Ah, the Python documentation notes that using datetime.datetime.now() with the tz arg is equivalent to:
EST.fromutc(datetime.utcnow().replace(tzinfo=EST))
Which means you're converting from UTC, which is safe with pytz. So that's why the first one works.
According to the documentation, the correct way to apply a time zone to a naive datetime is with the localize method.
ts1 = eastern.localize(datetime.datetime.now())
Also, I recommend you use avoid EST as a variable name, since it typically standard for "Eastern Standard Time", and America/New_York comprises both "Eastern Standard Time" (EST) and "Eastern Daylight Time" (EDT).

Python Django: tzinfo doesn't work for DB insertion. But why does .now(local_tz) work?

I am using Django.
in settings:
TIME_ZONE = 'Europe/Copenhagen'
USE_TZ = True
Due to DST, clock skips an hour on 2013-3-31. 01:59 goes to 03:00
I views:
The date and time are given in local time. I want these to be inserted as utc.
Code below correctly saves as UTC, but gives RuntimeWarning: DateTimeField received a naive datetime
the_date = datetime.datetime(2013, 3, 31, 1, 59)
hit = hits(date= the_date); hit.save(); # Correctly saved as 00:59:00
the_date = datetime.datetime(2013, 3, 31, 3, 1)
hit = hits(date= the_date); hit.save(); # Correctly saved as 01:01:00
I thought I could avoid the warning by making the datetime aware. It does avoid the warning, but the conversion is now wrong.
tz = timezone(settings.TIME_ZONE)
the_date = datetime.datetime(2013, 3, 31, 3, 1, tzinfo = tz)
hit = hits(date= the_date); hit.save(); # Incorrectly saved as 02:01:00
The following works, with no runtime error:
I have installed pytz.
the_date = local_tz.localize(datetime.datetime(2013, 3, 31, 3, 1))
Getting to my question:
I get that tzinfo doesn't work because it doesn't account for daylight savings time. Fine, I won't use it. But then I was confused when the following seemed to work:
the_date = datetime.datetime.now(local_tz)
This correctly inserted as utc both in wintertime (where it subtracted 1 hour to get utc) and when I changed my computer systime to a date in the summer (where it subtracted 2 hours to get utc).
My question:
Does .now(local_tz) work or did I test it wrong? Why is this different than tzinfo = tz? Or am I using tzinfo wrong?
I recommend converting to UTC as soon as possible and only use UTC internally. Save for cases where you move across timezones (for example a marine vessel) and really need to save timezone information, for the constant timezone case, it's much simpler to just use local time for input/output and convert it to UTC at the user interface.
To convert from localtime to UTC, you need to use the pytz.timezone.normalize method which handles daylight savings time and other timezone transition. See this section of the pytz documentation. In your case, to convert the local datetime to UTC you need the following:
from pytz import timezone, utc
local_tz = timezone(settings.TIME_ZONE)
local_dt = datetime.datetime(2013, 3, 31, 3, 1, tzinfo = local_tz)
utc_dt = utc.normalize(local_dt.astimezone(utc))

In python how do I convert a datetime in a specific local time (not my local) to UTC

I'm pulling data from a London based service and they are giving me date&time info in London local time.So UTC in winter and BST(UTC+1) in summer.
Internally we use UTC for everything, in Python how do I convert the London stuff to UTC in a way that will account for daylight savings?
I appreciate that some times around the DST rollover are ambiguous, that's acceptable as long as it works the rest of the year.
For completeness, I'm getting the following info from them:
dt="2012-10-12T19:30:00"
lcnid="LDN"
locale="en-gb"
You need to use a timezone object; these don't come with Python itself as the data changes too often. The pytz library is easily installed though.
Example conversion:
>>> import pytz
>>> import datetime
>>> bst = pytz.timezone('Europe/London')
>>> dt = datetime.datetime.strptime('2012-10-12T19:30:00', '%Y-%m-%dT%H:%M:%S')
>>> dt
datetime.datetime(2012, 10, 12, 19, 30)
>>> bst.localize(dt)
datetime.datetime(2012, 10, 12, 19, 30, tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>)
>>> bst.localize(dt).astimezone(pytz.utc)
datetime.datetime(2012, 10, 12, 18, 30, tzinfo=<UTC>)
import pytz
utc = pytz.utc
print(utc.localize(datetime.datetime(2012,10,12,19,30,00)))

pytz: Why do these different methods give different UTC offsets?

When creating a datetime object in a specific time zone using pytz I get a different UTC offset depending on whether I use datetime.datetime() or datetime.datetime.now().
now() seems to give the correct UTC offset for the time zone, datetime() gives an offset that I don't recognise.
Why are they different? What is the significance of the offset that datetime() assigns?
Here's my code:
import datetime
import pytz
la_paz = pytz.timezone('America/La_Paz')
a = datetime.datetime.now(la_paz)
print a, a.utcoffset()
# 2011-03-22 05:30:13-04:00 -1 day, 20:00:00
# -4 hours is the correct UTC offset for La Paz
b = datetime.datetime(2011, 03, 22, 5, 30, tzinfo=la_paz)
print b, b.utcoffset()
# 2011-03-22 05:30:00-04:33 -1 day, 19:27:00
# What is the significance of -4:33?
It seems that datetime() will use the first recorded timezone for the region by default, and in many cases (like in La Paz) this is old and no longer valid.
The datetime must instead be created naive and then localised like so:
b = la_paz.localize(datetime.datetime(2011, 03, 22, 5, 30))
print b, b.utcoffset()
now() appears to do the localization automatically.
From the pytz documentation:
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'
Or put another way:
b = datetime.datetime(2011, 03, 22, 5, 30, tzinfo=la_paz)
Is not supported by pytz

Categories