# HG changeset patch # User cmlenz # Date 1216073624 0 # Node ID ecf110e7f604e3038067dba9078cbab0dc693d58 # Parent 1c54b64a93bf063a4279a383e72de9f640822db6 Preliminary support for timedelta formatting (see #126), and import/expose the locale plural rules from the CLDR. diff --git a/babel/core.py b/babel/core.py --- a/babel/core.py +++ b/babel/core.py @@ -601,6 +601,21 @@ :type: `dict` """) + def plural_form(self): + return self._data['plural_form'] + plural_form = property(plural_form, doc="""\ + Plural rules for the locale. + + >>> Locale('en').plural_form(1) + 'one' + >>> Locale('en').plural_form(0) + 'other' + >>> Locale('fr').plural_form(0) + 'one' + + :type: `PluralRule` + """) + def default_locale(category=None): """Returns the system default locale for a given category, based on diff --git a/babel/dates.py b/babel/dates.py --- a/babel/dates.py +++ b/babel/dates.py @@ -578,7 +578,7 @@ if isinstance(time, datetime): if tzinfo is not None: time = time.astimezone(tzinfo) - if hasattr(tzinfo, 'localize'): # pytz + if hasattr(tzinfo, 'normalize'): # pytz time = tzinfo.normalize(time) time = time.timetz() elif tzinfo is not None: @@ -589,6 +589,42 @@ format = get_time_format(format, locale=locale) return parse_pattern(format).apply(time, locale) +TIMEDELTA_UNITS = ( + ('year', 3600 * 24 * 365), + ('month', 3600 * 24 * 30), + ('week', 3600 * 24 * 7), + ('day', 3600 * 24), + ('hour', 3600), + ('minute', 60), + ('second', 1) +) + +def format_timedelta(delta, granularity='second', threshold=.9, locale=LC_TIME): + """Return a time delta according to the rules of the given locale. + + >>> format_timedelta(timedelta(weeks=12), locale='en_US') + u'3 months' + >>> format_timedelta(timedelta(seconds=1), locale='es') + u'1 segundo' + >>> format_timedelta(timedelta(seconds=1), locale='en_US') + u'1 second' + + :param delta: a ``timedelta`` object representing the time difference to + format + + """ + locale = Locale.parse(locale) + seconds = int((delta.days * 86400) + delta.seconds) + + for unit, limit in TIMEDELTA_UNITS: + r = float(abs(seconds)) / float(limit) + if r >= threshold or unit == granularity: + r = int(round(r)) + plural_form = locale.plural_form(r) + pattern = locale._data['unit_patterns'][unit][plural_form] + return pattern.replace('{0}', str(r)) + return '' + def parse_date(string, locale=LC_TIME): """Parse a date from a string. diff --git a/babel/plural.py b/babel/plural.py --- a/babel/plural.py +++ b/babel/plural.py @@ -64,6 +64,9 @@ found.add(key) self.abstract.append((key, _Parser(expr).ast)) + def __repr__(self): + return '<%s %r>' % (type(self).__name__, self.abstract) + def parse(cls, rules): """Create a `PluralRule` instance for the given rules. If the rules are a `PluralRule` object, that object is returned. @@ -272,6 +275,7 @@ ] def __init__(self, string): + string = string.lower() result = [] pos = 0 end = len(string) @@ -320,7 +324,7 @@ def and_condition(self): op = self.relation() while self.skip('word', 'and'): - op = 'and', (op, self.realation()) + op = 'and', (op, self.relation()) return op def relation(self): diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -27,6 +27,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), '..')) from babel import dates, numbers +from babel.plural import PluralRule from babel.localedata import Alias weekdays = {'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5, @@ -131,6 +132,17 @@ containers |= territory_containment[group] containers.add(group) + # prepare the per-locale plural rules definitions + plural_rules = {} + prsup = parse(os.path.join(srcdir, 'supplemental', 'plurals.xml')) + for elem in prsup.findall('//plurals/pluralRules'): + rules = [] + for rule in elem.findall('pluralRule'): + rules.append((rule.attrib['count'], unicode(rule.text))) + pr = PluralRule(rules) + for locale in elem.attrib['locales'].split(): + plural_rules[locale] = pr + filenames = os.listdir(os.path.join(srcdir, 'main')) filenames.remove('root.xml') filenames.sort(lambda a,b: len(a)-len(b)) @@ -161,6 +173,14 @@ regions = territory_containment.get(territory, []) print>>sys.stderr, ' Regions: %r' % regions + # plural rules + locale_id = '_'.join(filter(None, [ + language, + territory != '001' and territory or None + ])) + if locale_id in plural_rules: + data['plural_form'] = plural_rules[locale_id] + # territories = data.setdefault('territories', {}) @@ -453,6 +473,16 @@ and 'choice' not in symbol.attrib: currency_symbols[code] = unicode(symbol.text) + # + + unit_patterns = data.setdefault('unit_patterns', {}) + for elem in tree.findall('//units/unit'): + unit_type = elem.attrib['type'] + unit_pattern = unit_patterns.setdefault(unit_type, {}) + for pattern in elem.findall('unitPattern'): + unit_patterns[unit_type][pattern.attrib['count']] = \ + unicode(pattern.text) + outfile = open(os.path.join(destdir, 'localedata', stem + '.dat'), 'wb') try: pickle.dump(data, outfile, 2)