23

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?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
jmetz
  • 815
  • 1
  • 9
  • 19
  • The U.S. Senate just voted to eliminate daylight saving transitions and keep us on daylight saving time year-round. I really hope this becomes law. – Mark Ransom Mar 15 '22 at 20:49
  • Could not agree more @MarkRansom . The Amount of time & effort that has been put in by countless individuals to work around Day Light Savings in programming doesn't make much sense – cdraper Mar 18 '22 at 17:25
  • 2
    If eliminating US daylight-saving does become law, it won't really save much work. Any system that wants to work with timestamps in the past, or in other countries, or in potential futures in which daylight-savings gets re-introduced, will all still have to be correctly coded. – Jonathan Hartley Apr 12 '22 at 20:27

4 Answers4

37

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.

Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    the preferable way to get the current time in a given timezone is: `datetime.now(tz)`. – jfs Dec 29 '14 at 01:26
  • @J.F.Sebastian I suspect that doesn't work reliably with `pytz` timezones, for the same reasons the `datetime` constructor doesn't work with them. – Mark Ransom May 23 '16 at 02:32
  • 2
    it does work: [when does `datetime.now(pytz_timezone)` fail?](http://stackoverflow.com/q/31886808/4279) – jfs May 23 '16 at 05:26
  • @J.F.Sebastian I tried it with a couple of problematic time zones such as [Hong Kong](http://stackoverflow.com/questions/11473721/weird-timezone-issue-with-pytz) and danged if it didn't work just fine; my fears were overblown. Thank you! – Mark Ransom May 25 '16 at 18:08
  • As of 2022-03-15 the docs say "Because naive datetime objects are treated by many datetime methods as local times, it is preferred to use aware datetimes to represent times in UTC. As such, the recommended way to create an object representing the current time in UTC is by calling datetime.now(timezone.utc)." – mike rodent Mar 15 '22 at 19:30
  • @mikerodent back when I wrote this answer, `timezone.utc` didn't exist. – Mark Ransom Mar 15 '22 at 20:44
10

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.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
user711413
  • 761
  • 5
  • 12
  • `now2` may return a wrong result (an hour off) during end-of-DST transitions ("fall back") even if you are in LA. `datetime.now(la)` works even during UTC transitions. – jfs Dec 29 '14 at 01:31
5

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.

MrE
  • 19,584
  • 12
  • 87
  • 105
  • Check this for more details: https://medium.com/@eleroy/10-things-you-need-to-know-about-date-and-time-in-python-with-datetime-pytz-dateutil-timedelta-309bfbafb3f7 – MrE Jul 09 '18 at 00:02
1

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.

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
  • `datetime.now(tz)` is the case then it works (you shouldn't use `.localize()` here). – jfs Mar 27 '14 at 08:05
  • 1
    You should use datetime.datetime.utcnow().astimezone(tz) -- This gets the time in UTC and then offsets it from UTC according to whatever rules apply in the timezone tz. (Passing a pytz timezone into the constructor will get you some offset that is not an even number of hours.) – jobermark May 12 '16 at 19:54
  • @jobermark: use `now(tz)` instead of `utcnow().astimezone(tz)`. [It works](http://stackoverflow.com/questions/12203676/daylight-savings-time-in-python/12204612#comment62276833_12204612) – jfs May 30 '16 at 09:10