[PATCH 1/2 v3] A function for resolving date format and order

Vratislav Podzimek vpodzime at redhat.com
Thu Oct 9 09:41:37 UTC 2014


It's not that easy to get right order of date fields (year, month, day) and
their formats so we should better have it in a standalone function with tests.

Related: rhbz#1044233
Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
---
 pyanaconda/localization.py                  | 87 +++++++++++++++++++++++++++++
 tests/pyanaconda_tests/localization_test.py | 17 ++++++
 2 files changed, 104 insertions(+)

diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py
index c95fa1d..2a2ed96 100644
--- a/pyanaconda/localization.py
+++ b/pyanaconda/localization.py
@@ -26,6 +26,7 @@ import re
 import langtable
 import locale as locale_mod
 import glob
+from collections import namedtuple
 
 from pyanaconda import constants
 from pyanaconda.iutil import upcase_first_letter
@@ -469,3 +470,89 @@ def load_firmware_language(lang):
 
     log.debug("Using UEFI PlatformLang '%s' ('%s') as our language.", d, locales[0])
     setup_locale(locales[0], lang)
+
+_DateFieldSpec = namedtuple("DateFieldSpec", ["format", "suffix"])
+
+def resolve_date_format(year, month, day, fail_safe=True):
+    """
+    Puts the year, month and day objects in the right order according to the
+    currently set locale and provides format specification for each of the
+    fields.
+
+    :param year: any object or value representing year
+    :type year: object
+    :param month: any object or value representing month
+    :type month: object
+    :param day: any object or value representing day
+    :type day: object
+    :param bool fail_safe: whether to fall back to default in case of invalid
+                           format or raise exception instead
+    :returns: a pair where the first field contains a tuple with the year, month
+              and day objects/values put in the right order and where the second
+              field contains a tuple with three :class:`_DateFieldSpec` objects
+              specifying formats respectively to the first (year, month, day)
+              field
+    :rtype: tuple
+    :raise ValueError: in case currently set locale has unsupported date
+                       format and fail_safe is set to False
+
+    """
+
+    fail_safe_default = "%Y-%m-%d"
+
+    def order_terms_formats(fmt_str):
+        # see date (1), 'O' (not '0') is a mystery, 'E' is Buddhist calendar, '(.*)'
+        # is an arbitrary suffix
+        field_spec_re = re.compile(r'([-_0OE^#]*)([yYmbBde])(.*)')
+
+        ordered_terms = []
+        ordered_formats = []
+        # see date (1)
+        fmt_str = fmt_str.replace("%F", "%Y-%m-%d")
+
+        # e.g. "%d.%m.%Y" -> ['d.', 'm.', 'Y']
+        fields = fmt_str.split("%")[1:]
+
+        for field in fields:
+            match = field_spec_re.match(field)
+            if not match:
+                # ignore fields we are not interested in (like %A for weekday name, etc.)
+                continue
+
+            prefix, item, suffix = match.groups()
+            if item in ("d", "e"):
+                # "e" is the same as "_d"
+                ordered_terms.append(day)
+            elif item in ("Y", "y"):
+                # 4-digit year, 2-digit year
+                ordered_terms.append(year)
+            elif item in ("m", "b", "B"):
+                # month number, short month name, long month name
+                ordered_terms.append(month)
+
+            # "%" + prefix + item gives a format for date/time formatting functions
+            ordered_formats.append(_DateFieldSpec("%" + prefix + item, suffix.strip()))
+
+        if len(ordered_terms) != 3 or len(ordered_formats) != 3:
+            raise ValueError("Not all fields successfully identified in the format '%s'" % fmt_str)
+
+        return (tuple(ordered_terms), tuple(ordered_formats))
+
+    fmt_str = locale_mod.nl_langinfo(locale_mod.D_FMT)
+
+    if not fmt_str or "%" not in fmt_str:
+        if fail_safe:
+            # use some sane default
+            fmt_str = fail_safe_default
+        else:
+            raise ValueError("Invalid date format string for current locale: '%s'" % fmt_str)
+
+    try:
+        return order_terms_formats(fmt_str)
+    except ValueError:
+        if not fail_safe:
+            raise
+        else:
+            # if this call fails too, something is going terribly wrong and we
+            # should be informed about it
+            return order_terms_formats(fail_safe_default)
diff --git a/tests/pyanaconda_tests/localization_test.py b/tests/pyanaconda_tests/localization_test.py
index 00ff31d..521fb4a 100644
--- a/tests/pyanaconda_tests/localization_test.py
+++ b/tests/pyanaconda_tests/localization_test.py
@@ -19,6 +19,8 @@
 #
 
 from pyanaconda import localization
+from pyanaconda.iutil import execReadlines
+import locale as locale_mod
 import unittest
 
 class ParsingTests(unittest.TestCase):
@@ -139,3 +141,18 @@ class LangcodeLocaleMatchingTests(unittest.TestCase):
         # no matches
         self.assertIsNone(localization.find_best_locale_match("pt_BR", ["en_BR", "en"]))
         self.assertIsNone(localization.find_best_locale_match("cs_CZ.UTF-8", ["en", "en.UTF-8"]))
+
+    def resolve_date_format_test(self):
+        """All locales' date formats should be properly resolved."""
+
+        locales = (line.strip() for line in execReadlines("locale", ["-a"]))
+        for locale in locales:
+            try:
+                locale_mod.setlocale(locale_mod.LC_ALL, locale)
+            except locale_mod.Error:
+                # cannot set locale (a bug in the locale module?)
+                continue
+
+            (order, formats) = localization.resolve_date_format(1, 2, 3, fail_safe=False)
+            for i in (1, 2, 3):
+                self.assertIn(i, order)
-- 
1.9.3



More information about the anaconda-patches mailing list