This Blog continues on http://aliafshar.github.io/blog

Friday, November 17, 2006

Python Dates

At first glance the datetime module in the python standard library (full documentation available at http://docs.python.org/lib/module-datetime.html appears as an insane mish-mash of things.

The types it makes available are date, time, datetime and timedelta. My initial confusion stemmed from the fact that the module name is the same as one of the type names (datetime and datetime.datetime) and this is one of the only times when I have thought that naming types in camel case is a good idea: datetime.DateTime might have been a saner approach. Nevertheless, we continue:

A datetime.time is a time, a datetime.date is a date, and a datetime.datetime is a date with a time. A datetime.timedelta is a difference between any of these times. Simple enough.

Usage of these types can be made easier by using their "alternative constructors" which are a set of class methods, and can in some cases be more useful than the actual constructor. For example:


>>> from datetime import date
>>> date(2000, 1, 1)
datetime.date(2000, 1, 1)
>>> date.today()
datetime.date(2006, 11, 17)


The actual constructor has the signature: date(year, month, day), but the alternate constructor that is very useful for us is the date.today() call, which returns today's date (as in the example).

The datetime type is similar to the date type but also comes with time information. Here the alternative constructor that I find most used is not today(), but now(). For example:


>>> from datetime import datetime
>>> datetime.now()
datetime.datetime(2006, 11, 17, 10, 1, 49, 913312)


The actual constructor here would want year, month, day and optional extra arguments for hour, minute, second and microsecond. This particular constructor (now) would be really useful for things such as SQLObject schemas, like:


>>> from sqlobject import SQLObject, DateCol
>>> class MySchema(SQLObject):
... date_of_event = DateCol(default=datetime.now)
...
>>>


SQLObject has this nice feature of calling a callable default value, and in this case creating that object would give it a date_of_event attribute representing the time and date it was created. A really useful pattern.

So we come to the time type. And remember we are datetime.time, not time.anything - the time module is something else entirely, and where this starts to become a bit more painful. datetime.time accepts (as you would expect) hour, minute, second, microsecond. But amazingly has no alternative constructors like the other types to return the current time. To do that we need to perform a small amount of trickery:


>>> from datetime import datetime
>>> datetime.now().time()
datetime.time(10, 20, 46, 720865)


Oh I know it's a bit ghastly, but it works. Now it wouldn't quite work if you wanted to use it for an SQLObject schema as above, and you would need to use a function or inline lambda, like so:


time_of_event = TimeCol(default=lambda: datetime.now().time())


To make sure the datetime object was called at the right time. Hoops are only fun if you have to jump through them!

Shall we leave it there, or can we stretch ourselves to a short discussion of timedeltas? If we keep it simple. A timedelta is a difference between two dates, or datetimes, but not two times. And the difference has to be calculate with the same types, for example:


>>> dt1 = datetime(2000, 1, 1)
>>> dt2 = datetime.now()
>>> delta = dt2 - dt1
>>> delta.days
2512


Apparently that is the number of days since the turn of the millennium. Sounds good to me.