#!/usr/bin/env python3 # converter.py # 用於西曆與農曆互轉,已封裝成類別 import os import json import argparse from datetime import datetime class Converter: """ 提供西曆與農曆互轉功能: - solar_to_lunar(date_str:str) -> dict - lunar_to_solar(year:int, month:int, day:int, leap:bool) -> str """ LUNAR_JSON_DIR = 'lunar_json' @classmethod def load_solar_to_lunar(cls, year: int) -> dict: path = os.path.join(cls.LUNAR_JSON_DIR, f"{year}.json") if not os.path.exists(path): raise FileNotFoundError(f"找不到 {path}") with open(path, encoding='utf-8') as f: return json.load(f) def solar_to_lunar(self, solar_date_str: str) -> dict: try: dt = datetime.strptime(solar_date_str, '%Y-%m-%d').date() except ValueError: raise ValueError("西曆日期格式應為 YYYY-MM-DD") data = self.load_solar_to_lunar(dt.year) if solar_date_str not in data: raise KeyError(f"沒有找到對應 {solar_date_str} 的農曆資料") return data[solar_date_str] def lunar_to_solar(self, lunar_year: int, lunar_month: int, lunar_day: int, leap: bool=False) -> str: solar_year = lunar_year + 1 if lunar_month in (11, 12) else lunar_year data = self.load_solar_to_lunar(solar_year) # 精確匹配 for solar, info in data.items(): lstr = info.get('農曆','') is_leap = lstr.startswith('閏') core = lstr[1:] if is_leap else lstr m_str, d_str = core.replace('月','-').replace('日','').split('-') if int(m_str)==lunar_month and int(d_str)==lunar_day and is_leap==leap: return solar # 回退到非閏月匹配 for solar, info in data.items(): lstr = info.get('農曆','') is_leap = lstr.startswith('閏') core = lstr[1:] if is_leap else lstr m_str, d_str = core.replace('月','-').replace('日','').split('-') if int(m_str)==lunar_month and int(d_str)==lunar_day and not is_leap: return solar raise KeyError(f"找不到對應農曆 {lunar_year}{'閏' if leap else ''}{lunar_month}月{lunar_day}日 的西曆") def main(): parser = argparse.ArgumentParser(description='西曆與農曆互轉工具') sub = parser.add_subparsers(dest='command', required=True) # 西曆 -> 農曆 p1 = sub.add_parser('solar2lunar', help='西曆轉農曆') p1.add_argument('date', type=str, help='西曆日期 YYYY-MM-DD') # 農曆 -> 西曆 p2 = sub.add_parser('lunar2solar', help='農曆轉西曆') p2.add_argument('date', type=str, help='農曆日期 YYYY-MM-DD') p2.add_argument('--leap', action='store_true', help='是否閏月') args = parser.parse_args() conv = Converter() if args.command == 'solar2lunar': info = conv.solar_to_lunar(args.date) print(json.dumps(info, ensure_ascii=False, indent=2)) elif args.command == 'lunar2solar': try: y_str, m_str, d_str = args.date.split('-',2) y, m, d = int(y_str), int(m_str), int(d_str) except Exception: raise ValueError("農曆日期格式應為 YYYY-MM-DD,閏月請加 --leap") solar = conv.lunar_to_solar(y, m, d, args.leap) info = conv.solar_to_lunar(solar) print(json.dumps(info, ensure_ascii=False, indent=2)) if __name__ == '__main__': main()