changeset 244:e6ed4e03074f stable

Fix timezone handling in database queries.
author Daniele Nicolodi <daniele@grinta.net>
date Fri, 16 Dec 2011 19:09:37 +0100
parents 826a29703975
children ce09aed4a90b
files src/ltpdarepo/__init__.py src/ltpdarepo/utils.py src/ltpdarepo/views/browse.py
diffstat 3 files changed, 67 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/src/ltpdarepo/__init__.py	Fri Dec 16 19:09:37 2011 +0100
+++ b/src/ltpdarepo/__init__.py	Fri Dec 16 19:09:37 2011 +0100
@@ -16,6 +16,7 @@
 import dateutil.tz
 
 from .security import secure, require, authenticate
+from .utils import datetimetz
 from .views.browse import module as browse
 from .views.databases import module as databases
 from .views.feed import url_for_atom_feed, module as feed
@@ -27,24 +28,26 @@
 SCHEMA = 31
 
 
-class datetimeutc(datetime):
-    # subclass of `datetime.datetime` with default string
-    # representation including the timezone name
-    def __str__(self):
-        return self.strftime('%Y-%m-%d %H:%M:%S %Z')
-
-
-# customize mysql types conversion for datetime fields to return
-# timezone aware objects in the UTC timezone
-def datetime_or_none_utc(s):
+# customize mysql types conversion from and to DATETIME fields to
+# return timezone aware datetime objects in the UTC timezone and
+# correclty convert timezone aware datetime objects to UTC timezone
+def datetime_or_none(s):
     value = converters.DateTime_or_None(s)
     if value is not None:
-        value = datetimeutc(value.year, value.month, value.day, value.hour,
-                            value.minute, value.second, value.microsecond,
-                            tzinfo=dateutil.tz.tzutc())
+        value = datetimetz(value.year, value.month, value.day, value.hour,
+                           value.minute, value.second, value.microsecond,
+                           tzinfo=dateutil.tz.tzutc())
     return value
+
+def datetime_to_literal(value, c):
+    if value.tzinfo is not None:
+        value = value.astimezone(dateutil.tz.tzutc())
+    return converters.DateTime2literal(value, c)
+
 conversions = converters.conversions.copy()
-conversions[mysql.constants.FIELD_TYPE.DATETIME] = datetime_or_none_utc
+conversions[mysql.constants.FIELD_TYPE.DATETIME] = datetime_or_none
+conversions[datetime] = datetime_to_literal
+conversions[datetimetz] = datetime_to_literal
 
 
 def before_request():
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ltpdarepo/utils.py	Fri Dec 16 19:09:37 2011 +0100
@@ -0,0 +1,40 @@
+# Copyright 2011 Daniele Nicolodi <nicolodi@science.unitn.it>
+#
+# This software may be used and distributed according to the terms of
+# the GNU Affero General Public License version 3 or any later version.
+
+
+from __future__ import absolute_import
+from datetime import *
+
+import dateutil.parser
+import dateutil.tz
+
+
+class datetimetz(datetime):
+    # subclass of `datetime.datetime` with default string
+    # representation including the timezone name
+    def __str__(self):
+        return self.strftime('%Y-%m-%d %H:%M:%S %Z')
+
+
+def parsedatetime(string):
+    # parse datetime string representation and returns a timezone
+    # aware subclass of datetime with default string representation
+    # including the timezone name
+
+    # parsing default is midnight today in UTC timezone
+    default = datetime.utcnow().replace(
+        tzinfo=dateutil.tz.tzutc(), hour=0, minute=0, second=0, microsecond=0)
+
+    value = dateutil.parser.parse(string, dayfirst=True, yearfirst=True, default=default)
+    return datetimetz(value.year, value.month, value.day, value.hour,
+                      value.minute, value.second, value.microsecond, value.tzinfo)
+
+
+def toDATETIME(value):
+    if not isinstance(value, datetime):
+        return value
+    if value.tzinfo is not None:
+        value = value.astimezone(dateutil.tz.tzutc())
+    return value.strftime('%Y-%m-%d %H:%M:%S')
--- a/src/ltpdarepo/views/browse.py	Fri Dec 16 19:09:37 2011 +0100
+++ b/src/ltpdarepo/views/browse.py	Fri Dec 16 19:09:37 2011 +0100
@@ -7,20 +7,18 @@
 from datetime import datetime
 from dateutil.relativedelta import relativedelta
 
-import dateutil.tz
-import dateutil.parser
-
-from flask import Blueprint, Markup, abort, g, request, render_template, json, make_response, url_for, redirect
+from flask import Blueprint, abort, g, request, render_template, make_response, url_for, redirect
 from MySQLdb.cursors import DictCursor
 from wtforms import Form
 from wtforms.fields import Field
 from wtforms.widgets import TextInput
 from wtforms.validators import ValidationError, Optional
 
+from ltpdarepo.database import Database
+from ltpdarepo.pagination import Pagination
+from ltpdarepo.query import Query
 from ltpdarepo.security import require, view
-from ltpdarepo.database import Database
-from ltpdarepo.query import Query
-from ltpdarepo.pagination import Pagination
+from ltpdarepo.utils import parsedatetime, toDATETIME
 
 try:
     from collections import OrderedDict
@@ -135,8 +133,8 @@
         self._end = None
 
     def timespan(self, start=None, end=None):
-        self._start = start
-        self._end = end
+        self._start = toDATETIME(start)
+        self._end = toDATETIME(end)
         return self
 
     @property
@@ -220,24 +218,6 @@
         return column, kind, values
 
 
-class datetimefield(datetime):
-    """Timezone aware subclass of `datetime.datetime` with a new
-    constructor that parses strings and a default string
-    representation including the timezone name."""
-
-    def __new__(cls, string):
-        # parsing default is midnight today in UTC timezone
-        default = datetime.utcnow().replace(
-            tzinfo=dateutil.tz.tzutc(), hour=0, minute=0, second=0, microsecond=0)
-
-        value = dateutil.parser.parse(string, dayfirst=True, yearfirst=True, default=default)
-        return datetime.__new__(cls, value.year, value.month, value.day, value.hour,
-                                value.minute, value.second, value.microsecond, value.tzinfo)
-
-    def __str__(self):
-        return self.strftime('%Y-%m-%d %H:%M:%S %Z')
-
-
 class Request(object):
     """Retrieves and validates query parameters from the request"""
 
@@ -246,7 +226,7 @@
                   'double': float,
                   'text': unicode,
                   'enum': unicode,
-                  'datetime': datetimefield}
+                  'datetime': parsedatetime}
 
     def __init__(self, formdata, columns, indexes={}):
         # incoming data
@@ -331,7 +311,7 @@
 
     def _value(self):
         if self.data:
-            return self.data.strftime('%Y-%m-%d %H:%M:%S %Z')
+            return str(self.data)
         return self.raw_data and u' '.join(self.raw_data) or u''
 
     def process_formdata(self, valuelist):
@@ -340,12 +320,8 @@
             if not datestr:
                 self.data = None
                 raise ValidationError(self.gettext(u'Input a datetime value'))
-
-            # parsing default is midnight today in UTC timezone
-            default = datetime.utcnow().replace(
-                tzinfo=dateutil.tz.tzutc(), hour=0, minute=0, second=0, microsecond=0)
             try:
-                self.data = dateutil.parser.parse(datestr, default=default, **self.parseargs)
+                self.data = parsedatetime(datestr)
             except ValueError:
                 self.data = None
                 raise ValidationError(self.gettext(u'Invalid datetime input'))