51

See the following code:

import datetime
import pytz

fmt = '%Y-%m-%d %H:%M:%S %Z'

d = datetime.datetime.now(pytz.timezone("America/New_York"))
d_string = d.strftime(fmt)
d2 = datetime.datetime.strptime(d_string, fmt)
print d_string 
print d2.strftime(fmt)

the output is

2013-02-07 17:42:31 EST
2013-02-07 17:42:31 

The timezone information simply got lost in the translation.

If I switch '%Z' to '%z', I get

ValueError: 'z' is a bad directive in format '%Y-%m-%d %H:%M:%S %z'

I know I can use python-dateutil, but I just found it bizzare that I can't achieve this simple feature in datetime and have to introduce more dependency?

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
CuriousMind
  • 15,168
  • 20
  • 82
  • 120

4 Answers4

33

Part of the problem here is that the strings usually used to represent timezones are not actually unique. "EST" only means "America/New_York" to people in North America. This is a limitation in the C time API, and the Python solution is… to add full tz features in some future version any day now, if anyone is willing to write the PEP.

You can format and parse a timezone as an offset, but that loses daylight savings/summer time information (e.g., you can't distinguish "America/Phoenix" from "America/Los_Angeles" in the summer). You can format a timezone as a 3-letter abbreviation, but you can't parse it back from that.

If you want something that's fuzzy and ambiguous but usually what you want, you need a third-party library like dateutil.

If you want something that's actually unambiguous, just append the actual tz name to the local datetime string yourself, and split it back off on the other end:

d = datetime.datetime.now(pytz.timezone("America/New_York"))
dtz_string = d.strftime(fmt) + ' ' + "America/New_York"

d_string, tz_string = dtz_string.rsplit(' ', 1)
d2 = datetime.datetime.strptime(d_string, fmt)
tz2 = pytz.timezone(tz_string)

print dtz_string 
print d2.strftime(fmt) + ' ' + tz_string

Or… halfway between those two, you're already using the pytz library, which can parse (according to some arbitrary but well-defined disambiguation rules) formats like "EST". So, if you really want to, you can leave the %Z in on the formatting side, then pull it off and parse it with pytz.timezone() before passing the rest to strptime.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I think that replacing `EST` by `America/New_York` is not a good idea: they are elements from different sets. `America/New_York` belongs to the set containing among others `Europe/Rome` and `Africa/Dakar`, while `EST` has as siblings `EDT`, `CET`, `UTC` as well as `EST` and `CEST`. Moreover, whether `Europe/Rome` means `CET` (UTC+1) or `CEST` (UTC+2) depends on the calendar day and on political choices, which may vary per year. I would follow @Johnny's answer. – mariotomo Dec 22 '17 at 14:36
11

Unfortunately, strptime() can only handle the timezone configured by your OS, and then only as a time offset, really. From the documentation:

Support for the %Z directive is based on the values contained in tzname and whether daylight is true. Because of this, it is platform-specific except for recognizing UTC and GMT which are always known (and are considered to be non-daylight savings timezones).

strftime() doesn't officially support %z.

You are stuck with python-dateutil to support timezone parsing, I am afraid.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Actually, `%z` _is_ supported, on most POSIX systems, under the "Additional directives may be supported on certain platforms" rule (one paragraph from the end of your link). See `http://docs.python.org/2/library/time.html#id2`at the bottom of the page, too. But it's not _documented_ to work on any particular system (in practice, it only works if your platform is POSIX and the native libc function supports it), and it's also not documented what happens if you use it on a system that can't handle it, and usually that's as bad as "doesn't work". – abarnert Feb 08 '13 at 00:14
  • Right; altered to 'officially' then. The support in the stdlib for timezones is lacking tremendously. Thanks for the explanation! – Martijn Pieters Feb 08 '13 at 00:32
  • I believe the proposal to add full tz features in some future version any day now is alive yet again. This time, the idea of just including tzdb with Python for Windows and other platforms that don't have it natively isn't being immediately laughed out of court because "making Python 131KB bigger" isn't very scary anymore. But there is the whole issue of keeping the database up to date… – abarnert Feb 08 '13 at 01:11
  • I always imagined that that last point is the reason why there is no good TZ support in the stdlib… – Martijn Pieters Feb 08 '13 at 01:13
  • Well, IIRC, the reason there was no support in 1.6 or 2.0 or whenever this was first suggested was actually more about about the 131KB… and the fact that some platform (SGI?) did come with zoneinfo but it was broken. But yeah, now that we're past those two problems, the idea that on Windows, 3.4.0 and 2.7.4 (or whatever) will be frozen at the 2013 definitions forever is pretty scary, considering how many people are still using 2.7.2, or even 2.6.x, today… – abarnert Feb 08 '13 at 01:24
4

Here is my answer in Python 2.7

Print current time with timezone

from datetime import datetime
import tzlocal  # pip install tzlocal

print datetime.now(tzlocal.get_localzone()).strftime("%Y-%m-%d %H:%M:%S %z")

Print current time with specific timezone

from datetime import datetime
import pytz # pip install pytz

print datetime.now(pytz.timezone('Asia/Taipei')).strftime("%Y-%m-%d %H:%M:%S %z")

It will print something like

2017-08-10 20:46:24 +0800
Johnny
  • 1,824
  • 23
  • 16
  • 1
    The meat of the question is `strptime()`. Also, totally misses the point about the lack of global standard timezone names. – jennykwan Mar 28 '18 at 19:36
0

Try this:

import pytz
import datetime

fmt = '%Y-%m-%d %H:%M:%S %Z'

d = datetime.datetime.now(pytz.timezone("America/New_York"))
d_string = d.strftime(fmt)
d2 = pytz.timezone('America/New_York').localize(d.strptime(d_string,fmt), is_dst=None)
print(d_string)
print(d2.strftime(fmt))
patcou
  • 41
  • 2