From 228938ccba51722a060f879e51a2de7954e45681 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Dec 2013 08:33:13 +0100 Subject: [PATCH] [ADD] formats support to date and datetime converters bzr revid: xmo@openerp.com-20131202073313-tu79esduu5bvyaai --- openerp/addons/base/ir/ir_qweb.py | 25 +++++-- .../addons/test_converter/tests/test_html.py | 16 +++++ openerp/tests/test_misc.py | 46 +++++++++++- openerp/tools/misc.py | 70 +++++++++++++++++++ 4 files changed, 149 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index a036dacf898..d2c9520cd6e 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -627,14 +627,21 @@ class DateConverter(osv.AbstractModel): def value_to_html(self, cr, uid, value, column, options=None, context=None): if not value: return '' lang = self.user_lang(cr, uid, context=context) - - out_format = lang.date_format.encode('utf-8') + locale = babel.Locale.parse(lang.code) if isinstance(value, basestring): value = datetime.datetime.strptime( value, openerp.tools.DEFAULT_SERVER_DATE_FORMAT) - return value.strftime(out_format) + if options and 'format' in options: + pattern = options['format'] + else: + strftime_pattern = lang.date_format + pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale) + + return babel.dates.format_datetime( + value, format=pattern, + locale=locale) class DateTimeConverter(osv.AbstractModel): _name = 'ir.qweb.field.datetime' @@ -643,17 +650,21 @@ class DateTimeConverter(osv.AbstractModel): def value_to_html(self, cr, uid, value, column, options=None, context=None): if not value: return '' lang = self.user_lang(cr, uid, context=context) - - out_format = (u"%s %s" % (lang.date_format, lang.time_format)).encode('utf-8') + locale = babel.Locale.parse(lang.code) if isinstance(value, basestring): value = datetime.datetime.strptime( value, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT) - value = column.context_timestamp( cr, uid, timestamp=value, context=context) - return value.strftime(out_format) + if options and 'format' in options: + pattern = options['format'] + else: + strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format)) + pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale) + + return babel.dates.format_datetime(value, format=pattern, locale=locale) class TextConverter(osv.AbstractModel): _name = 'ir.qweb.field.text' diff --git a/openerp/tests/addons/test_converter/tests/test_html.py b/openerp/tests/addons/test_converter/tests/test_html.py index 71c08a412f4..aaa0c12cdf5 100644 --- a/openerp/tests/addons/test_converter/tests/test_html.py +++ b/openerp/tests/addons/test_converter/tests/test_html.py @@ -304,6 +304,22 @@ class TestDatetimeExport(TestBasicExport): # default lang/format is US self.assertEqual(value, '05/03/2011 00:12:13') + def test_custom_format(self): + converter = self.get_converter('datetime') + converter2 = self.get_converter('date') + opts = {'format': 'MMMM d'} + + value = converter('2011-03-02 11:12:13', options=opts) + value2 = converter2('2001-03-02', options=opts) + self.assertEqual( + value, + 'March 2' + ) + self.assertEqual( + value2, + 'March 2' + ) + class TestDurationExport(TestBasicExport): def setUp(self): super(TestDurationExport, self).setUp() diff --git a/openerp/tests/test_misc.py b/openerp/tests/test_misc.py index 9540c0222f3..c5443cfd936 100644 --- a/openerp/tests/test_misc.py +++ b/openerp/tests/test_misc.py @@ -1,7 +1,12 @@ # This test can be run stand-alone with something like: # > PYTHONPATH=. python2 openerp/tests/test_misc.py - +import datetime +import locale import unittest2 + +import babel +import babel.dates + from ..tools import misc @@ -35,5 +40,44 @@ class test_countingstream(unittest2.TestCase): self.assertIsNone(next(s, None)) self.assertEqual(s.index, 0) +lname, _ = locale.getdefaultlocale() +class TestPosixToBabel(unittest2.TestCase): + def setUp(self): + super(TestPosixToBabel, self).setUp() + self.d = datetime.datetime(2007, 9, 8, 4, 5, 1) + + def assert_eq(self, fmt, d=None): + if d is None: d = self.d + + ldml_format = misc.posix_to_ldml(fmt, locale=babel.Locale.parse(lname)) + self.assertEqual( + d.strftime(fmt), + babel.dates.format_datetime(d, format=ldml_format), + "%r resulted in a different result than %r for %s" % ( + ldml_format, fmt, d)) + + def test_empty(self): + self.assert_eq("") + + def test_literal(self): + self.assert_eq("Raw test string") + + def test_mixed(self): + self.assert_eq("m:%m d:%d y:%y") + self.assert_eq("m:%m d:%d y:%y H:%H M:%M S:%S") + + def test_escape(self): + self.assert_eq("%%m:%m %%d:%d %%y:%y") + + def test_xX(self): + self.assert_eq('%x %X') + + def test_various_examples(self): + self.assert_eq("%x - %I:%M%p") + self.assert_eq('%Y-%m-%dT%H:%M:%S') + self.assert_eq("%Y-%j") + self.assert_eq("%a, %d %b %Y %H:%M:%S") + self.assert_eq("%a, %b %d %I:%M.%S") + if __name__ == '__main__': unittest2.main() diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index bdbb4d13e0f..653c9b44f8c 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -819,6 +819,76 @@ DATETIME_FORMATS_MAP = { '%Z': '', } +POSIX_TO_LDML = { + 'a': 'E', + 'A': 'EEEE', + 'b': 'MMM', + 'B': 'MMMM', + #'c': '', + 'd': 'dd', + 'H': 'HH', + 'I': 'hh', + 'j': 'DDD', + 'm': 'MM', + 'M': 'mm', + 'p': 'a', + 'S': 'ss', + 'U': 'w', + 'w': 'e', + 'W': 'w', + 'y': 'yy', + 'Y': 'yyyy', + # see comments above, and babel's format_datetime assumes an UTC timezone + # for naive datetime objects + #'z': 'Z', + #'Z': 'z', +} + +def posix_to_ldml(fmt, locale): + """ Converts a posix/strftime pattern into an LDML date format pattern. + + :param fmt: non-extended C89/C90 strftime pattern + :param locale: babel locale used for locale-specific conversions (e.g. %x and %X) + :return: unicode + """ + buf = [] + pc = False + quoted = [] + + for c in fmt: + # LDML date format patterns uses letters, so letters must be quoted + if not pc and c.isalpha(): + quoted.append(c if c != "'" else "''") + continue + if quoted: + buf.append("'") + buf.append(''.join(quoted)) + buf.append("'") + quoted = [] + + if pc: + if c == '%': # escaped percent + buf.append('%') + elif c == 'x': # date format, short seems to match + buf.append(locale.date_formats['short'].pattern) + elif c == 'X': # time format, seems to include seconds. short does not + buf.append(locale.time_formats['medium'].pattern) + else: # look up format char in static mapping + buf.append(POSIX_TO_LDML[c]) + pc = False + elif c == '%': + pc = True + else: + buf.append(c) + + # flush anything remaining in quoted buffer + if quoted: + buf.append("'") + buf.append(''.join(quoted)) + buf.append("'") + + return ''.join(buf) + def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name, tz_offset=True, ignore_unparsable_time=True): """