From 65e20c11bd8c8edececd54b20795e6a4f42d966e Mon Sep 17 00:00:00 2001 From: Michael Birtwell Date: Mon, 28 Sep 2015 17:44:30 +0100 Subject: [PATCH] Add support for date-time skeletons The skeletons for dates and times are described on http://cldr.unicode.org/translation/date-time-patterns under Additional Date-Time Formats. And are useful when you want to some more control over formatting dates and times but don't want to force all locales to use the same pattern. --- babel/core.py | 13 +++++++++++++ babel/dates.py | 28 ++++++++++++++++++++++++++++ scripts/import_cldr.py | 5 +++++ tests/test_core.py | 4 ++++ tests/test_dates.py | 9 +++++++++ 5 files changed, 59 insertions(+) diff --git a/babel/core.py b/babel/core.py index 813a61a9..c08cfa73 100644 --- a/babel/core.py +++ b/babel/core.py @@ -731,6 +731,19 @@ class Locale(object): """ return self._data['datetime_formats'] + @property + def datetime_skeletons(self): + """Locale patterns for formatting parts of a datetime. + + >>> Locale('en').datetime_skeletons['MEd'] + + >>> Locale('fr').datetime_skeletons['MEd'] + + >>> Locale('fr').datetime_skeletons['H'] + + """ + return self._data['datetime_skeletons'] + @property def plural_form(self): """Plural rules for the locale. diff --git a/babel/dates.py b/babel/dates.py index ab2d4fb2..4f66adbe 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -694,6 +694,34 @@ def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME): return parse_pattern(format).apply(time, locale) +def format_skeleton(skeleton, datetime=None, tzinfo=None, locale=LC_TIME): + r"""Return a time and/or date formatted according to the given pattern. + + The skeletons are defined in the CLDR data and provide more flexibility + than the simple short/long/medium formats, but are a bit harder to use. + The are defined using the date/time symbols without order or punctuation + and map to a suitable format for the given locale. + + >>> t = datetime(2007, 4, 1, 15, 30) + >>> format_skeleton('MMMEd', t, locale='fr') + u'dim. 1 avr.' + >>> format_skeleton('MMMEd', t, locale='en') + u'Sun, Apr 1' + + After the skeleton is resolved to a pattern `format_datetime` is called so + all timezone processing etc is the same as for that. + + :param skeleton: A date time skeleton as defined in the cldr data. + :param datetime: the ``time`` or ``datetime`` object; if `None`, the current + time in UTC is used + :param tzinfo: the time-zone to apply to the time for display + :param locale: a `Locale` object or a locale identifier + """ + locale = Locale.parse(locale) + format = locale.datetime_skeletons[skeleton] + return format_datetime(datetime, format, tzinfo, locale) + + TIMEDELTA_UNITS = ( ('year', 3600 * 24 * 365), ('month', 3600 * 24 * 30), diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 2925acc8..a5d0ad5d 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -576,6 +576,7 @@ def main(): ) datetime_formats = data.setdefault('datetime_formats', {}) + datetime_skeletons = data.setdefault('datetime_skeletons', {}) for format in calendar.findall('dateTimeFormats'): for elem in format.getiterator(): if elem.tag == 'dateTimeFormatLength': @@ -591,6 +592,10 @@ def main(): datetime_formats = Alias(_translate_alias( ['datetime_formats'], elem.attrib['path']) ) + elif elem.tag == 'availableFormats': + for datetime_skeleton in elem.findall('dateFormatItem'): + datetime_skeletons[datetime_skeleton.attrib['id']] = \ + dates.parse_pattern(text_type(datetime_skeleton.text)) # diff --git a/tests/test_core.py b/tests/test_core.py index 48eb9fe7..54cf37dd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -236,6 +236,10 @@ class TestLocaleClass: assert Locale('en').datetime_formats['full'] == u"{1} 'at' {0}" assert Locale('th').datetime_formats['medium'] == u'{1} {0}' + def test_datetime_skeleton_property(self): + assert Locale('en').datetime_skeletons['Md'].pattern == u"M/d" + assert Locale('th').datetime_skeletons['Md'].pattern == u'd/M' + def test_plural_form_property(self): assert Locale('en').plural_form(1) == 'one' assert Locale('en').plural_form(0) == 'other' diff --git a/tests/test_dates.py b/tests/test_dates.py index ea4805bb..30a0ea3d 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -498,6 +498,15 @@ def test_format_time(): assert us_east == u'3:30:00 PM Eastern Standard Time' +def test_format_skeleton(): + dt = datetime(2007, 4, 1, 15, 30) + assert (dates.format_skeleton('yMEd', dt, locale='en_US') == u'Sun, 4/1/2007') + assert (dates.format_skeleton('yMEd', dt, locale='th') == u'อา. 1/4/2007') + + assert (dates.format_skeleton('EHm', dt, locale='en') == u'Sun 15:30') + assert (dates.format_skeleton('EHm', dt, tzinfo=timezone('Asia/Bangkok'), locale='th') == u'อา. 22:30 น.') + + def test_format_timedelta(): assert (dates.format_timedelta(timedelta(weeks=12), locale='en_US') == u'3 months') -- 2.47.2