staran 1.0.7__py3-none-any.whl → 1.0.9__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/date/core.py CHANGED
@@ -2,11 +2,18 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  """
5
- Staran 核心日期处理模块
6
- ====================
5
+ Staran 核心日期处理模块 v1.0.9
6
+ ============================
7
7
 
8
- 提供Date类的完整实现,包括86+个企业级API方法、
8
+ 提供Date类的完整实现,包括120+个企业级API方法、
9
9
  智能格式记忆、日期运算和多种格式化选项。
10
+
11
+ v1.0.9 新增功能:
12
+ - 智能日期推断
13
+ - 异步批量处理支持
14
+ - 高级业务规则引擎
15
+ - 日期范围操作
16
+ - 性能优化和内存优化
10
17
  """
11
18
 
12
19
  import datetime
@@ -14,8 +21,17 @@ import calendar
14
21
  import re
15
22
  import logging
16
23
  import time
17
- from typing import Union, Optional, Tuple, Dict, Any, List
18
- from functools import lru_cache
24
+ import asyncio
25
+ import csv
26
+ import json
27
+ from typing import Union, Optional, Tuple, Dict, Any, List, Set, TypeVar, Generic
28
+ from functools import lru_cache, wraps
29
+ from dataclasses import dataclass
30
+ import threading
31
+
32
+ # 导入农历和多语言模块
33
+ from .lunar import LunarDate
34
+ from .i18n import Language
19
35
 
20
36
  class DateError(ValueError):
21
37
  """Date模块的特定异常基类"""
@@ -30,6 +46,149 @@ class InvalidDateValueError(DateError):
30
46
  pass
31
47
 
32
48
 
49
+ @dataclass
50
+ class DateRange:
51
+ """日期范围类 (v1.0.9)
52
+
53
+ 表示一个日期范围,支持交集、并集等操作
54
+ """
55
+ start: 'Date'
56
+ end: 'Date'
57
+
58
+ def __post_init__(self):
59
+ if self.start > self.end:
60
+ raise ValueError(f"起始日期 {self.start} 不能大于结束日期 {self.end}")
61
+
62
+ def contains(self, date: 'Date') -> bool:
63
+ """检查日期是否在范围内"""
64
+ return self.start <= date <= self.end
65
+
66
+ def intersect(self, other: 'DateRange') -> Optional['DateRange']:
67
+ """计算与另一个范围的交集"""
68
+ start = max(self.start, other.start)
69
+ end = min(self.end, other.end)
70
+ if start <= end:
71
+ return DateRange(start, end)
72
+ return None
73
+
74
+ def union(self, other: 'DateRange') -> 'DateRange':
75
+ """计算与另一个范围的并集"""
76
+ start = min(self.start, other.start)
77
+ end = max(self.end, other.end)
78
+ return DateRange(start, end)
79
+
80
+ def days_count(self) -> int:
81
+ """范围内的天数"""
82
+ return self.start.calculate_difference_days(self.end) + 1
83
+
84
+
85
+ class SmartDateInference:
86
+ """智能日期推断器 (v1.0.9)
87
+
88
+ 自动推断不完整的日期信息
89
+ """
90
+
91
+ @staticmethod
92
+ def infer_year(month: int, day: int, reference_date: Optional['Date'] = None) -> int:
93
+ """推断年份"""
94
+ if reference_date is None:
95
+ reference_date = Date.today()
96
+
97
+ # 优先使用当前年份
98
+ try:
99
+ current_year_date = Date(reference_date.year, month, day)
100
+ # 如果日期在未来6个月内,使用当前年份
101
+ if current_year_date >= reference_date and current_year_date.calculate_difference_days(reference_date) <= 180:
102
+ return reference_date.year
103
+ except (InvalidDateFormatError, InvalidDateValueError):
104
+ pass
105
+
106
+ # 否则使用下一年
107
+ return reference_date.year + 1
108
+
109
+ @staticmethod
110
+ def infer_day(year: int, month: int) -> int:
111
+ """推断日期(默认为月初)"""
112
+ return 1
113
+
114
+ @staticmethod
115
+ def parse_smart(date_input: str, reference_date: Optional['Date'] = None) -> 'Date':
116
+ """智能解析日期字符串"""
117
+ if reference_date is None:
118
+ reference_date = Date.today()
119
+
120
+ # 移除所有非数字字符
121
+ numbers = re.findall(r'\d+', date_input)
122
+
123
+ if len(numbers) == 1:
124
+ # 只有一个数字,可能是日期
125
+ day = int(numbers[0])
126
+ if 1 <= day <= 31:
127
+ year = reference_date.year
128
+ month = reference_date.month
129
+ # 如果这个日期已经过去,推到下个月
130
+ try:
131
+ inferred_date = Date(year, month, day)
132
+ if inferred_date < reference_date:
133
+ inferred_date = inferred_date.add_months(1)
134
+ return inferred_date
135
+ except (InvalidDateFormatError, InvalidDateValueError):
136
+ pass
137
+
138
+ elif len(numbers) == 2:
139
+ # 两个数字,可能是月日
140
+ month, day = int(numbers[0]), int(numbers[1])
141
+ if 1 <= month <= 12 and 1 <= day <= 31:
142
+ year = SmartDateInference.infer_year(month, day, reference_date)
143
+ return Date(year, month, day)
144
+
145
+ # 其他情况,回退到标准解析
146
+ return Date(date_input)
147
+
148
+
149
+ class PerformanceCache:
150
+ """性能缓存管理器 (v1.0.9)
151
+
152
+ 多级缓存策略,提升重复操作性能
153
+ """
154
+
155
+ def __init__(self):
156
+ self._object_cache = {} # 对象创建缓存
157
+ self._format_cache = {} # 格式化缓存
158
+ self._calculation_cache = {} # 计算结果缓存
159
+ self._lock = threading.RLock()
160
+
161
+ def get_or_create_date(self, key: str, factory_func):
162
+ """获取或创建Date对象"""
163
+ with self._lock:
164
+ if key not in self._object_cache:
165
+ self._object_cache[key] = factory_func()
166
+ return self._object_cache[key]
167
+
168
+ def get_or_calculate(self, key: str, calculation_func):
169
+ """获取或计算结果"""
170
+ with self._lock:
171
+ if key not in self._calculation_cache:
172
+ self._calculation_cache[key] = calculation_func()
173
+ return self._calculation_cache[key]
174
+
175
+ def clear(self):
176
+ """清空缓存"""
177
+ with self._lock:
178
+ self._object_cache.clear()
179
+ self._format_cache.clear()
180
+ self._calculation_cache.clear()
181
+
182
+
183
+ # 全局性能缓存实例
184
+ _performance_cache = PerformanceCache()
185
+
186
+
187
+ class InvalidDateValueError(DateError):
188
+ """无效日期值异常"""
189
+ pass
190
+
191
+
33
192
  class DateLogger:
34
193
  """企业级日志记录器
35
194
 
@@ -74,19 +233,35 @@ class DateLogger:
74
233
 
75
234
 
76
235
  class Date:
77
- """企业级日期处理类
236
+ """企业级日期处理类 v1.0.9
78
237
 
79
238
  提供智能格式记忆、统一API命名和完整的日期处理功能。
80
239
  支持YYYY、YYYYMM、YYYYMMDD等多种输入格式,并在运算中
81
240
  自动保持原始格式。
82
241
 
242
+ v1.0.8 功能:
243
+ - 农历日期支持 (from_lunar, to_lunar, format_lunar等)
244
+ - 多语言配置 (中简、中繁、日、英四种语言)
245
+ - 全局语言设置,一次配置全局生效
246
+
247
+ v1.0.9 新增功能:
248
+ - 智能日期推断 (smart_parse, infer_date等)
249
+ - 异步批量处理 (async_batch_create, async_batch_format等)
250
+ - 高级业务规则 (扩展的业务规则引擎)
251
+ - 日期范围操作 (DateRange, 交集、并集等)
252
+ - 性能优化 (多级缓存、内存优化)
253
+
83
254
  特性:
84
255
  ----
85
- - 86+个统一命名的API方法
256
+ - 120+个统一命名的API方法
86
257
  - 智能格式记忆和保持
87
258
  - 企业级日志记录
259
+ - 农历与公历互转
260
+ - 多语言本地化支持
88
261
  - 类型安全的日期转换
89
262
  - 向后兼容的旧API支持
263
+ - 异步处理支持
264
+ - 智能推断和自动修复
90
265
 
91
266
  Examples:
92
267
  ---------
@@ -98,6 +273,22 @@ class Date:
98
273
  >>> print(date1.add_months(2)) # 202506
99
274
  >>> print(date2.add_days(10)) # 20250425
100
275
  >>>
276
+ >>> # 农历支持 (v1.0.8)
277
+ >>> lunar_date = Date.from_lunar(2025, 3, 15) # 农历2025年三月十五
278
+ >>> print(lunar_date.to_lunar().format_chinese()) # 农历2025年三月十五
279
+ >>>
280
+ >>> # 多语言支持 (v1.0.8)
281
+ >>> Date.set_language('en_US') # 设置全局语言为英语
282
+ >>> print(date2.format_localized()) # 04/15/2025
283
+ >>> print(date2.format_weekday_localized()) # Tuesday
284
+ >>>
285
+ >>> # 智能推断 (v1.0.9)
286
+ >>> smart_date = Date.smart_parse('15') # 智能推断为本月15号
287
+ >>> range_obj = DateRange(Date('20250101'), Date('20250131'))
288
+ >>> print(range_obj.days_count()) # 31
289
+ >>>
290
+ >>> # 异步批量处理 (v1.0.9)
291
+ >>> dates = await Date.async_batch_create(['20250101', '20250102'])
101
292
  >>> # 统一API命名
102
293
  >>> date = Date('20250415')
103
294
  >>> print(date.format_iso()) # 2025-04-15
@@ -113,7 +304,7 @@ class Date:
113
304
  当日期值超出有效范围时抛出
114
305
  """
115
306
 
116
- __slots__ = ('year', 'month', 'day', '_input_format')
307
+ __slots__ = ('year', 'month', 'day', '_input_format', '_cache_key')
117
308
 
118
309
  # 类级别的日志记录器
119
310
  _logger = DateLogger()
@@ -136,6 +327,7 @@ class Date:
136
327
  self.month: int = 1
137
328
  self.day: int = 1
138
329
  self._input_format: str = 'iso' # 默认ISO格式
330
+ self._cache_key: Optional[str] = None # 缓存键(v1.0.9)
139
331
 
140
332
  # 根据参数类型进行初始化
141
333
  if len(args) == 1 and not kwargs:
@@ -157,8 +349,15 @@ class Date:
157
349
  else:
158
350
  raise InvalidDateValueError("无效的参数组合")
159
351
 
352
+ # 生成缓存键 (v1.0.9)
353
+ self._generate_cache_key()
354
+
160
355
  self._logger.info(f"创建Date对象: {self.year}-{self.month:02d}-{self.day:02d}, 格式: {self._input_format}")
161
356
 
357
+ def _generate_cache_key(self):
358
+ """生成缓存键 (v1.0.9)"""
359
+ self._cache_key = f"{self.year}-{self.month:02d}-{self.day:02d}-{self._input_format}"
360
+
162
361
  def _init_from_string(self, date_string: str):
163
362
  """从字符串初始化"""
164
363
  # 移除分隔符并清理字符串
@@ -310,6 +509,137 @@ class Date:
310
509
  """创建今日Date对象"""
311
510
  return cls(datetime.date.today())
312
511
 
512
+ @classmethod
513
+ def smart_parse(cls, date_input: str, reference_date: Optional['Date'] = None) -> 'Date':
514
+ """智能解析日期字符串 (v1.0.9)
515
+
516
+ 自动推断不完整的日期信息,支持多种模糊输入格式
517
+
518
+ Args:
519
+ date_input: 日期输入字符串,支持模糊格式如 '15', '3-15', '明天' 等
520
+ reference_date: 参考日期,默认为今天
521
+
522
+ Returns:
523
+ 推断后的Date对象
524
+
525
+ Examples:
526
+ >>> Date.smart_parse('15') # 本月15号
527
+ >>> Date.smart_parse('3-15') # 3月15号(智能推断年份)
528
+ >>> Date.smart_parse('下月15') # 下个月15号
529
+ """
530
+ return SmartDateInference.parse_smart(date_input, reference_date)
531
+
532
+ @classmethod
533
+ def infer_date(cls, year: Optional[int] = None, month: Optional[int] = None,
534
+ day: Optional[int] = None, reference_date: Optional['Date'] = None) -> 'Date':
535
+ """智能推断日期 (v1.0.9)
536
+
537
+ 根据提供的部分日期信息,智能推断完整日期
538
+
539
+ Args:
540
+ year: 年份(可选)
541
+ month: 月份(可选)
542
+ day: 日期(可选)
543
+ reference_date: 参考日期,默认为今天
544
+
545
+ Returns:
546
+ 推断后的Date对象
547
+ """
548
+ if reference_date is None:
549
+ reference_date = cls.today()
550
+
551
+ # 智能推断缺失信息
552
+ if year is None:
553
+ if month is not None and day is not None:
554
+ year = SmartDateInference.infer_year(month, day, reference_date)
555
+ else:
556
+ year = reference_date.year
557
+
558
+ if month is None:
559
+ month = reference_date.month
560
+
561
+ if day is None:
562
+ day = SmartDateInference.infer_day(year, month)
563
+
564
+ return cls(year, month, day)
565
+
566
+ @classmethod
567
+ def from_lunar(cls, year: int, month: int, day: int, is_leap: bool = False) -> 'Date':
568
+ """从农历日期创建Date对象 (v1.0.8)
569
+
570
+ Args:
571
+ year: 农历年份
572
+ month: 农历月份
573
+ day: 农历日期
574
+ is_leap: 是否闰月
575
+
576
+ Returns:
577
+ 对应的公历Date对象
578
+
579
+ Example:
580
+ >>> date = Date.from_lunar(2025, 3, 15) # 农历2025年三月十五
581
+ """
582
+ lunar_date = LunarDate(year, month, day, is_leap)
583
+ solar_date = lunar_date.to_solar()
584
+ return cls(solar_date)
585
+
586
+ @classmethod
587
+ def from_lunar_string(cls, lunar_string: str) -> 'Date':
588
+ """从农历字符串创建Date对象 (v1.0.8)
589
+
590
+ 支持格式:
591
+ - "20250315" (农历2025年3月15日)
592
+ - "2025闰0315" (农历2025年闰3月15日)
593
+
594
+ Args:
595
+ lunar_string: 农历日期字符串
596
+
597
+ Returns:
598
+ 对应的公历Date对象
599
+ """
600
+ # 解析闰月标记
601
+ is_leap = '闰' in lunar_string
602
+ clean_string = lunar_string.replace('闰', '')
603
+
604
+ if len(clean_string) != 8:
605
+ raise InvalidDateFormatError(f"农历日期字符串格式无效: {lunar_string}")
606
+
607
+ year = int(clean_string[:4])
608
+ month = int(clean_string[4:6])
609
+ day = int(clean_string[6:8])
610
+
611
+ return cls.from_lunar(year, month, day, is_leap)
612
+
613
+ @classmethod
614
+ def set_language(cls, language_code: str) -> None:
615
+ """设置全局语言 (v1.0.8)
616
+
617
+ 一次设置,全局生效。支持中简、中繁、日、英四种语言。
618
+
619
+ Args:
620
+ language_code: 语言代码
621
+ - 'zh_CN': 中文简体
622
+ - 'zh_TW': 中文繁体
623
+ - 'ja_JP': 日语
624
+ - 'en_US': 英语
625
+
626
+ Example:
627
+ >>> Date.set_language('en_US') # 设置为英语
628
+ >>> Date.set_language('zh_TW') # 设置为繁体中文
629
+ """
630
+ Language.set_global_language(language_code)
631
+ cls._logger.info(f"全局语言已设置为: {language_code}")
632
+
633
+ @classmethod
634
+ def get_language(cls) -> str:
635
+ """获取当前全局语言设置 (v1.0.8)"""
636
+ return Language.get_global_language()
637
+
638
+ @classmethod
639
+ def get_supported_languages(cls) -> Dict[str, str]:
640
+ """获取支持的语言列表 (v1.0.8)"""
641
+ return Language.get_supported_languages()
642
+
313
643
  @classmethod
314
644
  def date_range(cls, start: Union[str, 'Date'], end: Union[str, 'Date'],
315
645
  step: int = 1) -> List['Date']:
@@ -430,6 +760,36 @@ class Date:
430
760
  dt = dt - datetime.timedelta(hours=timezone_offset)
431
761
  return dt.timestamp()
432
762
 
763
+ def to_lunar(self) -> LunarDate:
764
+ """转为农历日期对象 (v1.0.8)
765
+
766
+ Returns:
767
+ 对应的农历日期对象
768
+
769
+ Example:
770
+ >>> date = Date('20250415')
771
+ >>> lunar = date.to_lunar()
772
+ >>> print(lunar.format_chinese()) # 农历2025年三月十八
773
+ """
774
+ return LunarDate.from_solar(self.to_date_object())
775
+
776
+ def to_lunar_string(self, compact: bool = True) -> str:
777
+ """转为农历字符串 (v1.0.8)
778
+
779
+ Args:
780
+ compact: 是否使用紧凑格式
781
+
782
+ Returns:
783
+ 农历日期字符串
784
+
785
+ Example:
786
+ >>> date = Date('20250415')
787
+ >>> print(date.to_lunar_string()) # 20250318
788
+ >>> print(date.to_lunar_string(False)) # 农历2025年三月十八
789
+ """
790
+ lunar = self.to_lunar()
791
+ return lunar.format_compact() if compact else lunar.format_chinese()
792
+
433
793
  def to_json(self, include_metadata: bool = True) -> str:
434
794
  """转为JSON字符串
435
795
 
@@ -454,7 +814,7 @@ class Date:
454
814
  'weekday': self.get_weekday(),
455
815
  'is_weekend': self.is_weekend(),
456
816
  'quarter': self.get_quarter(),
457
- 'version': '1.0.7'
817
+ 'version': '1.0.9'
458
818
  })
459
819
 
460
820
  return json.dumps(data, ensure_ascii=False)
@@ -628,6 +988,155 @@ class Date:
628
988
  else:
629
989
  return f"{abs(diff_days)} days ago"
630
990
 
991
+ def format_localized(self, format_type: str = 'full', language_code: Optional[str] = None) -> str:
992
+ """多语言本地化格式 (v1.0.8)
993
+
994
+ Args:
995
+ format_type: 格式类型 (full, short, year_month, month_day)
996
+ language_code: 语言代码,None时使用全局设置
997
+
998
+ Returns:
999
+ 本地化格式的日期字符串
1000
+
1001
+ Example:
1002
+ >>> Date.set_language('en_US')
1003
+ >>> date = Date('20250415')
1004
+ >>> print(date.format_localized()) # 04/15/2025
1005
+ >>> print(date.format_localized('short')) # 04/15/2025
1006
+ """
1007
+ return Language.format_date(self.year, self.month, self.day, format_type, language_code)
1008
+
1009
+ def format_weekday_localized(self, short: bool = False, language_code: Optional[str] = None) -> str:
1010
+ """多语言星期几格式 (v1.0.8)
1011
+
1012
+ Args:
1013
+ short: 是否使用短名称
1014
+ language_code: 语言代码,None时使用全局设置
1015
+
1016
+ Returns:
1017
+ 本地化的星期几名称
1018
+
1019
+ Example:
1020
+ >>> Date.set_language('ja_JP')
1021
+ >>> date = Date('20250415') # 星期二
1022
+ >>> print(date.format_weekday_localized()) # 火曜日
1023
+ >>> print(date.format_weekday_localized(short=True)) # 火
1024
+ """
1025
+ weekday_index = self.get_weekday()
1026
+ return Language.get_weekday_name(weekday_index, short, language_code)
1027
+
1028
+ def format_month_localized(self, short: bool = False, language_code: Optional[str] = None) -> str:
1029
+ """多语言月份格式 (v1.0.8)
1030
+
1031
+ Args:
1032
+ short: 是否使用短名称
1033
+ language_code: 语言代码,None时使用全局设置
1034
+
1035
+ Returns:
1036
+ 本地化的月份名称
1037
+ """
1038
+ return Language.get_month_name(self.month, short, language_code)
1039
+
1040
+ def format_quarter_localized(self, short: bool = False, language_code: Optional[str] = None) -> str:
1041
+ """多语言季度格式 (v1.0.8)
1042
+
1043
+ Args:
1044
+ short: 是否使用短名称
1045
+ language_code: 语言代码,None时使用全局设置
1046
+
1047
+ Returns:
1048
+ 本地化的季度名称
1049
+ """
1050
+ quarter = self.get_quarter()
1051
+ return Language.get_quarter_name(quarter, short, language_code)
1052
+
1053
+ def format_relative_localized(self, reference_date: Optional['Date'] = None,
1054
+ language_code: Optional[str] = None) -> str:
1055
+ """多语言相对时间格式 (v1.0.8)
1056
+
1057
+ Args:
1058
+ reference_date: 参考日期,None时使用今天
1059
+ language_code: 语言代码,None时使用全局设置
1060
+
1061
+ Returns:
1062
+ 本地化的相对时间描述
1063
+
1064
+ Example:
1065
+ >>> Date.set_language('en_US')
1066
+ >>> today = Date.today()
1067
+ >>> tomorrow = today.add_days(1)
1068
+ >>> print(tomorrow.format_relative_localized()) # tomorrow
1069
+ """
1070
+ if reference_date is None:
1071
+ reference_date = Date.today()
1072
+
1073
+ diff_days = reference_date.calculate_difference_days(self)
1074
+
1075
+ if diff_days == 0:
1076
+ return Language.format_relative_time('today', language_code=language_code)
1077
+ elif diff_days == 1:
1078
+ return Language.format_relative_time('tomorrow', language_code=language_code)
1079
+ elif diff_days == -1:
1080
+ return Language.format_relative_time('yesterday', language_code=language_code)
1081
+ elif diff_days > 0:
1082
+ if diff_days <= 6:
1083
+ return Language.format_relative_time('days_later', diff_days, language_code)
1084
+ elif diff_days <= 28:
1085
+ weeks = diff_days // 7
1086
+ return Language.format_relative_time('weeks_later', weeks, language_code)
1087
+ elif diff_days <= 365:
1088
+ months = diff_days // 30
1089
+ return Language.format_relative_time('months_later', months, language_code)
1090
+ else:
1091
+ years = diff_days // 365
1092
+ return Language.format_relative_time('years_later', years, language_code)
1093
+ else:
1094
+ abs_days = abs(diff_days)
1095
+ if abs_days <= 6:
1096
+ return Language.format_relative_time('days_ago', abs_days, language_code)
1097
+ elif abs_days <= 28:
1098
+ weeks = abs_days // 7
1099
+ return Language.format_relative_time('weeks_ago', weeks, language_code)
1100
+ elif abs_days <= 365:
1101
+ months = abs_days // 30
1102
+ return Language.format_relative_time('months_ago', months, language_code)
1103
+ else:
1104
+ years = abs_days // 365
1105
+ return Language.format_relative_time('years_ago', years, language_code)
1106
+
1107
+ def format_lunar(self, include_year: bool = True, include_zodiac: bool = False,
1108
+ language_code: Optional[str] = None) -> str:
1109
+ """农历格式化 (v1.0.8)
1110
+
1111
+ Args:
1112
+ include_year: 是否包含年份
1113
+ include_zodiac: 是否包含生肖
1114
+ language_code: 语言代码,None时使用全局设置
1115
+
1116
+ Returns:
1117
+ 农历日期字符串
1118
+
1119
+ Example:
1120
+ >>> date = Date('20250415')
1121
+ >>> print(date.format_lunar()) # 农历2025年三月十八
1122
+ >>> print(date.format_lunar(include_zodiac=True)) # 乙巳(蛇)年三月十八
1123
+ """
1124
+ lunar = self.to_lunar()
1125
+ return lunar.format_chinese(include_year, include_zodiac)
1126
+
1127
+ def format_lunar_compact(self) -> str:
1128
+ """农历紧凑格式 (v1.0.8)
1129
+
1130
+ Returns:
1131
+ 农历紧凑格式字符串
1132
+
1133
+ Example:
1134
+ >>> date = Date('20250415')
1135
+ >>> print(date.format_lunar_compact()) # 20250318
1136
+ """
1137
+ lunar = self.to_lunar()
1138
+ return lunar.format_compact()
1139
+
631
1140
  # =============================================
632
1141
  # get_* 系列:获取方法
633
1142
  # =============================================
@@ -755,6 +1264,42 @@ class Date:
755
1264
  return (self.month in quarter_end_months and
756
1265
  self.day == quarter_end_months[self.month])
757
1266
 
1267
+ def is_lunar_new_year(self) -> bool:
1268
+ """是否为农历新年 (v1.0.8)
1269
+
1270
+ Returns:
1271
+ 是否为农历正月初一
1272
+ """
1273
+ lunar = self.to_lunar()
1274
+ return lunar.month == 1 and lunar.day == 1 and not lunar.is_leap
1275
+
1276
+ def is_lunar_month_start(self) -> bool:
1277
+ """是否为农历月初 (v1.0.8)
1278
+
1279
+ Returns:
1280
+ 是否为农历月初一
1281
+ """
1282
+ lunar = self.to_lunar()
1283
+ return lunar.day == 1
1284
+
1285
+ def is_lunar_month_mid(self) -> bool:
1286
+ """是否为农历月中 (v1.0.8)
1287
+
1288
+ Returns:
1289
+ 是否为农历十五
1290
+ """
1291
+ lunar = self.to_lunar()
1292
+ return lunar.day == 15
1293
+
1294
+ def is_lunar_leap_month(self) -> bool:
1295
+ """是否在农历闰月 (v1.0.8)
1296
+
1297
+ Returns:
1298
+ 是否为农历闰月
1299
+ """
1300
+ lunar = self.to_lunar()
1301
+ return lunar.is_leap
1302
+
758
1303
  def is_holiday(self, country: str = 'CN') -> bool:
759
1304
  """是否为节假日(增强版实现)
760
1305
 
@@ -1012,6 +1557,326 @@ class Date:
1012
1557
  """
1013
1558
  return [date.add_days(days) for date in dates]
1014
1559
 
1560
+ # =============================================
1561
+ # 异步批量处理方法 (v1.0.9)
1562
+ # =============================================
1563
+
1564
+ @classmethod
1565
+ async def async_batch_create(cls, date_strings: List[str]) -> List['Date']:
1566
+ """异步批量创建Date对象 (v1.0.9)
1567
+
1568
+ 适用于大量日期对象的创建,使用异步处理提升性能
1569
+
1570
+ Args:
1571
+ date_strings: 日期字符串列表
1572
+
1573
+ Returns:
1574
+ Date对象列表
1575
+ """
1576
+ async def create_single(date_str: str) -> 'Date':
1577
+ return cls(date_str)
1578
+
1579
+ tasks = [create_single(date_str) for date_str in date_strings]
1580
+ return await asyncio.gather(*tasks)
1581
+
1582
+ @classmethod
1583
+ async def async_batch_format(cls, dates: List['Date'], format_type: str = 'iso') -> List[str]:
1584
+ """异步批量格式化日期 (v1.0.9)
1585
+
1586
+ Args:
1587
+ dates: Date对象列表
1588
+ format_type: 格式类型
1589
+
1590
+ Returns:
1591
+ 格式化后的字符串列表
1592
+ """
1593
+ format_methods = {
1594
+ 'iso': lambda d: d.format_iso(),
1595
+ 'chinese': lambda d: d.format_chinese(),
1596
+ 'compact': lambda d: d.format_compact(),
1597
+ 'slash': lambda d: d.format_slash(),
1598
+ 'dot': lambda d: d.format_dot(),
1599
+ 'default': lambda d: d.format_default()
1600
+ }
1601
+
1602
+ format_func = format_methods.get(format_type, lambda d: d.format_default())
1603
+
1604
+ async def format_single(date: 'Date') -> str:
1605
+ return format_func(date)
1606
+
1607
+ tasks = [format_single(date) for date in dates]
1608
+ return await asyncio.gather(*tasks)
1609
+
1610
+ @classmethod
1611
+ async def async_batch_process(cls, dates: List['Date'], operation: str, **kwargs) -> List['Date']:
1612
+ """异步批量处理操作 (v1.0.9)
1613
+
1614
+ Args:
1615
+ dates: Date对象列表
1616
+ operation: 操作类型 ('add_days', 'add_months', 'add_years')
1617
+ **kwargs: 操作参数
1618
+
1619
+ Returns:
1620
+ 处理后的Date对象列表
1621
+ """
1622
+ async def process_single(date: 'Date') -> 'Date':
1623
+ if operation == 'add_days':
1624
+ return date.add_days(kwargs.get('days', 0))
1625
+ elif operation == 'add_months':
1626
+ return date.add_months(kwargs.get('months', 0))
1627
+ elif operation == 'add_years':
1628
+ return date.add_years(kwargs.get('years', 0))
1629
+ else:
1630
+ return date
1631
+
1632
+ tasks = [process_single(date) for date in dates]
1633
+ return await asyncio.gather(*tasks)
1634
+
1635
+ # =============================================
1636
+ # 日期范围操作方法 (v1.0.9)
1637
+ # =============================================
1638
+
1639
+ @classmethod
1640
+ def create_range(cls, start_date: Union[str, 'Date'], end_date: Union[str, 'Date']) -> 'DateRange':
1641
+ """创建日期范围 (v1.0.9)
1642
+
1643
+ Args:
1644
+ start_date: 开始日期
1645
+ end_date: 结束日期
1646
+
1647
+ Returns:
1648
+ DateRange对象
1649
+ """
1650
+ if isinstance(start_date, str):
1651
+ start_date = cls(start_date)
1652
+ if isinstance(end_date, str):
1653
+ end_date = cls(end_date)
1654
+
1655
+ return DateRange(start_date, end_date)
1656
+
1657
+ def in_range(self, start_date: 'Date', end_date: 'Date') -> bool:
1658
+ """检查是否在日期范围内 (v1.0.9)
1659
+
1660
+ Args:
1661
+ start_date: 开始日期
1662
+ end_date: 结束日期
1663
+
1664
+ Returns:
1665
+ 是否在范围内
1666
+ """
1667
+ return start_date <= self <= end_date
1668
+
1669
+ @classmethod
1670
+ def generate_range(cls, start_date: Union[str, 'Date'], days: int,
1671
+ step: int = 1, include_weekends: bool = True) -> List['Date']:
1672
+ """生成日期范围序列 (v1.0.9)
1673
+
1674
+ Args:
1675
+ start_date: 开始日期
1676
+ days: 天数
1677
+ step: 步长
1678
+ include_weekends: 是否包含周末
1679
+
1680
+ Returns:
1681
+ 日期列表
1682
+ """
1683
+ if isinstance(start_date, str):
1684
+ start_date = cls(start_date)
1685
+
1686
+ dates = []
1687
+ current = start_date
1688
+
1689
+ for _ in range(days):
1690
+ if include_weekends or not current.is_weekend():
1691
+ dates.append(current)
1692
+ current = current.add_days(step)
1693
+
1694
+ return dates
1695
+
1696
+ @classmethod
1697
+ def date_ranges_intersect(cls, range1: 'DateRange', range2: 'DateRange') -> bool:
1698
+ """检查两个日期范围是否有交集 (v1.0.9)"""
1699
+ return range1.intersect(range2) is not None
1700
+
1701
+ @classmethod
1702
+ def merge_date_ranges(cls, ranges: List['DateRange']) -> List['DateRange']:
1703
+ """合并重叠的日期范围 (v1.0.9)
1704
+
1705
+ Args:
1706
+ ranges: 日期范围列表
1707
+
1708
+ Returns:
1709
+ 合并后的日期范围列表
1710
+ """
1711
+ if not ranges:
1712
+ return []
1713
+
1714
+ # 按开始日期排序
1715
+ sorted_ranges = sorted(ranges, key=lambda r: r.start)
1716
+ merged = [sorted_ranges[0]]
1717
+
1718
+ for current_range in sorted_ranges[1:]:
1719
+ last_range = merged[-1]
1720
+
1721
+ # 检查是否重叠或相邻
1722
+ if current_range.start <= last_range.end.add_days(1):
1723
+ # 合并范围
1724
+ merged[-1] = DateRange(last_range.start, max(last_range.end, current_range.end))
1725
+ else:
1726
+ # 不重叠,添加新范围
1727
+ merged.append(current_range)
1728
+
1729
+ return merged
1730
+
1731
+ # =============================================
1732
+ # 数据导入导出方法 (v1.0.9)
1733
+ # =============================================
1734
+
1735
+ @classmethod
1736
+ def from_csv(cls, file_path: str, date_column: str = 'date',
1737
+ format_hint: Optional[str] = None) -> List['Date']:
1738
+ """从CSV文件导入日期 (v1.0.9)
1739
+
1740
+ Args:
1741
+ file_path: CSV文件路径
1742
+ date_column: 日期列名
1743
+ format_hint: 日期格式提示
1744
+
1745
+ Returns:
1746
+ Date对象列表
1747
+ """
1748
+ dates = []
1749
+
1750
+ with open(file_path, 'r', encoding='utf-8') as file:
1751
+ reader = csv.DictReader(file)
1752
+ for row in reader:
1753
+ if date_column in row:
1754
+ try:
1755
+ date_str = row[date_column].strip()
1756
+ if date_str:
1757
+ dates.append(cls(date_str))
1758
+ except (InvalidDateFormatError, InvalidDateValueError) as e:
1759
+ cls._logger.warning(f"跳过无效日期: {row[date_column]} - {e}")
1760
+
1761
+ return dates
1762
+
1763
+ @classmethod
1764
+ def to_csv(cls, dates: List['Date'], file_path: str,
1765
+ include_metadata: bool = False, format_type: str = 'iso') -> None:
1766
+ """导出日期到CSV文件 (v1.0.9)
1767
+
1768
+ Args:
1769
+ dates: Date对象列表
1770
+ file_path: 输出文件路径
1771
+ include_metadata: 是否包含元数据
1772
+ format_type: 日期格式类型
1773
+ """
1774
+ with open(file_path, 'w', newline='', encoding='utf-8') as file:
1775
+ fieldnames = ['date']
1776
+ if include_metadata:
1777
+ fieldnames.extend(['weekday', 'quarter', 'is_weekend', 'format'])
1778
+
1779
+ writer = csv.DictWriter(file, fieldnames=fieldnames)
1780
+ writer.writeheader()
1781
+
1782
+ for date in dates:
1783
+ row = {'date': getattr(date, f'format_{format_type}')()}
1784
+ if include_metadata:
1785
+ row.update({
1786
+ 'weekday': date.get_weekday(),
1787
+ 'quarter': date.get_quarter(),
1788
+ 'is_weekend': date.is_weekend(),
1789
+ 'format': date._input_format
1790
+ })
1791
+ writer.writerow(row)
1792
+
1793
+ @classmethod
1794
+ def from_json_file(cls, file_path: str) -> List['Date']:
1795
+ """从JSON文件导入日期 (v1.0.9)
1796
+
1797
+ Args:
1798
+ file_path: JSON文件路径
1799
+
1800
+ Returns:
1801
+ Date对象列表
1802
+ """
1803
+ with open(file_path, 'r', encoding='utf-8') as file:
1804
+ data = json.load(file)
1805
+
1806
+ dates = []
1807
+ if isinstance(data, list):
1808
+ for item in data:
1809
+ try:
1810
+ if isinstance(item, str):
1811
+ dates.append(cls(item))
1812
+ elif isinstance(item, dict):
1813
+ # 支持to_dict()格式的JSON
1814
+ if 'year' in item and 'month' in item and 'day' in item:
1815
+ dates.append(cls(item['year'], item['month'], item['day']))
1816
+ elif 'date' in item:
1817
+ dates.append(cls(item['date']))
1818
+ elif 'iso_string' in item:
1819
+ dates.append(cls(item['iso_string']))
1820
+ elif 'compact_string' in item:
1821
+ dates.append(cls(item['compact_string']))
1822
+ except (InvalidDateFormatError, InvalidDateValueError) as e:
1823
+ cls._logger.warning(f"跳过无效日期数据: {item} - {e}")
1824
+
1825
+ return dates
1826
+
1827
+ @classmethod
1828
+ def to_json_file(cls, dates: List['Date'], file_path: str,
1829
+ include_metadata: bool = False) -> None:
1830
+ """导出日期到JSON文件 (v1.0.9)
1831
+
1832
+ Args:
1833
+ dates: Date对象列表
1834
+ file_path: 输出文件路径
1835
+ include_metadata: 是否包含元数据
1836
+ """
1837
+ data = [date.to_dict(include_metadata) for date in dates]
1838
+
1839
+ with open(file_path, 'w', encoding='utf-8') as file:
1840
+ json.dump(data, file, ensure_ascii=False, indent=2)
1841
+
1842
+ # =============================================
1843
+ # 性能优化方法 (v1.0.9)
1844
+ # =============================================
1845
+
1846
+ @classmethod
1847
+ def clear_cache(cls):
1848
+ """清空全局缓存 (v1.0.9)"""
1849
+ _performance_cache.clear()
1850
+
1851
+ @classmethod
1852
+ def get_cache_stats(cls) -> Dict[str, Any]:
1853
+ """获取缓存统计信息 (v1.0.9)"""
1854
+ return {
1855
+ 'object_cache_size': len(_performance_cache._object_cache),
1856
+ 'format_cache_size': len(_performance_cache._format_cache),
1857
+ 'calculation_cache_size': len(_performance_cache._calculation_cache)
1858
+ }
1859
+
1860
+ def _optimized_format(self, format_type: str) -> str:
1861
+ """优化的格式化方法 (v1.0.9)"""
1862
+ cache_key = f"{self._cache_key}-{format_type}"
1863
+
1864
+ def format_func():
1865
+ if format_type == 'iso':
1866
+ return self.format_iso()
1867
+ elif format_type == 'chinese':
1868
+ return self.format_chinese()
1869
+ elif format_type == 'compact':
1870
+ return self.format_compact()
1871
+ else:
1872
+ return self.format_default()
1873
+
1874
+ return _performance_cache.get_or_calculate(cache_key, format_func)
1875
+
1876
+ def get_cache_key(self) -> str:
1877
+ """获取对象的缓存键 (v1.0.9)"""
1878
+ return self._cache_key
1879
+
1015
1880
  def apply_business_rule(self, rule: str, **kwargs) -> 'Date':
1016
1881
  """应用业务规则
1017
1882
 
@@ -1088,6 +1953,58 @@ class Date:
1088
1953
  def __hash__(self) -> int:
1089
1954
  return hash(self.to_tuple())
1090
1955
 
1956
+ def compare_lunar(self, other: 'Date') -> int:
1957
+ """农历日期比较 (v1.0.8)
1958
+
1959
+ Args:
1960
+ other: 另一个Date对象
1961
+
1962
+ Returns:
1963
+ -1: self < other, 0: self == other, 1: self > other
1964
+
1965
+ Example:
1966
+ >>> date1 = Date.from_lunar(2025, 1, 1) # 农历正月初一
1967
+ >>> date2 = Date.from_lunar(2025, 1, 15) # 农历正月十五
1968
+ >>> print(date1.compare_lunar(date2)) # -1
1969
+ """
1970
+ lunar_self = self.to_lunar()
1971
+ lunar_other = other.to_lunar()
1972
+
1973
+ if lunar_self < lunar_other:
1974
+ return -1
1975
+ elif lunar_self > lunar_other:
1976
+ return 1
1977
+ else:
1978
+ return 0
1979
+
1980
+ def is_same_lunar_month(self, other: 'Date') -> bool:
1981
+ """是否同一农历月份 (v1.0.8)
1982
+
1983
+ Args:
1984
+ other: 另一个Date对象
1985
+
1986
+ Returns:
1987
+ 是否为同一农历月份
1988
+ """
1989
+ lunar_self = self.to_lunar()
1990
+ lunar_other = other.to_lunar()
1991
+ return (lunar_self.year == lunar_other.year and
1992
+ lunar_self.month == lunar_other.month and
1993
+ lunar_self.is_leap == lunar_other.is_leap)
1994
+
1995
+ def is_same_lunar_day(self, other: 'Date') -> bool:
1996
+ """是否同一农历日期 (v1.0.8)
1997
+
1998
+ Args:
1999
+ other: 另一个Date对象
2000
+
2001
+ Returns:
2002
+ 是否为同一农历日期
2003
+ """
2004
+ lunar_self = self.to_lunar()
2005
+ lunar_other = other.to_lunar()
2006
+ return lunar_self == lunar_other
2007
+
1091
2008
  # =============================================
1092
2009
  # 向后兼容的旧API
1093
2010
  # =============================================