isostrf.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. This module provides an alternative strftime method.
  3. The strftime method in this module allows only a subset of Python's strftime
  4. format codes, plus a few additional. It supports the full range of date values
  5. possible with standard Python date/time objects. Furthermore there are several
  6. pr-defined format strings in this module to make ease producing of ISO 8601
  7. conforming strings.
  8. """
  9. import re
  10. from datetime import date, timedelta
  11. from isodate.duration import Duration
  12. from isodate.isotzinfo import tz_isoformat
  13. # Date specific format strings
  14. DATE_BAS_COMPLETE = "%Y%m%d"
  15. DATE_EXT_COMPLETE = "%Y-%m-%d"
  16. DATE_BAS_WEEK_COMPLETE = "%YW%W%w"
  17. DATE_EXT_WEEK_COMPLETE = "%Y-W%W-%w"
  18. DATE_BAS_ORD_COMPLETE = "%Y%j"
  19. DATE_EXT_ORD_COMPLETE = "%Y-%j"
  20. DATE_BAS_WEEK = "%YW%W"
  21. DATE_EXT_WEEK = "%Y-W%W"
  22. DATE_BAS_MONTH = "%Y%m"
  23. DATE_EXT_MONTH = "%Y-%m"
  24. DATE_YEAR = "%Y"
  25. DATE_CENTURY = "%C"
  26. # Time specific format strings
  27. TIME_BAS_COMPLETE = "%H%M%S"
  28. TIME_EXT_COMPLETE = "%H:%M:%S"
  29. TIME_BAS_MINUTE = "%H%M"
  30. TIME_EXT_MINUTE = "%H:%M"
  31. TIME_HOUR = "%H"
  32. # Time zone formats
  33. TZ_BAS = "%z"
  34. TZ_EXT = "%Z"
  35. TZ_HOUR = "%h"
  36. # DateTime formats
  37. DT_EXT_COMPLETE = DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
  38. DT_BAS_COMPLETE = DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
  39. DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
  40. DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
  41. DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
  42. DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
  43. # Duration formts
  44. D_DEFAULT = "P%P"
  45. D_WEEK = "P%p"
  46. D_ALT_EXT = "P" + DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE
  47. D_ALT_BAS = "P" + DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE
  48. D_ALT_EXT_ORD = "P" + DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE
  49. D_ALT_BAS_ORD = "P" + DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE
  50. STRF_DT_MAP = {
  51. "%d": lambda tdt, yds: "%02d" % tdt.day,
  52. "%f": lambda tdt, yds: "%06d" % tdt.microsecond,
  53. "%H": lambda tdt, yds: "%02d" % tdt.hour,
  54. "%j": lambda tdt, yds: "%03d"
  55. % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1),
  56. "%m": lambda tdt, yds: "%02d" % tdt.month,
  57. "%M": lambda tdt, yds: "%02d" % tdt.minute,
  58. "%S": lambda tdt, yds: "%02d" % tdt.second,
  59. "%w": lambda tdt, yds: "%1d" % tdt.isoweekday(),
  60. "%W": lambda tdt, yds: "%02d" % tdt.isocalendar()[1],
  61. "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + (("%%0%dd" % yds) % tdt.year),
  62. "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
  63. + (("%%0%dd" % (yds - 2)) % (tdt.year / 100)),
  64. "%h": lambda tdt, yds: tz_isoformat(tdt, "%h"),
  65. "%Z": lambda tdt, yds: tz_isoformat(tdt, "%Z"),
  66. "%z": lambda tdt, yds: tz_isoformat(tdt, "%z"),
  67. "%%": lambda tdt, yds: "%",
  68. }
  69. STRF_D_MAP = {
  70. "%d": lambda tdt, yds: "%02d" % tdt.days,
  71. "%f": lambda tdt, yds: "%06d" % tdt.microseconds,
  72. "%H": lambda tdt, yds: "%02d" % (tdt.seconds / 60 / 60),
  73. "%m": lambda tdt, yds: "%02d" % tdt.months,
  74. "%M": lambda tdt, yds: "%02d" % ((tdt.seconds / 60) % 60),
  75. "%S": lambda tdt, yds: "%02d" % (tdt.seconds % 60),
  76. "%W": lambda tdt, yds: "%02d" % (abs(tdt.days / 7)),
  77. "%Y": lambda tdt, yds: (((yds != 4) and "+") or "")
  78. + (("%%0%dd" % yds) % tdt.years),
  79. "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
  80. + (("%%0%dd" % (yds - 2)) % (tdt.years / 100)),
  81. "%%": lambda tdt, yds: "%",
  82. }
  83. def _strfduration(tdt, format, yeardigits=4):
  84. """
  85. this is the work method for timedelta and Duration instances.
  86. see strftime for more details.
  87. """
  88. def repl(match):
  89. """
  90. lookup format command and return corresponding replacement.
  91. """
  92. if match.group(0) in STRF_D_MAP:
  93. return STRF_D_MAP[match.group(0)](tdt, yeardigits)
  94. elif match.group(0) == "%P":
  95. ret = []
  96. if isinstance(tdt, Duration):
  97. if tdt.years:
  98. ret.append("%sY" % abs(tdt.years))
  99. if tdt.months:
  100. ret.append("%sM" % abs(tdt.months))
  101. usecs = abs(
  102. (tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds
  103. )
  104. seconds, usecs = divmod(usecs, 1000000)
  105. minutes, seconds = divmod(seconds, 60)
  106. hours, minutes = divmod(minutes, 60)
  107. days, hours = divmod(hours, 24)
  108. if days:
  109. ret.append("%sD" % days)
  110. if hours or minutes or seconds or usecs:
  111. ret.append("T")
  112. if hours:
  113. ret.append("%sH" % hours)
  114. if minutes:
  115. ret.append("%sM" % minutes)
  116. if seconds or usecs:
  117. if usecs:
  118. ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0"))
  119. else:
  120. ret.append("%d" % seconds)
  121. ret.append("S")
  122. # at least one component has to be there.
  123. return ret and "".join(ret) or "0D"
  124. elif match.group(0) == "%p":
  125. return str(abs(tdt.days // 7)) + "W"
  126. return match.group(0)
  127. return re.sub("%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p", repl, format)
  128. def _strfdt(tdt, format, yeardigits=4):
  129. """
  130. this is the work method for time and date instances.
  131. see strftime for more details.
  132. """
  133. def repl(match):
  134. """
  135. lookup format command and return corresponding replacement.
  136. """
  137. if match.group(0) in STRF_DT_MAP:
  138. return STRF_DT_MAP[match.group(0)](tdt, yeardigits)
  139. return match.group(0)
  140. return re.sub("%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%", repl, format)
  141. def strftime(tdt, format, yeardigits=4):
  142. """Directive Meaning Notes
  143. %d Day of the month as a decimal number [01,31].
  144. %f Microsecond as a decimal number [0,999999], zero-padded
  145. on the left (1)
  146. %H Hour (24-hour clock) as a decimal number [00,23].
  147. %j Day of the year as a decimal number [001,366].
  148. %m Month as a decimal number [01,12].
  149. %M Minute as a decimal number [00,59].
  150. %S Second as a decimal number [00,61]. (3)
  151. %w Weekday as a decimal number [0(Monday),6].
  152. %W Week number of the year (Monday as the first day of the week)
  153. as a decimal number [00,53]. All days in a new year preceding the
  154. first Monday are considered to be in week 0. (4)
  155. %Y Year with century as a decimal number. [0000,9999]
  156. %C Century as a decimal number. [00,99]
  157. %z UTC offset in the form +HHMM or -HHMM (empty string if the
  158. object is naive). (5)
  159. %Z Time zone name (empty string if the object is naive).
  160. %P ISO8601 duration format.
  161. %p ISO8601 duration format in weeks.
  162. %% A literal '%' character.
  163. """
  164. if isinstance(tdt, (timedelta, Duration)):
  165. return _strfduration(tdt, format, yeardigits)
  166. return _strfdt(tdt, format, yeardigits)