staran 1.0.10__tar.gz → 1.0.11__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.10/staran.egg-info → staran-1.0.11}/PKG-INFO +1 -1
- {staran-1.0.10 → staran-1.0.11}/setup.py +1 -1
- staran-1.0.11/staran/__init__.py +61 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/__init__.py +1 -1
- staran-1.0.11/staran/date/core/lunar.py +150 -0
- {staran-1.0.10 → staran-1.0.11/staran.egg-info}/PKG-INFO +1 -1
- staran-1.0.10/staran/__init__.py +0 -0
- staran-1.0.10/staran/date/core/lunar.py +0 -320
- {staran-1.0.10 → staran-1.0.11}/LICENSE +0 -0
- {staran-1.0.10 → staran-1.0.11}/README.md +0 -0
- {staran-1.0.10 → staran-1.0.11}/setup.cfg +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/core/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/core/core.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/core/i18n.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/basic_usage.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/enhanced_features.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/v1010_features_demo.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/v108_features_demo.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/examples/v109_features_demo.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/extensions/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/extensions/expressions.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/extensions/solar_terms.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/extensions/timezone.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/integrations/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/integrations/api_server.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/integrations/visualization.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/run_tests.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/test_core.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/test_enhancements.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/test_v1010_features.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/test_v108_features.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/tests/test_v109_features.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/utils/__init__.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran/date/utils/helpers.py +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran.egg-info/SOURCES.txt +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran.egg-info/dependency_links.txt +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran.egg-info/entry_points.txt +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran.egg-info/requires.txt +0 -0
- {staran-1.0.10 → staran-1.0.11}/staran.egg-info/top_level.txt +0 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Staran - 企业级Python日期处理库
|
6
|
+
"""
|
7
|
+
|
8
|
+
__version__ = "1.0.11"
|
9
|
+
__author__ = "StarAn"
|
10
|
+
__email__ = "starlxa@icloud.com"
|
11
|
+
__license__ = "MIT"
|
12
|
+
|
13
|
+
# 导入核心功能
|
14
|
+
try:
|
15
|
+
from .date import (
|
16
|
+
Date,
|
17
|
+
DateRange,
|
18
|
+
DateError,
|
19
|
+
LunarDate,
|
20
|
+
Language,
|
21
|
+
today,
|
22
|
+
from_string,
|
23
|
+
from_lunar,
|
24
|
+
parse_expression,
|
25
|
+
get_version_info,
|
26
|
+
get_feature_status
|
27
|
+
)
|
28
|
+
except ImportError as e:
|
29
|
+
import warnings
|
30
|
+
warnings.warn(f"Staran核心功能导入失败: {e}")
|
31
|
+
|
32
|
+
Date = None
|
33
|
+
DateRange = None
|
34
|
+
DateError = Exception
|
35
|
+
LunarDate = None
|
36
|
+
Language = None
|
37
|
+
|
38
|
+
def today():
|
39
|
+
raise ImportError("Staran核心功能不可用")
|
40
|
+
|
41
|
+
def from_string(date_string: str):
|
42
|
+
raise ImportError("Staran核心功能不可用")
|
43
|
+
|
44
|
+
def from_lunar(year: int, month: int, day: int, is_leap: bool = False):
|
45
|
+
raise ImportError("Staran核心功能不可用")
|
46
|
+
|
47
|
+
def parse_expression(expression: str):
|
48
|
+
raise ImportError("Staran核心功能不可用")
|
49
|
+
|
50
|
+
def get_version_info():
|
51
|
+
return {'version': __version__, 'status': 'core_unavailable'}
|
52
|
+
|
53
|
+
def get_feature_status():
|
54
|
+
return {'core_available': False}
|
55
|
+
|
56
|
+
__all__ = [
|
57
|
+
'__version__', '__author__', '__email__', '__license__',
|
58
|
+
'Date', 'DateRange', 'DateError', 'LunarDate', 'Language',
|
59
|
+
'today', 'from_string', 'from_lunar', 'parse_expression',
|
60
|
+
'get_version_info', 'get_feature_status'
|
61
|
+
]
|
@@ -0,0 +1,150 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
农历计算模块
|
6
|
+
============
|
7
|
+
|
8
|
+
提供公历与农历互转功能,支持农历日期的创建、输出和比较。
|
9
|
+
基于中国传统农历历法计算,支持1900-2100年范围。
|
10
|
+
"""
|
11
|
+
from typing import Tuple, Optional, Union, TYPE_CHECKING
|
12
|
+
import datetime
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from .core import Date
|
16
|
+
|
17
|
+
|
18
|
+
class LunarDate:
|
19
|
+
"""农历日期类
|
20
|
+
|
21
|
+
支持农历与公历的互相转换,农历日期的创建和格式化输出。
|
22
|
+
"""
|
23
|
+
|
24
|
+
# 农历数据表 (1900-2100年)
|
25
|
+
_LUNAR_INFO = [
|
26
|
+
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
|
27
|
+
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
|
28
|
+
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
|
29
|
+
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
|
30
|
+
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
|
31
|
+
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
|
32
|
+
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
|
33
|
+
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
|
34
|
+
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
|
35
|
+
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
|
36
|
+
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
|
37
|
+
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
|
38
|
+
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
|
39
|
+
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
|
40
|
+
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
|
41
|
+
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
|
42
|
+
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
|
43
|
+
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
|
44
|
+
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
|
45
|
+
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
|
46
|
+
0x0d520
|
47
|
+
]
|
48
|
+
|
49
|
+
def __init__(self, year: int, month: int, day: int, is_leap: bool = False):
|
50
|
+
self.year = year
|
51
|
+
self.month = month
|
52
|
+
self.day = day
|
53
|
+
self.is_leap = is_leap
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def from_solar(cls, solar_date):
|
57
|
+
"""从公历日期转换为农历日期"""
|
58
|
+
if hasattr(solar_date, 'to_datetime_object'):
|
59
|
+
solar_date = solar_date.to_datetime_object()
|
60
|
+
|
61
|
+
# 计算距离1900年1月31日的天数
|
62
|
+
base_date = datetime.date(1900, 1, 31)
|
63
|
+
delta = solar_date - base_date
|
64
|
+
offset = delta.days
|
65
|
+
|
66
|
+
# 计算农历年份
|
67
|
+
lunar_year = 1900
|
68
|
+
while offset > 0:
|
69
|
+
year_days = cls._get_lunar_year_days(lunar_year)
|
70
|
+
if offset >= year_days:
|
71
|
+
offset -= year_days
|
72
|
+
lunar_year += 1
|
73
|
+
else:
|
74
|
+
break
|
75
|
+
|
76
|
+
# 计算农历月份和日期
|
77
|
+
lunar_month = 1
|
78
|
+
is_leap = False
|
79
|
+
|
80
|
+
while offset > 0:
|
81
|
+
month_days = cls._get_lunar_month_days(lunar_year, lunar_month, False)
|
82
|
+
leap_month = cls._get_leap_month(lunar_year)
|
83
|
+
|
84
|
+
if offset >= month_days:
|
85
|
+
offset -= month_days
|
86
|
+
if lunar_month == leap_month and not is_leap:
|
87
|
+
leap_days = cls._get_lunar_month_days(lunar_year, lunar_month, True)
|
88
|
+
if offset >= leap_days:
|
89
|
+
offset -= leap_days
|
90
|
+
lunar_month += 1
|
91
|
+
else:
|
92
|
+
is_leap = True
|
93
|
+
break
|
94
|
+
else:
|
95
|
+
lunar_month += 1
|
96
|
+
else:
|
97
|
+
break
|
98
|
+
|
99
|
+
lunar_day = offset + 1
|
100
|
+
return cls(lunar_year, lunar_month, lunar_day, is_leap)
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def _get_lunar_year_days(cls, year):
|
104
|
+
"""获取农历年的总天数"""
|
105
|
+
if year < 1900 or year > 2100:
|
106
|
+
return 0
|
107
|
+
|
108
|
+
info = cls._LUNAR_INFO[year - 1900]
|
109
|
+
days = 0
|
110
|
+
|
111
|
+
# 12个月的天数
|
112
|
+
for month in range(1, 13):
|
113
|
+
days += 29 if (info & (0x10000 >> month)) == 0 else 30
|
114
|
+
|
115
|
+
# 闰月天数
|
116
|
+
leap_month = cls._get_leap_month(year)
|
117
|
+
if leap_month > 0:
|
118
|
+
days += cls._get_lunar_month_days(year, leap_month, True)
|
119
|
+
|
120
|
+
return days
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def _get_lunar_month_days(cls, year, month, is_leap=False):
|
124
|
+
"""获取农历月的天数"""
|
125
|
+
if year < 1900 or year > 2100:
|
126
|
+
return 0
|
127
|
+
|
128
|
+
info = cls._LUNAR_INFO[year - 1900]
|
129
|
+
|
130
|
+
if is_leap:
|
131
|
+
leap_month = cls._get_leap_month(year)
|
132
|
+
if month != leap_month:
|
133
|
+
return 0
|
134
|
+
return 29 if (info & 0x10000) == 0 else 30
|
135
|
+
else:
|
136
|
+
return 29 if (info & (0x10000 >> month)) == 0 else 30
|
137
|
+
|
138
|
+
@classmethod
|
139
|
+
def _get_leap_month(cls, year):
|
140
|
+
"""获取闰月月份"""
|
141
|
+
if year < 1900 or year > 2100:
|
142
|
+
return 0
|
143
|
+
|
144
|
+
info = cls._LUNAR_INFO[year - 1900]
|
145
|
+
return info & 0xf
|
146
|
+
|
147
|
+
def format_chinese(self):
|
148
|
+
"""格式化为中文"""
|
149
|
+
leap_str = "闰" if self.is_leap else ""
|
150
|
+
return f"农历{self.year}年{leap_str}{self.month}月{self.day}日"
|
staran-1.0.10/staran/__init__.py
DELETED
File without changes
|
@@ -1,320 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
|
4
|
-
"""
|
5
|
-
农历计算模块
|
6
|
-
============
|
7
|
-
|
8
|
-
提供公历与农历互转功能,支持农历日期的创建、输出和比较。
|
9
|
-
基于中国传统农历历法计算,支持1900-2100年范围。
|
10
|
-
"""
|
11
|
-
|
12
|
-
import datetime
|
13
|
-
from typing import Tuple, Optional, Union, TYPE_CHECKING
|
14
|
-
|
15
|
-
if TYPE_CHECKING:
|
16
|
-
from .core import Date
|
17
|
-
|
18
|
-
|
19
|
-
class LunarDate:
|
20
|
-
"""农历日期类
|
21
|
-
|
22
|
-
支持农历与公历的互相转换,农历日期的创建和格式化输出。
|
23
|
-
"""
|
24
|
-
|
25
|
-
# 农历数据表 (1900-2100年)
|
26
|
-
# 每个数字的低12位表示12个月,第13位表示闰月月份,第14-17位表示闰月天数
|
27
|
-
_LUNAR_INFO = [
|
28
|
-
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
|
29
|
-
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
|
30
|
-
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
|
31
|
-
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
|
32
|
-
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
|
33
|
-
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
|
34
|
-
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
|
35
|
-
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
|
36
|
-
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
|
37
|
-
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
|
38
|
-
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
|
39
|
-
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
|
40
|
-
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
|
41
|
-
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
|
42
|
-
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
|
43
|
-
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
|
44
|
-
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
|
45
|
-
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
|
46
|
-
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
|
47
|
-
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
|
48
|
-
0x0d520
|
49
|
-
]
|
50
|
-
|
51
|
-
# 农历月份名称
|
52
|
-
_LUNAR_MONTHS = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']
|
53
|
-
_LUNAR_DAYS = ['初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
|
54
|
-
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
|
55
|
-
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十']
|
56
|
-
|
57
|
-
# 天干地支
|
58
|
-
_TIANGAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']
|
59
|
-
_DIZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
|
60
|
-
_ZODIAC = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
|
61
|
-
|
62
|
-
def __init__(self, year: int, month: int, day: int, is_leap: bool = False):
|
63
|
-
"""初始化农历日期
|
64
|
-
|
65
|
-
Args:
|
66
|
-
year: 农历年份
|
67
|
-
month: 农历月份
|
68
|
-
day: 农历日期
|
69
|
-
is_leap: 是否闰月
|
70
|
-
"""
|
71
|
-
self.year = year
|
72
|
-
self.month = month
|
73
|
-
self.day = day
|
74
|
-
self.is_leap = is_leap
|
75
|
-
|
76
|
-
if not self._is_valid():
|
77
|
-
raise ValueError(f"无效的农历日期: {year}年{month}月{day}日")
|
78
|
-
|
79
|
-
def _is_valid(self) -> bool:
|
80
|
-
"""验证农历日期是否有效"""
|
81
|
-
if not (1900 <= self.year <= 2100):
|
82
|
-
return False
|
83
|
-
if not (1 <= self.month <= 12):
|
84
|
-
return False
|
85
|
-
if not (1 <= self.day <= 30):
|
86
|
-
return False
|
87
|
-
return True
|
88
|
-
|
89
|
-
@classmethod
|
90
|
-
def from_solar(cls, solar_date: Union[datetime.date, 'Date']) -> 'LunarDate':
|
91
|
-
"""从公历日期转换为农历日期
|
92
|
-
|
93
|
-
Args:
|
94
|
-
solar_date: 公历日期对象
|
95
|
-
|
96
|
-
Returns:
|
97
|
-
农历日期对象
|
98
|
-
"""
|
99
|
-
if hasattr(solar_date, 'to_datetime_object'):
|
100
|
-
# 处理自定义Date对象
|
101
|
-
solar_date = solar_date.to_datetime_object()
|
102
|
-
|
103
|
-
year = solar_date.year
|
104
|
-
month = solar_date.month
|
105
|
-
day = solar_date.day
|
106
|
-
|
107
|
-
# 计算距离1900年1月31日(农历1900年正月初一)的天数
|
108
|
-
base_date = datetime.date(1900, 1, 31)
|
109
|
-
delta = solar_date - base_date
|
110
|
-
offset = delta.days
|
111
|
-
|
112
|
-
# 计算农历年份
|
113
|
-
lunar_year = 1900
|
114
|
-
while offset > 0:
|
115
|
-
year_days = cls._get_lunar_year_days(lunar_year)
|
116
|
-
if offset >= year_days:
|
117
|
-
offset -= year_days
|
118
|
-
lunar_year += 1
|
119
|
-
else:
|
120
|
-
break
|
121
|
-
|
122
|
-
# 计算农历月份和日期
|
123
|
-
lunar_month = 1
|
124
|
-
is_leap = False
|
125
|
-
|
126
|
-
while offset > 0:
|
127
|
-
month_days = cls._get_lunar_month_days(lunar_year, lunar_month, False)
|
128
|
-
leap_month = cls._get_leap_month(lunar_year)
|
129
|
-
|
130
|
-
if offset >= month_days:
|
131
|
-
offset -= month_days
|
132
|
-
if lunar_month == leap_month and not is_leap:
|
133
|
-
# 处理闰月
|
134
|
-
leap_days = cls._get_lunar_month_days(lunar_year, lunar_month, True)
|
135
|
-
if offset >= leap_days:
|
136
|
-
offset -= leap_days
|
137
|
-
lunar_month += 1
|
138
|
-
else:
|
139
|
-
is_leap = True
|
140
|
-
break
|
141
|
-
else:
|
142
|
-
lunar_month += 1
|
143
|
-
else:
|
144
|
-
break
|
145
|
-
|
146
|
-
lunar_day = offset + 1
|
147
|
-
|
148
|
-
return cls(lunar_year, lunar_month, lunar_day, is_leap)
|
149
|
-
|
150
|
-
def to_solar(self) -> datetime.date:
|
151
|
-
"""转换为公历日期
|
152
|
-
|
153
|
-
Returns:
|
154
|
-
公历日期对象
|
155
|
-
"""
|
156
|
-
# 计算农历日期距离1900年正月初一的天数
|
157
|
-
offset = 0
|
158
|
-
|
159
|
-
# 累加年份天数
|
160
|
-
for year in range(1900, self.year):
|
161
|
-
offset += self._get_lunar_year_days(year)
|
162
|
-
|
163
|
-
# 累加月份天数
|
164
|
-
for month in range(1, self.month):
|
165
|
-
offset += self._get_lunar_month_days(self.year, month, False)
|
166
|
-
# 如果有闰月且月份匹配,还要加上闰月天数
|
167
|
-
leap_month = self._get_leap_month(self.year)
|
168
|
-
if month == leap_month:
|
169
|
-
offset += self._get_lunar_month_days(self.year, month, True)
|
170
|
-
|
171
|
-
# 如果是闰月,还要加上正常月份的天数
|
172
|
-
if self.is_leap:
|
173
|
-
offset += self._get_lunar_month_days(self.year, self.month, False)
|
174
|
-
|
175
|
-
# 加上日期天数
|
176
|
-
offset += self.day - 1
|
177
|
-
|
178
|
-
# 基准日期: 1900年1月31日(农历1900年正月初一)
|
179
|
-
base_date = datetime.date(1900, 1, 31)
|
180
|
-
return base_date + datetime.timedelta(days=offset)
|
181
|
-
|
182
|
-
@classmethod
|
183
|
-
def _get_lunar_year_days(cls, year: int) -> int:
|
184
|
-
"""获取农历年的总天数"""
|
185
|
-
if year < 1900 or year > 2100:
|
186
|
-
return 0
|
187
|
-
|
188
|
-
info = cls._LUNAR_INFO[year - 1900]
|
189
|
-
days = 0
|
190
|
-
|
191
|
-
# 12个月的天数
|
192
|
-
for i in range(12):
|
193
|
-
days += 29 if (info & (0x10000 >> i)) == 0 else 30
|
194
|
-
|
195
|
-
# 闰月天数
|
196
|
-
leap_month = cls._get_leap_month(year)
|
197
|
-
if leap_month > 0:
|
198
|
-
days += cls._get_lunar_month_days(year, leap_month, True)
|
199
|
-
|
200
|
-
return days
|
201
|
-
|
202
|
-
@classmethod
|
203
|
-
def _get_lunar_month_days(cls, year: int, month: int, is_leap: bool = False) -> int:
|
204
|
-
"""获取农历月的天数"""
|
205
|
-
if year < 1900 or year > 2100:
|
206
|
-
return 0
|
207
|
-
|
208
|
-
info = cls._LUNAR_INFO[year - 1900]
|
209
|
-
|
210
|
-
if is_leap:
|
211
|
-
# 闰月天数
|
212
|
-
leap_month = cls._get_leap_month(year)
|
213
|
-
if month != leap_month:
|
214
|
-
return 0
|
215
|
-
return 29 if (info & 0x10000) == 0 else 30
|
216
|
-
else:
|
217
|
-
# 正常月份天数
|
218
|
-
return 29 if (info & (0x10000 >> month)) == 0 else 30
|
219
|
-
|
220
|
-
@classmethod
|
221
|
-
def _get_leap_month(cls, year: int) -> int:
|
222
|
-
"""获取闰月月份,如果没有闰月返回0"""
|
223
|
-
if year < 1900 or year > 2100:
|
224
|
-
return 0
|
225
|
-
|
226
|
-
info = cls._LUNAR_INFO[year - 1900]
|
227
|
-
return info & 0xf
|
228
|
-
|
229
|
-
def get_ganzhi_year(self) -> str:
|
230
|
-
"""获取天干地支年份"""
|
231
|
-
# 甲子年为1984年,每60年一个周期
|
232
|
-
offset = (self.year - 1984) % 60
|
233
|
-
tiangan_index = offset % 10
|
234
|
-
dizhi_index = offset % 12
|
235
|
-
return self._TIANGAN[tiangan_index] + self._DIZHI[dizhi_index]
|
236
|
-
|
237
|
-
def get_zodiac(self) -> str:
|
238
|
-
"""获取生肖"""
|
239
|
-
return self._ZODIAC[(self.year - 1900) % 12]
|
240
|
-
|
241
|
-
def format_chinese(self, include_year: bool = True, include_zodiac: bool = False) -> str:
|
242
|
-
"""格式化为中文农历日期
|
243
|
-
|
244
|
-
Args:
|
245
|
-
include_year: 是否包含年份
|
246
|
-
include_zodiac: 是否包含生肖
|
247
|
-
|
248
|
-
Returns:
|
249
|
-
中文农历日期字符串
|
250
|
-
"""
|
251
|
-
result = ""
|
252
|
-
|
253
|
-
if include_year:
|
254
|
-
if include_zodiac:
|
255
|
-
result += f"{self.get_ganzhi_year()}({self.get_zodiac()})年"
|
256
|
-
else:
|
257
|
-
result += f"农历{self.year}年"
|
258
|
-
|
259
|
-
# 月份
|
260
|
-
if self.is_leap:
|
261
|
-
result += f"闰{self._LUNAR_MONTHS[self.month - 1]}月"
|
262
|
-
else:
|
263
|
-
result += f"{self._LUNAR_MONTHS[self.month - 1]}月"
|
264
|
-
|
265
|
-
# 日期
|
266
|
-
result += self._LUNAR_DAYS[self.day - 1]
|
267
|
-
|
268
|
-
return result
|
269
|
-
|
270
|
-
def format_compact(self) -> str:
|
271
|
-
"""紧凑格式"""
|
272
|
-
leap_prefix = "闰" if self.is_leap else ""
|
273
|
-
return f"{self.year}{leap_prefix}{self.month:02d}{self.day:02d}"
|
274
|
-
|
275
|
-
def format_iso_like(self) -> str:
|
276
|
-
"""类ISO格式"""
|
277
|
-
leap_suffix = "L" if self.is_leap else ""
|
278
|
-
return f"{self.year}-{self.month:02d}{leap_suffix}-{self.day:02d}"
|
279
|
-
|
280
|
-
def __str__(self) -> str:
|
281
|
-
"""字符串表示"""
|
282
|
-
return self.format_chinese()
|
283
|
-
|
284
|
-
def __repr__(self) -> str:
|
285
|
-
"""调试表示"""
|
286
|
-
return f"LunarDate({self.year}, {self.month}, {self.day}, is_leap={self.is_leap})"
|
287
|
-
|
288
|
-
def __eq__(self, other) -> bool:
|
289
|
-
"""相等比较"""
|
290
|
-
if not isinstance(other, LunarDate):
|
291
|
-
return False
|
292
|
-
return (self.year == other.year and
|
293
|
-
self.month == other.month and
|
294
|
-
self.day == other.day and
|
295
|
-
self.is_leap == other.is_leap)
|
296
|
-
|
297
|
-
def __lt__(self, other) -> bool:
|
298
|
-
"""小于比较"""
|
299
|
-
if not isinstance(other, LunarDate):
|
300
|
-
return NotImplemented
|
301
|
-
|
302
|
-
if self.year != other.year:
|
303
|
-
return self.year < other.year
|
304
|
-
if self.month != other.month:
|
305
|
-
return self.month < other.month
|
306
|
-
if self.is_leap != other.is_leap:
|
307
|
-
return not self.is_leap # 正常月份小于闰月
|
308
|
-
return self.day < other.day
|
309
|
-
|
310
|
-
def __le__(self, other) -> bool:
|
311
|
-
"""小于等于比较"""
|
312
|
-
return self == other or self < other
|
313
|
-
|
314
|
-
def __gt__(self, other) -> bool:
|
315
|
-
"""大于比较"""
|
316
|
-
return not self <= other
|
317
|
-
|
318
|
-
def __ge__(self, other) -> bool:
|
319
|
-
"""大于等于比较"""
|
320
|
-
return not self < other
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|