You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

timesince.py 3.1KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import calendar
  2. import datetime
  3. from django.utils.html import avoid_wrapping
  4. from django.utils.timezone import is_aware, utc
  5. from django.utils.translation import gettext, ngettext_lazy
  6. TIME_STRINGS = {
  7. 'year': ngettext_lazy('%d year', '%d years'),
  8. 'month': ngettext_lazy('%d month', '%d months'),
  9. 'week': ngettext_lazy('%d week', '%d weeks'),
  10. 'day': ngettext_lazy('%d day', '%d days'),
  11. 'hour': ngettext_lazy('%d hour', '%d hours'),
  12. 'minute': ngettext_lazy('%d minute', '%d minutes'),
  13. }
  14. TIMESINCE_CHUNKS = (
  15. (60 * 60 * 24 * 365, 'year'),
  16. (60 * 60 * 24 * 30, 'month'),
  17. (60 * 60 * 24 * 7, 'week'),
  18. (60 * 60 * 24, 'day'),
  19. (60 * 60, 'hour'),
  20. (60, 'minute'),
  21. )
  22. def timesince(d, now=None, reversed=False, time_strings=None):
  23. """
  24. Take two datetime objects and return the time between d and now as a nicely
  25. formatted string, e.g. "10 minutes". If d occurs after now, return
  26. "0 minutes".
  27. Units used are years, months, weeks, days, hours, and minutes.
  28. Seconds and microseconds are ignored. Up to two adjacent units will be
  29. displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
  30. possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
  31. `time_strings` is an optional dict of strings to replace the default
  32. TIME_STRINGS dict.
  33. Adapted from
  34. https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
  35. """
  36. if time_strings is None:
  37. time_strings = TIME_STRINGS
  38. # Convert datetime.date to datetime.datetime for comparison.
  39. if not isinstance(d, datetime.datetime):
  40. d = datetime.datetime(d.year, d.month, d.day)
  41. if now and not isinstance(now, datetime.datetime):
  42. now = datetime.datetime(now.year, now.month, now.day)
  43. now = now or datetime.datetime.now(utc if is_aware(d) else None)
  44. if reversed:
  45. d, now = now, d
  46. delta = now - d
  47. # Deal with leapyears by subtracing the number of leapdays
  48. leapdays = calendar.leapdays(d.year, now.year)
  49. if leapdays != 0:
  50. if calendar.isleap(d.year):
  51. leapdays -= 1
  52. elif calendar.isleap(now.year):
  53. leapdays += 1
  54. delta -= datetime.timedelta(leapdays)
  55. # ignore microseconds
  56. since = delta.days * 24 * 60 * 60 + delta.seconds
  57. if since <= 0:
  58. # d is in the future compared to now, stop processing.
  59. return avoid_wrapping(gettext('0 minutes'))
  60. for i, (seconds, name) in enumerate(TIMESINCE_CHUNKS):
  61. count = since // seconds
  62. if count != 0:
  63. break
  64. result = avoid_wrapping(time_strings[name] % count)
  65. if i + 1 < len(TIMESINCE_CHUNKS):
  66. # Now get the second item
  67. seconds2, name2 = TIMESINCE_CHUNKS[i + 1]
  68. count2 = (since - (seconds * count)) // seconds2
  69. if count2 != 0:
  70. result += gettext(', ') + avoid_wrapping(time_strings[name2] % count2)
  71. return result
  72. def timeuntil(d, now=None, time_strings=None):
  73. """
  74. Like timesince, but return a string measuring the time until the given time.
  75. """
  76. return timesince(d, now, reversed=True, time_strings=time_strings)