diff --git a/datetime_formatter/README.rst b/datetime_formatter/README.rst
new file mode 100644
index 000000000..bf5e28101
--- /dev/null
+++ b/datetime_formatter/README.rst
@@ -0,0 +1,72 @@
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+
+=====================
+Date & Time Formatter
+=====================
+
+This module was written to extend the functionality of Odoo language engine to
+support formatting `Date`, `Time` and `Datetime` fields easily and allow you to
+print them in the best format for the user.
+
+Usage
+=====
+
+This module adds a technical programming feature, and it should be used by
+addon developers, not by end users. This means that you must not expect to see
+any changes if you are a user and install this, but if you find you have it
+already installed, it's probably because you have another modules that depend
+on this one.
+
+If you are a developer, to use this module, you need to:
+
+* Call anywhere in your code::
+
+ formatted_string = self.env["res.lang"].datetime_formatter(datetime_value)
+
+* If you use Qweb::
+
+
+
+* If you call it from a record that has a `lang` field::
+
+ formatted_string = record.lang.datetime_formatter(record.datetime_field)
+
+* ``models.ResLang.datetime_formatter`` docstring explains its usage.
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+ :alt: Try me on Runbot
+ :target: https://runbot.odoo-community.org/runbot/149/9.0
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues
+`_. In case of trouble, please
+check there if your issue has already been reported. If you spotted it first,
+help us smashing it by providing a detailed and welcomed feedback.
+
+Credits
+=======
+
+Contributors
+------------
+
+* Jairo Llopis
+* Vicent Cubells
+
+Maintainer
+----------
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+This module is maintained by the OCA.
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+To contribute to this module, please visit http://odoo-community.org.
diff --git a/datetime_formatter/__init__.py b/datetime_formatter/__init__.py
new file mode 100644
index 000000000..e2c3bb8f2
--- /dev/null
+++ b/datetime_formatter/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from . import models
diff --git a/datetime_formatter/__openerp__.py b/datetime_formatter/__openerp__.py
new file mode 100644
index 000000000..659b241b2
--- /dev/null
+++ b/datetime_formatter/__openerp__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+{
+ "name": "Date & Time Formatter",
+ "summary": "Helper functions to give correct format to date[time] fields",
+ "version": "9.0.1.0.0",
+ "category": "Tools",
+ "website": "https://tecnativa.com",
+ "author": "Grupo ESOC Ingeniería de Servicios, "
+ "Tecnativa,"
+ "Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "installable": True,
+ "depends": [
+ "base",
+ ],
+}
diff --git a/datetime_formatter/exceptions.py b/datetime_formatter/exceptions.py
new file mode 100644
index 000000000..42b5f2850
--- /dev/null
+++ b/datetime_formatter/exceptions.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from openerp import _, exceptions
+
+
+class BestMatchedLanguageNotFoundError(exceptions.MissingError):
+ def __init__(self, lang):
+ msg = (_("Best matched language (%s) not found.") % lang)
+ super(BestMatchedLanguageNotFoundError, self).__init__(msg)
+ self.lang = lang
diff --git a/datetime_formatter/i18n/de.po b/datetime_formatter/i18n/de.po
new file mode 100644
index 000000000..c4a24aaac
--- /dev/null
+++ b/datetime_formatter/i18n/de.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Rudolf Schnapka , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-19 14:44+0000\n"
+"PO-Revision-Date: 2016-01-18 14:00+0000\n"
+"Last-Translator: Rudolf Schnapka \n"
+"Language-Team: German (http://www.transifex.com/oca/OCA-server-tools-8-0/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr "Sprache (%s) mit bester Übereinstimmung nicht gefunden"
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Sprachen"
diff --git a/datetime_formatter/i18n/es.po b/datetime_formatter/i18n/es.po
new file mode 100644
index 000000000..535b5ffce
--- /dev/null
+++ b/datetime_formatter/i18n/es.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Antonio Trueba, 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-19 14:44+0000\n"
+"PO-Revision-Date: 2016-02-10 16:46+0000\n"
+"Last-Translator: Antonio Trueba\n"
+"Language-Team: Spanish (http://www.transifex.com/oca/OCA-server-tools-8-0/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr ""
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Idiomas"
diff --git a/datetime_formatter/i18n/fr.po b/datetime_formatter/i18n/fr.po
new file mode 100644
index 000000000..555768396
--- /dev/null
+++ b/datetime_formatter/i18n/fr.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Christophe CHAUVET , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-08-21 06:45+0000\n"
+"PO-Revision-Date: 2016-08-21 07:41+0000\n"
+"Last-Translator: Christophe CHAUVET \n"
+"Language-Team: French (http://www.transifex.com/oca/OCA-server-tools-8-0/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr ""
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Langues"
diff --git a/datetime_formatter/i18n/it.po b/datetime_formatter/i18n/it.po
new file mode 100644
index 000000000..d18433dcf
--- /dev/null
+++ b/datetime_formatter/i18n/it.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Paolo Valier, 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-03-17 15:37+0000\n"
+"PO-Revision-Date: 2016-03-13 09:34+0000\n"
+"Last-Translator: Paolo Valier\n"
+"Language-Team: Italian (http://www.transifex.com/oca/OCA-server-tools-8-0/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr ""
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Lingue"
diff --git a/datetime_formatter/i18n/sl.po b/datetime_formatter/i18n/sl.po
new file mode 100644
index 000000000..71f00600e
--- /dev/null
+++ b/datetime_formatter/i18n/sl.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Matjaž Mozetič , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-11-27 14:01+0000\n"
+"PO-Revision-Date: 2015-11-27 04:11+0000\n"
+"Last-Translator: Matjaž Mozetič \n"
+"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-server-tools-8-0/language/sl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: sl\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr "Najbolj ujemajoč jezik (%s) ni najden."
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Jeziki"
diff --git a/datetime_formatter/i18n/tr.po b/datetime_formatter/i18n/tr.po
new file mode 100644
index 000000000..9ea89c70d
--- /dev/null
+++ b/datetime_formatter/i18n/tr.po
@@ -0,0 +1,30 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * datetime_formatter
+#
+# Translators:
+# Ahmet Altınışık , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: server-tools (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-19 14:44+0000\n"
+"PO-Revision-Date: 2016-01-31 12:31+0000\n"
+"Last-Translator: Ahmet Altınışık \n"
+"Language-Team: Turkish (http://www.transifex.com/oca/OCA-server-tools-8-0/language/tr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: tr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: datetime_formatter
+#: code:addons/datetime_formatter/exceptions.py:9
+#, python-format
+msgid "Best matched language (%s) not found."
+msgstr "En iyi eşleşen dil (%s) bulunamadı."
+
+#. module: datetime_formatter
+#: model:ir.model,name:datetime_formatter.model_res_lang
+msgid "Languages"
+msgstr "Diller"
diff --git a/datetime_formatter/models.py b/datetime_formatter/models.py
new file mode 100644
index 000000000..90816df38
--- /dev/null
+++ b/datetime_formatter/models.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from datetime import datetime, timedelta
+from openerp import api, fields, models
+from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
+ DEFAULT_SERVER_TIME_FORMAT)
+from . import exceptions as ex
+
+# Available modes for :param:`.ResLang.datetime_formatter.template`
+MODE_DATETIME = "MODE_DATETIME"
+MODE_DATE = "MODE_DATE"
+MODE_TIME = "MODE_TIME"
+
+
+class ResLang(models.Model):
+ _inherit = "res.lang"
+
+ @api.model
+ @api.returns('self')
+ def best_match(self, lang=None, failure_safe=True):
+ """Get best match of current default lang.
+
+ :param str lang:
+ If a language in the form of "en_US" is supplied, it will have the
+ highest priority.
+
+ :param bool failure_safe:
+ If ``False`` and the best matched language is not found installed,
+ an exception will be raised. Otherwise, the first installed
+ language found in the DB will be returned.
+ """
+ # Find some installed language, as fallback
+ first_installed = self.search([("active", "=", True)], limit=1)
+
+ if not lang:
+ lang = (
+ # Object's language, if called like
+ # ``record.lang.datetime_formatter(datetime_obj)``
+ (self.ids and self[0].code) or
+
+ # Context language
+ self.env.context.get("lang") or
+
+ # User's language
+ self.env.user.lang or
+
+ # First installed language found
+ first_installed.code)
+
+ # Get DB lang record
+ record = self.search([("code", "=", lang)])
+
+ try:
+ record.ensure_one()
+ except ValueError:
+ if not failure_safe:
+ raise ex.BestMatchedLanguageNotFoundError(lang)
+ else:
+ record = first_installed
+
+ return record
+
+ @api.model
+ def datetime_formatter(self, value, lang=None, template=MODE_DATETIME,
+ separator=" ", failure_safe=True):
+ """Convert a datetime field to lang's default format.
+
+ :type value: `str`, `float` or `datetime.datetime`
+ :param value:
+ Datetime that will be formatted to the user's preferred format.
+
+ :param str lang:
+ See :param:`lang` from :meth:`~.best_match`.
+
+ :param bool failure_safe:
+ See :param:`failure_safe` from :meth:`~.best_match`.
+
+ :param str template:
+ Will be used to format :param:`value`. If it is one of the special
+ constants :const:`MODE_DATETIME`, :const:`MODE_DATE` or
+ :const:`MODE_TIME`, it will use the :param:`lang`'s default
+ template for that mode.
+
+ :param str separator:
+ Only used when :param:`template` is :const:`MODE_DATETIME`, as the
+ separator between the date and time parts.
+ """
+ # Get the correct lang
+ lang = self.best_match(lang)
+
+ # Get the template
+ if template in {MODE_DATETIME, MODE_DATE, MODE_TIME}:
+ defaults = []
+ if "DATE" in template:
+ defaults.append(lang.date_format or
+ DEFAULT_SERVER_DATE_FORMAT)
+ if "TIME" in template:
+ defaults.append(lang.time_format or
+ DEFAULT_SERVER_TIME_FORMAT)
+ template = separator.join(defaults)
+
+ # Convert str to datetime objects
+ if isinstance(value, (str, unicode)):
+ try:
+ value = fields.Datetime.from_string(value)
+ except ValueError:
+ # Probably failed due to value being only time
+ value = datetime.strptime(value, DEFAULT_SERVER_TIME_FORMAT)
+
+ # Time-only fields are floats for Odoo
+ elif isinstance(value, float):
+ # Patch values >= 24 hours
+ if value >= 24:
+ template = template.replace("%H", "%d" % value)
+
+ # Convert to time
+ value = (datetime.min + timedelta(hours=value)).time()
+
+ return value.strftime(template)
diff --git a/datetime_formatter/static/description/icon.png b/datetime_formatter/static/description/icon.png
new file mode 100644
index 000000000..3a0328b51
Binary files /dev/null and b/datetime_formatter/static/description/icon.png differ
diff --git a/datetime_formatter/tests/__init__.py b/datetime_formatter/tests/__init__.py
new file mode 100644
index 000000000..e1f602d5d
--- /dev/null
+++ b/datetime_formatter/tests/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from . import test_best_matcher, test_formatter
diff --git a/datetime_formatter/tests/test_best_matcher.py b/datetime_formatter/tests/test_best_matcher.py
new file mode 100644
index 000000000..e022dfd18
--- /dev/null
+++ b/datetime_formatter/tests/test_best_matcher.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from openerp.tests.common import TransactionCase
+from .. import exceptions
+
+
+class BasicCase(TransactionCase):
+ def setUp(self):
+ super(BasicCase, self).setUp()
+ self.langs = ("en_US", "es_ES", "it_IT", "pt_PT", "zh_CN")
+ self.rl = self.env["res.lang"]
+ for lang in self.langs:
+ if not self.rl.search([("code", "=", lang)]):
+ self.rl.load_lang(lang)
+
+ def test_explicit(self):
+ """When an explicit lang is used."""
+ for lang in self.langs:
+ self.assertEqual(self.rl.best_match(lang).code, lang)
+
+ def test_record(self):
+ """When called from a ``res.lang`` record."""
+ rl = self.rl.with_context(lang="it_IT")
+ rl.env.user.lang = "pt_PT"
+
+ for lang in self.langs:
+ self.assertEqual(
+ rl.search([("code", "=", lang)]).best_match().code,
+ lang)
+
+ def test_context(self):
+ """When called with a lang in context."""
+ self.env.user.lang = "pt_PT"
+
+ for lang in self.langs:
+ self.assertEqual(
+ self.rl.with_context(lang=lang).best_match().code,
+ lang)
+
+ def test_user(self):
+ """When lang not specified in context."""
+ for lang in self.langs:
+ self.env.user.lang = lang
+
+ # Lang is False in context
+ self.assertEqual(
+ self.rl.with_context(lang=False).best_match().code,
+ lang)
+
+ # Lang not found in context
+ self.assertEqual(
+ self.rl.with_context(dict()).best_match().code,
+ lang)
+
+ def test_first_installed(self):
+ """When falling back to first installed language."""
+ first = self.rl.search([("active", "=", True)], limit=1)
+ self.env.user.lang = False
+ self.assertEqual(
+ self.rl.with_context(lang=False).best_match().code,
+ first.code)
+
+ def test_unavailable(self):
+ """When matches to an unavailable language."""
+ self.env.user.lang = False
+ self.rl = self.rl.with_context(lang=False)
+ first = self.rl.search([("active", "=", True)], limit=1)
+
+ # Safe mode
+ self.assertEqual(self.rl.best_match("fake_LANG").code, first.code)
+
+ # Unsafe mode
+ with self.assertRaises(exceptions.BestMatchedLanguageNotFoundError):
+ self.rl.best_match("fake_LANG", failure_safe=False)
diff --git a/datetime_formatter/tests/test_formatter.py b/datetime_formatter/tests/test_formatter.py
new file mode 100644
index 000000000..cd3833dca
--- /dev/null
+++ b/datetime_formatter/tests/test_formatter.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
+# © 2016 Tecnativa, S.L. - Vicent Cubells
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+import datetime
+from random import random
+from openerp.tests.common import TransactionCase
+from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
+ DEFAULT_SERVER_TIME_FORMAT,
+ DEFAULT_SERVER_DATETIME_FORMAT)
+from ..models import MODE_DATE, MODE_TIME, MODE_DATETIME
+
+
+class FormatterCase(TransactionCase):
+ def setUp(self):
+ super(FormatterCase, self).setUp()
+ self.rl = self.env["res.lang"]
+ self.bm = self.rl.best_match()
+ self.dt = datetime.datetime.now()
+ self.d_fmt = self.bm.date_format or DEFAULT_SERVER_DATE_FORMAT
+ self.t_fmt = self.bm.time_format or DEFAULT_SERVER_TIME_FORMAT
+ self.kwargs = dict()
+
+ def tearDown(self):
+ # This should be returned
+ self.expected = self.dt.strftime(self.format)
+
+ # Pass a datetime object
+ self.assertEqual(
+ self.expected,
+ self.rl.datetime_formatter(
+ self.dt,
+ **self.kwargs))
+
+ # When the date comes as a string
+ if isinstance(self.dt, datetime.datetime):
+ self.dt_str = self.dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ elif isinstance(self.dt, datetime.date):
+ self.dt_str = self.dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
+ elif isinstance(self.dt, datetime.time):
+ self.dt_str = self.dt.strftime(DEFAULT_SERVER_TIME_FORMAT)
+
+ # Pass a string
+ self.assertEqual(
+ self.expected,
+ self.rl.datetime_formatter(
+ self.dt_str,
+ **self.kwargs))
+
+ # Pass a unicode
+ self.assertEqual(
+ self.expected,
+ self.rl.datetime_formatter(
+ unicode(self.dt_str),
+ **self.kwargs))
+
+ super(FormatterCase, self).tearDown()
+
+ def test_datetime(self):
+ """Format a datetime."""
+ self.format = "%s %s" % (self.d_fmt, self.t_fmt)
+ self.kwargs = {"template": MODE_DATETIME}
+
+ def test_date(self):
+ """Format a date."""
+ self.format = self.d_fmt
+ self.kwargs = {"template": MODE_DATE}
+ self.dt = self.dt.date()
+
+ def test_time(self):
+ """Format times, including float ones."""
+ self.format = self.t_fmt
+ self.kwargs = {"template": MODE_TIME}
+ self.dt = self.dt.time()
+
+ # Test float times
+ for n in range(50):
+ n = n + random()
+
+ # Patch values with >= 24 hours
+ fmt = self.format.replace("%H", "%02d" % n)
+
+ time = (datetime.datetime.min +
+ datetime.timedelta(hours=n)).time()
+ self.assertEqual(
+ time.strftime(fmt),
+ self.rl.datetime_formatter(n, **self.kwargs))
+
+ def test_custom_separator(self):
+ """Format a datetime with a custom separator."""
+ sep = "T"
+ self.format = "%s%s%s" % (self.d_fmt, sep, self.t_fmt)
+ self.kwargs = {"template": MODE_DATETIME, "separator": sep}