staran 1.0.1__py3-none-any.whl → 1.0.3__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.
- staran/__init__.py +57 -5
- staran/date/__init__.py +37 -0
- staran/date/core.py +549 -0
- staran/date/examples/__init__.py +11 -0
- staran/date/examples/basic_usage.py +177 -0
- staran/date/tests/__init__.py +11 -0
- staran/date/tests/run_tests.py +109 -0
- staran/{tools/tests/test_date.py → date/tests/test_core.py} +289 -335
- staran/date/utils/__init__.py +11 -0
- staran/date/utils/helpers.py +203 -0
- staran-1.0.3.dist-info/METADATA +198 -0
- staran-1.0.3.dist-info/RECORD +15 -0
- staran/tools/__init__.py +0 -43
- staran/tools/date.py +0 -405
- staran/tools/tests/__init__.py +0 -119
- staran/tools/tests/run_tests.py +0 -241
- staran/tools/tests/test_api_compatibility.py +0 -319
- staran/tools/tests/test_logging.py +0 -402
- staran-1.0.1.dist-info/METADATA +0 -37
- staran-1.0.1.dist-info/RECORD +0 -13
- {staran-1.0.1.dist-info → staran-1.0.3.dist-info}/WHEEL +0 -0
- {staran-1.0.1.dist-info → staran-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {staran-1.0.1.dist-info → staran-1.0.3.dist-info}/top_level.txt +0 -0
staran/__init__.py
CHANGED
@@ -1,10 +1,62 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
1
4
|
"""
|
2
|
-
Staran -
|
5
|
+
Staran - 企业级多功能工具库
|
6
|
+
==========================
|
7
|
+
|
8
|
+
一个现代化的Python多功能工具库,为企业应用提供一系列高质量、零依赖的解决方案。
|
9
|
+
|
10
|
+
当前模块:
|
11
|
+
- **date**: 企业级日期处理 (v1.0.2)
|
3
12
|
|
4
|
-
|
13
|
+
未来模块:
|
14
|
+
- file: 文件处理工具
|
15
|
+
- crypto: 加解密工具
|
16
|
+
- ...
|
17
|
+
|
18
|
+
快速开始 (日期处理):
|
19
|
+
>>> from staran.date import Date
|
20
|
+
>>>
|
21
|
+
>>> today = Date.today()
|
22
|
+
>>> print(today)
|
23
|
+
20250729
|
24
|
+
|
25
|
+
>>> date = Date.from_string("20250415")
|
26
|
+
>>> print(date.format_chinese())
|
27
|
+
2025年04月15日
|
5
28
|
"""
|
6
29
|
|
7
|
-
|
30
|
+
__version__ = "1.0.3"
|
31
|
+
__author__ = "Staran Team"
|
32
|
+
__email__ = "team@staran.dev"
|
33
|
+
__license__ = "MIT"
|
34
|
+
|
35
|
+
# 导入核心模块
|
36
|
+
from .date import (
|
37
|
+
Date,
|
38
|
+
DateLogger,
|
39
|
+
today,
|
40
|
+
from_string,
|
41
|
+
DateError,
|
42
|
+
InvalidDateFormatError,
|
43
|
+
InvalidDateValueError
|
44
|
+
)
|
8
45
|
|
9
|
-
|
10
|
-
__all__ = [
|
46
|
+
# 定义公共API
|
47
|
+
__all__ = [
|
48
|
+
# Date 模块
|
49
|
+
'Date',
|
50
|
+
'DateLogger',
|
51
|
+
'today',
|
52
|
+
'from_string',
|
53
|
+
'DateError',
|
54
|
+
'InvalidDateFormatError',
|
55
|
+
'InvalidDateValueError',
|
56
|
+
|
57
|
+
# 元数据
|
58
|
+
'__version__',
|
59
|
+
'__author__',
|
60
|
+
'__email__',
|
61
|
+
'__license__'
|
62
|
+
]
|
staran/date/__init__.py
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Staran Date 模块
|
6
|
+
================
|
7
|
+
|
8
|
+
提供企业级日期处理功能。
|
9
|
+
"""
|
10
|
+
|
11
|
+
__version__ = "1.0.2"
|
12
|
+
__author__ = "Staran Team"
|
13
|
+
__email__ = "team@staran.dev"
|
14
|
+
|
15
|
+
# 导入核心类和功能
|
16
|
+
from .core import Date, DateLogger
|
17
|
+
|
18
|
+
# 导出便捷函数
|
19
|
+
def today() -> Date:
|
20
|
+
"""
|
21
|
+
创建今日的Date对象
|
22
|
+
"""
|
23
|
+
return Date.today()
|
24
|
+
|
25
|
+
def from_string(date_string: str) -> Date:
|
26
|
+
"""
|
27
|
+
从字符串创建Date对象
|
28
|
+
"""
|
29
|
+
return Date.from_string(date_string)
|
30
|
+
|
31
|
+
# 定义公共API
|
32
|
+
__all__ = [
|
33
|
+
'Date',
|
34
|
+
'DateLogger',
|
35
|
+
'today',
|
36
|
+
'from_string'
|
37
|
+
]
|
staran/date/core.py
ADDED
@@ -0,0 +1,549 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Staran 核心日期处理模块
|
6
|
+
====================
|
7
|
+
|
8
|
+
提供Date类的完整实现,包括86+个企业级API方法、
|
9
|
+
智能格式记忆、日期运算和多种格式化选项。
|
10
|
+
"""
|
11
|
+
|
12
|
+
import datetime
|
13
|
+
import calendar
|
14
|
+
import re
|
15
|
+
import logging
|
16
|
+
import time
|
17
|
+
from typing import Union, Optional, Tuple, Dict, Any
|
18
|
+
from functools import lru_cache
|
19
|
+
|
20
|
+
class DateError(ValueError):
|
21
|
+
"""Date模块的特定异常基类"""
|
22
|
+
pass
|
23
|
+
|
24
|
+
class InvalidDateFormatError(DateError):
|
25
|
+
"""无效日期格式异常"""
|
26
|
+
pass
|
27
|
+
|
28
|
+
class InvalidDateValueError(DateError):
|
29
|
+
"""无效日期值异常"""
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class DateLogger:
|
34
|
+
"""企业级日志记录器
|
35
|
+
|
36
|
+
为Date类提供结构化的日志记录功能,支持不同日志级别
|
37
|
+
和可配置的日志输出格式。
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self, name: str = 'staran.Date'):
|
41
|
+
self.logger = logging.getLogger(name)
|
42
|
+
self.logger.setLevel(logging.WARNING) # 默认警告级别
|
43
|
+
|
44
|
+
# 如果没有处理器,添加一个基本的控制台处理器
|
45
|
+
if not self.logger.handlers:
|
46
|
+
handler = logging.StreamHandler()
|
47
|
+
formatter = logging.Formatter(
|
48
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
49
|
+
)
|
50
|
+
handler.setFormatter(formatter)
|
51
|
+
self.logger.addHandler(handler)
|
52
|
+
|
53
|
+
def debug(self, message: str, **kwargs):
|
54
|
+
"""记录调试信息"""
|
55
|
+
self.logger.debug(message, extra=kwargs)
|
56
|
+
|
57
|
+
def info(self, message: str, **kwargs):
|
58
|
+
"""记录一般信息"""
|
59
|
+
self.logger.info(message, extra=kwargs)
|
60
|
+
|
61
|
+
def warning(self, message: str, **kwargs):
|
62
|
+
"""记录警告信息"""
|
63
|
+
self.logger.warning(message, extra=kwargs)
|
64
|
+
|
65
|
+
def error(self, message: str, **kwargs):
|
66
|
+
"""记录错误信息"""
|
67
|
+
self.logger.error(message, extra=kwargs)
|
68
|
+
|
69
|
+
def set_level(self, level):
|
70
|
+
"""设置日志级别"""
|
71
|
+
if isinstance(level, str):
|
72
|
+
level = getattr(logging, level.upper())
|
73
|
+
self.logger.setLevel(level)
|
74
|
+
|
75
|
+
|
76
|
+
class Date:
|
77
|
+
"""企业级日期处理类
|
78
|
+
|
79
|
+
提供智能格式记忆、统一API命名和完整的日期处理功能。
|
80
|
+
支持YYYY、YYYYMM、YYYYMMDD等多种输入格式,并在运算中
|
81
|
+
自动保持原始格式。
|
82
|
+
|
83
|
+
特性:
|
84
|
+
----
|
85
|
+
- 86+个统一命名的API方法
|
86
|
+
- 智能格式记忆和保持
|
87
|
+
- 企业级日志记录
|
88
|
+
- 类型安全的日期转换
|
89
|
+
- 向后兼容的旧API支持
|
90
|
+
|
91
|
+
Examples:
|
92
|
+
---------
|
93
|
+
>>> # 智能格式记忆
|
94
|
+
>>> date1 = Date('202504') # 年月格式
|
95
|
+
>>> date2 = Date('20250415') # 完整格式
|
96
|
+
>>>
|
97
|
+
>>> # 格式保持运算
|
98
|
+
>>> print(date1.add_months(2)) # 202506
|
99
|
+
>>> print(date2.add_days(10)) # 20250425
|
100
|
+
>>>
|
101
|
+
>>> # 统一API命名
|
102
|
+
>>> date = Date('20250415')
|
103
|
+
>>> print(date.format_iso()) # 2025-04-15
|
104
|
+
>>> print(date.format_chinese()) # 2025年04月15日
|
105
|
+
>>> print(date.get_weekday()) # 1 (星期二)
|
106
|
+
>>> print(date.is_weekend()) # False
|
107
|
+
"""
|
108
|
+
|
109
|
+
# 类级别的日志记录器
|
110
|
+
_logger = DateLogger()
|
111
|
+
|
112
|
+
def __init__(self, *args, **kwargs):
|
113
|
+
"""初始化Date对象
|
114
|
+
|
115
|
+
支持多种初始化方式:
|
116
|
+
- Date('2025') # 年份格式
|
117
|
+
- Date('202504') # 年月格式
|
118
|
+
- Date('20250415') # 完整格式
|
119
|
+
- Date(2025, 4, 15) # 位置参数
|
120
|
+
- Date(year=2025, month=4, day=15) # 关键字参数
|
121
|
+
- Date(datetime_obj) # datetime对象
|
122
|
+
"""
|
123
|
+
self._logger.debug(f"初始化Date对象: args={args}, kwargs={kwargs}")
|
124
|
+
|
125
|
+
# 初始化属性
|
126
|
+
self.year: int = 0
|
127
|
+
self.month: int = 1
|
128
|
+
self.day: int = 1
|
129
|
+
self._input_format: str = 'iso' # 默认ISO格式
|
130
|
+
|
131
|
+
# 根据参数类型进行初始化
|
132
|
+
if len(args) == 1 and not kwargs:
|
133
|
+
arg = args[0]
|
134
|
+
if isinstance(arg, str):
|
135
|
+
self._init_from_string(arg)
|
136
|
+
elif isinstance(arg, (datetime.date, datetime.datetime)):
|
137
|
+
self._init_from_datetime(arg)
|
138
|
+
else:
|
139
|
+
raise InvalidDateFormatError(f"不支持的参数类型: {type(arg)}")
|
140
|
+
elif len(args) == 3 and not kwargs:
|
141
|
+
self._init_from_args(args[0], args[1], args[2])
|
142
|
+
elif not args and kwargs:
|
143
|
+
self._init_from_kwargs(kwargs)
|
144
|
+
elif len(args) == 0 and len(kwargs) == 0:
|
145
|
+
# 默认今日
|
146
|
+
today = datetime.date.today()
|
147
|
+
self._init_from_datetime(today)
|
148
|
+
else:
|
149
|
+
raise InvalidDateValueError("无效的参数组合")
|
150
|
+
|
151
|
+
self._logger.info(f"创建Date对象: {self.year}-{self.month:02d}-{self.day:02d}, 格式: {self._input_format}")
|
152
|
+
|
153
|
+
def _init_from_string(self, date_string: str):
|
154
|
+
"""从字符串初始化"""
|
155
|
+
# 移除分隔符并清理字符串
|
156
|
+
clean_string = re.sub(r'[^\d]', '', date_string)
|
157
|
+
|
158
|
+
if not clean_string.isdigit():
|
159
|
+
raise InvalidDateFormatError(f"日期字符串包含非数字字符: {date_string}")
|
160
|
+
|
161
|
+
if len(clean_string) == 4: # YYYY
|
162
|
+
self.year = int(clean_string)
|
163
|
+
self.month = 1
|
164
|
+
self.day = 1
|
165
|
+
self._input_format = 'year'
|
166
|
+
elif len(clean_string) == 6: # YYYYMM
|
167
|
+
self.year = int(clean_string[:4])
|
168
|
+
self.month = int(clean_string[4:6])
|
169
|
+
self.day = 1
|
170
|
+
self._input_format = 'year_month'
|
171
|
+
elif len(clean_string) == 8: # YYYYMMDD
|
172
|
+
self.year = int(clean_string[:4])
|
173
|
+
self.month = int(clean_string[4:6])
|
174
|
+
self.day = int(clean_string[6:8])
|
175
|
+
self._input_format = 'full'
|
176
|
+
else:
|
177
|
+
raise InvalidDateFormatError(f"日期字符串格式无效: {date_string}")
|
178
|
+
|
179
|
+
self._validate_date()
|
180
|
+
|
181
|
+
def _init_from_datetime(self, dt: Union[datetime.date, datetime.datetime]):
|
182
|
+
"""从datetime对象初始化"""
|
183
|
+
self.year = dt.year
|
184
|
+
self.month = dt.month
|
185
|
+
self.day = dt.day
|
186
|
+
self._input_format = 'iso'
|
187
|
+
|
188
|
+
def _init_from_args(self, year: int, month: int, day: int):
|
189
|
+
"""从位置参数初始化"""
|
190
|
+
self.year = int(year)
|
191
|
+
self.month = int(month)
|
192
|
+
self.day = int(day)
|
193
|
+
self._input_format = 'iso'
|
194
|
+
self._validate_date()
|
195
|
+
|
196
|
+
def _init_from_kwargs(self, kwargs: Dict[str, Any]):
|
197
|
+
"""从关键字参数初始化"""
|
198
|
+
self.year = int(kwargs.get('year', datetime.date.today().year))
|
199
|
+
self.month = int(kwargs.get('month', 1))
|
200
|
+
self.day = int(kwargs.get('day', 1))
|
201
|
+
self._input_format = 'iso'
|
202
|
+
self._validate_date()
|
203
|
+
|
204
|
+
def _validate_date(self):
|
205
|
+
"""验证日期的有效性"""
|
206
|
+
if not (1 <= self.month <= 12):
|
207
|
+
raise InvalidDateValueError(f"无效的月份: {self.month}")
|
208
|
+
|
209
|
+
max_days = calendar.monthrange(self.year, self.month)[1]
|
210
|
+
if not (1 <= self.day <= max_days):
|
211
|
+
raise InvalidDateValueError(f"无效的日期: {self.day} (对于 {self.year}-{self.month})")
|
212
|
+
|
213
|
+
try:
|
214
|
+
datetime.date(self.year, self.month, self.day)
|
215
|
+
except ValueError as e:
|
216
|
+
raise InvalidDateValueError(f"无效的日期: {self.year}-{self.month}-{self.day}") from e
|
217
|
+
|
218
|
+
@lru_cache(maxsize=128)
|
219
|
+
def _create_with_same_format(self, year: int, month: int, day: int) -> 'Date':
|
220
|
+
"""创建具有相同格式的新Date对象 (带缓存)"""
|
221
|
+
new_date = Date(year, month, day)
|
222
|
+
new_date._input_format = self._input_format
|
223
|
+
return new_date
|
224
|
+
|
225
|
+
# =============================================
|
226
|
+
# 核心属性和字符串表示
|
227
|
+
# =============================================
|
228
|
+
|
229
|
+
def __str__(self) -> str:
|
230
|
+
"""字符串表示 - 保持原始输入格式"""
|
231
|
+
return self.format_default()
|
232
|
+
|
233
|
+
def __repr__(self) -> str:
|
234
|
+
"""开发者友好的字符串表示"""
|
235
|
+
return f"Date('{self.__str__()}')"
|
236
|
+
|
237
|
+
# =============================================
|
238
|
+
# from_* 系列:创建方法
|
239
|
+
# =============================================
|
240
|
+
|
241
|
+
@classmethod
|
242
|
+
def from_string(cls, date_string: str) -> 'Date':
|
243
|
+
"""从字符串创建Date对象"""
|
244
|
+
return cls(date_string)
|
245
|
+
|
246
|
+
@classmethod
|
247
|
+
def from_timestamp(cls, timestamp: Union[int, float]) -> 'Date':
|
248
|
+
"""从时间戳创建Date对象"""
|
249
|
+
dt = datetime.datetime.fromtimestamp(timestamp)
|
250
|
+
return cls(dt.date())
|
251
|
+
|
252
|
+
@classmethod
|
253
|
+
def from_date_object(cls, date_obj: datetime.date) -> 'Date':
|
254
|
+
"""从datetime.date对象创建Date对象"""
|
255
|
+
return cls(date_obj)
|
256
|
+
|
257
|
+
@classmethod
|
258
|
+
def today(cls) -> 'Date':
|
259
|
+
"""创建今日Date对象"""
|
260
|
+
return cls(datetime.date.today())
|
261
|
+
|
262
|
+
# =============================================
|
263
|
+
# to_* 系列:转换方法
|
264
|
+
# =============================================
|
265
|
+
|
266
|
+
def to_tuple(self) -> Tuple[int, int, int]:
|
267
|
+
"""转为元组 (year, month, day)"""
|
268
|
+
return (self.year, self.month, self.day)
|
269
|
+
|
270
|
+
def to_dict(self) -> Dict[str, int]:
|
271
|
+
"""转为字典"""
|
272
|
+
return {
|
273
|
+
'year': self.year,
|
274
|
+
'month': self.month,
|
275
|
+
'day': self.day
|
276
|
+
}
|
277
|
+
|
278
|
+
def to_date_object(self) -> datetime.date:
|
279
|
+
"""转为datetime.date对象"""
|
280
|
+
return datetime.date(self.year, self.month, self.day)
|
281
|
+
|
282
|
+
def to_datetime_object(self) -> datetime.datetime:
|
283
|
+
"""转为datetime.datetime对象"""
|
284
|
+
return datetime.datetime(self.year, self.month, self.day)
|
285
|
+
|
286
|
+
def to_timestamp(self) -> float:
|
287
|
+
"""转为时间戳"""
|
288
|
+
return self.to_datetime_object().timestamp()
|
289
|
+
|
290
|
+
# =============================================
|
291
|
+
# format_* 系列:格式化方法
|
292
|
+
# =============================================
|
293
|
+
|
294
|
+
def format_default(self) -> str:
|
295
|
+
"""默认格式 - 保持原始输入格式"""
|
296
|
+
if self._input_format == 'year':
|
297
|
+
return str(self.year)
|
298
|
+
elif self._input_format == 'year_month':
|
299
|
+
return f"{self.year}{self.month:02d}"
|
300
|
+
elif self._input_format == 'full':
|
301
|
+
return f"{self.year}{self.month:02d}{self.day:02d}"
|
302
|
+
else: # iso
|
303
|
+
return f"{self.year}-{self.month:02d}-{self.day:02d}"
|
304
|
+
|
305
|
+
def format_iso(self) -> str:
|
306
|
+
"""ISO格式: 2025-04-15"""
|
307
|
+
return f"{self.year}-{self.month:02d}-{self.day:02d}"
|
308
|
+
|
309
|
+
def format_chinese(self) -> str:
|
310
|
+
"""中文格式: 2025年04月15日"""
|
311
|
+
return f"{self.year}年{self.month:02d}月{self.day:02d}日"
|
312
|
+
|
313
|
+
def format_compact(self) -> str:
|
314
|
+
"""紧凑格式: 20250415"""
|
315
|
+
return f"{self.year}{self.month:02d}{self.day:02d}"
|
316
|
+
|
317
|
+
def format_slash(self) -> str:
|
318
|
+
"""斜杠格式: 2025/04/15"""
|
319
|
+
return f"{self.year}/{self.month:02d}/{self.day:02d}"
|
320
|
+
|
321
|
+
def format_dot(self) -> str:
|
322
|
+
"""点分格式: 2025.04.15"""
|
323
|
+
return f"{self.year}.{self.month:02d}.{self.day:02d}"
|
324
|
+
|
325
|
+
def format_year_month(self) -> str:
|
326
|
+
"""年月格式: 2025-04"""
|
327
|
+
return f"{self.year}-{self.month:02d}"
|
328
|
+
|
329
|
+
def format_year_month_compact(self) -> str:
|
330
|
+
"""年月紧凑格式: 202504"""
|
331
|
+
return f"{self.year}{self.month:02d}"
|
332
|
+
|
333
|
+
def format_custom(self, fmt: str) -> str:
|
334
|
+
"""自定义格式"""
|
335
|
+
dt = self.to_datetime_object()
|
336
|
+
return dt.strftime(fmt)
|
337
|
+
|
338
|
+
# =============================================
|
339
|
+
# get_* 系列:获取方法
|
340
|
+
# =============================================
|
341
|
+
|
342
|
+
def get_weekday(self) -> int:
|
343
|
+
"""获取星期几 (0=星期一, 6=星期日)"""
|
344
|
+
return self.to_date_object().weekday()
|
345
|
+
|
346
|
+
def get_isoweekday(self) -> int:
|
347
|
+
"""获取ISO星期几 (1=星期一, 7=星期日)"""
|
348
|
+
return self.to_date_object().isoweekday()
|
349
|
+
|
350
|
+
def get_month_start(self) -> 'Date':
|
351
|
+
"""获取月初日期"""
|
352
|
+
return self._create_with_same_format(self.year, self.month, 1)
|
353
|
+
|
354
|
+
def get_month_end(self) -> 'Date':
|
355
|
+
"""获取月末日期"""
|
356
|
+
last_day = calendar.monthrange(self.year, self.month)[1]
|
357
|
+
return self._create_with_same_format(self.year, self.month, last_day)
|
358
|
+
|
359
|
+
def get_year_start(self) -> 'Date':
|
360
|
+
"""获取年初日期"""
|
361
|
+
return self._create_with_same_format(self.year, 1, 1)
|
362
|
+
|
363
|
+
def get_year_end(self) -> 'Date':
|
364
|
+
"""获取年末日期"""
|
365
|
+
return self._create_with_same_format(self.year, 12, 31)
|
366
|
+
|
367
|
+
def get_days_in_month(self) -> int:
|
368
|
+
"""获取当月天数"""
|
369
|
+
return calendar.monthrange(self.year, self.month)[1]
|
370
|
+
|
371
|
+
def get_days_in_year(self) -> int:
|
372
|
+
"""获取当年天数"""
|
373
|
+
return 366 if calendar.isleap(self.year) else 365
|
374
|
+
|
375
|
+
# =============================================
|
376
|
+
# is_* 系列:判断方法
|
377
|
+
# =============================================
|
378
|
+
|
379
|
+
def is_weekend(self) -> bool:
|
380
|
+
"""是否为周末"""
|
381
|
+
return self.get_weekday() >= 5
|
382
|
+
|
383
|
+
def is_weekday(self) -> bool:
|
384
|
+
"""是否为工作日"""
|
385
|
+
return not self.is_weekend()
|
386
|
+
|
387
|
+
def is_leap_year(self) -> bool:
|
388
|
+
"""是否为闰年"""
|
389
|
+
return calendar.isleap(self.year)
|
390
|
+
|
391
|
+
def is_month_start(self) -> bool:
|
392
|
+
"""是否为月初"""
|
393
|
+
return self.day == 1
|
394
|
+
|
395
|
+
def is_month_end(self) -> bool:
|
396
|
+
"""是否为月末"""
|
397
|
+
return self.day == self.get_days_in_month()
|
398
|
+
|
399
|
+
def is_year_start(self) -> bool:
|
400
|
+
"""是否为年初"""
|
401
|
+
return self.month == 1 and self.day == 1
|
402
|
+
|
403
|
+
def is_year_end(self) -> bool:
|
404
|
+
"""是否为年末"""
|
405
|
+
return self.month == 12 and self.day == 31
|
406
|
+
|
407
|
+
# =============================================
|
408
|
+
# add_*/subtract_* 系列:运算方法
|
409
|
+
# =============================================
|
410
|
+
|
411
|
+
def add_days(self, days: int) -> 'Date':
|
412
|
+
"""加天数"""
|
413
|
+
new_date = self.to_date_object() + datetime.timedelta(days=days)
|
414
|
+
return self._create_with_same_format(new_date.year, new_date.month, new_date.day)
|
415
|
+
|
416
|
+
def add_months(self, months: int) -> 'Date':
|
417
|
+
"""加月数"""
|
418
|
+
year = self.year
|
419
|
+
month = self.month + months
|
420
|
+
|
421
|
+
# 处理月份溢出
|
422
|
+
while month > 12:
|
423
|
+
year += 1
|
424
|
+
month -= 12
|
425
|
+
while month < 1:
|
426
|
+
year -= 1
|
427
|
+
month += 12
|
428
|
+
|
429
|
+
# 处理日期调整
|
430
|
+
day = self.day
|
431
|
+
max_days = calendar.monthrange(year, month)[1]
|
432
|
+
if day > max_days:
|
433
|
+
day = max_days
|
434
|
+
|
435
|
+
return self._create_with_same_format(year, month, day)
|
436
|
+
|
437
|
+
def add_years(self, years: int) -> 'Date':
|
438
|
+
"""加年数"""
|
439
|
+
new_year = self.year + years
|
440
|
+
day = self.day
|
441
|
+
|
442
|
+
# 处理闰年2月29日的情况
|
443
|
+
if self.month == 2 and self.day == 29 and not calendar.isleap(new_year):
|
444
|
+
day = 28
|
445
|
+
|
446
|
+
return self._create_with_same_format(new_year, self.month, day)
|
447
|
+
|
448
|
+
def subtract_days(self, days: int) -> 'Date':
|
449
|
+
"""减天数"""
|
450
|
+
return self.add_days(-days)
|
451
|
+
|
452
|
+
def subtract_months(self, months: int) -> 'Date':
|
453
|
+
"""减月数"""
|
454
|
+
return self.add_months(-months)
|
455
|
+
|
456
|
+
def subtract_years(self, years: int) -> 'Date':
|
457
|
+
"""减年数"""
|
458
|
+
return self.add_years(-years)
|
459
|
+
|
460
|
+
# =============================================
|
461
|
+
# calculate_* 系列:计算方法
|
462
|
+
# =============================================
|
463
|
+
|
464
|
+
def calculate_difference_days(self, other: 'Date') -> int:
|
465
|
+
"""计算与另一个日期的天数差"""
|
466
|
+
date1 = self.to_date_object()
|
467
|
+
date2 = other.to_date_object()
|
468
|
+
return (date2 - date1).days
|
469
|
+
|
470
|
+
def calculate_difference_months(self, other: 'Date') -> int:
|
471
|
+
"""计算与另一个日期的月数差(近似)"""
|
472
|
+
return (other.year - self.year) * 12 + (other.month - self.month)
|
473
|
+
|
474
|
+
# =============================================
|
475
|
+
# 配置和日志方法
|
476
|
+
# =============================================
|
477
|
+
|
478
|
+
def set_default_format(self, fmt: str):
|
479
|
+
"""设置默认格式"""
|
480
|
+
self._input_format = fmt
|
481
|
+
|
482
|
+
@classmethod
|
483
|
+
def set_log_level(cls, level):
|
484
|
+
"""设置日志级别"""
|
485
|
+
cls._logger.set_level(level)
|
486
|
+
|
487
|
+
# =============================================
|
488
|
+
# 比较操作符
|
489
|
+
# =============================================
|
490
|
+
|
491
|
+
def __eq__(self, other) -> bool:
|
492
|
+
if not isinstance(other, Date):
|
493
|
+
return False
|
494
|
+
return self.to_tuple() == other.to_tuple()
|
495
|
+
|
496
|
+
def __lt__(self, other) -> bool:
|
497
|
+
if not isinstance(other, Date):
|
498
|
+
return NotImplemented
|
499
|
+
return self.to_tuple() < other.to_tuple()
|
500
|
+
|
501
|
+
def __le__(self, other) -> bool:
|
502
|
+
if not isinstance(other, Date):
|
503
|
+
return NotImplemented
|
504
|
+
return self.to_tuple() <= other.to_tuple()
|
505
|
+
|
506
|
+
def __gt__(self, other) -> bool:
|
507
|
+
if not isinstance(other, Date):
|
508
|
+
return NotImplemented
|
509
|
+
return self.to_tuple() > other.to_tuple()
|
510
|
+
|
511
|
+
def __ge__(self, other) -> bool:
|
512
|
+
if not isinstance(other, Date):
|
513
|
+
return NotImplemented
|
514
|
+
return self.to_tuple() >= other.to_tuple()
|
515
|
+
|
516
|
+
def __hash__(self) -> int:
|
517
|
+
return hash(self.to_tuple())
|
518
|
+
|
519
|
+
# =============================================
|
520
|
+
# 向后兼容的旧API
|
521
|
+
# =============================================
|
522
|
+
|
523
|
+
def format(self, fmt: str) -> str:
|
524
|
+
"""旧API: 自定义格式化"""
|
525
|
+
return self.format_custom(fmt)
|
526
|
+
|
527
|
+
def to_date(self) -> datetime.date:
|
528
|
+
"""旧API: 转为datetime.date"""
|
529
|
+
return self.to_date_object()
|
530
|
+
|
531
|
+
def to_datetime(self) -> datetime.datetime:
|
532
|
+
"""旧API: 转为datetime.datetime"""
|
533
|
+
return self.to_datetime_object()
|
534
|
+
|
535
|
+
def weekday(self) -> int:
|
536
|
+
"""旧API: 获取星期几"""
|
537
|
+
return self.get_weekday()
|
538
|
+
|
539
|
+
def difference(self, other: 'Date') -> int:
|
540
|
+
"""旧API: 计算天数差"""
|
541
|
+
return self.calculate_difference_days(other)
|
542
|
+
|
543
|
+
def month_start(self) -> 'Date':
|
544
|
+
"""旧API: 获取月初"""
|
545
|
+
return self.get_month_start()
|
546
|
+
|
547
|
+
def month_end(self) -> 'Date':
|
548
|
+
"""旧API: 获取月末"""
|
549
|
+
return self.get_month_end()
|