hutool-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. hutool_python-1.0.0.dist-info/top_level.txt +1 -0
hutool/core/date.py ADDED
@@ -0,0 +1,1024 @@
1
+ """
2
+ 日期时间工具模块,对应 Java Hutool 的 cn.hutool.core.date.DateUtil 和 cn.hutool.core.date.DateTime。
3
+
4
+ 使用 pendulum 作为底层日期时间库,提供丰富的日期操作方法。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time as _time
10
+ from datetime import date, datetime, timedelta
11
+ from typing import List, Optional, Union
12
+
13
+ import pendulum
14
+ from pendulum import DateTime as PendulumDateTime
15
+
16
+
17
+ class DateTime:
18
+ """日期时间封装类,包装 pendulum.DateTime。
19
+
20
+ 与 Java Hutool 的 cn.hutool.core.date.DateTime 对应,
21
+ 对 pendulum 的 DateTime 进行封装,提供链式调用和便捷方法。
22
+ """
23
+
24
+ def __init__(self, dt: Union[datetime, str, PendulumDateTime, DateTime, None] = None):
25
+ """构造 DateTime 对象。
26
+
27
+ :param dt: 可以是 None(当前时间)、字符串(自动解析)、
28
+ datetime 对象、pendulum.DateTime 或另一个 DateTime。
29
+ """
30
+ if dt is None:
31
+ self._dt: PendulumDateTime = pendulum.now()
32
+ elif isinstance(dt, DateTime):
33
+ self._dt = dt._dt
34
+ elif isinstance(dt, str):
35
+ self._dt = pendulum.parse(dt)
36
+ elif isinstance(dt, datetime):
37
+ self._dt = pendulum.instance(dt)
38
+ elif isinstance(dt, PendulumDateTime):
39
+ self._dt = dt
40
+ else:
41
+ raise TypeError(f"不支持的类型: {type(dt)}")
42
+
43
+ @property
44
+ def dt(self) -> PendulumDateTime:
45
+ """获取内部 pendulum.DateTime 对象。"""
46
+ return self._dt
47
+
48
+ # ---- 日期部分获取 ----
49
+
50
+ def year(self) -> int:
51
+ """获取年份。"""
52
+ return self._dt.year
53
+
54
+ def month(self) -> int:
55
+ """获取月份(1-12)。"""
56
+ return self._dt.month
57
+
58
+ def day_of_month(self) -> int:
59
+ """获取日(1-31)。"""
60
+ return self._dt.day
61
+
62
+ def hour(self) -> int:
63
+ """获取小时(0-23)。"""
64
+ return self._dt.hour
65
+
66
+ def minute(self) -> int:
67
+ """获取分钟(0-59)。"""
68
+ return self._dt.minute
69
+
70
+ def second(self) -> int:
71
+ """获取秒(0-59)。"""
72
+ return self._dt.second
73
+
74
+ def day_of_week(self) -> int:
75
+ """获取星期几(ISO 标准:1=周一, 7=周日)。"""
76
+ return self._dt.isoweekday()
77
+
78
+ def quarter(self) -> int:
79
+ """获取季度(1-4)。"""
80
+ return (self._dt.month - 1) // 3 + 1
81
+
82
+ def week_of_year(self) -> int:
83
+ """获取一年中的第几周(ISO 标准)。"""
84
+ return self._dt.week_of_year
85
+
86
+ # ---- 格式化与转换 ----
87
+
88
+ def to_str(self, fmt: str = "YYYY-MM-DD HH:mm:ss") -> str:
89
+ """格式化为字符串。
90
+
91
+ :param fmt: 格式化模式,使用 pendulum 的格式化占位符。
92
+ :return: 格式化后的字符串。
93
+ """
94
+ return self._dt.format(fmt)
95
+
96
+ def to_date(self) -> date:
97
+ """转换为 date 对象。"""
98
+ return self._dt.date()
99
+
100
+ def to_datetime(self) -> datetime:
101
+ """转换为标准 datetime 对象。"""
102
+ return self._dt
103
+
104
+ # ---- 日期偏移 ----
105
+
106
+ def offset(self, **kwargs) -> DateTime:
107
+ """偏移日期时间。
108
+
109
+ :param kwargs: 偏移参数,支持 years, months, weeks, days, hours, minutes, seconds。
110
+ :return: 偏移后的新 DateTime 对象。
111
+
112
+ 示例:
113
+ DateTime().offset(days=1) # 明天此时
114
+ DateTime().offset(months=-1) # 上月此时
115
+ """
116
+ return DateTime(self._dt.add(**kwargs))
117
+
118
+ def begin_of_day(self) -> DateTime:
119
+ """获取当天开始时间(00:00:00)。"""
120
+ return DateTime(self._dt.start_of("day"))
121
+
122
+ def end_of_day(self) -> DateTime:
123
+ """获取当天结束时间(23:59:59.999999)。"""
124
+ return DateTime(self._dt.end_of("day"))
125
+
126
+ def begin_of_month(self) -> DateTime:
127
+ """获取当月开始时间。"""
128
+ return DateTime(self._dt.start_of("month"))
129
+
130
+ def end_of_month(self) -> DateTime:
131
+ """获取当月结束时间。"""
132
+ return DateTime(self._dt.end_of("month"))
133
+
134
+ def begin_of_year(self) -> DateTime:
135
+ """获取当年开始时间。"""
136
+ return DateTime(self._dt.start_of("year"))
137
+
138
+ def end_of_year(self) -> DateTime:
139
+ """获取当年结束时间。"""
140
+ return DateTime(self._dt.end_of("year"))
141
+
142
+ def begin_of_week(self) -> DateTime:
143
+ """获取本周开始时间(周一)。"""
144
+ return DateTime(self._dt.start_of("week"))
145
+
146
+ def end_of_week(self) -> DateTime:
147
+ """获取本周结束时间(周日)。"""
148
+ return DateTime(self._dt.end_of("week"))
149
+
150
+ def begin_of_quarter(self) -> DateTime:
151
+ """获取本季度开始时间。"""
152
+ return DateTime(self._dt.start_of("quarter"))
153
+
154
+ def end_of_quarter(self) -> DateTime:
155
+ """获取本季度结束时间。"""
156
+ return DateTime(self._dt.end_of("quarter"))
157
+
158
+ # ---- 魔术方法 ----
159
+
160
+ def __repr__(self) -> str:
161
+ return f"DateTime('{self._dt.to_datetime_string()}')"
162
+
163
+ def __str__(self) -> str:
164
+ return self._dt.to_datetime_string()
165
+
166
+ def __eq__(self, other: object) -> bool:
167
+ if isinstance(other, DateTime):
168
+ return self._dt == other._dt
169
+ if isinstance(other, datetime):
170
+ return self._dt == pendulum.instance(other)
171
+ return NotImplemented
172
+
173
+ def __lt__(self, other: object) -> bool:
174
+ if isinstance(other, DateTime):
175
+ return self._dt < other._dt
176
+ if isinstance(other, datetime):
177
+ return self._dt < pendulum.instance(other)
178
+ return NotImplemented
179
+
180
+ def __gt__(self, other: object) -> bool:
181
+ if isinstance(other, DateTime):
182
+ return self._dt > other._dt
183
+ if isinstance(other, datetime):
184
+ return self._dt > pendulum.instance(other)
185
+ return NotImplemented
186
+
187
+ def __le__(self, other: object) -> bool:
188
+ if isinstance(other, DateTime):
189
+ return self._dt <= other._dt
190
+ if isinstance(other, datetime):
191
+ return self._dt <= pendulum.instance(other)
192
+ return NotImplemented
193
+
194
+ def __ge__(self, other: object) -> bool:
195
+ if isinstance(other, DateTime):
196
+ return self._dt >= other._dt
197
+ if isinstance(other, datetime):
198
+ return self._dt >= pendulum.instance(other)
199
+ return NotImplemented
200
+
201
+ def __hash__(self) -> int:
202
+ return hash(self._dt)
203
+
204
+ def __sub__(self, other: object) -> timedelta:
205
+ if isinstance(other, DateTime):
206
+ return self._dt - other._dt
207
+ if isinstance(other, datetime):
208
+ return self._dt - pendulum.instance(other)
209
+ return NotImplemented
210
+
211
+ def __add__(self, other: object) -> DateTime:
212
+ if isinstance(other, timedelta):
213
+ return DateTime(self._dt + other)
214
+ return NotImplemented
215
+
216
+
217
+ def _to_pendulum(dt: Union[DateTime, datetime, PendulumDateTime]) -> PendulumDateTime:
218
+ """将各种日期类型统一转换为 pendulum.DateTime。
219
+
220
+ :param dt: DateTime、datetime 或 pendulum.DateTime 对象。
221
+ :return: pendulum.DateTime 对象。
222
+ """
223
+ if isinstance(dt, DateTime):
224
+ return dt.dt
225
+ if isinstance(dt, PendulumDateTime):
226
+ return dt
227
+ if isinstance(dt, datetime):
228
+ return pendulum.instance(dt)
229
+ if isinstance(dt, date):
230
+ return pendulum.instance(datetime(dt.year, dt.month, dt.day))
231
+ raise TypeError(f"不支持的日期类型: {type(dt)}")
232
+
233
+
234
+ class DateUtil:
235
+ """日期工具类,对应 Java cn.hutool.core.date.DateUtil。
236
+
237
+ 提供静态方法,用于日期的创建、格式化、解析、偏移、比较、时间差计算等操作。
238
+ """
239
+
240
+ # ---- 格式常量 ----
241
+
242
+ NORM_DATETIME_PATTERN: str = "YYYY-MM-DD HH:mm:ss"
243
+ """标准日期时间格式:yyyy-MM-dd HH:mm:ss"""
244
+
245
+ NORM_DATE_PATTERN: str = "YYYY-MM-DD"
246
+ """标准日期格式:yyyy-MM-dd"""
247
+
248
+ NORM_TIME_PATTERN: str = "HH:mm:ss"
249
+ """标准时间格式:HH:mm:ss"""
250
+
251
+ ISO8601_PATTERN: str = "YYYY-MM-DDTHH:mm:ssZ"
252
+ """ISO 8601 格式"""
253
+
254
+ # ================================================================
255
+ # 基础方法
256
+ # ================================================================
257
+
258
+ @staticmethod
259
+ def date() -> DateTime:
260
+ """获取当前日期时间。
261
+
262
+ :return: 当前时间的 DateTime 对象。
263
+ """
264
+ return DateTime()
265
+
266
+ @staticmethod
267
+ def now() -> str:
268
+ """获取当前时间的标准格式字符串(yyyy-MM-dd HH:mm:ss)。
269
+
270
+ :return: 当前时间字符串。
271
+ """
272
+ return pendulum.now().format(DateUtil.NORM_DATETIME_PATTERN)
273
+
274
+ @staticmethod
275
+ def today() -> str:
276
+ """获取今天日期的标准格式字符串(yyyy-MM-dd)。
277
+
278
+ :return: 今天日期字符串。
279
+ """
280
+ return pendulum.now().format(DateUtil.NORM_DATE_PATTERN)
281
+
282
+ @staticmethod
283
+ def current(is_millis: bool = True) -> int:
284
+ """获取当前时间戳。
285
+
286
+ :param is_millis: True 返回毫秒时间戳,False 返回秒级时间戳。
287
+ :return: 当前时间戳。
288
+ """
289
+ if is_millis:
290
+ return int(_time.time() * 1000)
291
+ return int(_time.time())
292
+
293
+ @staticmethod
294
+ def current_seconds() -> int:
295
+ """获取当前秒级时间戳。
296
+
297
+ :return: 秒级时间戳。
298
+ """
299
+ return int(_time.time())
300
+
301
+ # ================================================================
302
+ # 格式化方法
303
+ # ================================================================
304
+
305
+ @staticmethod
306
+ def format(dt: Union[DateTime, datetime, date], fmt: str = "YYYY-MM-DD HH:mm:ss") -> str:
307
+ """格式化日期时间。
308
+
309
+ :param dt: 日期时间对象。
310
+ :param fmt: 格式化模式(使用 pendulum 占位符)。
311
+ :return: 格式化后的字符串。
312
+ """
313
+ if isinstance(dt, date) and not isinstance(dt, datetime):
314
+ return pendulum.instance(datetime(dt.year, dt.month, dt.day)).format(fmt)
315
+ pd = _to_pendulum(dt)
316
+ return pd.format(fmt)
317
+
318
+ @staticmethod
319
+ def format_date_time(dt: Union[DateTime, datetime]) -> str:
320
+ """格式化为标准日期时间字符串(yyyy-MM-dd HH:mm:ss)。
321
+
322
+ :param dt: 日期时间对象。
323
+ :return: 格式化后的字符串。
324
+ """
325
+ return DateUtil.format(dt, DateUtil.NORM_DATETIME_PATTERN)
326
+
327
+ @staticmethod
328
+ def format_date(dt: Union[DateTime, datetime, date]) -> str:
329
+ """格式化为标准日期字符串(yyyy-MM-dd)。
330
+
331
+ :param dt: 日期时间对象。
332
+ :return: 格式化后的字符串。
333
+ """
334
+ return DateUtil.format(dt, DateUtil.NORM_DATE_PATTERN)
335
+
336
+ @staticmethod
337
+ def format_time(dt: Union[DateTime, datetime]) -> str:
338
+ """格式化为标准时间字符串(HH:mm:ss)。
339
+
340
+ :param dt: 日期时间对象。
341
+ :return: 格式化后的字符串。
342
+ """
343
+ return DateUtil.format(dt, DateUtil.NORM_TIME_PATTERN)
344
+
345
+ # ================================================================
346
+ # 解析方法
347
+ # ================================================================
348
+
349
+ @staticmethod
350
+ def parse(date_str: str, fmt: Optional[str] = None) -> DateTime:
351
+ """解析日期时间字符串。
352
+
353
+ :param date_str: 日期时间字符串。
354
+ :param fmt: 格式化模式,为 None 时自动识别。
355
+ :return: DateTime 对象。
356
+ """
357
+ if fmt is not None:
358
+ dt = pendulum.from_format(date_str, fmt)
359
+ return DateTime(dt)
360
+ return DateTime(pendulum.parse(date_str))
361
+
362
+ @staticmethod
363
+ def parse_date_time(date_str: str) -> DateTime:
364
+ """解析标准日期时间字符串(yyyy-MM-dd HH:mm:ss)。
365
+
366
+ :param date_str: 日期时间字符串,如 "2023-10-01 12:30:00"。
367
+ :return: DateTime 对象。
368
+ """
369
+ return DateUtil.parse(date_str, DateUtil.NORM_DATETIME_PATTERN)
370
+
371
+ @staticmethod
372
+ def parse_date(date_str: str) -> DateTime:
373
+ """解析标准日期字符串(yyyy-MM-dd)。
374
+
375
+ :param date_str: 日期字符串,如 "2023-10-01"。
376
+ :return: DateTime 对象。
377
+ """
378
+ return DateUtil.parse(date_str, DateUtil.NORM_DATE_PATTERN)
379
+
380
+ @staticmethod
381
+ def parse_time(time_str: str) -> DateTime:
382
+ """解析标准时间字符串(HH:mm:ss),日期部分取今天。
383
+
384
+ :param time_str: 时间字符串,如 "12:30:00"。
385
+ :return: DateTime 对象。
386
+ """
387
+ today = pendulum.today()
388
+ parts = time_str.split(":")
389
+ h = int(parts[0]) if len(parts) > 0 else 0
390
+ m = int(parts[1]) if len(parts) > 1 else 0
391
+ s = int(parts[2]) if len(parts) > 2 else 0
392
+ return DateTime(today.set(hour=h, minute=m, second=s))
393
+
394
+ @staticmethod
395
+ def parse_iso8601(iso_str: str) -> DateTime:
396
+ """解析 ISO 8601 格式字符串。
397
+
398
+ :param iso_str: ISO 8601 格式字符串,如 "2023-10-01T12:30:00+08:00"。
399
+ :return: DateTime 对象。
400
+ """
401
+ return DateTime(pendulum.parse(iso_str))
402
+
403
+ # ================================================================
404
+ # 日期部分提取(静态方法版本)
405
+ # ================================================================
406
+
407
+ @staticmethod
408
+ def year(dt: Union[DateTime, datetime]) -> int:
409
+ """获取年份。
410
+
411
+ :param dt: 日期时间对象。
412
+ :return: 年份。
413
+ """
414
+ return _to_pendulum(dt).year
415
+
416
+ @staticmethod
417
+ def month(dt: Union[DateTime, datetime]) -> int:
418
+ """获取月份(1-12)。
419
+
420
+ :param dt: 日期时间对象。
421
+ :return: 月份。
422
+ """
423
+ return _to_pendulum(dt).month
424
+
425
+ @staticmethod
426
+ def day_of_month(dt: Union[DateTime, datetime]) -> int:
427
+ """获取日(1-31)。
428
+
429
+ :param dt: 日期时间对象。
430
+ :return: 日。
431
+ """
432
+ return _to_pendulum(dt).day
433
+
434
+ @staticmethod
435
+ def hour(dt: Union[DateTime, datetime]) -> int:
436
+ """获取小时(0-23)。
437
+
438
+ :param dt: 日期时间对象。
439
+ :return: 小时。
440
+ """
441
+ return _to_pendulum(dt).hour
442
+
443
+ @staticmethod
444
+ def minute(dt: Union[DateTime, datetime]) -> int:
445
+ """获取分钟(0-59)。
446
+
447
+ :param dt: 日期时间对象。
448
+ :return: 分钟。
449
+ """
450
+ return _to_pendulum(dt).minute
451
+
452
+ @staticmethod
453
+ def second(dt: Union[DateTime, datetime]) -> int:
454
+ """获取秒(0-59)。
455
+
456
+ :param dt: 日期时间对象。
457
+ :return: 秒。
458
+ """
459
+ return _to_pendulum(dt).second
460
+
461
+ @staticmethod
462
+ def day_of_week(dt: Union[DateTime, datetime]) -> int:
463
+ """获取星期几(ISO 标准:1=周一, 7=周日)。
464
+
465
+ :param dt: 日期时间对象。
466
+ :return: 星期几(1-7)。
467
+ """
468
+ return _to_pendulum(dt).isoweekday()
469
+
470
+ @staticmethod
471
+ def quarter(dt: Union[DateTime, datetime]) -> int:
472
+ """获取季度(1-4)。
473
+
474
+ :param dt: 日期时间对象。
475
+ :return: 季度。
476
+ """
477
+ return (_to_pendulum(dt).month - 1) // 3 + 1
478
+
479
+ @staticmethod
480
+ def week_of_year(dt: Union[DateTime, datetime]) -> int:
481
+ """获取一年中的第几周(ISO 标准)。
482
+
483
+ :param dt: 日期时间对象。
484
+ :return: 周数。
485
+ """
486
+ return _to_pendulum(dt).week_of_year
487
+
488
+ @staticmethod
489
+ def is_weekend(dt: Union[DateTime, datetime]) -> bool:
490
+ """判断是否为周末(周六或周日)。
491
+
492
+ :param dt: 日期时间对象。
493
+ :return: 是否为周末。
494
+ """
495
+ return _to_pendulum(dt).isoweekday() >= 6
496
+
497
+ @staticmethod
498
+ def is_am(dt: Union[DateTime, datetime]) -> bool:
499
+ """判断是否为上午。
500
+
501
+ :param dt: 日期时间对象。
502
+ :return: 是否为上午。
503
+ """
504
+ return _to_pendulum(dt).hour < 12
505
+
506
+ @staticmethod
507
+ def is_pm(dt: Union[DateTime, datetime]) -> bool:
508
+ """判断是否为下午。
509
+
510
+ :param dt: 日期时间对象。
511
+ :return: 是否为下午。
512
+ """
513
+ return _to_pendulum(dt).hour >= 12
514
+
515
+ # ================================================================
516
+ # 偏移方法
517
+ # ================================================================
518
+
519
+ @staticmethod
520
+ def offset_day(dt: Union[DateTime, datetime], n: int) -> DateTime:
521
+ """偏移天数。
522
+
523
+ :param dt: 日期时间对象。
524
+ :param n: 偏移天数,正数向后,负数向前。
525
+ :return: 偏移后的 DateTime 对象。
526
+ """
527
+ return DateTime(_to_pendulum(dt).add(days=n))
528
+
529
+ @staticmethod
530
+ def offset_week(dt: Union[DateTime, datetime], n: int) -> DateTime:
531
+ """偏移周数。
532
+
533
+ :param dt: 日期时间对象。
534
+ :param n: 偏移周数。
535
+ :return: 偏移后的 DateTime 对象。
536
+ """
537
+ return DateTime(_to_pendulum(dt).add(weeks=n))
538
+
539
+ @staticmethod
540
+ def offset_month(dt: Union[DateTime, datetime], n: int) -> DateTime:
541
+ """偏移月数。
542
+
543
+ :param dt: 日期时间对象。
544
+ :param n: 偏移月数。
545
+ :return: 偏移后的 DateTime 对象。
546
+ """
547
+ return DateTime(_to_pendulum(dt).add(months=n))
548
+
549
+ @staticmethod
550
+ def offset_year(dt: Union[DateTime, datetime], n: int) -> DateTime:
551
+ """偏移年数。
552
+
553
+ :param dt: 日期时间对象。
554
+ :param n: 偏移年数。
555
+ :return: 偏移后的 DateTime 对象。
556
+ """
557
+ return DateTime(_to_pendulum(dt).add(years=n))
558
+
559
+ @staticmethod
560
+ def offset_hour(dt: Union[DateTime, datetime], n: int) -> DateTime:
561
+ """偏移小时数。
562
+
563
+ :param dt: 日期时间对象。
564
+ :param n: 偏移小时数。
565
+ :return: 偏移后的 DateTime 对象。
566
+ """
567
+ return DateTime(_to_pendulum(dt).add(hours=n))
568
+
569
+ @staticmethod
570
+ def offset_minute(dt: Union[DateTime, datetime], n: int) -> DateTime:
571
+ """偏移分钟数。
572
+
573
+ :param dt: 日期时间对象。
574
+ :param n: 偏移分钟数。
575
+ :return: 偏移后的 DateTime 对象。
576
+ """
577
+ return DateTime(_to_pendulum(dt).add(minutes=n))
578
+
579
+ @staticmethod
580
+ def offset_second(dt: Union[DateTime, datetime], n: int) -> DateTime:
581
+ """偏移秒数。
582
+
583
+ :param dt: 日期时间对象。
584
+ :param n: 偏移秒数。
585
+ :return: 偏移后的 DateTime 对象。
586
+ """
587
+ return DateTime(_to_pendulum(dt).add(seconds=n))
588
+
589
+ @staticmethod
590
+ def yesterday() -> DateTime:
591
+ """获取昨天的日期。
592
+
593
+ :return: 昨天的 DateTime 对象。
594
+ """
595
+ return DateTime(pendulum.yesterday())
596
+
597
+ @staticmethod
598
+ def tomorrow() -> DateTime:
599
+ """获取明天的日期。
600
+
601
+ :return: 明天的 DateTime 对象。
602
+ """
603
+ return DateTime(pendulum.tomorrow())
604
+
605
+ @staticmethod
606
+ def last_week() -> DateTime:
607
+ """获取上周的今天。
608
+
609
+ :return: 上周今天的 DateTime 对象。
610
+ """
611
+ return DateTime(pendulum.now().subtract(weeks=1))
612
+
613
+ @staticmethod
614
+ def next_week() -> DateTime:
615
+ """获取下周的今天。
616
+
617
+ :return: 下周今天的 DateTime 对象。
618
+ """
619
+ return DateTime(pendulum.now().add(weeks=1))
620
+
621
+ @staticmethod
622
+ def last_month() -> DateTime:
623
+ """获取上月的今天。
624
+
625
+ :return: 上月今天的 DateTime 对象。
626
+ """
627
+ return DateTime(pendulum.now().subtract(months=1))
628
+
629
+ @staticmethod
630
+ def next_month() -> DateTime:
631
+ """获取下月的今天。
632
+
633
+ :return: 下月今天的 DateTime 对象。
634
+ """
635
+ return DateTime(pendulum.now().add(months=1))
636
+
637
+ # ================================================================
638
+ # 开始 / 结束方法
639
+ # ================================================================
640
+
641
+ @staticmethod
642
+ def begin_of_day(dt: Union[DateTime, datetime, None] = None) -> DateTime:
643
+ """获取一天的开始时间(00:00:00)。
644
+
645
+ :param dt: 日期时间对象,为 None 时取当前时间。
646
+ :return: 当天开始的 DateTime 对象。
647
+ """
648
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
649
+ return DateTime(pd.start_of("day"))
650
+
651
+ @staticmethod
652
+ def end_of_day(dt: Union[DateTime, datetime, None] = None) -> DateTime:
653
+ """获取一天的结束时间(23:59:59.999999)。
654
+
655
+ :param dt: 日期时间对象,为 None 时取当前时间。
656
+ :return: 当天结束的 DateTime 对象。
657
+ """
658
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
659
+ return DateTime(pd.end_of("day"))
660
+
661
+ @staticmethod
662
+ def begin_of_week(dt: Union[DateTime, datetime, None] = None) -> DateTime:
663
+ """获取一周的开始时间(周一 00:00:00)。
664
+
665
+ :param dt: 日期时间对象,为 None 时取当前时间。
666
+ :return: 本周开始的 DateTime 对象。
667
+ """
668
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
669
+ return DateTime(pd.start_of("week"))
670
+
671
+ @staticmethod
672
+ def end_of_week(dt: Union[DateTime, datetime, None] = None) -> DateTime:
673
+ """获取一周的结束时间(周日 23:59:59.999999)。
674
+
675
+ :param dt: 日期时间对象,为 None 时取当前时间。
676
+ :return: 本周结束的 DateTime 对象。
677
+ """
678
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
679
+ return DateTime(pd.end_of("week"))
680
+
681
+ @staticmethod
682
+ def begin_of_month(dt: Union[DateTime, datetime, None] = None) -> DateTime:
683
+ """获取一月的开始时间(1号 00:00:00)。
684
+
685
+ :param dt: 日期时间对象,为 None 时取当前时间。
686
+ :return: 本月开始的 DateTime 对象。
687
+ """
688
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
689
+ return DateTime(pd.start_of("month"))
690
+
691
+ @staticmethod
692
+ def end_of_month(dt: Union[DateTime, datetime, None] = None) -> DateTime:
693
+ """获取一月的结束时间(月末 23:59:59.999999)。
694
+
695
+ :param dt: 日期时间对象,为 None 时取当前时间。
696
+ :return: 本月结束的 DateTime 对象。
697
+ """
698
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
699
+ return DateTime(pd.end_of("month"))
700
+
701
+ @staticmethod
702
+ def begin_of_quarter(dt: Union[DateTime, datetime, None] = None) -> DateTime:
703
+ """获取一季度的开始时间。
704
+
705
+ :param dt: 日期时间对象,为 None 时取当前时间。
706
+ :return: 本季度开始的 DateTime 对象。
707
+ """
708
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
709
+ return DateTime(pd.start_of("quarter"))
710
+
711
+ @staticmethod
712
+ def end_of_quarter(dt: Union[DateTime, datetime, None] = None) -> DateTime:
713
+ """获取一季度的结束时间。
714
+
715
+ :param dt: 日期时间对象,为 None 时取当前时间。
716
+ :return: 本季度结束的 DateTime 对象。
717
+ """
718
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
719
+ return DateTime(pd.end_of("quarter"))
720
+
721
+ @staticmethod
722
+ def begin_of_year(dt: Union[DateTime, datetime, None] = None) -> DateTime:
723
+ """获取一年的开始时间(1月1日 00:00:00)。
724
+
725
+ :param dt: 日期时间对象,为 None 时取当前时间。
726
+ :return: 本年开始的 DateTime 对象。
727
+ """
728
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
729
+ return DateTime(pd.start_of("year"))
730
+
731
+ @staticmethod
732
+ def end_of_year(dt: Union[DateTime, datetime, None] = None) -> DateTime:
733
+ """获取一年的结束时间(12月31日 23:59:59.999999)。
734
+
735
+ :param dt: 日期时间对象,为 None 时取当前时间。
736
+ :return: 本年结束的 DateTime 对象。
737
+ """
738
+ pd = _to_pendulum(dt) if dt is not None else pendulum.now()
739
+ return DateTime(pd.end_of("year"))
740
+
741
+ # ================================================================
742
+ # 时间差计算
743
+ # ================================================================
744
+
745
+ @staticmethod
746
+ def between(
747
+ start: Union[DateTime, datetime],
748
+ end: Union[DateTime, datetime],
749
+ unit: str = "ms",
750
+ ) -> int:
751
+ """计算两个时间之间的差值。
752
+
753
+ :param start: 起始时间。
754
+ :param end: 结束时间。
755
+ :param unit: 单位,可选值:ms(毫秒)、s(秒)、min(分钟)、hour(小时)、
756
+ day(天)、week(周)、month(月)、year(年)。
757
+ :return: 时间差(整数)。
758
+ """
759
+ pd_start = _to_pendulum(start)
760
+ pd_end = _to_pendulum(end)
761
+ diff = pd_start.diff(pd_end)
762
+
763
+ unit_lower = unit.lower()
764
+ if unit_lower in ("ms", "millis", "millisecond", "milliseconds"):
765
+ return diff.in_seconds() * 1000 + diff.microseconds // 1000
766
+ elif unit_lower in ("s", "sec", "second", "seconds"):
767
+ return diff.in_seconds()
768
+ elif unit_lower in ("min", "minute", "minutes"):
769
+ return diff.in_minutes()
770
+ elif unit_lower in ("h", "hour", "hours"):
771
+ return diff.in_hours()
772
+ elif unit_lower in ("d", "day", "days"):
773
+ return diff.in_days()
774
+ elif unit_lower in ("w", "week", "weeks"):
775
+ return diff.in_weeks()
776
+ elif unit_lower in ("mon", "month", "months"):
777
+ return diff.in_months()
778
+ elif unit_lower in ("y", "year", "years"):
779
+ return diff.in_years()
780
+ else:
781
+ raise ValueError(f"不支持的时间单位: {unit}")
782
+
783
+ @staticmethod
784
+ def between_ms(
785
+ start: Union[DateTime, datetime],
786
+ end: Union[DateTime, datetime],
787
+ ) -> int:
788
+ """计算两个时间之间的毫秒差。
789
+
790
+ :param start: 起始时间。
791
+ :param end: 结束时间。
792
+ :return: 毫秒差。
793
+ """
794
+ return DateUtil.between(start, end, "ms")
795
+
796
+ @staticmethod
797
+ def between_day(
798
+ start: Union[DateTime, datetime],
799
+ end: Union[DateTime, datetime],
800
+ is_reset: bool = False,
801
+ ) -> int:
802
+ """计算两个时间之间的天数差。
803
+
804
+ :param start: 起始时间。
805
+ :param end: 结束时间。
806
+ :param is_reset: 是否重置时间为当天开始(忽略时分秒)。
807
+ :return: 天数差。
808
+ """
809
+ pd_start = _to_pendulum(start)
810
+ pd_end = _to_pendulum(end)
811
+ if is_reset:
812
+ pd_start = pd_start.start_of("day")
813
+ pd_end = pd_end.start_of("day")
814
+ return pd_start.diff(pd_end).in_days()
815
+
816
+ @staticmethod
817
+ def between_week(
818
+ start: Union[DateTime, datetime],
819
+ end: Union[DateTime, datetime],
820
+ is_reset: bool = False,
821
+ ) -> int:
822
+ """计算两个时间之间的周数差。
823
+
824
+ :param start: 起始时间。
825
+ :param end: 结束时间。
826
+ :param is_reset: 是否重置时间为当天开始。
827
+ :return: 周数差。
828
+ """
829
+ pd_start = _to_pendulum(start)
830
+ pd_end = _to_pendulum(end)
831
+ if is_reset:
832
+ pd_start = pd_start.start_of("day")
833
+ pd_end = pd_end.start_of("day")
834
+ return pd_start.diff(pd_end).in_weeks()
835
+
836
+ @staticmethod
837
+ def between_month(
838
+ start: Union[DateTime, datetime],
839
+ end: Union[DateTime, datetime],
840
+ is_reset: bool = False,
841
+ ) -> int:
842
+ """计算两个时间之间的月数差。
843
+
844
+ :param start: 起始时间。
845
+ :param end: 结束时间。
846
+ :param is_reset: 是否重置时间为当月开始。
847
+ :return: 月数差。
848
+ """
849
+ pd_start = _to_pendulum(start)
850
+ pd_end = _to_pendulum(end)
851
+ if is_reset:
852
+ pd_start = pd_start.start_of("month")
853
+ pd_end = pd_end.start_of("month")
854
+ return pd_start.diff(pd_end).in_months()
855
+
856
+ @staticmethod
857
+ def between_year(
858
+ start: Union[DateTime, datetime],
859
+ end: Union[DateTime, datetime],
860
+ is_reset: bool = False,
861
+ ) -> int:
862
+ """计算两个时间之间的年数差。
863
+
864
+ :param start: 起始时间。
865
+ :param end: 结束时间。
866
+ :param is_reset: 是否重置时间为当年开始。
867
+ :return: 年数差。
868
+ """
869
+ pd_start = _to_pendulum(start)
870
+ pd_end = _to_pendulum(end)
871
+ if is_reset:
872
+ pd_start = pd_start.start_of("year")
873
+ pd_end = pd_end.start_of("year")
874
+ return pd_start.diff(pd_end).in_years()
875
+
876
+ @staticmethod
877
+ def format_between(
878
+ start: Union[DateTime, datetime],
879
+ end: Union[DateTime, datetime],
880
+ level: str = "ms",
881
+ ) -> str:
882
+ """格式化时间差为中文可读字符串。
883
+
884
+ 根据 level 参数决定精度:
885
+ - "ms": 毫秒级,如 "3天2小时5分10秒123毫秒"
886
+ - "s": 秒级, 如 "3天2小时5分10秒"
887
+ - "min": 分钟级, 如 "3天2小时5分"
888
+ - "hour": 小时级, 如 "3天2小时"
889
+ - "day": 天级, 如 "3天"
890
+
891
+ :param start: 起始时间。
892
+ :param end: 结束时间。
893
+ :param level: 精度级别。
894
+ :return: 中文格式的时间差字符串。
895
+ """
896
+ pd_start = _to_pendulum(start)
897
+ pd_end = _to_pendulum(end)
898
+
899
+ # 总毫秒数(绝对值)
900
+ total_ms = int(abs((pd_end - pd_start).total_seconds()) * 1000)
901
+
902
+ if total_ms == 0:
903
+ return "0毫秒" if level == "ms" else "0秒"
904
+
905
+ # 逐步分解
906
+ ms = total_ms
907
+ days = ms // (24 * 3600 * 1000)
908
+ ms %= 24 * 3600 * 1000
909
+ hours = ms // (3600 * 1000)
910
+ ms %= 3600 * 1000
911
+ minutes = ms // (60 * 1000)
912
+ ms %= 60 * 1000
913
+ seconds = ms // 1000
914
+ millis = ms % 1000
915
+
916
+ parts: List[str] = []
917
+ if days > 0:
918
+ parts.append(f"{days}天")
919
+ if hours > 0:
920
+ parts.append(f"{hours}小时")
921
+ if minutes > 0:
922
+ parts.append(f"{minutes}分")
923
+
924
+ level_lower = level.lower()
925
+ if level_lower in ("ms", "millis", "millisecond", "milliseconds"):
926
+ if seconds > 0:
927
+ parts.append(f"{seconds}秒")
928
+ if millis > 0:
929
+ parts.append(f"{millis}毫秒")
930
+ elif level_lower in ("s", "sec", "second", "seconds"):
931
+ if seconds > 0:
932
+ parts.append(f"{seconds}秒")
933
+ elif level_lower in ("min", "minute", "minutes"):
934
+ pass # 已经处理到分钟级
935
+ elif level_lower in ("h", "hour", "hours"):
936
+ pass # 已经处理到小时级
937
+ elif level_lower in ("d", "day", "days"):
938
+ pass # 已经处理到天级
939
+ else:
940
+ # 默认秒级
941
+ if seconds > 0:
942
+ parts.append(f"{seconds}秒")
943
+
944
+ return "".join(parts) if parts else "0秒"
945
+
946
+ # ================================================================
947
+ # 比较方法
948
+ # ================================================================
949
+
950
+ @staticmethod
951
+ def is_same_day(
952
+ dt1: Union[DateTime, datetime],
953
+ dt2: Union[DateTime, datetime],
954
+ ) -> bool:
955
+ """判断两个时间是否为同一天。
956
+
957
+ :param dt1: 第一个时间。
958
+ :param dt2: 第二个时间。
959
+ :return: 是否为同一天。
960
+ """
961
+ pd1 = _to_pendulum(dt1)
962
+ pd2 = _to_pendulum(dt2)
963
+ return pd1.year == pd2.year and pd1.month == pd2.month and pd1.day == pd2.day
964
+
965
+ @staticmethod
966
+ def is_same_time(
967
+ dt1: Union[DateTime, datetime],
968
+ dt2: Union[DateTime, datetime],
969
+ ) -> bool:
970
+ """判断两个时间是否为同一时刻。
971
+
972
+ :param dt1: 第一个时间。
973
+ :param dt2: 第二个时间。
974
+ :return: 是否为同一时刻。
975
+ """
976
+ return _to_pendulum(dt1) == _to_pendulum(dt2)
977
+
978
+ @staticmethod
979
+ def is_in(
980
+ dt: Union[DateTime, datetime],
981
+ start: Union[DateTime, datetime],
982
+ end: Union[DateTime, datetime],
983
+ ) -> bool:
984
+ """判断时间是否在指定范围内 [start, end](含边界)。
985
+
986
+ :param dt: 待判断的时间。
987
+ :param start: 范围起始时间。
988
+ :param end: 范围结束时间。
989
+ :return: 是否在范围内。
990
+ """
991
+ pd = _to_pendulum(dt)
992
+ pd_start = _to_pendulum(start)
993
+ pd_end = _to_pendulum(end)
994
+ return pd_start <= pd <= pd_end
995
+
996
+ @staticmethod
997
+ def is_leap_year(year: int) -> bool:
998
+ """判断是否为闰年。
999
+
1000
+ :param year: 年份。
1001
+ :return: 是否为闰年。
1002
+ """
1003
+ return pendulum.instance(datetime(year, 1, 1)).is_leap_year()
1004
+
1005
+ # ================================================================
1006
+ # 计时工具
1007
+ # ================================================================
1008
+
1009
+ @staticmethod
1010
+ def timer() -> int:
1011
+ """返回当前毫秒时间戳(用于计时)。
1012
+
1013
+ :return: 当前毫秒时间戳。
1014
+ """
1015
+ return int(_time.time() * 1000)
1016
+
1017
+ @staticmethod
1018
+ def spend_ms(pre_time: int) -> int:
1019
+ """计算从 pre_time 到当前的毫秒差。
1020
+
1021
+ :param pre_time: 之前记录的毫秒时间戳(通过 timer() 获取)。
1022
+ :return: 经过的毫秒数。
1023
+ """
1024
+ return int(_time.time() * 1000) - pre_time