cubicweb #4848923 Use TZDateTime for creation_date and modification_date [validation pending]

I did this on my database, and here are the things I can tell about it. Beware that it is not a complete analysis of the matter, only the few things I did to get things apparently work.

  • metadata hooks

    The following code, in my cube, which only replace by datetime.utcnow(), seems to do the job:

    from datetime import datetime
    from cubicweb.hooks import metadata
    from cubicweb.predicates import yes
    class InitMetaAttrsHook(metadata.InitMetaAttrsHook):
        __select__ = metadata.InitMetaAttrsHook.__select__ & yes()
        def __call__(self):
            timestamp = datetime.utcnow()
            edited = self.entity.cw_edited
            if not edited.get('creation_date'):
                edited['creation_date'] = timestamp
            if not edited.get('modification_date'):
                edited['modification_date'] = timestamp
            if not self._cw.get_shared_data('do-not-insert-cwuri'):
                cwuri = u'%s%s' % (self._cw.base_url(), self.entity.eid)
                edited.setdefault('cwuri', cwuri)
    class UpdateMetaAttrsHook(metadata.UpdateMetaAttrsHook):
        __select__ = metadata.UpdateMetaAttrsHook.__select__ & yes()
        def __call__(self):
            if not self._cw.vreg.config.repairing:
                    'modification_date', datetime.utcnow())
  • conversion to utc:

    import cubicweb.server.sources.rql2sql
    import pytz
    def utcdatetime(dt):
        if dt.tzinfo is None:
            return dt
        return dt.astimezone(pytz.utc).replace(tzinfo=None) = utcdatetime
    logilab.database.utcdatetime = utcdatetime
    cubicweb.server.sources.rql2sql.utcdatetime = utcdatetime
  • for rqlquery:

    # Workaround issue with tz-ed dates in rql queries.
    import iso8601
    import datetime
    import rqlquery.filter
    def read_tzdatetime(value):
        if not value:
            return None
        if not isinstance(value, datetime.datetime):
            value = iso8601.parse_date(value)
        value = utcdatetime(value)
        return value
    rqlquery.filter.FilterParser.value_parser['TZDatetime'] = read_tzdatetime
  • DateTimeField. When dealing with TZDateTime, we like to see manipulate in localtime. Here is how we do it, but it is wrong because we do not use the actual localtime of the user. To find it, we can use and store it in the session. Meanwhile, we use only the Paris localtime:

    from babel.dates import get_timezone
    tz = get_timezone("Europe/Paris")
    utc = get_timezone("UTC")
    class TZDateTimeField(formfields.DateTimeField):
        def typed_value(self, form):
            value = super(TZDateTimeField, self).typed_value(form)
            if value:
                # From UTC to local
                value = tz.fromutc(value).replace(tzinfo=None)
            return value
        def _ensure_correctly_typed(self, form, value):
            value = super(TZDateTimeField, self)._ensure_correctly_typed(
                form, value)
            if value:
                # From local to UTC
                value =
            return value
    formfields.FIELDS['TZDatetime'] = TZDateTimeField
  • data migration

    The conversion from one type to another in postgres can be done like this:

    sql("SET TIME ZONE 'Europe/Paris'")
    for entity in schema.entities():
            change_attribute_type(entity.type, 'creation_date', 'TZDatetime')
            change_attribute_type(entity.type, 'modification_date', 'TZDatetime')
        except KeyError:
done in3.22.0
load left0.000
closed by#63d860a14a17 [schema] Use TZDatetime for creation_date and modification_date
patchUse UTC for creation_date and modification_date. [applied]