time_util.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import re
  2. from datetime import datetime
  3. from typing import Any
  4. class TimeUtil:
  5. """
  6. 时间格式化工具类
  7. """
  8. DEFAULT_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
  9. @classmethod
  10. def object_format_datetime(cls, obj: Any) -> Any:
  11. """
  12. 格式化对象中的 datetime 属性为默认字符串格式。
  13. 参数:
  14. - obj (Any): 输入对象。
  15. 返回:
  16. - Any: 格式化后的对象。
  17. """
  18. for attr in dir(obj):
  19. if not attr.startswith("_"): # 跳过私有属性
  20. value = getattr(obj, attr)
  21. if isinstance(value, datetime):
  22. setattr(obj, attr, value.strftime(cls.DEFAULT_DATETIME_FORMAT))
  23. return obj
  24. @classmethod
  25. def list_format_datetime(cls, lst: list[Any]) -> list[Any]:
  26. """
  27. 格式化列表内每个对象的 datetime 属性。
  28. 参数:
  29. - lst (List[Any]): 对象列表。
  30. 返回:
  31. - list[Any]: 格式化后的对象列表。
  32. """
  33. return [cls.object_format_datetime(obj) for obj in lst]
  34. @classmethod
  35. def format_datetime_dict_list(cls, dicts: list[dict]) -> list[dict]:
  36. """
  37. 递归格式化字典列表中的 datetime 值为默认字符串格式。
  38. 参数:
  39. - dicts (list[dict]): 字典列表。
  40. 返回:
  41. - list[dict]: 格式化后的字典列表。
  42. """
  43. def _format_value(value: Any) -> Any:
  44. if isinstance(value, dict):
  45. return {k: _format_value(v) for k, v in value.items()}
  46. if isinstance(value, list):
  47. return [_format_value(item) for item in value]
  48. if isinstance(value, datetime):
  49. return value.strftime(cls.DEFAULT_DATETIME_FORMAT)
  50. return value
  51. return [_format_value(item) for item in dicts]
  52. @classmethod
  53. def __valid_range(cls, search_str: str, start_range: int, end_range: int) -> bool:
  54. """
  55. 校验范围字符串是否合法。
  56. 参数:
  57. - search_str (str): 范围字符串(例如:"1-5")。
  58. - start_range (int): 允许的最小范围值。
  59. - end_range (int): 允许的最大范围值。
  60. 返回:
  61. - bool: 校验是否通过。
  62. """
  63. match = re.match(r"^(\d+)-(\d+)$", search_str)
  64. if match:
  65. start, end = int(match.group(1)), int(match.group(2))
  66. return start_range <= start < end <= end_range
  67. return False
  68. @classmethod
  69. def __valid_sum(
  70. cls,
  71. search_str: str,
  72. start_range_a: int,
  73. start_range_b: int,
  74. end_range_a: int,
  75. end_range_b: int,
  76. sum_range: int,
  77. ) -> bool:
  78. """
  79. 校验和字符串是否合法。
  80. 参数:
  81. - search_str (str): 和字符串(例如:"1/5")。
  82. - start_range_a (int): 允许的最小范围值A。
  83. - start_range_b (int): 允许的最大范围值A。
  84. - end_range_a (int): 允许的最小范围值B。
  85. - end_range_b (int): 允许的最大范围值B。
  86. - sum_range (int): 允许的最大和值。
  87. 返回:
  88. - bool: 校验是否通过。
  89. """
  90. match = re.match(r"^(\d+)/(\d+)$", search_str)
  91. if match:
  92. start, end = int(match.group(1)), int(match.group(2))
  93. return (
  94. start_range_a <= start <= start_range_b
  95. and end_range_a <= end <= end_range_b
  96. and start + end <= sum_range
  97. )
  98. return False
  99. @classmethod
  100. def validate_second_or_minute(cls, second_or_minute: str) -> bool:
  101. """
  102. 校验秒或分钟字段的合法性。
  103. 参数:
  104. - second_or_minute (str): 秒或分钟值。
  105. 返回:
  106. - bool: 校验是否通过。
  107. """
  108. return bool(
  109. second_or_minute == "*"
  110. or ("-" in second_or_minute and cls.__valid_range(second_or_minute, 0, 59))
  111. or ("/" in second_or_minute and cls.__valid_sum(second_or_minute, 0, 58, 1, 59, 59))
  112. or re.match(r"^(?:[0-5]?\d|59)(?:,[0-5]?\d|59)*$", second_or_minute)
  113. )
  114. @classmethod
  115. def validate_hour(cls, hour: str) -> bool:
  116. """
  117. 校验小时字段的合法性。
  118. 参数:
  119. - hour (str): 小时值。
  120. 返回:
  121. - bool: 校验是否通过。
  122. """
  123. return bool(
  124. hour == "*"
  125. or ("-" in hour and cls.__valid_range(hour, 0, 23))
  126. or ("/" in hour and cls.__valid_sum(hour, 0, 22, 1, 23, 23))
  127. or re.match(r"^(?:0|[1-9]|1\d|2[0-3])(?:,(?:0|[1-9]|1\d|2[0-3]))*$", hour)
  128. )
  129. @classmethod
  130. def validate_day(cls, day: str) -> bool:
  131. """
  132. 校验日期字段的合法性。
  133. 参数:
  134. - day (str): 日值。
  135. 返回:
  136. - bool: 校验是否通过。
  137. """
  138. return bool(
  139. day in ["*", "?", "L"]
  140. or ("-" in day and cls.__valid_range(day, 1, 31))
  141. or ("/" in day and cls.__valid_sum(day, 1, 30, 1, 30, 31))
  142. or ("W" in day and re.match(r"^(?:[1-9]|1\d|2\d|3[01])W$", day))
  143. or re.match(
  144. r"^(?:0|[1-9]|1\d|2[0-9]|3[0-1])(?:,(?:0|[1-9]|1\d|2[0-9]|3[0-1]))*$",
  145. day,
  146. )
  147. )
  148. @classmethod
  149. def validate_month(cls, month: str) -> bool:
  150. """
  151. 校验月份字段的合法性。
  152. 参数:
  153. - month (str): 月值。
  154. 返回:
  155. - bool: 校验是否通过。
  156. """
  157. return bool(
  158. month == "*"
  159. or ("-" in month and cls.__valid_range(month, 1, 12))
  160. or ("/" in month and cls.__valid_sum(month, 1, 11, 1, 11, 12))
  161. or re.match(r"^(?:0|[1-9]|1[0-2])(?:,(?:0|[1-9]|1[0-2]))*$", month)
  162. )
  163. @classmethod
  164. def validate_week(cls, week: str) -> bool:
  165. """
  166. 校验星期字段的合法性。
  167. 参数:
  168. - week (str): 周值。
  169. 返回:
  170. - bool: 校验是否通过。
  171. """
  172. return bool(
  173. week in ["*", "?"]
  174. or ("-" in week and cls.__valid_range(week, 1, 7))
  175. or ("#" in week and re.match(r"^[1-7]#[1-4]$", week))
  176. or ("L" in week and re.match(r"^[1-7]L$", week))
  177. or re.match(r"^[1-7](?:(,[1-7]))*$", week)
  178. )
  179. @classmethod
  180. def validate_year(cls, year: str) -> bool:
  181. """
  182. 校验年份字段的合法性。
  183. 参数:
  184. - year (str): 年值。
  185. 返回:
  186. - bool: 校验是否通过。
  187. """
  188. current_year = int(datetime.now().year)
  189. future_years = [current_year + i for i in range(9)]
  190. return bool(
  191. year == "*"
  192. or ("-" in year and cls.__valid_range(year, current_year, 2099))
  193. or (
  194. "/" in year
  195. and cls.__valid_sum(year, current_year, 2098, 1, 2099 - current_year, 2099)
  196. )
  197. or ("#" in year and re.match(r"^[1-7]#[1-4]$", year))
  198. or ("L" in year and re.match(r"^[1-7]L$", year))
  199. or (
  200. (len(year) == 4 or "," in year)
  201. and all(
  202. int(item) in future_years and current_year <= int(item) <= 2099
  203. for item in year.split(",")
  204. )
  205. )
  206. )
  207. @classmethod
  208. def validate_cron_expression(cls, cron_expression: str):
  209. """
  210. 校验 Cron 表达式是否正确。
  211. * * * * * *
  212. | | | | | |
  213. | | | | | +--- 星期(0-7,0和7都表示星期日)
  214. | | | | +----- 月份(1-12)
  215. | | | +------- 日期(1-31)
  216. | | +--------- 小时(0-23)
  217. | +----------- 分钟(0-59)
  218. +------------- 秒(0-59),部分环境不支持秒字段。
  219. 参数:
  220. - cron_expression (str): Cron 表达式。
  221. 返回:
  222. - bool: 校验是否通过。
  223. """
  224. values = cron_expression.split()
  225. if len(values) != 6 and len(values) != 7:
  226. return False
  227. second_validation = cls.validate_second_or_minute(values[0])
  228. minute_validation = cls.validate_second_or_minute(values[1])
  229. hour_validation = cls.validate_hour(values[2])
  230. day_validation = cls.validate_day(values[3])
  231. month_validation = cls.validate_month(values[4])
  232. week_validation = cls.validate_week(values[5])
  233. validation = (
  234. second_validation
  235. and minute_validation
  236. and hour_validation
  237. and day_validation
  238. and month_validation
  239. and week_validation
  240. )
  241. if len(values) == 6:
  242. return validation
  243. if len(values) == 7:
  244. year_validation = cls.validate_year(values[6])
  245. return validation and year_validation