_datetime.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import re
  2. from calendar import day_abbr, day_name, month_abbr, month_name
  3. from datetime import datetime as datetime_
  4. from datetime import timedelta, timezone
  5. from functools import lru_cache, partial
  6. from time import localtime, strftime
  7. tokens = r"H{1,2}|h{1,2}|m{1,2}|s{1,2}|S+|YYYY|YY|M{1,4}|D{1,4}|Z{1,2}|zz|A|X|x|E|Q|dddd|ddd|d"
  8. pattern = re.compile(r"(?:{0})|\[(?:{0}|!UTC|)\]".format(tokens))
  9. def _builtin_datetime_formatter(is_utc, format_string, dt):
  10. if is_utc:
  11. dt = dt.astimezone(timezone.utc)
  12. return dt.strftime(format_string)
  13. def _loguru_datetime_formatter(is_utc, format_string, formatters, dt):
  14. if is_utc:
  15. dt = dt.astimezone(timezone.utc)
  16. t = dt.timetuple()
  17. args = tuple(f(t, dt) for f in formatters)
  18. return format_string % args
  19. def _default_datetime_formatter(dt):
  20. return "%04d-%02d-%02d %02d:%02d:%02d.%03d" % (
  21. dt.year,
  22. dt.month,
  23. dt.day,
  24. dt.hour,
  25. dt.minute,
  26. dt.second,
  27. dt.microsecond // 1000,
  28. )
  29. def _format_timezone(tzinfo, *, sep):
  30. offset = tzinfo.utcoffset(None).total_seconds()
  31. sign = "+" if offset >= 0 else "-"
  32. (h, m), s = divmod(abs(offset // 60), 60), abs(offset) % 60
  33. z = "%s%02d%s%02d" % (sign, h, sep, m)
  34. if s > 0:
  35. if s.is_integer():
  36. z += "%s%02d" % (sep, s)
  37. else:
  38. z += "%s%09.06f" % (sep, s)
  39. return z
  40. @lru_cache(maxsize=32)
  41. def _compile_format(spec):
  42. if spec == "YYYY-MM-DD HH:mm:ss.SSS":
  43. return _default_datetime_formatter
  44. is_utc = spec.endswith("!UTC")
  45. if is_utc:
  46. spec = spec[:-4]
  47. if not spec:
  48. spec = "%Y-%m-%dT%H:%M:%S.%f%z"
  49. if "%" in spec:
  50. return partial(_builtin_datetime_formatter, is_utc, spec)
  51. if "SSSSSSS" in spec:
  52. raise ValueError(
  53. "Invalid time format: the provided format string contains more than six successive "
  54. "'S' characters. This may be due to an attempt to use nanosecond precision, which "
  55. "is not supported."
  56. )
  57. rep = {
  58. "YYYY": ("%04d", lambda t, dt: t.tm_year),
  59. "YY": ("%02d", lambda t, dt: t.tm_year % 100),
  60. "Q": ("%d", lambda t, dt: (t.tm_mon - 1) // 3 + 1),
  61. "MMMM": ("%s", lambda t, dt: month_name[t.tm_mon]),
  62. "MMM": ("%s", lambda t, dt: month_abbr[t.tm_mon]),
  63. "MM": ("%02d", lambda t, dt: t.tm_mon),
  64. "M": ("%d", lambda t, dt: t.tm_mon),
  65. "DDDD": ("%03d", lambda t, dt: t.tm_yday),
  66. "DDD": ("%d", lambda t, dt: t.tm_yday),
  67. "DD": ("%02d", lambda t, dt: t.tm_mday),
  68. "D": ("%d", lambda t, dt: t.tm_mday),
  69. "dddd": ("%s", lambda t, dt: day_name[t.tm_wday]),
  70. "ddd": ("%s", lambda t, dt: day_abbr[t.tm_wday]),
  71. "d": ("%d", lambda t, dt: t.tm_wday),
  72. "E": ("%d", lambda t, dt: t.tm_wday + 1),
  73. "HH": ("%02d", lambda t, dt: t.tm_hour),
  74. "H": ("%d", lambda t, dt: t.tm_hour),
  75. "hh": ("%02d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),
  76. "h": ("%d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),
  77. "mm": ("%02d", lambda t, dt: t.tm_min),
  78. "m": ("%d", lambda t, dt: t.tm_min),
  79. "ss": ("%02d", lambda t, dt: t.tm_sec),
  80. "s": ("%d", lambda t, dt: t.tm_sec),
  81. "S": ("%d", lambda t, dt: dt.microsecond // 100000),
  82. "SS": ("%02d", lambda t, dt: dt.microsecond // 10000),
  83. "SSS": ("%03d", lambda t, dt: dt.microsecond // 1000),
  84. "SSSS": ("%04d", lambda t, dt: dt.microsecond // 100),
  85. "SSSSS": ("%05d", lambda t, dt: dt.microsecond // 10),
  86. "SSSSSS": ("%06d", lambda t, dt: dt.microsecond),
  87. "A": ("%s", lambda t, dt: "AM" if t.tm_hour < 12 else "PM"),
  88. "Z": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep=":")),
  89. "ZZ": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep="")),
  90. "zz": ("%s", lambda t, dt: (dt.tzinfo or timezone.utc).tzname(dt) or ""),
  91. "X": ("%d", lambda t, dt: dt.timestamp()),
  92. "x": ("%d", lambda t, dt: int(dt.timestamp() * 1000000 + dt.microsecond)),
  93. }
  94. format_string = ""
  95. formatters = []
  96. pos = 0
  97. for match in pattern.finditer(spec):
  98. start, end = match.span()
  99. format_string += spec[pos:start]
  100. pos = end
  101. token = match.group(0)
  102. try:
  103. specifier, formatter = rep[token]
  104. except KeyError:
  105. format_string += token[1:-1]
  106. else:
  107. format_string += specifier
  108. formatters.append(formatter)
  109. format_string += spec[pos:]
  110. return partial(_loguru_datetime_formatter, is_utc, format_string, formatters)
  111. class datetime(datetime_): # noqa: N801
  112. def __format__(self, fmt):
  113. return _compile_format(fmt)(self)
  114. def aware_now():
  115. now = datetime_.now()
  116. timestamp = now.timestamp()
  117. local = localtime(timestamp)
  118. try:
  119. seconds = local.tm_gmtoff
  120. zone = local.tm_zone
  121. except AttributeError:
  122. # Workaround for Python 3.5.
  123. utc_naive = datetime_.fromtimestamp(timestamp, tz=timezone.utc).replace(tzinfo=None)
  124. offset = datetime_.fromtimestamp(timestamp) - utc_naive
  125. seconds = offset.total_seconds()
  126. zone = strftime("%Z")
  127. tzinfo = timezone(timedelta(seconds=seconds), zone)
  128. return datetime.combine(now.date(), now.time().replace(tzinfo=tzinfo))