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 d788399b3a
20 changed files with 2202 additions and 0 deletions

99
age_calculator.py Normal file
View File

@@ -0,0 +1,99 @@
# age_calculator.py
import datetime
from collections import namedtuple
from loguru import logger # type: ignore[reportMissingImports]
# 從 converter.py 這個檔案,匯入 Converter 這個 class
from converter import Converter
# --- 類型定義 ---
AgeResult = namedtuple("AgeResult", ["real_age", "east_asian_age"])
# ======================================================================
# 主要類別AgeCalculator
# ======================================================================
class AgeCalculator:
"""
提供精準的年齡計算功能 (實歲與虛歲)。
初始化時需要傳入一個 Converter 物件。
"""
def __init__(self, converter: Converter):
"""
初始化年齡計算機。
這個設計模式稱為「依賴注入 (Dependency Injection)」,
AgeCalculator 本身不建立 Converter而是由外部提供讓架構更靈活。
:param converter: 一個 Converter 的實體物件。
"""
if not isinstance(converter, Converter):
raise TypeError("初始化 AgeCalculator 時,必須提供一個有效的 Converter 物件")
self.converter = converter
def calculate(
self,
solar_birth_date: datetime.date,
lunar_birth_info: dict,
today: datetime.date
) -> AgeResult:
"""
根據生日資訊,使用農曆轉換器精準計算實歲和虛歲。
"""
# 虛歲計算 (邏輯不變)
east_asian_age = today.year - solar_birth_date.year + 1
# 實歲計算 (使用 self.converter)
try:
# 用 converter 找出「今年的農曆生日」對應的西曆日期
this_year_lunar_bday_solar_str = self.converter.lunar_to_solar(
lunar_year=today.year,
lunar_month=lunar_birth_info['month'],
lunar_day=lunar_birth_info['day'],
leap=lunar_birth_info['leap']
)
this_year_lunar_bday_date = datetime.datetime.strptime(this_year_lunar_bday_solar_str, '%Y-%m-%d').date()
except KeyError:
# Edge case: 若今年沒有該農曆日期(如閏月),則認定生日已過,讓邏輯更強健
this_year_lunar_bday_date = today - datetime.timedelta(days=1)
real_age = today.year - solar_birth_date.year
# 核心判斷:如果今天還沒到「今年的農曆生日(的西曆日期)」,那實歲就 -1
if today < this_year_lunar_bday_date:
real_age -= 1
return AgeResult(real_age=real_age, east_asian_age=east_asian_age)
# ======================================================================
# 主程式區塊:作為使用範例
# ======================================================================
if __name__ == "__main__":
# --- 作為範例的資料 ---
TODAY_OVERRIDE = datetime.date(2025, 7, 30)
SOLAR_BIRTH_DATE_STR = "1977-10-12"
LUNAR_BIRTH_INFO = {"month": 8, "day": 30, "leap": False}
try:
# --- 步驟 1: 建立依賴物件 (工具人) ---
converter = Converter()
# --- 步驟 2: 建立主物件,並注入依賴 (主角登場,並把工具人交給他) ---
age_calculator = AgeCalculator(converter=converter)
# --- 步驟 3: 執行計算 (叫主角開始幹活) ---
solar_birth_date = datetime.datetime.strptime(SOLAR_BIRTH_DATE_STR, "%Y-%m-%d").date()
age_result = age_calculator.calculate(
solar_birth_date=solar_birth_date,
lunar_birth_info=LUNAR_BIRTH_INFO,
today=TODAY_OVERRIDE,
)
# --- 步驟 4: 使用 loguru 輸出結果 ---
logger.remove()
logger.add(lambda msg: print(msg, end=""), format="{message}")
logger.info(f"實歲 (足歲){age_result.real_age} 歲。")
logger.info(f"虛歲:{age_result.east_asian_age} 歲。")
except Exception as e:
logger.error(f"執行時發生錯誤: {e}")