summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorJannis Leidel <jannis@leidel.info>2011-09-23 16:45:40 +0000
committerJannis Leidel <jannis@leidel.info>2011-09-23 16:45:40 +0000
commit6cae2a550d0d09d26554b9c0c0fbab2859ca45ec (patch)
tree7d5cd4de248746200ccd5728991b253abbfea805 /django
parent33ad413c1ce7e30d339bd0c4f387162af26251f5 (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.po187
-rw-r--r--django/contrib/humanize/models.py0
-rw-r--r--django/contrib/humanize/templatetags/humanize.py93
-rw-r--r--django/contrib/humanize/tests.py162
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')