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/tools/date.py DELETED
@@ -1,405 +0,0 @@
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"
@@ -1,119 +0,0 @@
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环境正确安装")