Python And Time Conversion

Posted:9/28/2013 3:13PM

Python And Time Conversion

Mike Mclain discusses Python 2.7.X and Time Zone Name Conversion with the datetime module

Preface:

I was recently working on a site-map generator for my little python web system and during the process (of reading about the sitemap protocol) I discovered that the time standard utilized by the protocol was based upon the W3C Datetime format.

While I will admit that both Datetime formats and Regular expressions have a nasty tendency to make me snarl (if not swear) and (as expected) implementing this particular attribute into my site-map generator turned out to be rather painful.

Now, I know what you are thinking... Mike, why on earth would this task be hard?!? Python has a assortment of modules that can easily achieve this simplistic task!

To which I would reply... Alas, when I originally implemented my web system I designed it to allow the user to specify the date by the following encoding scheme %m/%d/%Y %I:%M%p (I did this to allow backdating and to keep the transition between scrivener and the web system a metaphoric one way street), thus I opted to encode the datetime as %Y-%m-%d %H:%M:%S (to allow for date sorting in sqlite) rather than the W3C Datetime format, since (once again) timestamps make me snarl and I just wanted something that was not overly complicated.

Well, a nasty problem arose upon attempting to convert my simplistic date format into the expected W3C Datetime format, since the W3C Format is YYYY-MM-DDThh:mm:ssTZD and I had used Y-%m-%d %H:%M:%S thus (upon attempting to utilize the date conversion %Y-%m-%dT%H:%M:%S%z) the %z or Time Zone Name variable was returning null.

At this point I thought to myself, O.K., the Time Zone Name variable was not defined so no default value was assumed by python hence why it is reporting null (I will admit that I was hoping for something non-null to be assumed by python here). Likewise the python time manual seems to validate this assessment.

Further Exanimation:

Nevertheless, upon recognizing this particular attribute, I decided that I would simply augment the conversion string with a added Time Zone abbreviation variable --- yep a hack ---, reconvert the datetime into the W3C Format, and keep on trucking like so.

mydate = datetime.strptime(row["sdate"]+" est","%Y-%m-%d %H:%M:%S %Z")
w3date=mydate.strftime("%Y-%m-%dT%H:%M:%S%z")

Where row["sdate"] is in %Y-%m-%d %H:%M:%S format.

Well, initially this idea seem to be working (at least until I started getting does not match format '%Y-%m-%d %H:%M:%S %Z' errors) and attempts to look up valid terms for %Z was far from fruitful (as it appears that this term is a local time zone variable or OS dependent variable, and there are a number of complaints on the web about this particular module in general).

Thus, after a hour of searching the web and finding nothing, I got frustrated and decided to dig into the \lib\_strptime.py source module to find a answer by adding the following code:

for tz_names in self.locale_time.timezone:
    for tz in tz_names:
        print tz

to the def __init__(self): function and see what values of %Z I got back. Well, upon running my added code I got the following values of %Z back (at least for my computer and python distribution):

and upon trying to convert the string YYYY-MM-DDTHH:MM:SS eastern standard time, via:

mydate = datetime.strptime(row["sdate"]+" eastern standard time","%Y-%m-%d %H:%M:%S %Z")
w3date=mydate.strftime("%Y-%m-%dT%H:%M:%S%z")

the function successfully runs (as in it does not throw an error); however it fails to encode the time correctly as the %z of strftime is still printed as null!

Likewise, this is a very interesting observation because strptime will accept a named %Z by giving it a valid tz index within _strptime but this index fails to be associated with a valid tzinfo upon invoking a struct_time object via:

return (time.struct_time((year, month, day, hour, minute, second,weekday, julian, tz)), fraction)

So clearly, this basic python function need some improvement (something I am not willing to do myself) to either correctly load timezone information or fail if no tzinfo was associated with the struct_time. Yes I know most people would recommend a solution from either dateutil or pytz but (at this point) I was fueled by principle to make this approach work...

Yet on a more sane note, if you are planning on using the strptime command to perform a date conversion on data that has time zone information, I recommend you seek another module to perform this task (you might want to give pytz some consideration)!

Also note, if you are curious as to what values of %Z are supported on your machine and (unlike me) do not feel like editing the python core, the following code will print this information without requiring any python core modifications:

from _strptime import LocaleTime
locale_time = LocaleTime()
for tz_names in locale_time.timezone:
    for tz in tz_names:
        print tz

A Possible Solution:

To continue this discussion further, once I figured out that the timezone information fails to be associated with a valid tzinfo object (since apparently the online consensuses is datetime is deliberately a dumb object and needs a manual tzinfo assignment) I started looking for information about how to associate a tzinfo with a datetime.

After searching the web and the python manuals the following code manifested itself:

from datetime import datetime,tzinfo,timedelta
class TimeZoneInfo(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def dst(self, dt):
        return timedelta(hours=1) if self.isdst else timedelta(0)
        def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def tzname(self,dt):
        return self.name

BlogTime = TimeZoneInfo(-5,False,'BlogTime')
pydate = datetime.strptime(row["sdate"],"%Y-%m-%d %H:%M:%S")
pydate = pydate.replace(tzinfo=BlogTime)

and upon running this code:

w3date=pydate.strftime("%Y-%m-%dT%H:%M:%S%z")
print w3date

Results in 2013-09-27T19:35:00-0500, which almost matches the example W3C Datetime format of 1997-07-16T19:20:30+01:00.

Concluding Remarks:

So now that things are getting close to being correct (as 95% of programming is easy but the last 5% that will kill you) and given that the datetime module only supports timezone name %Z and the UTC offset %z, some slight modification to the code above is required to make everything W3C compliant.

Thus was this unholy string hack born:

w3date_A=pydate.strftime("%Y-%m-%dT%H:%M:%S")
w3date_B=pydate.strftime("%z")
w3date=w3date_A+ w3date_B[:3] + ':' + w3date_B[3:]

and this folks is why I hate timestamps! In conclusion, I do not recommend utilizing this method unless (like me) you only needed to augment a time zone to an existing date else (if you need to do anything else) save yourself a ton of trouble and seek out a better datetime method!

Enjoy!

Comments:

comments powered by Disqus