[FIX] reports: formatLang() should render datetime values in appropriate timezone
authorOlivier Dony <odo@openerp.com>
Wed, 15 Feb 2012 13:37:48 +0000 (14:37 +0100)
committerOlivier Dony <odo@openerp.com>
Wed, 15 Feb 2012 13:37:48 +0000 (14:37 +0100)
lp bug: https://launchpad.net/bugs/932170 fixed

bzr revid: odo@openerp.com-20120215133748-2iodxq0z1vqhyz13

openerp/osv/fields.py
openerp/report/report_sxw.py

index 43dca72..7c1761a 100644 (file)
@@ -284,14 +284,19 @@ class date(_column):
            :param datetime timestamp: optional datetime value to use instead of
                                       the current date and time (must be a
                                       datetime, regular dates can't be converted
-                                      between timezones.)"""
+                                      between timezones.)
+           :param dict context: the 'tz' key in the context should give the
+                                name of the User/Client timezone (otherwise
+                                UTC is used)
+           :rtype: str 
+        """
         today = timestamp or DT.datetime.now()
         context_today = None
         if context and context.get('tz'):
             try:
                 utc = pytz.timezone('UTC')
                 context_tz = pytz.timezone(context['tz'])
-                utc_today = utc.localize(today, is_dst=False)
+                utc_today = utc.localize(today, is_dst=False) # UTC = no DST
                 context_today = utc_today.astimezone(context_tz)
             except Exception:
                 _logger.debug("failed to compute context/client-specific today date, "
@@ -312,6 +317,36 @@ class datetime(_column):
         return DT.datetime.now().strftime(
             tools.DEFAULT_SERVER_DATETIME_FORMAT)
 
+    @staticmethod
+    def context_timestamp(cr, uid, timestamp, context=None):
+        """Returns the given timestamp converted to the client's timezone.
+           This method is *not* meant for use as a _defaults initializer,
+           because datetime fields are automatically converted upon
+           display on client side. For _defaults you :meth:`fields.datetime.now`
+           should be used instead.
+
+           :param datetime timestamp: naive datetime value (expressed in UTC)
+                                      to be converted to the client timezone
+           :param dict context: the 'tz' key in the context should give the
+                                name of the User/Client timezone (otherwise
+                                UTC is used)
+           :rtype: datetime
+           :return: timestamp converted to timezone-aware datetime in context
+                    timezone
+        """
+        assert isinstance(timestamp, DT.datetime), 'Datetime instance expected'
+        if context and context.get('tz'):
+            try:
+                utc = pytz.timezone('UTC')
+                context_tz = pytz.timezone(context['tz'])
+                utc_timestamp = utc.localize(timestamp, is_dst=False) # UTC = no DST
+                return utc_timestamp.astimezone(context_tz)
+            except Exception:
+                _logger.debug("failed to compute context/client-specific timestamp, "
+                              "using the UTC value",
+                              exc_info=True)
+        return timestamp
+
 class time(_column):
     _type = 'time'
     _deprecated = True
index a1ceae9..106144f 100644 (file)
@@ -33,15 +33,12 @@ import openerp.pooler as pooler
 import openerp.tools as tools
 import zipfile
 import common
-from openerp.osv.fields import float as float_class, function as function_class
+from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field
 from openerp.tools.translate import _
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
 
 _logger = logging.getLogger(__name__)
 
-DT_FORMAT = '%Y-%m-%d'
-DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
-HM_FORMAT = '%H:%M:%S'
-
 rml_parents = {
     'tr':1,
     'li':1,
@@ -69,7 +66,7 @@ rml2sxw = {
     'para': 'p',
 }
 
-def get_date_length(date_format=DT_FORMAT):
+def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT):
     return len((datetime.now()).strftime(date_format))
 
 class _format(object):
@@ -110,7 +107,7 @@ class _date_format(str, _format):
     def __str__(self):
         if self.val:
             if getattr(self,'name', None):
-                date = datetime.strptime(self.name[:get_date_length()], DT_FORMAT)
+                date = datetime.strptime(self.name[:get_date_length()], DEFAULT_SERVER_DATE_FORMAT)
                 return date.strftime(str(self.lang_obj.date_format))
         return self.val
 
@@ -121,7 +118,7 @@ class _dttime_format(str, _format):
 
     def __str__(self):
         if self.val and getattr(self,'name', None):
-            return datetime.strptime(self.name, DHM_FORMAT)\
+            return datetime.strptime(self.name, DEFAULT_SERVER_DATETIME_FORMAT)\
                    .strftime("%s %s"%(str(self.lang_obj.date_format),
                                       str(self.lang_obj.time_format)))
         return self.val
@@ -264,7 +261,7 @@ class rml_parse(object):
             else:
                 d = res_digits(self.cr)[1]
         elif (hasattr(obj, '_field') and\
-                isinstance(obj._field, (float_class, function_class)) and\
+                isinstance(obj._field, (float_field, function_field)) and\
                 obj._field.digits):
                 d = obj._field.digits[1] or DEFAULT_DIGITS
         return d
@@ -295,16 +292,22 @@ class rml_parse(object):
                 return ''
 
             date_format = self.lang_dict['date_format']
-            parse_format = DT_FORMAT
+            parse_format = DEFAULT_SERVER_DATE_FORMAT
             if date_time:
-                value=value.split('.')[0]
+                value = value.split('.')[0]
                 date_format = date_format + " " + self.lang_dict['time_format']
-                parse_format = DHM_FORMAT
-            if not isinstance(value, time.struct_time):
-                return time.strftime(date_format, time.strptime(value[:get_date_length(parse_format)], parse_format))
-
+                parse_format = DEFAULT_SERVER_DATETIME_FORMAT
+            if isinstance(value, basestring):
+                date = datetime.strptime(value[:get_date_length(parse_format)], parse_format)
+            elif isinstance(value, time.struct_time):
+                date = datetime(*value[:6])
             else:
                 date = datetime(*value.timetuple()[:6])
+            if date_time:
+                # Convert datetime values to the expected client/context timezone
+                date = datetime_field.context_timestamp(self.cr, self.uid,
+                                                        timestamp=date,
+                                                        context=self.localcontext)
             return date.strftime(date_format)
 
         res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)