diff options
| author | Jannis Leidel <jannis@leidel.info> | 2011-09-23 16:45:40 +0000 |
|---|---|---|
| committer | Jannis Leidel <jannis@leidel.info> | 2011-09-23 16:45:40 +0000 |
| commit | 6cae2a550d0d09d26554b9c0c0fbab2859ca45ec (patch) | |
| tree | 7d5cd4de248746200ccd5728991b253abbfea805 /django | |
| parent | 33ad413c1ce7e30d339bd0c4f387162af26251f5 (diff) | |
Fixed #16878 -- Improved intword filter to support numbers up to decillion and googol. Thanks to crodjer for the initial patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16897 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/humanize/locale/en/LC_MESSAGES/django.po | 187 | ||||
| -rw-r--r-- | django/contrib/humanize/models.py | 0 | ||||
| -rw-r--r-- | django/contrib/humanize/templatetags/humanize.py | 93 | ||||
| -rw-r--r-- | django/contrib/humanize/tests.py | 162 |
4 files changed, 378 insertions, 64 deletions
diff --git a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po index 360bdd94e6..435aacca0c 100644 --- a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po @@ -4,14 +4,27 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-05-16 17:30+0200\n" +"POT-Creation-Date: 2011-09-23 17:43+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English <en@li.org>\n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: tests.py:101 templatetags/humanize.py:170 +msgid "today" +msgstr "" + +#: tests.py:101 templatetags/humanize.py:174 +msgid "yesterday" +msgstr "" + +#: tests.py:101 templatetags/humanize.py:172 +msgid "tomorrow" +msgstr "" + #: templatetags/humanize.py:25 msgid "th" msgstr "" @@ -28,148 +41,248 @@ msgstr "" msgid "rd" msgstr "" -#: templatetags/humanize.py:78 +#: templatetags/humanize.py:57 #, python-format msgid "%(value).1f million" msgid_plural "%(value).1f million" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:79 +#: templatetags/humanize.py:58 #, python-format msgid "%(value)s million" msgid_plural "%(value)s million" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:84 +#: templatetags/humanize.py:61 #, python-format msgid "%(value).1f billion" msgid_plural "%(value).1f billion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:85 +#: templatetags/humanize.py:62 #, python-format msgid "%(value)s billion" msgid_plural "%(value)s billion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:90 +#: templatetags/humanize.py:65 #, python-format msgid "%(value).1f trillion" msgid_plural "%(value).1f trillion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:91 +#: templatetags/humanize.py:66 #, python-format msgid "%(value)s trillion" msgid_plural "%(value)s trillion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:69 +#, python-format +msgid "%(value).1f quadrillion" +msgid_plural "%(value).1f quadrillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:70 +#, python-format +msgid "%(value)s quadrillion" +msgid_plural "%(value)s quadrillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:73 +#, python-format +msgid "%(value).1f quintillion" +msgid_plural "%(value).1f quintillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:74 +#, python-format +msgid "%(value)s quintillion" +msgid_plural "%(value)s quintillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:77 +#, python-format +msgid "%(value).1f sextillion" +msgid_plural "%(value).1f sextillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:78 +#, python-format +msgid "%(value)s sextillion" +msgid_plural "%(value)s sextillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:81 +#, python-format +msgid "%(value).1f septillion" +msgid_plural "%(value).1f septillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:82 +#, python-format +msgid "%(value)s septillion" +msgid_plural "%(value)s septillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:85 +#, python-format +msgid "%(value).1f octillion" +msgid_plural "%(value).1f octillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:86 +#, python-format +msgid "%(value)s octillion" +msgid_plural "%(value)s octillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:89 +#, python-format +msgid "%(value).1f nonillion" +msgid_plural "%(value).1f nonillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:90 +#, python-format +msgid "%(value)s nonillion" +msgid_plural "%(value)s nonillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:93 +#, python-format +msgid "%(value).1f decillion" +msgid_plural "%(value).1f decillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:94 +#, python-format +msgid "%(value)s decillion" +msgid_plural "%(value)s decillion" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:97 +#, python-format +msgid "%(value).1f googol" +msgid_plural "%(value).1f googol" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:98 +#, python-format +msgid "%(value)s googol" +msgid_plural "%(value)s googol" +msgstr[0] "" +msgstr[1] "" + +#: templatetags/humanize.py:147 msgid "one" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "two" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "three" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "four" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "five" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "six" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "seven" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "eight" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "nine" msgstr "" -#: templatetags/humanize.py:131 -msgid "today" -msgstr "" - -#: templatetags/humanize.py:133 -msgid "tomorrow" -msgstr "" - -#: templatetags/humanize.py:135 -msgid "yesterday" -msgstr "" - -#: templatetags/humanize.py:160 +#: templatetags/humanize.py:199 #, python-format msgctxt "naturaltime" msgid "%(delta)s ago" msgstr "" -#: templatetags/humanize.py:163 templatetags/humanize.py:185 +#: templatetags/humanize.py:202 templatetags/humanize.py:224 msgid "now" msgstr "" -#: templatetags/humanize.py:166 +#: templatetags/humanize.py:205 #, python-format msgid "a second ago" msgid_plural "%(count)s seconds ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:171 +#: templatetags/humanize.py:210 #, python-format msgid "a minute ago" msgid_plural "%(count)s minutes ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:176 +#: templatetags/humanize.py:215 #, python-format msgid "an hour ago" msgid_plural "%(count)s hours ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:182 +#: templatetags/humanize.py:221 #, python-format msgctxt "naturaltime" msgid "%(delta)s from now" msgstr "" -#: templatetags/humanize.py:188 +#: templatetags/humanize.py:227 #, python-format msgid "a second from now" msgid_plural "%(count)s seconds from now" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:193 +#: templatetags/humanize.py:232 #, python-format msgid "a minute from now" msgid_plural "%(count)s minutes from now" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:198 +#: templatetags/humanize.py:237 #, python-format msgid "an hour from now" msgid_plural "%(count)s hours from now" diff --git a/django/contrib/humanize/models.py b/django/contrib/humanize/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/django/contrib/humanize/models.py diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 0da7e8e453..d071c3a5be 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -4,7 +4,6 @@ from datetime import date, datetime, timedelta from django import template from django.conf import settings from django.template import defaultfilters -from django.utils.datetime_safe import datetime, date from django.utils.encoding import force_unicode from django.utils.formats import number_format from django.utils.translation import pgettext, ungettext, ugettext as _ @@ -13,6 +12,7 @@ from django.utils.tzinfo import LocalTimezone register = template.Library() + def ordinal(value): """ Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd', @@ -22,10 +22,10 @@ def ordinal(value): value = int(value) except (TypeError, ValueError): return value - t = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th')) + suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th')) if value % 100 in (11, 12, 13): # special case - return u"%d%s" % (value, t[0]) - return u'%d%s' % (value, t[value % 10]) + return u"%d%s" % (value, suffixes[0]) + return u"%d%s" % (value, suffixes[value % 10]) ordinal.is_safe = True register.filter(ordinal) @@ -51,16 +51,65 @@ def intcomma(value, use_l10n=True): intcomma.is_safe = True register.filter(intcomma) +# A dictionary of standard large number to their converters +intword_converters = { + 6: lambda number: ( + ungettext('%(value).1f million', '%(value).1f million', number), + ungettext('%(value)s million', '%(value)s million', number), + ), + 9: lambda number: ( + ungettext('%(value).1f billion', '%(value).1f billion', number), + ungettext('%(value)s billion', '%(value)s billion', number), + ), + 12: lambda number: ( + ungettext('%(value).1f trillion', '%(value).1f trillion', number), + ungettext('%(value)s trillion', '%(value)s trillion', number), + ), + 15: lambda number: ( + ungettext('%(value).1f quadrillion', '%(value).1f quadrillion', number), + ungettext('%(value)s quadrillion', '%(value)s quadrillion', number), + ), + 18: lambda number: ( + ungettext('%(value).1f quintillion', '%(value).1f quintillion', number), + ungettext('%(value)s quintillion', '%(value)s quintillion', number), + ), + 21: lambda number: ( + ungettext('%(value).1f sextillion', '%(value).1f sextillion', number), + ungettext('%(value)s sextillion', '%(value)s sextillion', number), + ), + 24: lambda number: ( + ungettext('%(value).1f septillion', '%(value).1f septillion', number), + ungettext('%(value)s septillion', '%(value)s septillion', number), + ), + 27: lambda number: ( + ungettext('%(value).1f octillion', '%(value).1f octillion', number), + ungettext('%(value)s octillion', '%(value)s octillion', number), + ), + 30: lambda number: ( + ungettext('%(value).1f nonillion', '%(value).1f nonillion', number), + ungettext('%(value)s nonillion', '%(value)s nonillion', number), + ), + 33: lambda number: ( + ungettext('%(value).1f decillion', '%(value).1f decillion', number), + ungettext('%(value)s decillion', '%(value)s decillion', number), + ), + 100: lambda number: ( + ungettext('%(value).1f googol', '%(value).1f googol', number), + ungettext('%(value)s googol', '%(value)s googol', number), + ), +} + def intword(value): """ - Converts a large integer to a friendly text representation. Works best for - numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000 - becomes '1.2 million' and '1200000000' becomes '1.2 billion'. + Converts a large integer to a friendly text representation. Works best + for numbers over 1 million. For example, 1000000 becomes '1.0 million', + 1200000 becomes '1.2 million' and '1200000000' becomes '1.2 billion'. """ try: value = int(value) except (TypeError, ValueError): return value + if value < 1000000: return value @@ -69,27 +118,17 @@ def intword(value): Use the i18n enabled defaultfilters.floatformat if possible """ if settings.USE_L10N: - return defaultfilters.floatformat(value, 1), string_formatted - return value, float_formatted + value = defaultfilters.floatformat(value, 1) + template = string_formatted + else: + template = float_formatted + return template % {'value': value} - if value < 1000000000: - new_value = value / 1000000.0 - new_value, value_string = _check_for_i18n(new_value, - ungettext('%(value).1f million', '%(value).1f million', new_value), - ungettext('%(value)s million', '%(value)s million', new_value)) - return value_string % {'value': new_value} - if value < 1000000000000: - new_value = value / 1000000000.0 - new_value, value_string = _check_for_i18n(new_value, - ungettext('%(value).1f billion', '%(value).1f billion', new_value), - ungettext('%(value)s billion', '%(value)s billion', new_value)) - return value_string % {'value': new_value} - if value < 1000000000000000: - new_value = value / 1000000000000.0 - new_value, value_string = _check_for_i18n(new_value, - ungettext('%(value).1f trillion', '%(value).1f trillion', new_value), - ungettext('%(value)s trillion', '%(value)s trillion', new_value)) - return value_string % {'value': new_value} + for exponent, converters in intword_converters.items(): + large_number = 10 ** exponent + if value < large_number * 1000: + new_value = value / float(large_number) + return _check_for_i18n(new_value, *converters(new_value)) return value intword.is_safe = False register.filter(intword) diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py new file mode 100644 index 0000000000..611314881f --- /dev/null +++ b/django/contrib/humanize/tests.py @@ -0,0 +1,162 @@ +from __future__ import with_statement +from datetime import timedelta, date, datetime + +from django.template import Template, Context, add_to_builtins, defaultfilters +from django.test import TestCase +from django.utils import translation, tzinfo +from django.utils.translation import ugettext as _ +from django.utils.html import escape + +add_to_builtins('django.contrib.humanize.templatetags.humanize') + + +class HumanizeTests(TestCase): + + def humanize_tester(self, test_list, result_list, method): + # Using max below ensures we go through both lists + # However, if the lists are not equal length, this raises an exception + for test_content, result in zip(test_list, result_list): + t = Template('{{ test_content|%s }}' % method) + rendered = t.render(Context(locals())).strip() + self.assertEqual(rendered, escape(result), + msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result)) + + def test_ordinal(self): + test_list = ('1', '2', '3', '4', '11', '12', + '13', '101', '102', '103', '111', + 'something else', None) + result_list = ('1st', '2nd', '3rd', '4th', '11th', + '12th', '13th', '101st', '102nd', '103rd', + '111th', 'something else', None) + + self.humanize_tester(test_list, result_list, 'ordinal') + + def test_intcomma(self): + test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25, + '100', '1000', '10123', '10311', '1000000', '1234567.1234567', + None) + result_list = ('100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25', + '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567', + None) + + self.humanize_tester(test_list, result_list, 'intcomma') + + def test_l10n_intcomma(self): + test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25, + '100', '1000', '10123', '10311', '1000000', '1234567.1234567', + None) + result_list = ('100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25', + '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567', + None) + + with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False): + self.humanize_tester(test_list, result_list, 'intcomma') + + def test_intword(self): + test_list = ('100', '1000000', '1200000', '1290000', + '1000000000', '2000000000', '6000000000000', + '1300000000000000', '3500000000000000000000', + '8100000000000000000000000000000000', None) + result_list = ('100', '1.0 million', '1.2 million', '1.3 million', + '1.0 billion', '2.0 billion', '6.0 trillion', + '1.3 quadrillion', '3.5 sextillion', + '8.1 decillion', None) + self.humanize_tester(test_list, result_list, 'intword') + + def test_i18n_intcomma(self): + test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25, + '100', '1000', '10123', '10311', '1000000', None) + result_list = ('100', '1.000', '10.123', '10.311', '1.000.000', '1.234.567,25', + '100', '1.000', '10.123', '10.311', '1.000.000', None) + with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): + with translation.override('de'): + self.humanize_tester(test_list, result_list, 'intcomma') + + def test_i18n_intword(self): + test_list = ('100', '1000000', '1200000', '1290000', + '1000000000', '2000000000', '6000000000000') + result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen', + '1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen') + with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): + with translation.override('de'): + self.humanize_tester(test_list, result_list, 'intword') + + def test_apnumber(self): + test_list = [str(x) for x in range(1, 11)] + test_list.append(None) + result_list = (u'one', u'two', u'three', u'four', u'five', u'six', + u'seven', u'eight', u'nine', u'10', None) + + self.humanize_tester(test_list, result_list, 'apnumber') + + def test_naturalday(self): + today = date.today() + yesterday = today - timedelta(days=1) + tomorrow = today + timedelta(days=1) + someday = today - timedelta(days=10) + notdate = u"I'm not a date value" + + test_list = (today, yesterday, tomorrow, someday, notdate, None) + someday_result = defaultfilters.date(someday) + result_list = (_(u'today'), _(u'yesterday'), _(u'tomorrow'), + someday_result, u"I'm not a date value", None) + self.humanize_tester(test_list, result_list, 'naturalday') + + def test_naturalday_tz(self): + from django.contrib.humanize.templatetags.humanize import naturalday + + today = date.today() + tz_one = tzinfo.FixedOffset(timedelta(hours=-12)) + tz_two = tzinfo.FixedOffset(timedelta(hours=12)) + + # Can be today or yesterday + date_one = datetime(today.year, today.month, today.day, tzinfo=tz_one) + naturalday_one = naturalday(date_one) + # Can be today or tomorrow + date_two = datetime(today.year, today.month, today.day, tzinfo=tz_two) + naturalday_two = naturalday(date_two) + + # As 24h of difference they will never be the same + self.assertNotEqual(naturalday_one, naturalday_two) + + def test_naturaltime(self): + now = datetime.now() + test_list = [ + now, + now - timedelta(seconds=1), + now - timedelta(seconds=30), + now - timedelta(minutes=1, seconds=30), + now - timedelta(minutes=2), + now - timedelta(hours=1, minutes=30, seconds=30), + now - timedelta(hours=23, minutes=50, seconds=50), + now - timedelta(days=1), + now - timedelta(days=500), + now + timedelta(seconds=1), + now + timedelta(seconds=30), + now + timedelta(minutes=1, seconds=30), + now + timedelta(minutes=2), + now + timedelta(hours=1, minutes=30, seconds=30), + now + timedelta(hours=23, minutes=50, seconds=50), + now + timedelta(days=1), + now + timedelta(days=500), + ] + result_list = [ + 'now', + 'a second ago', + '30 seconds ago', + 'a minute ago', + '2 minutes ago', + 'an hour ago', + '23 hours ago', + '1 day ago', + '1 year, 4 months ago', + 'a second from now', + '30 seconds from now', + 'a minute from now', + '2 minutes from now', + 'an hour from now', + '23 hours from now', + '1 day from now', + '1 year, 4 months from now', + ] + self.humanize_tester(test_list, result_list, 'naturaltime') |
