Skip to content

Commit

Permalink
Add support for date-time skeletons
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Michael Birtwell committed Jan 4, 2016
1 parent 07aa84f commit 65e20c1
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 0 deletions.
13 changes: 13 additions & 0 deletions babel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,19 @@ def datetime_formats(self):
"""
return self._data['datetime_formats']

@property
def datetime_skeletons(self):
"""Locale patterns for formatting parts of a datetime.
>>> Locale('en').datetime_skeletons['MEd']
<DateTimePattern u'E, M/d'>
>>> Locale('fr').datetime_skeletons['MEd']
<DateTimePattern u'E dd/MM'>
>>> Locale('fr').datetime_skeletons['H']
<DateTimePattern u"HH 'h'">
"""
return self._data['datetime_skeletons']

@property
def plural_form(self):
"""Plural rules for the locale.
Expand Down
28 changes: 28 additions & 0 deletions babel/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
5 changes: 5 additions & 0 deletions scripts/import_cldr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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))

# <numbers>

Expand Down
4 changes: 4 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ def test_datetime_formats_property(self):
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'

This comment has been minimized.

Copy link
@sils

sils Jan 4, 2016

Member

just quickly glancing over here, but aren't those cases covered by the doctest?

This comment has been minimized.

Copy link
@akx

akx Jan 4, 2016

Member

The thai locale isn't. I don't think this is a deal-breaker really :)

This comment has been minimized.

Copy link
@sils

sils Jan 4, 2016

Member

right. (Just trying to keep redundancy low.)


def test_plural_form_property(self):
assert Locale('en').plural_form(1) == 'one'
assert Locale('en').plural_form(0) == 'other'
Expand Down
9 changes: 9 additions & 0 deletions tests/test_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down

0 comments on commit 65e20c1

Please sign in to comment.