Python and SQLite3 Dates
Recently, I had to insert some data into an SQLite3 database. One of the columns was a Date. Since SQLite3 doesn’t support a direct date column but supports convenient functions for handling date, one’s left with a question, what’s a best choice for storing dates in SQLite3 DB. Of course integers are perfect and SQLite supports them. But when you want to store date as integers, specifically something like Unix timestamps, one has to deal with a number of issues, most notably is the timezone. The time offsets could mess things around. I’d had this trouble in the past when I developed code on my Laptop in IST timezone and then deployed it on a server in UK. I had somehow figured around it back then, mostly in a hacky way and got it going and I faced same problem again! (for a very related purpose). I thought it’s worthwhile to compile this at one place for my reference.
I’d be using python time
and datetime
modules for this. Python’s time
module has got a function time.time()
, which gives number of seconds since Unix Epoch (1st Jan 1970 00:00 UTC). Note, this is just a float and if the local time is synced on machines - doesn’t matter what timezone one is in time.time()
is always going to return same float. So if I have to store a date in SQLite3, all I have to do is - really save this number and use various date-time functions to read it as a date. Also dealing with numbers is a lot easier. The catch is the python’s functions and the timezones make it a little tricky.
So here’s the problem statement - I have a date string say in the format ‘YYYY-MM-DD’. I have to store this as a number in a date field in database. And subequently, I want to be able to read this number and get back the same date in the above format. Regardless of which timezone I run my code in.
So I quickly wrote a script to do this. I have used time.mktime
to get the right timestamp. This timestamp will be stored in the DB. The trouble with time.mktime
is, it doesn’t accept timezone as an argument but only works on local timezone. Interestingly, one can use time.tzset
to set a timezone to appropriate value. This question on SO discusses the same issue and proposes an approach - where you set TZ
environment variable and then call time.tzset
to use the ‘UTC’ timezone, then call a time.mktime
to get the right ~tuple~ timestamp. This can then be used with something like datetime.datetime.utcfromtimestamp
again (or SQLite Datetime function with ‘unixepoch’ modifier). Below’s the code that does this.
# A simple script that uses mktime with right environment set for UTC timestamps
import os
import time
from datetime import datetime as dt
import sys
def get_ts_for_datestr(date='2015-10-04', fmt='%Y-%m-%d'):
if os.environ.has_key('TZ'):
old_tz = os.environ['TZ']
else:
old_tz = None
os.environ['TZ'] = ''
time.tzset()
ts = time.mktime(time.strptime(date,fmt))
if old_tz is not None:
os.environ['TZ'] = old_tz
else:
os.environ.pop('TZ')
time.tzset()
return ts
def get_datestr_from_ts(ts, fmt='%Y-%m-%d'):
d = dt.utcfromtimestamp(ts)
return time.strftime(fmt, d.timetuple())
# To run the script, don't forget to pass the date as command like argument
print get_datestr_from_ts(time.mktime(time.strptime(sys.argv[1],'%Y-%m-%d')), '%Y-%m-%d')
print get_datestr_from_ts(get_ts_for_datestr(sys.argv[1],'%Y-%m-%d'), '%Y-%m-%d')
Most of the code is self explanatory. In place of the get_datestr_from_ts
above, we could directly use SQlite functions as follows
Datetime(1443978000, 'unixepoch') # Does what we've done above
Datetime(1443978000, 'unixepoch', 'localtime') # takes into consideration local time as well
Hopefully, I don’t need to ‘understand’ this again and can just refer to this post.
Edit 1:
Whatever complicated stuff we are doing above in get_ts_from_datestr
can be done by subtracting the time.timezone
offset from the output of time.time
, but I didn’t try it in many timezones especially with daylight saving. The approach above should regardless work, though it could be a bit complicated.