_string_parsers.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import datetime
  2. import re
  3. class Frequencies:
  4. @staticmethod
  5. def hourly(t):
  6. dt = t + datetime.timedelta(hours=1)
  7. return dt.replace(minute=0, second=0, microsecond=0)
  8. @staticmethod
  9. def daily(t):
  10. dt = t + datetime.timedelta(days=1)
  11. return dt.replace(hour=0, minute=0, second=0, microsecond=0)
  12. @staticmethod
  13. def weekly(t):
  14. dt = t + datetime.timedelta(days=7 - t.weekday())
  15. return dt.replace(hour=0, minute=0, second=0, microsecond=0)
  16. @staticmethod
  17. def monthly(t):
  18. if t.month == 12:
  19. y, m = t.year + 1, 1
  20. else:
  21. y, m = t.year, t.month + 1
  22. return t.replace(year=y, month=m, day=1, hour=0, minute=0, second=0, microsecond=0)
  23. @staticmethod
  24. def yearly(t):
  25. y = t.year + 1
  26. return t.replace(year=y, month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
  27. def parse_size(size):
  28. size = size.strip()
  29. reg = re.compile(r"([e\+\-\.\d]+)\s*([kmgtpezy])?(i)?(b)", flags=re.I)
  30. match = reg.fullmatch(size)
  31. if not match:
  32. return None
  33. s, u, i, b = match.groups()
  34. try:
  35. s = float(s)
  36. except ValueError as e:
  37. raise ValueError("Invalid float value while parsing size: '%s'" % s) from e
  38. u = "kmgtpezy".index(u.lower()) + 1 if u else 0
  39. i = 1024 if i else 1000
  40. b = {"b": 8, "B": 1}[b] if b else 1
  41. return s * i**u / b
  42. def parse_duration(duration):
  43. duration = duration.strip()
  44. reg = r"(?:([e\+\-\.\d]+)\s*([a-z]+)[\s\,]*)"
  45. units = [
  46. ("y|years?", 31536000),
  47. ("months?", 2628000),
  48. ("w|weeks?", 604800),
  49. ("d|days?", 86400),
  50. ("h|hours?", 3600),
  51. ("min(?:ute)?s?", 60),
  52. ("s|sec(?:ond)?s?", 1), # spellchecker: disable-line
  53. ("ms|milliseconds?", 0.001),
  54. ("us|microseconds?", 0.000001),
  55. ]
  56. if not re.fullmatch(reg + "+", duration, flags=re.I):
  57. return None
  58. seconds = 0
  59. for value, unit in re.findall(reg, duration, flags=re.I):
  60. try:
  61. value = float(value)
  62. except ValueError as e:
  63. raise ValueError("Invalid float value while parsing duration: '%s'" % value) from e
  64. try:
  65. unit = next(u for r, u in units if re.fullmatch(r, unit, flags=re.I))
  66. except StopIteration:
  67. raise ValueError("Invalid unit value while parsing duration: '%s'" % unit) from None
  68. seconds += value * unit
  69. return datetime.timedelta(seconds=seconds)
  70. def parse_frequency(frequency):
  71. frequencies = {
  72. "hourly": Frequencies.hourly,
  73. "daily": Frequencies.daily,
  74. "weekly": Frequencies.weekly,
  75. "monthly": Frequencies.monthly,
  76. "yearly": Frequencies.yearly,
  77. }
  78. frequency = frequency.strip().lower()
  79. return frequencies.get(frequency, None)
  80. def parse_day(day):
  81. days = {
  82. "monday": 0,
  83. "tuesday": 1,
  84. "wednesday": 2,
  85. "thursday": 3,
  86. "friday": 4,
  87. "saturday": 5,
  88. "sunday": 6,
  89. }
  90. day = day.strip().lower()
  91. if day in days:
  92. return days[day]
  93. if day.startswith("w") and day[1:].isdigit():
  94. day = int(day[1:])
  95. if not 0 <= day < 7:
  96. raise ValueError("Invalid weekday value while parsing day (expected [0-6]): '%d'" % day)
  97. else:
  98. day = None
  99. return day
  100. def parse_time(time):
  101. time = time.strip()
  102. reg = re.compile(r"^[\d\.\:]+\s*(?:[ap]m)?$", flags=re.I)
  103. if not reg.match(time):
  104. return None
  105. formats = [
  106. "%H",
  107. "%H:%M",
  108. "%H:%M:%S",
  109. "%H:%M:%S.%f",
  110. "%I %p",
  111. "%I:%M %S",
  112. "%I:%M:%S %p",
  113. "%I:%M:%S.%f %p",
  114. ]
  115. for format_ in formats:
  116. try:
  117. dt = datetime.datetime.strptime(time, format_)
  118. except ValueError:
  119. pass
  120. else:
  121. return dt.time()
  122. raise ValueError("Unrecognized format while parsing time: '%s'" % time)
  123. def parse_daytime(daytime):
  124. daytime = daytime.strip()
  125. reg = re.compile(r"^(.*?)\s+at\s+(.*)$", flags=re.I)
  126. match = reg.match(daytime)
  127. if match:
  128. day, time = match.groups()
  129. else:
  130. day = time = daytime
  131. try:
  132. parsed_day = parse_day(day)
  133. if match and parsed_day is None:
  134. raise ValueError("Unparsable day")
  135. except ValueError as e:
  136. raise ValueError("Invalid day while parsing daytime: '%s'" % day) from e
  137. try:
  138. parsed_time = parse_time(time)
  139. if match and parsed_time is None:
  140. raise ValueError("Unparsable time")
  141. except ValueError as e:
  142. raise ValueError("Invalid time while parsing daytime: '%s'" % time) from e
  143. if parsed_day is None and parsed_time is None:
  144. return None
  145. return parsed_day, parsed_time