django timezone view handling

One simple way to handle user-time-zone localization in an app is to always store the timestamp in UTC, and localize viewing/editing of that timestamp in a specific timezone.

By default, Django traffics in the single timezone.

Timezone handling appear incomplete. The two options you’ll run across are this snippet for a timezone-localizing filter and django-timezones.

django-timezones has support for a TimeZone model/view field, and a model/view LocalizedDateTimeField. The View field, however, will only do the parse-time conversion of the datetime value in to the settings-defined TZ. This only solves half of the problem, since you still need to convert the settings-defined TZ into the user’s TZ, and there’s no component for that.

The following is a LocalizedDateTimeInput widget which will handle this last step, building on django.forms.DateTimeInput and django-timezones’ utility code:

from django import forms
from timezones.utils import adjust_datetime_to_timezone
class LocalizedDateTimeInput (forms.DateTimeInput):
    def __init__(self, tz):
        self._tz = tz
        super(LocalizedDateTimeInput, self).__init__()
    def render(self, name, value, attrs=None):
        if isinstance(value, datetime):
            value = adjust_datetime_to_timezone(value, 'UTC', self._tz)
        # @fixme: output the string rep of the timezone, probably after the <input />
        return super(LocalizedDateTimeInput, self).render(name, value, attrs)

Unfortunately, you’ll need to add a level of indirection to your view Form constructors to bind the ‘tz’ argument on the widget. Instead of a normal:

class MumbleForm (forms.Form):
    name = forms.CharField()
    date = forms.DateTimeField()

You’ll want to do something like:

def MumbleForm(user, *args, **kwargs):
    tz = settings.TIME_ZONE
    if user and hasattr(user, 'get_profile'):
        tz = user.get_profile().timezone
    class _MumbleForm (forms.Form):
        name = forms.CharField()
        date = LocalizedDateTimeField(tz, widget=LocalizedDateTimeInput(tz))
    return _MumbleForm(*args, **kwargs)

You can see more usage examples from the brew-journal commit.

Any tips or improvements welcome, of course.

Update, 2009-11-28: I finally got around to moving to using mysql instead of sqlite for this project, and discovered that the above solution needs one more piece. The default LocalizedDatetimeField seems to return datetimes which are “[tz] aware” in python’s vernacular, but mysql complains that datetimes with a tz portion are not allowed. So, we add a SafeLocalizedDateTimeField that returns “naive” datetimes instead:

class SafeLocalizedDateTimeField (LocalizedDateTimeField):
    def clean(self, value):
        val = super(SafeLocalizedDateTimeField, self).clean(value)
        if val is not None:
            val = val.replace(tzinfo=None)
        return val