[time zone] support for TZDatetime and TZTime data type
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 01 Apr 2011 15:04:47 +0200
changeset 7166 dde161937d3e
parent 7165 b817d44cb606
child 7167 fd502219eb76
[time zone] support for TZDatetime and TZTime data type Should be usable and cause no crash, though some stuff may still be refined (test value generation, display in views/forms...) Proprify some data structures dealing with yams base types along the way and adding warning when some were missing (eg Interval usually).
__pkginfo__.py
cwvreg.py
debian/control
devtools/fill.py
misc/migration/3.12.0_Any.py
server/migractions.py
server/sources/rql2sql.py
server/sqlutils.py
server/test/data/schema.py
server/test/data/sources_fti
server/test/data/sources_postgres
server/test/unittest_fti.py
server/test/unittest_postgres.py
server/test/unittest_querier.py
server/test/unittest_rql2sql.py
test/unittest_schema.py
uilib.py
web/formfields.py
web/test/unittest_application.py
web/views/owl.py
web/views/plots.py
web/views/sparql.py
web/views/xmlrss.py
--- a/__pkginfo__.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/__pkginfo__.py	Fri Apr 01 15:04:47 2011 +0200
@@ -43,7 +43,7 @@
     'logilab-common': '>= 0.55.2',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.28.0',
-    'yams': '>= 0.30.4',
+    'yams': '>= 0.32.0',
     'docutils': '>= 0.6',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
@@ -52,7 +52,7 @@
     'Twisted': '',
     # XXX graphviz
     # server dependencies
-    'logilab-database': '>= 1.4.0',
+    'logilab-database': '>= 1.5.0',
     'pysqlite': '>= 2.5.5', # XXX install pysqlite2
     }
 
--- a/cwvreg.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/cwvreg.py	Fri Apr 01 15:04:47 2011 +0200
@@ -839,18 +839,24 @@
         return self['views'].select(__vid, req, rset=rset, **kwargs)
 
 
+import decimal
 from datetime import datetime, date, time, timedelta
 
-YAMS_TO_PY = {
-    'Boolean':  bool,
+YAMS_TO_PY = { # XXX unify with yams.constraints.BASE_CONVERTERS?
     'String' :  unicode,
+    'Bytes':    Binary,
     'Password': str,
-    'Bytes':    Binary,
+
+    'Boolean':  bool,
     'Int':      int,
     'Float':    float,
-    'Date':     date,
-    'Datetime': datetime,
-    'Time':     time,
-    'Interval': timedelta,
+    'Decimal':  decimal.Decimal,
+
+    'Date':       date,
+    'Datetime':   datetime,
+    'TZDatetime': datetime,
+    'Time':       time,
+    'TZTime':     time,
+    'Interval':   timedelta,
     }
 
--- a/debian/control	Fri Apr 01 14:38:16 2011 +0200
+++ b/debian/control	Fri Apr 01 15:04:47 2011 +0200
@@ -33,7 +33,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.4.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.5.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
 Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
 Description: server part of the CubicWeb framework
  CubicWeb is a semantic web application framework.
@@ -97,7 +97,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.32.0), python-rql (>= 0.28.0), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/fill.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/devtools/fill.py	Fri Apr 01 15:04:47 2011 +0200
@@ -152,6 +152,8 @@
         base = datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
         return self._constrained_generate(entity, attrname, base, timedelta(hours=1), index)
 
+    generate_tzdatetime = generate_datetime # XXX implementation should add a timezone
+
     def generate_date(self, entity, attrname, index):
         """generates a random date (format is 'yyyy-mm-dd')"""
         base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365))
@@ -166,6 +168,8 @@
         """generates a random time (format is ' HH:MM')"""
         return time(11, index%60) #'11:%02d' % (index % 60)
 
+    generate_tztime = generate_time # XXX implementation should add a timezone
+
     def generate_bytes(self, entity, attrname, index, format=None):
         fakefile = Binary("%s%s" % (attrname, index))
         fakefile.filename = u"file_%s" % attrname
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.12.0_Any.py	Fri Apr 01 15:04:47 2011 +0200
@@ -0,0 +1,2 @@
+add_entity_type('TZDatetime')
+add_entity_type('TZTime')
--- a/server/migractions.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/migractions.py	Fri Apr 01 15:04:47 2011 +0200
@@ -758,9 +758,9 @@
         targeted type is known
         """
         instschema = self.repo.schema
-        assert not etype in instschema, \
+        eschema = self.fs_schema.eschema(etype)
+        assert eschema.final or not etype in instschema, \
                '%s already defined in the instance schema' % etype
-        eschema = self.fs_schema.eschema(etype)
         confirm = self.verbosity >= 2
         groupmap = self.group_mapping()
         cstrtypemap = self.cstrtype_mapping()
--- a/server/sources/rql2sql.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/sources/rql2sql.py	Fri Apr 01 15:04:47 2011 +0200
@@ -50,7 +50,9 @@
 __docformat__ = "restructuredtext en"
 
 import threading
+from datetime import datetime, time
 
+from logilab.common.date import utcdatetime, utctime
 from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY
 
 from rql import BadRQLQuery, CoercionError
@@ -1171,6 +1173,14 @@
                 _id = value
                 if isinstance(_id, unicode):
                     _id = _id.encode()
+                # convert timestamp to utc.
+                # expect SET TiME ZONE to UTC at connection opening time.
+                # This shouldn't change anything for datetime without TZ.
+                value = self._args[_id]
+                if isinstance(value, datetime) and value.tzinfo is not None:
+                    self._query_attrs[_id] = utcdatetime(value)
+                elif isinstance(value, time) and value.tzinfo is not None:
+                    self._query_attrs[_id] = utctime(value)
         else:
             _id = str(id(constant)).replace('-', '', 1)
             self._query_attrs[_id] = value
--- a/server/sqlutils.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/sqlutils.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -25,7 +25,7 @@
 
 from logilab import database as db, common as lgc
 from logilab.common.shellutils import ProgressBar
-from logilab.common.date import todate, todatetime
+from logilab.common.date import todate, todatetime, utcdatetime, utctime
 from logilab.database.sqlgen import SQLGenerator
 
 from cubicweb import Binary, ConfigurationError
@@ -274,10 +274,15 @@
                         value = crypt_password(value)
                     value = self._binary(value)
                 # XXX needed for sqlite but I don't think it is for other backends
-                elif atype == 'Datetime' and isinstance(value, date):
+                # Note: use is __class__ since issubclass(datetime, date)
+                elif atype in ('Datetime', 'TZDatetime') and value.__class__ is date:
                     value = todatetime(value)
                 elif atype == 'Date' and isinstance(value, datetime):
                     value = todate(value)
+                elif atype == 'TZDatetime' and getattr(value, 'tzinfo', None):
+                    value = utcdatetime(value)
+                elif atype == 'TZTime' and getattr(value, 'tzinfo', None):
+                    value = utctime(value)
                 elif isinstance(value, Binary):
                     value = self._binary(value.getvalue())
             attrs[SQL_PREFIX+str(attr)] = value
@@ -326,3 +331,10 @@
 
 sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
 sqlite_hooks.append(init_sqlite_connexion)
+
+
+def init_postgres_connexion(cnx):
+    cnx.cursor().execute('SET TIME ZONE UTC')
+
+postgres_hooks = SQL_CONNECT_HOOKS.setdefault('postgres', [])
+postgres_hooks.append(init_postgres_connexion)
--- a/server/test/data/schema.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/test/data/schema.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,7 +18,7 @@
 
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, RichString, String, Int, Float,
-                            Boolean, Datetime)
+                            Boolean, Datetime, TZDatetime)
 from yams.constraints import SizeConstraint
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLUniqueConstraint,
@@ -114,6 +114,7 @@
     tel    = Int()
     fax    = Int()
     datenaiss = Datetime()
+    tzdatenaiss = TZDatetime()
     test   = Boolean(__permissions__={
         'read': ('managers', 'users', 'guests'),
         'update': ('managers',),
--- a/server/test/data/sources_fti	Fri Apr 01 14:38:16 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-[system]
-
-db-driver   = postgres
-db-host     = localhost
-db-port     = 
-adapter     = native
-db-name     = cw_fti_test
-db-encoding = UTF-8
-db-user     = syt
-db-password = syt
-
-[admin]
-login = admin
-password = gingkow
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/sources_postgres	Fri Apr 01 15:04:47 2011 +0200
@@ -0,0 +1,14 @@
+[system]
+
+db-driver   = postgres
+db-host     = localhost
+db-port     = 5433
+adapter     = native
+db-name     = cw_fti_test
+db-encoding = UTF-8
+db-user     = syt
+db-password = syt
+
+[admin]
+login = admin
+password = gingkow
--- a/server/test/unittest_fti.py	Fri Apr 01 14:38:16 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-from __future__ import with_statement
-
-import socket
-
-from logilab.common.testlib import SkipTest
-
-from cubicweb.devtools import ApptestConfiguration
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.selectors import is_instance
-from cubicweb.entities.adapters import IFTIndexableAdapter
-
-AT_LOGILAB = socket.gethostname().endswith('.logilab.fr')
-
-
-class PostgresFTITC(CubicWebTC):
-    config = ApptestConfiguration('data', sourcefile='sources_fti')
-
-    @classmethod
-    def setUpClass(cls):
-        if not AT_LOGILAB:
-            raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
-
-    def test_occurence_count(self):
-        req = self.request()
-        c1 = req.create_entity('Card', title=u'c1',
-                               content=u'cubicweb cubicweb cubicweb')
-        c2 = req.create_entity('Card', title=u'c3',
-                               content=u'cubicweb')
-        c3 = req.create_entity('Card', title=u'c2',
-                               content=u'cubicweb cubicweb')
-        self.commit()
-        self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                          [[c1.eid], [c3.eid], [c2.eid]])
-
-
-    def test_attr_weight(self):
-        class CardIFTIndexableAdapter(IFTIndexableAdapter):
-            __select__ = is_instance('Card')
-            attr_weight = {'title': 'A'}
-        with self.temporary_appobjects(CardIFTIndexableAdapter):
-            req = self.request()
-            c1 = req.create_entity('Card', title=u'c1',
-                                   content=u'cubicweb cubicweb cubicweb')
-            c2 = req.create_entity('Card', title=u'c2',
-                                   content=u'cubicweb cubicweb')
-            c3 = req.create_entity('Card', title=u'cubicweb',
-                                   content=u'autre chose')
-            self.commit()
-            self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                              [[c3.eid], [c1.eid], [c2.eid]])
-
-    def test_entity_weight(self):
-        class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
-            __select__ = is_instance('Personne')
-            entity_weight = 2.0
-        with self.temporary_appobjects(PersonneIFTIndexableAdapter):
-            req = self.request()
-            c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
-            c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
-            c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
-            self.commit()
-            self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                              [[c1.eid], [c3.eid], [c2.eid]])
-
-
-if __name__ == '__main__':
-    from logilab.common.testlib import unittest_main
-    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_postgres.py	Fri Apr 01 15:04:47 2011 +0200
@@ -0,0 +1,77 @@
+from __future__ import with_statement
+
+import socket
+from datetime import datetime
+
+from logilab.common.testlib import SkipTest
+
+from cubicweb.devtools import ApptestConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.selectors import is_instance
+from cubicweb.entities.adapters import IFTIndexableAdapter
+
+AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') # XXX
+
+from unittest_querier import FixedOffset
+
+class PostgresFTITC(CubicWebTC):
+    config = ApptestConfiguration('data', sourcefile='sources_postgres')
+
+    @classmethod
+    def setUpClass(cls):
+        if not AT_LOGILAB: # XXX here until we can raise SkipTest in setUp to detect we can't connect to the db
+            raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
+
+    def test_occurence_count(self):
+        req = self.request()
+        c1 = req.create_entity('Card', title=u'c1',
+                               content=u'cubicweb cubicweb cubicweb')
+        c2 = req.create_entity('Card', title=u'c3',
+                               content=u'cubicweb')
+        c3 = req.create_entity('Card', title=u'c2',
+                               content=u'cubicweb cubicweb')
+        self.commit()
+        self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                          [[c1.eid], [c3.eid], [c2.eid]])
+
+
+    def test_attr_weight(self):
+        class CardIFTIndexableAdapter(IFTIndexableAdapter):
+            __select__ = is_instance('Card')
+            attr_weight = {'title': 'A'}
+        with self.temporary_appobjects(CardIFTIndexableAdapter):
+            req = self.request()
+            c1 = req.create_entity('Card', title=u'c1',
+                                   content=u'cubicweb cubicweb cubicweb')
+            c2 = req.create_entity('Card', title=u'c2',
+                                   content=u'cubicweb cubicweb')
+            c3 = req.create_entity('Card', title=u'cubicweb',
+                                   content=u'autre chose')
+            self.commit()
+            self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                              [[c3.eid], [c1.eid], [c2.eid]])
+
+    def test_entity_weight(self):
+        class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
+            __select__ = is_instance('Personne')
+            entity_weight = 2.0
+        with self.temporary_appobjects(PersonneIFTIndexableAdapter):
+            req = self.request()
+            c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
+            c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
+            c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
+            self.commit()
+            self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                              [[c1.eid], [c3.eid], [c2.eid]])
+
+
+    def test_tz_datetime(self):
+        self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+                     {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+        datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+        self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()
--- a/server/test/unittest_querier.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/test/unittest_querier.py	Fri Apr 01 15:04:47 2011 +0200
@@ -18,7 +18,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
 """
-from datetime import date, datetime
+from datetime import date, datetime, timedelta, tzinfo
 
 from logilab.common.testlib import TestCase, unittest_main
 from rql import BadRQLQuery, RQLSyntaxError
@@ -32,6 +32,14 @@
 from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
 from unittest_session import Variable
 
+class FixedOffset(tzinfo):
+    def __init__(self, hours=0):
+        self.hours = hours
+    def utcoffset(self, dt):
+        return timedelta(hours=self.hours)
+    def dst(self, dt):
+        return timedelta(0)
+
 
 # register priority/severity sorting registered procedure
 from rql.utils import register_function, FunctionDescr
@@ -1215,11 +1223,11 @@
         self.assertEqual(rset.description, [('CWUser',)])
 
     def test_update_upassword(self):
-        cursor = self.pool['system']
         rset = self.execute("INSERT CWUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'})
         self.assertEqual(rset.description[0][0], 'CWUser')
         rset = self.execute("SET X upassword %(pwd)s WHERE X is CWUser, X login 'bob'",
                             {'pwd': 'tutu'})
+        cursor = self.pool['system']
         cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
@@ -1229,7 +1237,15 @@
         self.assertEqual(len(rset.rows), 1)
         self.assertEqual(rset.description, [('CWUser',)])
 
-    # non regression tests ####################################################
+    # ZT datetime tests ########################################################
+
+    def test_tz_datetime(self):
+        self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+                     {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+        datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+        self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+
+    # non regression tests #####################################################
 
     def test_nonregr_1(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
--- a/server/test/unittest_rql2sql.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/server/test/unittest_rql2sql.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1219,9 +1219,13 @@
             yield self._check, rql, sql
 
     def _checkall(self, rql, sql):
+        if isinstance(rql, tuple):
+            rql, args = rql
+        else:
+            args = None
         try:
             rqlst = self._prepare(rql)
-            r, args, cbs = self.o.generate(rqlst)
+            r, args, cbs = self.o.generate(rqlst, args)
             self.assertEqual((r.strip(), args), sql)
         except Exception, ex:
             print rql
@@ -1233,7 +1237,7 @@
         return
 
     def test1(self):
-        self._checkall('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s',
+        self._checkall(('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s', {'x': None}),
                        ("""SELECT COUNT(T1.C0) FROM (SELECT _RDEF.cw_eid AS C0
 FROM cw_CWAttribute AS _RDEF
 WHERE _RDEF.cw_relation_type=%(x)s
@@ -1244,7 +1248,7 @@
                        )
 
     def test2(self):
-        self._checkall('Any X WHERE C comments X, C eid %(x)s',
+        self._checkall(('Any X WHERE C comments X, C eid %(x)s', {'x': None}),
                        ('''SELECT rel_comments0.eid_to
 FROM comments_relation AS rel_comments0
 WHERE rel_comments0.eid_from=%(x)s''', {})
--- a/test/unittest_schema.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/test/unittest_schema.py	Fri Apr 01 15:04:47 2011 +0200
@@ -169,7 +169,7 @@
                              'Password', 'Personne',
                              'RQLExpression',
                              'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
-                             'TZDatetime', 'TZTime', 'Tag', 'Time', 'Transition', 'TrInfo',
+                             'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo',
                              'Workflow', 'WorkflowTransition']
         self.assertListEqual(sorted(expected_entities), entities)
         relations = sorted([str(r) for r in schema.relations()])
--- a/uilib.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/uilib.py	Fri Apr 01 15:04:47 2011 +0200
@@ -62,9 +62,9 @@
         return value
     if attrtype == 'Date':
         return ustrftime(value, req.property_value('ui.date-format'))
-    if attrtype == 'Time':
+    if attrtype in ('Time', 'TZTime'):
         return ustrftime(value, req.property_value('ui.time-format'))
-    if attrtype == 'Datetime':
+    if attrtype in ('Datetime', 'TZDatetime'):
         if displaytime:
             return ustrftime(value, req.property_value('ui.datetime-format'))
         return ustrftime(value, req.property_value('ui.date-format'))
@@ -72,8 +72,9 @@
         if value:
             return req._('yes')
         return req._('no')
-    if attrtype == 'Float':
+    if attrtype in ('Float', 'Decimal'):
         value = req.property_value('ui.float-format') % value
+    # XXX Interval
     return unicode(value)
 
 
--- a/web/formfields.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/formfields.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1200,14 +1200,19 @@
 
 
 FIELDS = {
-    'Boolean':  BooleanField,
+    'String' :  StringField,
     'Bytes':    FileField,
-    'Date':     DateField,
-    'Datetime': DateTimeField,
+    'Password': PasswordField,
+
+    'Boolean':  BooleanField,
     'Int':      IntField,
     'Float':    FloatField,
     'Decimal':  StringField,
-    'Password': PasswordField,
-    'String' :  StringField,
-    'Time':     TimeField,
+
+    'Date':       DateField,
+    'Datetime':   DateTimeField,
+    'TZDatetime': DateTimeField,
+    'Time':       TimeField,
+    'TZTime':     TimeField,
+    # XXX implement 'Interval': TimeIntervalField,
     }
--- a/web/test/unittest_application.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/test/unittest_application.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/views/owl.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/views/owl.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -35,15 +35,19 @@
                 }
 
 OWL_TYPE_MAP = {'String': 'xsd:string',
-                'Datetime': 'xsd:dateTime',
                 'Bytes': 'xsd:byte',
-                'Float': 'xsd:float',
+                'Password': 'xsd:byte',
+
                 'Boolean': 'xsd:boolean',
                 'Int': 'xsd:int',
+                'Float': 'xsd:float',
+                'Decimal' : 'xsd:decimal',
+
                 'Date':'xsd:date',
+                'Datetime': 'xsd:dateTime',
+                'TZDatetime': 'xsd:dateTime',
                 'Time': 'xsd:time',
-                'Password': 'xsd:byte',
-                'Decimal' : 'xsd:decimal',
+                'TZTime': 'xsd:time',
                 'Interval': 'xsd:duration'
                 }
 
--- a/web/views/plots.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/views/plots.py	Fri Apr 01 15:04:47 2011 +0200
@@ -47,7 +47,7 @@
 @objectify_selector
 def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
     etypes = rset.description[0]
-    if etypes[0] not in ('Date', 'Datetime'):
+    if etypes[0] not in ('Date', 'Datetime', 'TZDatetime'):
         return 0
     for etype in etypes[1:]:
         if etype not in ('Int', 'Float'):
--- a/web/views/sparql.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/views/sparql.py	Fri Apr 01 15:04:47 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -77,17 +77,22 @@
 
 YAMS_XMLSCHEMA_MAPPING = {
     'String': 'string',
+
+    'Boolean': 'boolean',
     'Int': 'integer',
     'Float': 'float',
-    'Boolean': 'boolean',
+
     'Datetime': 'dateTime',
+    'TZDatetime': 'dateTime',
     'Date': 'date',
     'Time': 'time',
+    'TZTime': 'time',
+
     # XXX the following types don't have direct mapping
     'Decimal': 'string',
     'Interval': 'duration',
+    'Bytes': 'base64Binary',
     'Password': 'string',
-    'Bytes': 'base64Binary',
     }
 
 def xmlschema(yamstype):
--- a/web/views/xmlrss.py	Fri Apr 01 14:38:16 2011 +0200
+++ b/web/views/xmlrss.py	Fri Apr 01 15:04:47 2011 +0200
@@ -42,6 +42,8 @@
     'Date': lambda x: x.strftime('%Y-%m-%d'),
     'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'),
     'Time': lambda x: x.strftime('%H:%M:%S'),
+    'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
+    'TZTime': lambda x: x.strftime('%H:%M:%S'),
     'Interval': lambda x: x.days * 60*60*24 + x.seconds,
     }