dt.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # -*- coding: utf-8 -*-
  2. # -*- test-case-name: pytils.test.test_dt -*-
  3. """
  4. Russian dates without locales
  5. """
  6. import datetime
  7. from pytils import numeral
  8. from pytils.utils import check_positive
  9. DAY_ALTERNATIVES = {
  10. 1: ("вчера", "завтра"),
  11. 2: ("позавчера", "послезавтра")
  12. } #: Day alternatives (i.e. one day ago -> yesterday)
  13. DAY_VARIANTS = (
  14. "день",
  15. "дня",
  16. "дней",
  17. ) #: Forms (1, 2, 5) for noun 'day'
  18. HOUR_VARIANTS = (
  19. "час",
  20. "часа",
  21. "часов",
  22. ) #: Forms (1, 2, 5) for noun 'hour'
  23. MINUTE_VARIANTS = (
  24. "минуту",
  25. "минуты",
  26. "минут",
  27. ) #: Forms (1, 2, 5) for noun 'minute'
  28. PREFIX_IN = "через" #: Prefix 'in' (i.e. B{in} three hours)
  29. SUFFIX_AGO = "назад" #: Prefix 'ago' (i.e. three hours B{ago})
  30. MONTH_NAMES = (
  31. ("янв", "январь", "января"),
  32. ("фев", "февраль", "февраля"),
  33. ("мар", "март", "марта"),
  34. ("апр", "апрель", "апреля"),
  35. ("май", "май", "мая"),
  36. ("июн", "июнь", "июня"),
  37. ("июл", "июль", "июля"),
  38. ("авг", "август", "августа"),
  39. ("сен", "сентябрь", "сентября"),
  40. ("окт", "октябрь", "октября"),
  41. ("ноя", "ноябрь", "ноября"),
  42. ("дек", "декабрь", "декабря"),
  43. ) #: Month names (abbreviated, full, inflected)
  44. DAY_NAMES = (
  45. ("пн", "понедельник", "понедельник", "в\xa0"),
  46. ("вт", "вторник", "вторник", "во\xa0"),
  47. ("ср", "среда", "среду", "в\xa0"),
  48. ("чт", "четверг", "четверг", "в\xa0"),
  49. ("пт", "пятница", "пятницу", "в\xa0"),
  50. ("сб", "суббота", "субботу", "в\xa0"),
  51. ("вск", "воскресенье", "воскресенье", "в\xa0")
  52. ) #: Day names (abbreviated, full, inflected, preposition)
  53. def distance_of_time_in_words(from_time, accuracy=1, to_time=None):
  54. """
  55. Represents distance of time in words
  56. @param from_time: source time (in seconds from epoch)
  57. @type from_time: C{int}, C{float} or C{datetime.datetime}
  58. @param accuracy: level of accuracy (1..3), default=1
  59. @type accuracy: C{int}
  60. @param to_time: target time (in seconds from epoch),
  61. default=None translates to current time
  62. @type to_time: C{int}, C{float} or C{datetime.datetime}
  63. @return: distance of time in words
  64. @rtype: C{str}
  65. @raise ValueError: accuracy is lesser or equal zero
  66. """
  67. current = False
  68. if to_time is None:
  69. current = True
  70. to_time = datetime.datetime.now()
  71. check_positive(accuracy, strict=True)
  72. if not isinstance(from_time, datetime.datetime):
  73. from_time = datetime.datetime.fromtimestamp(from_time)
  74. if not isinstance(to_time, datetime.datetime):
  75. to_time = datetime.datetime.fromtimestamp(to_time)
  76. if from_time.tzinfo and not to_time.tzinfo:
  77. to_time = to_time.replace(tzinfo=from_time.tzinfo)
  78. dt_delta = to_time - from_time
  79. difference = dt_delta.days*86400 + dt_delta.seconds
  80. minutes_orig = int(abs(difference)/60.0)
  81. hours_orig = int(abs(difference)/3600.0)
  82. days_orig = int(abs(difference)/86400.0)
  83. in_future = from_time > to_time
  84. words = []
  85. values = []
  86. alternatives = []
  87. days = days_orig
  88. hours = hours_orig - days_orig*24
  89. words.append("%d %s" % (days, numeral.choose_plural(days, DAY_VARIANTS)))
  90. values.append(days)
  91. words.append("%d %s" %
  92. (hours, numeral.choose_plural(hours, HOUR_VARIANTS)))
  93. values.append(hours)
  94. days == 0 and hours == 1 and current and alternatives.append("час")
  95. minutes = minutes_orig - hours_orig*60
  96. words.append("%d %s" % (minutes,
  97. numeral.choose_plural(minutes, MINUTE_VARIANTS)))
  98. values.append(minutes)
  99. days == 0 and hours == 0 and minutes == 1 and current and \
  100. alternatives.append("минуту")
  101. # убираем из values и words конечные нули
  102. while values and not values[-1]:
  103. values.pop()
  104. words.pop()
  105. # убираем из values и words начальные нули
  106. while values and not values[0]:
  107. values.pop(0)
  108. words.pop(0)
  109. limit = min(accuracy, len(words))
  110. real_words = words[:limit]
  111. real_values = values[:limit]
  112. # снова убираем конечные нули
  113. while real_values and not real_values[-1]:
  114. real_values.pop()
  115. real_words.pop()
  116. limit -= 1
  117. real_str = u" ".join(real_words)
  118. # альтернативные варианты нужны только если в real_words одно значение
  119. # и, вдобавок, если используется текущее время
  120. alter_str = limit == 1 and current and alternatives and \
  121. alternatives[0]
  122. _result_str = alter_str or real_str
  123. result_str = in_future and "%s %s" % (PREFIX_IN, _result_str) \
  124. or "%s %s" % (_result_str, SUFFIX_AGO)
  125. # если же прошло менее минуты, то real_words -- пустой, и поэтому
  126. # нужно брать alternatives[0], а не result_str
  127. zero_str = minutes == 0 and not real_words and \
  128. (in_future and "менее чем через минуту"
  129. or "менее минуты назад")
  130. # нужно использовать вчера/позавчера/завтра/послезавтра
  131. # если days 1..2 и в real_words одно значение
  132. day_alternatives = DAY_ALTERNATIVES.get(days, False)
  133. alternate_day = day_alternatives and current and limit == 1 and \
  134. ((in_future and day_alternatives[1])
  135. or day_alternatives[0])
  136. final_str = not real_words and zero_str or alternate_day or result_str
  137. return final_str
  138. def ru_strftime(format="%d.%m.%Y", date=None, inflected=False,
  139. inflected_day=False, preposition=False):
  140. """
  141. Russian strftime without locale
  142. @param format: strftime format, default='%d.%m.%Y'
  143. @type format: C{str}
  144. @param date: date value, default=None translates to today
  145. @type date: C{datetime.date} or C{datetime.datetime}
  146. @param inflected: is month inflected, default False
  147. @type inflected: C{bool}
  148. @param inflected_day: is day inflected, default False
  149. @type inflected: C{bool}
  150. @param preposition: is preposition used, default False
  151. preposition=True automatically implies inflected_day=True
  152. @type preposition: C{bool}
  153. @return: strftime string
  154. @rtype: C{str}
  155. """
  156. if date is None:
  157. date = datetime.datetime.today()
  158. weekday = date.weekday()
  159. prepos = preposition and DAY_NAMES[weekday][3] or u""
  160. month_idx = inflected and 2 or 1
  161. day_idx = (inflected_day or preposition) and 2 or 1
  162. # for russian typography standard,
  163. # 1 April 2007, but 01.04.2007
  164. if '%b' in format or '%B' in format:
  165. format = format.replace('%d', str(date.day))
  166. format = format.replace('%a', prepos+DAY_NAMES[weekday][0])
  167. format = format.replace('%A', prepos+DAY_NAMES[weekday][day_idx])
  168. format = format.replace('%b', MONTH_NAMES[date.month-1][0])
  169. format = format.replace('%B', MONTH_NAMES[date.month-1][month_idx])
  170. u_res = date.strftime(format)
  171. return u_res