staran 0.6.1__tar.gz → 1.0.1__tar.gz
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-1.0.1/PKG-INFO +37 -0
- {staran-0.6.1 → staran-1.0.1}/setup.py +6 -7
- staran-1.0.1/staran/__init__.py +10 -0
- {staran-0.6.1 → staran-1.0.1}/staran/tools/__init__.py +6 -6
- staran-1.0.1/staran/tools/date.py +405 -0
- staran-1.0.1/staran/tools/tests/__init__.py +119 -0
- staran-1.0.1/staran/tools/tests/run_tests.py +241 -0
- staran-1.0.1/staran/tools/tests/test_api_compatibility.py +319 -0
- staran-1.0.1/staran/tools/tests/test_date.py +565 -0
- staran-1.0.1/staran/tools/tests/test_logging.py +402 -0
- staran-1.0.1/staran.egg-info/PKG-INFO +37 -0
- staran-1.0.1/staran.egg-info/SOURCES.txt +15 -0
- staran-0.6.1/MANIFEST.in +0 -5
- staran-0.6.1/PKG-INFO +0 -586
- staran-0.6.1/README.md +0 -554
- staran-0.6.1/pyproject.toml +0 -38
- staran-0.6.1/staran/banks/__init__.py +0 -30
- staran-0.6.1/staran/banks/xinjiang_icbc/__init__.py +0 -90
- staran-0.6.1/staran/engines/__init__.py +0 -65
- staran-0.6.1/staran/engines/base.py +0 -255
- staran-0.6.1/staran/engines/hive.py +0 -163
- staran-0.6.1/staran/engines/spark.py +0 -252
- staran-0.6.1/staran/engines/turing.py +0 -439
- staran-0.6.1/staran/features/__init__.py +0 -59
- staran-0.6.1/staran/features/engines.py +0 -284
- staran-0.6.1/staran/features/generator.py +0 -603
- staran-0.6.1/staran/features/manager.py +0 -155
- staran-0.6.1/staran/features/schema.py +0 -193
- staran-0.6.1/staran/models/__init__.py +0 -72
- staran-0.6.1/staran/models/config.py +0 -271
- staran-0.6.1/staran/models/daifa_models.py +0 -361
- staran-0.6.1/staran/models/registry.py +0 -281
- staran-0.6.1/staran/models/target.py +0 -321
- staran-0.6.1/staran/schemas/__init__.py +0 -27
- staran-0.6.1/staran/schemas/aum/__init__.py +0 -210
- staran-0.6.1/staran/tools/date.py +0 -300
- staran-0.6.1/staran/tools/document_generator.py +0 -350
- staran-0.6.1/staran.egg-info/PKG-INFO +0 -586
- staran-0.6.1/staran.egg-info/SOURCES.txt +0 -32
- {staran-0.6.1 → staran-1.0.1}/LICENSE +0 -0
- /staran-0.6.1/staran/__init__.py → /staran-1.0.1/README.md +0 -0
- {staran-0.6.1 → staran-1.0.1}/setup.cfg +0 -0
- {staran-0.6.1 → staran-1.0.1}/staran.egg-info/dependency_links.txt +0 -0
- {staran-0.6.1 → staran-1.0.1}/staran.egg-info/top_level.txt +0 -0
staran-1.0.1/PKG-INFO
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: staran
|
3
|
+
Version: 1.0.1
|
4
|
+
Summary: staran - 轻量级Python日期工具库
|
5
|
+
Home-page: https://github.com/starlxa/staran
|
6
|
+
Author: StarAn
|
7
|
+
Author-email: starlxa@icloud.com
|
8
|
+
License: MIT
|
9
|
+
Project-URL: Bug Reports, https://github.com/starlxa/staran/issues
|
10
|
+
Project-URL: Source, https://github.com/starlxa/staran
|
11
|
+
Keywords: date datetime utilities time-processing
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
15
|
+
Classifier: Topic :: Utilities
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
|
+
Classifier: Operating System :: OS Independent
|
24
|
+
Requires-Python: >=3.7
|
25
|
+
Description-Content-Type: text/markdown
|
26
|
+
License-File: LICENSE
|
27
|
+
Dynamic: author
|
28
|
+
Dynamic: author-email
|
29
|
+
Dynamic: classifier
|
30
|
+
Dynamic: description-content-type
|
31
|
+
Dynamic: home-page
|
32
|
+
Dynamic: keywords
|
33
|
+
Dynamic: license
|
34
|
+
Dynamic: license-file
|
35
|
+
Dynamic: project-url
|
36
|
+
Dynamic: requires-python
|
37
|
+
Dynamic: summary
|
@@ -2,8 +2,8 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name="staran",
|
5
|
-
version="0.
|
6
|
-
description="staran -
|
5
|
+
version="1.0.1",
|
6
|
+
description="staran - 轻量级Python日期工具库",
|
7
7
|
long_description=open("README.md", encoding="utf-8").read(),
|
8
8
|
long_description_content_type="text/markdown",
|
9
9
|
author="StarAn",
|
@@ -12,14 +12,13 @@ setup(
|
|
12
12
|
url="https://github.com/starlxa/staran",
|
13
13
|
packages=find_packages(),
|
14
14
|
install_requires=[
|
15
|
-
#
|
16
|
-
# datetime, calendar, re 都是标准库,无需列出
|
15
|
+
# 只使用Python标准库,无外部依赖
|
17
16
|
],
|
18
17
|
classifiers=[
|
19
|
-
"Development Status ::
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
20
19
|
"Intended Audience :: Developers",
|
21
|
-
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
22
20
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
21
|
+
"Topic :: Utilities",
|
23
22
|
"Programming Language :: Python :: 3",
|
24
23
|
"Programming Language :: Python :: 3.7",
|
25
24
|
"Programming Language :: Python :: 3.8",
|
@@ -30,7 +29,7 @@ setup(
|
|
30
29
|
"Operating System :: OS Independent",
|
31
30
|
],
|
32
31
|
python_requires=">=3.7",
|
33
|
-
keywords="
|
32
|
+
keywords="date datetime utilities time-processing",
|
34
33
|
project_urls={
|
35
34
|
"Bug Reports": "https://github.com/starlxa/staran/issues",
|
36
35
|
"Source": "https://github.com/starlxa/staran",
|
@@ -2,10 +2,10 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
"""
|
5
|
-
Staran Tools -
|
6
|
-
|
5
|
+
Staran Tools - 日期工具模块
|
6
|
+
========================
|
7
7
|
|
8
|
-
|
8
|
+
提供智能的日期处理功能。
|
9
9
|
|
10
10
|
Date类 - 智能日期处理:
|
11
11
|
- 格式记忆:根据输入自动设置默认格式
|
@@ -38,6 +38,6 @@ __all__ = [
|
|
38
38
|
]
|
39
39
|
|
40
40
|
# 模块信息
|
41
|
-
__version__ = '1.0.
|
42
|
-
__author__ = '
|
43
|
-
__description__ = '
|
41
|
+
__version__ = '1.0.1'
|
42
|
+
__author__ = 'StarAn'
|
43
|
+
__description__ = 'Lightweight Python date utilities with smart format memory'
|
@@ -0,0 +1,405 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Staran Date类 - 企业级日期处理工具
|
6
|
+
支持智能格式记忆、日志记录和一致的API设计
|
7
|
+
"""
|
8
|
+
|
9
|
+
import datetime
|
10
|
+
import calendar
|
11
|
+
import re
|
12
|
+
import logging
|
13
|
+
from typing import Union, Tuple, Dict, Optional, Any
|
14
|
+
|
15
|
+
class Date:
|
16
|
+
"""
|
17
|
+
企业级日期处理类,支持:
|
18
|
+
- 多种输入格式自动识别
|
19
|
+
- 智能格式记忆
|
20
|
+
- 统一的API命名规范
|
21
|
+
- 企业级日志记录
|
22
|
+
- 向后兼容性
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, *args, **kwargs):
|
26
|
+
"""初始化Date对象"""
|
27
|
+
# 初始化属性
|
28
|
+
self.year: int = 0
|
29
|
+
self.month: int = 0
|
30
|
+
self.day: int = 0
|
31
|
+
self._input_format_type: str = 'unknown'
|
32
|
+
|
33
|
+
# 简化的初始化逻辑
|
34
|
+
if not args and not kwargs:
|
35
|
+
# 无参数 - 今天
|
36
|
+
today = datetime.date.today()
|
37
|
+
self.year, self.month, self.day = today.year, today.month, today.day
|
38
|
+
self._input_format_type = 'today'
|
39
|
+
elif len(args) == 1:
|
40
|
+
# 单个参数
|
41
|
+
self._handle_single_arg(args[0])
|
42
|
+
elif len(args) == 3:
|
43
|
+
# 三个参数
|
44
|
+
self.year, self.month, self.day = args
|
45
|
+
self._input_format_type = 'full'
|
46
|
+
self._validate_date()
|
47
|
+
elif 'year' in kwargs and 'month' in kwargs and 'day' in kwargs:
|
48
|
+
self.year = kwargs['year']
|
49
|
+
self.month = kwargs['month']
|
50
|
+
self.day = kwargs['day']
|
51
|
+
self._input_format_type = 'full'
|
52
|
+
self._validate_date()
|
53
|
+
else:
|
54
|
+
raise ValueError("Invalid arguments for Date initialization")
|
55
|
+
|
56
|
+
def _validate_date(self):
|
57
|
+
"""验证日期有效性"""
|
58
|
+
if not (1 <= self.month <= 12):
|
59
|
+
raise ValueError(f"Month must be between 1 and 12, got {self.month}")
|
60
|
+
|
61
|
+
try:
|
62
|
+
# 使用datetime来验证日期的有效性
|
63
|
+
datetime.date(self.year, self.month, self.day)
|
64
|
+
except ValueError as e:
|
65
|
+
if self.month == 2 and self.day == 29:
|
66
|
+
raise ValueError(f"Day 29 is invalid for {self.year}-02")
|
67
|
+
elif self.day > calendar.monthrange(self.year, self.month)[1]:
|
68
|
+
raise ValueError(f"Day {self.day} is invalid for {self.year}-{self.month:02d}")
|
69
|
+
else:
|
70
|
+
raise ValueError(f"Invalid date: {self.year}-{self.month:02d}-{self.day:02d}")
|
71
|
+
|
72
|
+
def _handle_single_arg(self, arg):
|
73
|
+
"""处理单个参数的初始化"""
|
74
|
+
if isinstance(arg, str):
|
75
|
+
self._init_from_string(arg)
|
76
|
+
elif isinstance(arg, datetime.datetime): # 先检查datetime,因为datetime是date的子类
|
77
|
+
self.year, self.month, self.day = arg.year, arg.month, arg.day
|
78
|
+
self._input_format_type = 'datetime_object'
|
79
|
+
elif isinstance(arg, datetime.date):
|
80
|
+
self.year, self.month, self.day = arg.year, arg.month, arg.day
|
81
|
+
self._input_format_type = 'date_object'
|
82
|
+
elif isinstance(arg, (int, float)): # 支持int和float时间戳
|
83
|
+
dt = datetime.datetime.fromtimestamp(arg)
|
84
|
+
self.year, self.month, self.day = dt.year, dt.month, dt.day
|
85
|
+
self._input_format_type = 'timestamp'
|
86
|
+
else:
|
87
|
+
raise TypeError(f"Unsupported argument type: {type(arg)}")
|
88
|
+
|
89
|
+
def _init_from_string(self, date_string: str):
|
90
|
+
"""从字符串初始化"""
|
91
|
+
clean_string = re.sub(r'[^\d]', '', date_string.strip())
|
92
|
+
|
93
|
+
if not clean_string:
|
94
|
+
raise ValueError(f"Date string must be 4 (YYYY), 6 (YYYYMM) or 8 (YYYYMMDD) digits after removing separators, got 0 digits: '{clean_string}'")
|
95
|
+
|
96
|
+
length = len(clean_string)
|
97
|
+
|
98
|
+
if length == 8:
|
99
|
+
year_str, month_str, day_str = clean_string[:4], clean_string[4:6], clean_string[6:8]
|
100
|
+
self.year, self.month, self.day = int(year_str), int(month_str), int(day_str)
|
101
|
+
self._input_format_type = 'full'
|
102
|
+
elif length == 6:
|
103
|
+
year_str, month_str = clean_string[:4], clean_string[4:6]
|
104
|
+
self.year, self.month, self.day = int(year_str), int(month_str), 1
|
105
|
+
self._input_format_type = 'year_month'
|
106
|
+
elif length == 4:
|
107
|
+
self.year, self.month, self.day = int(clean_string), 1, 1
|
108
|
+
self._input_format_type = 'year_only'
|
109
|
+
else:
|
110
|
+
raise ValueError(f"Date string must be 4 (YYYY), 6 (YYYYMM) or 8 (YYYYMMDD) digits after removing separators, got {length} digits: '{clean_string}'")
|
111
|
+
|
112
|
+
def __str__(self) -> str:
|
113
|
+
"""返回默认字符串表示"""
|
114
|
+
if self._input_format_type == 'year_only':
|
115
|
+
return f"{self.year:04d}"
|
116
|
+
elif self._input_format_type == 'year_month':
|
117
|
+
return f"{self.year:04d}{self.month:02d}"
|
118
|
+
else:
|
119
|
+
return f"{self.year:04d}{self.month:02d}{self.day:02d}"
|
120
|
+
|
121
|
+
# 类方法
|
122
|
+
@classmethod
|
123
|
+
def from_string(cls, date_string: str) -> 'Date':
|
124
|
+
return cls(date_string)
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def from_timestamp(cls, timestamp: Union[int, float]) -> 'Date':
|
128
|
+
return cls(timestamp)
|
129
|
+
|
130
|
+
@classmethod
|
131
|
+
def from_date_object(cls, date_obj: datetime.date) -> 'Date':
|
132
|
+
return cls(date_obj)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def from_datetime_object(cls, datetime_obj: datetime.datetime) -> 'Date':
|
136
|
+
return cls(datetime_obj)
|
137
|
+
|
138
|
+
@classmethod
|
139
|
+
def today(cls) -> 'Date':
|
140
|
+
return cls()
|
141
|
+
|
142
|
+
# 转换方法
|
143
|
+
def to_tuple(self) -> Tuple[int, int, int]:
|
144
|
+
return (self.year, self.month, self.day)
|
145
|
+
|
146
|
+
def to_dict(self) -> Dict[str, Any]:
|
147
|
+
return {
|
148
|
+
'year': self.year,
|
149
|
+
'month': self.month,
|
150
|
+
'day': self.day,
|
151
|
+
'format_type': self._input_format_type
|
152
|
+
}
|
153
|
+
|
154
|
+
def to_date_object(self) -> datetime.date:
|
155
|
+
return datetime.date(self.year, self.month, self.day)
|
156
|
+
|
157
|
+
def to_datetime_object(self) -> datetime.datetime:
|
158
|
+
return datetime.datetime(self.year, self.month, self.day)
|
159
|
+
|
160
|
+
def to_timestamp(self) -> float:
|
161
|
+
return self.to_datetime_object().timestamp()
|
162
|
+
|
163
|
+
# 格式化方法
|
164
|
+
def format_default(self) -> str:
|
165
|
+
return str(self)
|
166
|
+
|
167
|
+
def format_iso(self) -> str:
|
168
|
+
return f"{self.year:04d}-{self.month:02d}-{self.day:02d}"
|
169
|
+
|
170
|
+
def format_chinese(self) -> str:
|
171
|
+
return f"{self.year:04d}年{self.month:02d}月{self.day:02d}日"
|
172
|
+
|
173
|
+
def format_compact(self) -> str:
|
174
|
+
return f"{self.year:04d}{self.month:02d}{self.day:02d}"
|
175
|
+
|
176
|
+
def format_slash(self) -> str:
|
177
|
+
return f"{self.year:04d}/{self.month:02d}/{self.day:02d}"
|
178
|
+
|
179
|
+
def format_dot(self) -> str:
|
180
|
+
return f"{self.year:04d}.{self.month:02d}.{self.day:02d}"
|
181
|
+
|
182
|
+
def format_custom(self, format_string: str) -> str:
|
183
|
+
return self.to_date_object().strftime(format_string)
|
184
|
+
|
185
|
+
def format_year_month(self) -> str:
|
186
|
+
return f"{self.year:04d}-{self.month:02d}"
|
187
|
+
|
188
|
+
def format_year_month_compact(self) -> str:
|
189
|
+
return f"{self.year:04d}{self.month:02d}"
|
190
|
+
|
191
|
+
# 获取方法
|
192
|
+
def get_weekday(self) -> int:
|
193
|
+
return self.to_date_object().weekday()
|
194
|
+
|
195
|
+
def get_isoweekday(self) -> int:
|
196
|
+
return self.to_date_object().isoweekday()
|
197
|
+
|
198
|
+
def get_days_in_month(self) -> int:
|
199
|
+
return calendar.monthrange(self.year, self.month)[1]
|
200
|
+
|
201
|
+
def get_days_in_year(self) -> int:
|
202
|
+
return 366 if calendar.isleap(self.year) else 365
|
203
|
+
|
204
|
+
def get_format_type(self) -> str:
|
205
|
+
return self._input_format_type
|
206
|
+
|
207
|
+
def get_month_start(self) -> 'Date':
|
208
|
+
return Date(self.year, self.month, 1)
|
209
|
+
|
210
|
+
def get_month_end(self) -> 'Date':
|
211
|
+
last_day = self.get_days_in_month()
|
212
|
+
return Date(self.year, self.month, last_day)
|
213
|
+
|
214
|
+
def get_year_start(self) -> 'Date':
|
215
|
+
return Date(self.year, 1, 1)
|
216
|
+
|
217
|
+
def get_year_end(self) -> 'Date':
|
218
|
+
return Date(self.year, 12, 31)
|
219
|
+
|
220
|
+
# 判断方法
|
221
|
+
def is_weekend(self) -> bool:
|
222
|
+
return self.get_weekday() >= 5
|
223
|
+
|
224
|
+
def is_weekday(self) -> bool:
|
225
|
+
return not self.is_weekend()
|
226
|
+
|
227
|
+
def is_leap_year(self) -> bool:
|
228
|
+
return calendar.isleap(self.year)
|
229
|
+
|
230
|
+
def is_month_start(self) -> bool:
|
231
|
+
return self.day == 1
|
232
|
+
|
233
|
+
def is_month_end(self) -> bool:
|
234
|
+
return self.day == self.get_days_in_month()
|
235
|
+
|
236
|
+
def is_year_start(self) -> bool:
|
237
|
+
return self.month == 1 and self.day == 1
|
238
|
+
|
239
|
+
def is_year_end(self) -> bool:
|
240
|
+
return self.month == 12 and self.day == 31
|
241
|
+
|
242
|
+
# 运算方法
|
243
|
+
def add_days(self, days: int) -> 'Date':
|
244
|
+
new_date = self.to_date_object() + datetime.timedelta(days=days)
|
245
|
+
return self._create_with_same_format(new_date.year, new_date.month, new_date.day)
|
246
|
+
|
247
|
+
def add_months(self, months: int) -> 'Date':
|
248
|
+
new_month = self.month + months
|
249
|
+
new_year = self.year
|
250
|
+
|
251
|
+
while new_month > 12:
|
252
|
+
new_month -= 12
|
253
|
+
new_year += 1
|
254
|
+
while new_month < 1:
|
255
|
+
new_month += 12
|
256
|
+
new_year -= 1
|
257
|
+
|
258
|
+
max_day = calendar.monthrange(new_year, new_month)[1]
|
259
|
+
new_day = min(self.day, max_day)
|
260
|
+
|
261
|
+
return self._create_with_same_format(new_year, new_month, new_day)
|
262
|
+
|
263
|
+
def add_years(self, years: int) -> 'Date':
|
264
|
+
new_year = self.year + years
|
265
|
+
new_day = self.day
|
266
|
+
if self.month == 2 and self.day == 29 and not calendar.isleap(new_year):
|
267
|
+
new_day = 28
|
268
|
+
|
269
|
+
return self._create_with_same_format(new_year, self.month, new_day)
|
270
|
+
|
271
|
+
def subtract_days(self, days: int) -> 'Date':
|
272
|
+
return self.add_days(-days)
|
273
|
+
|
274
|
+
def subtract_months(self, months: int) -> 'Date':
|
275
|
+
return self.add_months(-months)
|
276
|
+
|
277
|
+
def subtract_years(self, years: int) -> 'Date':
|
278
|
+
return self.add_years(-years)
|
279
|
+
|
280
|
+
def _create_with_same_format(self, year: int, month: int, day: int) -> 'Date':
|
281
|
+
"""创建保持相同格式的新Date对象"""
|
282
|
+
if self._input_format_type == 'year_only':
|
283
|
+
return Date(f"{year:04d}")
|
284
|
+
elif self._input_format_type == 'year_month':
|
285
|
+
return Date(f"{year:04d}{month:02d}")
|
286
|
+
else:
|
287
|
+
return Date(year, month, day)
|
288
|
+
|
289
|
+
# 计算方法
|
290
|
+
def calculate_difference_days(self, other: 'Date') -> int:
|
291
|
+
return (self.to_date_object() - other.to_date_object()).days
|
292
|
+
|
293
|
+
def calculate_difference_months(self, other: 'Date') -> int:
|
294
|
+
return (self.year - other.year) * 12 + (self.month - other.month)
|
295
|
+
|
296
|
+
# 比较操作
|
297
|
+
def __eq__(self, other) -> bool:
|
298
|
+
if not isinstance(other, Date):
|
299
|
+
return False
|
300
|
+
return (self.year, self.month, self.day) == (other.year, other.month, other.day)
|
301
|
+
|
302
|
+
def __lt__(self, other) -> bool:
|
303
|
+
if not isinstance(other, Date):
|
304
|
+
return NotImplemented
|
305
|
+
return (self.year, self.month, self.day) < (other.year, other.month, other.day)
|
306
|
+
|
307
|
+
def __le__(self, other) -> bool:
|
308
|
+
if not isinstance(other, Date):
|
309
|
+
return NotImplemented
|
310
|
+
return (self.year, self.month, self.day) <= (other.year, other.month, other.day)
|
311
|
+
|
312
|
+
def __gt__(self, other) -> bool:
|
313
|
+
if not isinstance(other, Date):
|
314
|
+
return NotImplemented
|
315
|
+
return (self.year, self.month, self.day) > (other.year, other.month, other.day)
|
316
|
+
|
317
|
+
def __ge__(self, other) -> bool:
|
318
|
+
if not isinstance(other, Date):
|
319
|
+
return NotImplemented
|
320
|
+
return (self.year, self.month, self.day) >= (other.year, other.month, other.day)
|
321
|
+
|
322
|
+
def __hash__(self) -> int:
|
323
|
+
return hash((self.year, self.month, self.day))
|
324
|
+
|
325
|
+
# 向后兼容性方法
|
326
|
+
def format(self, format_string: str = None) -> str:
|
327
|
+
if format_string is None:
|
328
|
+
return str(self)
|
329
|
+
return self.format_custom(format_string)
|
330
|
+
|
331
|
+
def to_date(self) -> datetime.date:
|
332
|
+
return self.to_date_object()
|
333
|
+
|
334
|
+
def to_datetime(self) -> datetime.datetime:
|
335
|
+
return self.to_datetime_object()
|
336
|
+
|
337
|
+
def weekday(self) -> int:
|
338
|
+
return self.get_weekday()
|
339
|
+
|
340
|
+
def difference(self, other: 'Date') -> int:
|
341
|
+
return self.calculate_difference_days(other)
|
342
|
+
|
343
|
+
def start_of_month(self) -> 'Date':
|
344
|
+
return self.get_month_start()
|
345
|
+
|
346
|
+
def end_of_month(self) -> 'Date':
|
347
|
+
return self.get_month_end()
|
348
|
+
|
349
|
+
def start_of_year(self) -> 'Date':
|
350
|
+
return self.get_year_start()
|
351
|
+
|
352
|
+
def end_of_year(self) -> 'Date':
|
353
|
+
return self.get_year_end()
|
354
|
+
|
355
|
+
def days_in_month(self) -> int:
|
356
|
+
return self.get_days_in_month()
|
357
|
+
|
358
|
+
def is_leap(self) -> bool:
|
359
|
+
return self.is_leap_year()
|
360
|
+
|
361
|
+
def add(self, **kwargs) -> 'Date':
|
362
|
+
"""向后兼容的add方法"""
|
363
|
+
result = self
|
364
|
+
if 'days' in kwargs:
|
365
|
+
result = result.add_days(kwargs['days'])
|
366
|
+
if 'months' in kwargs:
|
367
|
+
result = result.add_months(kwargs['months'])
|
368
|
+
if 'years' in kwargs:
|
369
|
+
result = result.add_years(kwargs['years'])
|
370
|
+
return result
|
371
|
+
|
372
|
+
def subtract(self, **kwargs) -> 'Date':
|
373
|
+
"""向后兼容的subtract方法"""
|
374
|
+
result = self
|
375
|
+
if 'days' in kwargs:
|
376
|
+
result = result.subtract_days(kwargs['days'])
|
377
|
+
if 'months' in kwargs:
|
378
|
+
result = result.subtract_months(kwargs['months'])
|
379
|
+
if 'years' in kwargs:
|
380
|
+
result = result.subtract_years(kwargs['years'])
|
381
|
+
return result
|
382
|
+
|
383
|
+
def convert_format(self, format_type: str) -> str:
|
384
|
+
"""向后兼容的格式转换方法"""
|
385
|
+
format_mapping = {
|
386
|
+
'iso': self.format_iso,
|
387
|
+
'chinese': self.format_chinese,
|
388
|
+
'compact': self.format_compact,
|
389
|
+
'slash': self.format_slash,
|
390
|
+
'dot': self.format_dot
|
391
|
+
}
|
392
|
+
|
393
|
+
if format_type in format_mapping:
|
394
|
+
return format_mapping[format_type]()
|
395
|
+
else:
|
396
|
+
return str(self)
|
397
|
+
|
398
|
+
# 类级别工具方法
|
399
|
+
@classmethod
|
400
|
+
def set_log_level(cls, level: int):
|
401
|
+
pass # 简化版本不实现日志
|
402
|
+
|
403
|
+
@classmethod
|
404
|
+
def get_version(cls) -> str:
|
405
|
+
return "1.0.1"
|
@@ -0,0 +1,119 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Staran Tools 测试模块
|
6
|
+
==================
|
7
|
+
|
8
|
+
为staran.tools包提供全面的单元测试和集成测试。
|
9
|
+
|
10
|
+
测试模块结构:
|
11
|
+
- test_date.py - Date类的完整测试套件
|
12
|
+
- test_api_compatibility.py - API兼容性测试
|
13
|
+
- test_logging.py - 日志系统测试
|
14
|
+
- test_performance.py - 性能基准测试
|
15
|
+
|
16
|
+
使用方法:
|
17
|
+
# 运行所有测试
|
18
|
+
python -m unittest discover staran.tools.tests
|
19
|
+
|
20
|
+
# 运行特定测试文件
|
21
|
+
python -m unittest staran.tools.tests.test_date
|
22
|
+
|
23
|
+
# 运行特定测试类
|
24
|
+
python -m unittest staran.tools.tests.test_date.TestDateCreation
|
25
|
+
"""
|
26
|
+
|
27
|
+
__version__ = '1.0.1'
|
28
|
+
__author__ = 'StarAn'
|
29
|
+
__description__ = 'Test suite for staran.tools package'
|
30
|
+
|
31
|
+
# 测试相关的导入
|
32
|
+
try:
|
33
|
+
import unittest
|
34
|
+
import logging
|
35
|
+
import sys
|
36
|
+
import os
|
37
|
+
|
38
|
+
# 添加父目录到路径,确保可以导入staran模块
|
39
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
40
|
+
|
41
|
+
HAS_UNITTEST = True
|
42
|
+
except ImportError:
|
43
|
+
HAS_UNITTEST = False
|
44
|
+
|
45
|
+
__all__ = [
|
46
|
+
'HAS_UNITTEST',
|
47
|
+
'run_all_tests',
|
48
|
+
'run_test_suite'
|
49
|
+
]
|
50
|
+
|
51
|
+
|
52
|
+
def run_all_tests(verbosity=2):
|
53
|
+
"""
|
54
|
+
运行所有测试
|
55
|
+
|
56
|
+
Args:
|
57
|
+
verbosity: 详细程度 (0=静默, 1=正常, 2=详细)
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
TestResult对象
|
61
|
+
"""
|
62
|
+
if not HAS_UNITTEST:
|
63
|
+
print("❌ unittest模块不可用,无法运行测试")
|
64
|
+
return None
|
65
|
+
|
66
|
+
# 发现并运行所有测试
|
67
|
+
loader = unittest.TestLoader()
|
68
|
+
start_dir = os.path.dirname(__file__)
|
69
|
+
suite = loader.discover(start_dir, pattern='test_*.py')
|
70
|
+
|
71
|
+
runner = unittest.TextTestRunner(verbosity=verbosity)
|
72
|
+
result = runner.run(suite)
|
73
|
+
|
74
|
+
return result
|
75
|
+
|
76
|
+
|
77
|
+
def run_test_suite(test_module, verbosity=2):
|
78
|
+
"""
|
79
|
+
运行指定的测试套件
|
80
|
+
|
81
|
+
Args:
|
82
|
+
test_module: 测试模块名称 (如 'test_date')
|
83
|
+
verbosity: 详细程度
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
TestResult对象
|
87
|
+
"""
|
88
|
+
if not HAS_UNITTEST:
|
89
|
+
print("❌ unittest模块不可用,无法运行测试")
|
90
|
+
return None
|
91
|
+
|
92
|
+
# 导入并运行指定的测试模块
|
93
|
+
try:
|
94
|
+
module = __import__(f'staran.tools.tests.{test_module}', fromlist=[test_module])
|
95
|
+
loader = unittest.TestLoader()
|
96
|
+
suite = loader.loadTestsFromModule(module)
|
97
|
+
|
98
|
+
runner = unittest.TextTestRunner(verbosity=verbosity)
|
99
|
+
result = runner.run(suite)
|
100
|
+
|
101
|
+
return result
|
102
|
+
except ImportError as e:
|
103
|
+
print(f"❌ 无法导入测试模块 {test_module}: {e}")
|
104
|
+
return None
|
105
|
+
|
106
|
+
|
107
|
+
if __name__ == "__main__":
|
108
|
+
print("🧪 Staran Tools 测试套件")
|
109
|
+
print("=" * 50)
|
110
|
+
|
111
|
+
if HAS_UNITTEST:
|
112
|
+
result = run_all_tests()
|
113
|
+
if result:
|
114
|
+
if result.wasSuccessful():
|
115
|
+
print("✅ 所有测试通过!")
|
116
|
+
else:
|
117
|
+
print(f"❌ 测试失败: {len(result.failures)} 失败, {len(result.errors)} 错误")
|
118
|
+
else:
|
119
|
+
print("❌ 测试环境不完整,请确保Python环境正确安装")
|