Initial commit: Lunar calendar converter project

- Add lunar calendar astronomical calculation engine (lunar_calendar.py)
- Add converter utilities for solar/lunar date conversion
- Add historical era name conversions (chrono_converter.py, converter_with_eras.py)
- Add FastAPI REST API for date conversion services
- Add age calculator using lunar calendar
- Add batch generation script for lunar calendar JSON data
- Add project documentation (CLAUDE.md, pyproject.toml)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 13:51:45 +08:00
commit ae21a0ae66
19 changed files with 1977 additions and 0 deletions

213
chrono_converter.py Normal file
View File

@@ -0,0 +1,213 @@
from datetime import date
class ChronoConverter:
# 太平天國
TP_START = (1851, 2, 1)
TP_END = (1872, 7, 5)
# 清朝年號區間(含康熙到宣統)
KX_START = date(1662, 2, 18)
KX_END = date(1723, 2, 4)
YZ_START = date(1723, 2, 5)
YZ_END = date(1736, 2, 11)
QL_START = date(1736, 2, 12)
QL_END = date(1796, 2, 8)
JQ_START = date(1796, 2, 9)
JQ_END = date(1821, 2, 2)
DG_START = date(1821, 2, 3)
DG_END = date(1851, 1, 31)
XF_START = date(1851, 2, 1)
XF_END = date(1862, 1, 29)
TZ_START = date(1862, 1, 30)
TZ_END = date(1875, 2, 5)
GX_START = date(1875, 2, 6)
GX_END = date(1909, 1, 21)
XT_START = date(1909, 1, 22)
XT_END = date(1912, 2, 17)
# 清太祖天命 & 順治
TN_START = date(1616, 2, 17)
TN_END = date(1627, 2, 15)
SZ_START = date(1644, 2, 8)
SZ_END = date(1662, 2, 17)
# 明朝年號
WL_START = date(1573, 1, 30)
WL_END = date(1620, 8, 18)
TQ_START = date(1620, 8, 19)
TQ_END = date(1627, 9, 29)
CZ_START = date(1628, 2, 5)
CZ_END = date(1644, 4, 25)
@staticmethod
def to_minguo(year: int) -> str:
if year < 1912:
return f"西元 {year} 年,尚未進入民國"
return f"民國 {year - 1911}"
@staticmethod
def to_ad_from_minguo(minguo_year: int) -> str:
if minguo_year <= 0:
return "民國元年從西元 1912 年開始,不能為 0 或負數"
return f"西元 {minguo_year + 1911}"
@classmethod
def to_taiping(cls, year: int, month: int = 1, day: int = 1) -> str:
if (year, month, day) < cls.TP_START:
return f"西元 {year}{month}{day} 日尚未進入太平天國年間"
elif (year, month, day) > cls.TP_END:
return f"西元 {year}{month}{day} 日已超過太平天國年間"
else:
tp_year = year - 1850
return f"太平天國 {tp_year}"
@staticmethod
def to_ad_from_taiping(tp_year: int) -> str:
if 1 <= tp_year <= 22:
return f"西元 {tp_year + 1850} 年(約太平天國 {tp_year} 年)"
else:
return f"太平天國 {tp_year} 年超出有效範圍122"
@classmethod
def to_tianming(cls, y: int, m: int = 1, d: int = 1) -> str:
try:
input_date = date(y, m, d)
except ValueError:
return "無效的日期"
if input_date < cls.TN_START:
return f"西元 {y}{m}{d} 日尚未進入天命年間"
elif input_date > cls.TN_END:
return f"西元 {y}{m}{d} 日已超過天命年間"
else:
return f"清太祖天命 {y - 1615}"
@staticmethod
def to_ad_from_tianming(tn_year: int) -> str:
if 1 <= tn_year <= 12:
return f"西元 {tn_year + 1615} 年(約天命 {tn_year} 年)"
else:
return "天命年間僅有 112 年"
@classmethod
def to_shunzhi(cls, y: int, m: int = 1, d: int = 1) -> str:
try:
input_date = date(y, m, d)
except ValueError:
return "無效的日期"
if input_date < cls.SZ_START:
return f"西元 {y}{m}{d} 日尚未進入順治年間"
elif input_date > cls.SZ_END:
return f"西元 {y}{m}{d} 日已超過順治年間"
else:
return f"清世祖順治 {y - 1643}"
@staticmethod
def to_ad_from_shunzhi(sz_year: int) -> str:
if 1 <= sz_year <= 18:
return f"西元 {sz_year + 1643} 年(約順治 {sz_year} 年)"
else:
return "順治年間僅有 118 年"
@classmethod
def to_qing_dynasty(cls, y: int, m: int = 1, d: int = 1) -> str:
try:
input_date = date(y, m, d)
except ValueError:
return "無效的日期"
ranges = [
(cls.KX_START, cls.KX_END, 1661, "清聖祖康熙", 1),
(cls.YZ_START, cls.YZ_END, 1722, "清世宗雍正", 1),
(cls.QL_START, cls.QL_END, 1735, "清高宗乾隆", 1),
(cls.JQ_START, cls.JQ_END, 1795, "清仁宗嘉慶", 1),
(cls.DG_START, cls.DG_END, 1820, "清宣宗道光", 1),
(cls.XF_START, cls.XF_END, 1850, "清文宗咸豐", 1),
(cls.TZ_START, cls.TZ_END, 1861, "清穆宗同治", 1),
(cls.GX_START, cls.GX_END, 1874, "清德宗光緒", 1),
(cls.XT_START, cls.XT_END, 1908, "清遜帝宣統", 1),
]
for start, end, base_year, label, base_era in ranges:
if start <= input_date <= end:
return f"{label} {y - base_year + base_era}"
return f"西元 {y}{m}{d} 日不在清代指定年號範圍內"
@classmethod
def to_ming_dynasty(cls, y: int, m: int = 1, d: int = 1) -> str:
try:
input_date = date(y, m, d)
except ValueError:
return "無效的日期"
ranges = [
(cls.WL_START, cls.WL_END, 1572, "明神宗萬曆", 1),
(cls.TQ_START, cls.TQ_END, 1619, "明熹宗天啟", 1),
(cls.CZ_START, cls.CZ_END, 1627, "明思宗崇禎", 1),
]
for start, end, base_year, label, base_era in ranges:
if start <= input_date <= end:
return f"{label} {y - base_year + base_era}"
return f"西元 {y}{m}{d} 日不在明代指定年號範圍內"
@staticmethod
def weekday_str(y: int, m: int, d: int) -> str:
try:
dt = date(y, m, d)
return dt.strftime("%A")
except ValueError:
return "無效日期"
# 檢查當前程式是否被作為主程式執行
if __name__ == "__main__":
def convert_date(y: int, m: int, d: int) -> dict:
cc = ChronoConverter()
raw = {
"民國": cc.to_minguo(y),
"太平天國": cc.to_taiping(y, m, d),
"清太祖天命": cc.to_tianming(y, m, d),
"清世祖順治": cc.to_shunzhi(y, m, d),
"清朝其他年號": cc.to_qing_dynasty(y, m, d),
"明朝晚期年號": cc.to_ming_dynasty(y, m, d),
"星期": cc.weekday_str(y, m, d),
}
# 過濾掉無效的年號資訊
filtered = {
k: v for k, v in raw.items()
if not (
"尚未進入" in v or
"已超過" in v or
"不在" in v or
"無效" in v
)
}
return filtered
# result = convert_date(1851, 2, 1)
# result = convert_date(1977, 10, 12)
result = convert_date(1906, 2, 7)
# result = convert_date(1967, 10, 17)
print(result)
for key, val in result.items():
print(f"{key}{val}")