diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 838d669834..6095813b06 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -9,7 +9,8 @@ from django.utils.formats import number_format from django.utils.safestring import mark_safe from django.utils.timezone import is_aware, utc from django.utils.translation import ( - gettext as _, ngettext, npgettext_lazy, pgettext, + gettext as _, gettext_lazy, ngettext, ngettext_lazy, npgettext_lazy, + pgettext, ) register = template.Library() @@ -214,79 +215,89 @@ def naturaltime(value): For date and time values show how many seconds, minutes, or hours ago compared to current timestamp return representing string. """ - if not isinstance(value, date): # datetime is a subclass of date - return value + return NaturalTimeFormatter.string_for(value) - now = datetime.now(utc if is_aware(value) else None) - if value < now: - delta = now - value - if delta.days != 0: - # Translators: delta will contain a string like '2 months' or '1 month, 2 weeks' - return _('%(delta)s ago') % {'delta': defaultfilters.timesince(value, now, time_strings={ - # Translators: 'naturaltime-past' strings will be included in - # '%(delta)s ago' - 'year': npgettext_lazy('naturaltime-past', '%d year', '%d years'), - 'month': npgettext_lazy('naturaltime-past', '%d month', '%d months'), - 'week': npgettext_lazy('naturaltime-past', '%d week', '%d weeks'), - 'day': npgettext_lazy('naturaltime-past', '%d day', '%d days'), - 'hour': npgettext_lazy('naturaltime-past', '%d hour', '%d hours'), - 'minute': npgettext_lazy('naturaltime-past', '%d minute', '%d minutes') - })} - elif delta.seconds == 0: - return _('now') - elif delta.seconds < 60: - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'a second ago', '%(count)s seconds ago', delta.seconds - ) % {'count': delta.seconds} - elif delta.seconds // 60 < 60: - count = delta.seconds // 60 - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'a minute ago', '%(count)s minutes ago', count - ) % {'count': count} + +class NaturalTimeFormatter: + time_strings = { + # Translators: delta will contain a string like '2 months' or '1 month, 2 weeks' + 'past-day': gettext_lazy('%(delta)s ago'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'past-hour': ngettext_lazy('an hour ago', '%(count)s hours ago', 'count'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'past-minute': ngettext_lazy('a minute ago', '%(count)s minutes ago', 'count'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'past-second': ngettext_lazy('a second ago', '%(count)s seconds ago', 'count'), + 'now': gettext_lazy('now'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'future-second': ngettext_lazy('a second from now', '%(count)s seconds from now', 'count'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'future-minute': ngettext_lazy('a minute from now', '%(count)s minutes from now', 'count'), + # Translators: please keep a non-breaking space (U+00A0) between count + # and time unit. + 'future-hour': ngettext_lazy('an hour from now', '%(count)s hours from now', 'count'), + # Translators: delta will contain a string like '2 months' or '1 month, 2 weeks' + 'future-day': gettext_lazy('%(delta)s from now'), + } + past_substrings = { + # Translators: 'naturaltime-past' strings will be included in '%(delta)s ago' + 'year': npgettext_lazy('naturaltime-past', '%d year', '%d years'), + 'month': npgettext_lazy('naturaltime-past', '%d month', '%d months'), + 'week': npgettext_lazy('naturaltime-past', '%d week', '%d weeks'), + 'day': npgettext_lazy('naturaltime-past', '%d day', '%d days'), + 'hour': npgettext_lazy('naturaltime-past', '%d hour', '%d hours'), + 'minute': npgettext_lazy('naturaltime-past', '%d minute', '%d minutes'), + } + future_substrings = { + # Translators: 'naturaltime-future' strings will be included in '%(delta)s from now' + 'year': npgettext_lazy('naturaltime-future', '%d year', '%d years'), + 'month': npgettext_lazy('naturaltime-future', '%d month', '%d months'), + 'week': npgettext_lazy('naturaltime-future', '%d week', '%d weeks'), + 'day': npgettext_lazy('naturaltime-future', '%d day', '%d days'), + 'hour': npgettext_lazy('naturaltime-future', '%d hour', '%d hours'), + 'minute': npgettext_lazy('naturaltime-future', '%d minute', '%d minutes'), + } + + @classmethod + def string_for(cls, value): + if not isinstance(value, date): # datetime is a subclass of date + return value + + now = datetime.now(utc if is_aware(value) else None) + if value < now: + delta = now - value + if delta.days != 0: + return cls.time_strings['past-day'] % { + 'delta': defaultfilters.timesince(value, now, time_strings=cls.past_substrings), + } + elif delta.seconds == 0: + return cls.time_strings['now'] + elif delta.seconds < 60: + return cls.time_strings['past-second'] % {'count': delta.seconds} + elif delta.seconds // 60 < 60: + count = delta.seconds // 60 + return cls.time_strings['past-minute'] % {'count': count} + else: + count = delta.seconds // 60 // 60 + return cls.time_strings['past-hour'] % {'count': count} else: - count = delta.seconds // 60 // 60 - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'an hour ago', '%(count)s hours ago', count - ) % {'count': count} - else: - delta = value - now - if delta.days != 0: - # Translators: delta will contain a string like '2 months' or '1 month, 2 weeks' - return _('%(delta)s from now') % {'delta': defaultfilters.timeuntil(value, now, time_strings={ - # Translators: 'naturaltime-future' strings will be included in - # '%(delta)s from now' - 'year': npgettext_lazy('naturaltime-future', '%d year', '%d years'), - 'month': npgettext_lazy('naturaltime-future', '%d month', '%d months'), - 'week': npgettext_lazy('naturaltime-future', '%d week', '%d weeks'), - 'day': npgettext_lazy('naturaltime-future', '%d day', '%d days'), - 'hour': npgettext_lazy('naturaltime-future', '%d hour', '%d hours'), - 'minute': npgettext_lazy('naturaltime-future', '%d minute', '%d minutes') - })} - elif delta.seconds == 0: - return _('now') - elif delta.seconds < 60: - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'a second from now', '%(count)s seconds from now', delta.seconds - ) % {'count': delta.seconds} - elif delta.seconds // 60 < 60: - count = delta.seconds // 60 - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'a minute from now', '%(count)s minutes from now', count - ) % {'count': count} - else: - count = delta.seconds // 60 // 60 - return ngettext( - # Translators: please keep a non-breaking space (U+00A0) - # between count and time unit. - 'an hour from now', '%(count)s hours from now', count - ) % {'count': count} + delta = value - now + if delta.days != 0: + return cls.time_strings['future-day'] % { + 'delta': defaultfilters.timeuntil(value, now, time_strings=cls.future_substrings), + } + elif delta.seconds == 0: + return cls.time_strings['now'] + elif delta.seconds < 60: + return cls.time_strings['future-second'] % {'count': delta.seconds} + elif delta.seconds // 60 < 60: + count = delta.seconds // 60 + return cls.time_strings['future-minute'] % {'count': count} + else: + count = delta.seconds // 60 // 60 + return cls.time_strings['future-hour'] % {'count': count}