Python datetime strptime() and strftime(): how to preserve the timezone information - python

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?

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.

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.

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

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

Related

PYTZ always 7 hours ahead

so I'm trying to make a discord bot in python and make a command that will display all timezones that I choose, but the problem is that all of these timezones are around 7-8 hours ahead of normal.
import datetime
from pytz import timezone
localFormat = "%Y-%m-%d %H:%M:%S, %Z%z"
UTC=datetime.datetime.utcnow()
timezonelist = ["US/Eastern", "US/Central", "US/Mountain", "US/Pacific", "Etc/UTC", "Europe/Berlin", "Australia/North", "Australia/South", "Australia/West"]
for tz in timezonelist:
localDatetime = UTC.astimezone(timezone(tz))
x = localDatetime.strftime(localFormat)
print(tz + " " + x)
for example, Etc/UTC outputs 05:56:25 when it should output 22:56:25, other timezones follow this example, EST outputs 00:56:25 when it should be 17:56:25, or MST (where I am), 22:56:25 instead of 15:56:25. However, datetime.utcnow() returns the correct time.
I had done some research and it said that PYTZ uses the LMT but since all are forward by an amount I do not think it has to do with that.
utcnow() gives you a naive datetime object - it is not aware that it's in UTC although the numbers show UTC. If you convert to another time zone, Python will assume all naive datetime objects are local time. Your local time (MST) is UTC-7, that's why you're off by 7 hours.
The behaviour of utcnow is confusing and can lead to unexpected results - Stop using utcnow and utcfromtimestamp. Instead, use now() and set the tz explicitly:
UTC = datetime.datetime.now(timezone('UTC'))

How should I serialize a dateutil.tz.tzlocal object?

I want to capture a timestamp and the current timezone and serialize it into a file (in JSON or YAML, but that's not really my question) for later retrieval on a different computer.
The timestamp is easy, I'll just use time.time().
For getting the current timezone, I read another SO question and it seems appropriate to use dateutil.tz.tzlocal
to get the current timezone.
Now I just need to figure out how to serialize it. The name is easy, that's just a string, but the offset seems to be weird; I was expecting just a number:
import time
import datetime
import dateutil
now = datetime.datetime.utcfromtimestamp(time.time())
tzlocal = dateutil.tz.tzlocal()
print tzlocal.tzname(now)
print tzlocal.utcoffset(now)
but this prints
US Mountain Standard Time
-1 day, 17:00:00
and the result of utcoffset appears to be an object. How do I just get the number?
Oh, never mind, tzlocal.utcoffset(now) returns a datetime.timedelta and I can just call total_seconds():
import time
import datetime
import dateutil
import json
now = datetime.datetime.utcfromtimestamp(time.time())
tzlocal = dateutil.tz.tzlocal()
info_str = json.dumps(dict(name=tzlocal.tzname(now),
offset=tzlocal.utcoffset(now).total_seconds()))
print info_str
which prints (on my PC)
{"name": "US Mountain Standard Time", "offset": -25200.0}
I'm not sure what your application is but as a default I recommend serializing to ISO 8601 timestamps with a time zone offset. Even better, convert to UTC first... this makes things easier for humans who happen to browse the serialized data, because they don't have to do the date math in their head.
There may be performance reasons for sticking with numeric timestamps, but I'd want proof this was a bottleneck in my application before giving up the human-readable bonus of ISO timestamps.

How can I convert an RFC 822 timestamp into a human readable format in Python?

Does anyone know of a Python module that will convert an RFC 822 timestamp into a human readable format (like Twitter does) in Python?
I found parsedatetime, which seems to do the reverse.
In python, you can use rfc822 module. This module provides the parsedate method.
Attempts to parse a date according to the rules in RFC 2822.
However, this module is deprecated.
Deprecated since version 2.3: The email package should be used in preference to the rfc822 module. This module is present only to maintain backward compatibility, and has been removed in 3.0.
According to this comment, it's better to use the parsedate method from the email.utils module.
email.utils.parsedate(date)
EDIT:
Example code :
import email.utils
from time import mktime
from datetime import datetime
example_date = "Sat, 02 Mar 2011 15:00:00"
date_parsed = email.utils.parsedate(example_date)
dt = datetime.fromtimestamp(mktime(date_parsed))
today = datetime.today()
diff_date = today - dt # timedelta object
print "%s days, %s hours ago" \
% (diff_date.days, diff_date.seconds / 3600)
Output (for now) :
31 days, 2 hours ago
datetime.strptime will turn the times stamp into a datetime object which you can format with datetime.strftime
http://docs.python.org/library/datetime.html#strftime-strptime-behavior
See
http://docs.python.org/library/rfc822.html#rfc822.parsedate

How can I get a human-readable timezone name in Python?

In a Python project I'm working on, I'd like to be able to get a "human-readable" timezone name of the form America/New_York, corresponding to the system local timezone, to display to the user. Every piece of code I've seen that accesses timezone information only returns either a numeric offset (-0400) or a letter code (EDT) or sometimes both. Is there some Python library that can access this information, or if not that, convert the offset/letter code into a human-readable name?
If there's more than one human-readable name corresponding to a particular timezone, either a list of the possible results or any one of them is fine, and if there is no human-readable name corresponding to the current time zone, I'll take either an exception or None or [] or whatever.
A clarification: I don't remember exactly what I had in mind when I originally wrote this question, but I think what I really wanted was a way to turn a timezone into a human-readable name. I don't think this question was meant to focus on how to get the system local timezone specifically, but for the specific use case I had in mind, it just happened that the local timezone was the one I wanted the name for. I'm not editing the bit about the local timezone out of the question because there are answers focusing on both aspects.
The following generates a defaultdict mapping timezone offsets (e.g. '-0400') and abbreviations (e.g. 'EDT') to common geographic timezone names (e.g. 'America/New_York').
import os
import dateutil.tz as dtz
import pytz
import datetime as dt
import collections
result = collections.defaultdict(list)
for name in pytz.common_timezones:
timezone = dtz.gettz(name)
now = dt.datetime.now(timezone)
offset = now.strftime('%z')
abbrev = now.strftime('%Z')
result[offset].append(name)
result[abbrev].append(name)
for k, v in result.items():
print(k, v)
Note that timezone abbreviations can have vastly different meanings. For example, 'EST' could stand for Eastern Summer Time (UTC+10) in Australia, or Eastern Standard Time (UTC-5) in North America.
Also, the offsets and abbreviations may change for regions that use daylight standard time. So saving the static dict may not provide the correct timezone name 365 days a year.
I'd like to be able to get a "human-readable" timezone name of the form America/New_York, corresponding to the system local timezone, to display to the user.
There is tzlocal module that returns a pytz tzinfo object (before tzlocal 3.0 version) that corresponds to the system local timezone:
#!/usr/bin/env python
import tzlocal # $ pip install tzlocal
# display "human-readable" name (tzid)
print(tzlocal.get_localzone_name())
# Example Results:
# -> Europe/Moscow
# -> America/Chicago
To answer the question in the title (for people from google), you could use %Z%z to print the local time zone info:
#!/usr/bin/env python
import time
print(time.strftime('%Z%z'))
# Example Results:
# -> MSK+0300
# -> CDT-0500
It prints the current timezone abbreviation and the utc offset corresponding to your local timezone.
http://pytz.sourceforge.net/ may be of help. If nothing else, you may be able to grab a list of all of the timezones and then iterate through until you find one that matches your offset.
This may not have been around when this question was originally written, but here is a snippet to get the time zone official designation:
>>> eastern = timezone('US/Eastern')
>>> eastern.zone
'US/Eastern'
Further, this can be used with a non-naive datetime object (aka a datetime where the actual timezone has been set using pytz.<timezone>.localize(<datetime_object>) or datetime_object.astimezone(pytz.<timezone>) as follows:
>>> import datetime, pytz
>>> todaynow = datetime.datetime.now(tz=pytz.timezone('US/Hawaii'))
>>> todaynow.tzinfo # turned into a string, it can be split/parsed
<DstTzInfo 'US/Hawaii' HST-1 day, 14:00:00 STD>
>>> todaynow.strftime("%Z")
'HST'
>>> todaynow.tzinfo.zone
'US/Hawaii'
This is, of course, for the edification of those search engine users who landed here. ... See more at the pytz module site.
If you want only literally what you asked for, "the timezone name of the form America/New_York, corresponding to the system local timezone", and if you only care about Linux (and similar), then this should do the job:
import os
import os.path
import sys
def main(argv):
tzname = os.environ.get('TZ')
if tzname:
print tzname
elif os.path.exists('/etc/timezone'):
print file('/etc/timezone').read()
else:
sys.exit(1)
if __name__ == '__main__':
main(sys.argv)
Of course it would be nicer to have a library that encapsulates this in a cleaner way, and that perhaps handles the other cases you mention in comments like already having a tzinfo object. I think you can do that with pytz mentioned by Amber but it's not obvious to me just how...
Check out python-dateutil
py> from dateutil.tz import *
py> ny = gettz('America/New York')
py> ny._filename
'/usr/share/zoneinfo/America/New_York'
py> ny._filename.split('/', 4)[-1]
'America/New_York'
# use tzlocal library
from tzlocal import get_localzone
current_timezone = get_localzone()
zone = current_timezone.zone
print(zone)
Working with the latest version of tzlocal which is 4.1 as of today, tzlocal.get_localzone().key produces the following error: AttributeError: '_PytzShimTimezone' object has no attribute 'key'. But tzlocal.get_localzone().zone works lovely.

how to find time at particular timezone from anywhere

I need to know the current time at CDT when my Python script is run. However this script will be run in multiple different timezones so a simple offset won't work.
I only need a solution for Linux, but a cross platform solution would be ideal.
pytz or dateutil.tz is the trick here. Basically it's something like this:
>>> from pytz import timezone
>>> mytz = timezone('Europe/Paris')
>>> yourtz = timezone('US/Eastern')
>>> from datetime import datetime
>>> now = datetime.now(mytz)
>>> alsonow = now.astimezone(yourtz)
The difficulty actually lies in figuring out which timezone you are in. dateutil.tz is better at that.
>>> from dateutil.tz import tzlocal, gettz
>>> mytz = tzlocal()
>>> yourtz = gettz('US/Eastern')
If you want all the nitty gritty details of why timezones are evil, they are here:
http://regebro.wordpress.com/2007/12/18/python-and-time-zones-fighting-the-beast/
http://regebro.wordpress.com/2008/05/10/python-and-time-zones-part-2-the-beast-returns/
http://regebro.wordpress.com/2008/05/13/thanks-for-the-testing-help-conclusions/
You can use time.gmtime() to get time GMT (UTC) from any machine no matter the timezone, then you can apply your offset.
A simple offset will work, you just need to offset from UTC.
Using datetime you can get the current utc (gmt) time and use datetime objects:
datetime.datetime.utcnow() - Provides time at UTC
datetime.datetime.now() - Provides time at local machine
To get the CT time from any system you need to know the CT time offset from UTC.
Then to account for daylight savings time code a function to get the current offset.
>>> import datetime
>>> utc = datetime.datetime.utcnow()
>>> current_ct_offset = get_current_ct_offset()
>>> ct_datetime = utc + datetime.timedelta(hours=current_ct_offset)
I could be overlooking something here, but if your only concerned about one timezone and your not doing tz name handling, it's pretty straight forward.

Categories