#!/usr/bin/env python3 # 提供農曆-西曆轉換的 API 服務(FastAPI 版本) # 依賴: fastapi, uvicorn, converter.py # /// script # requires-python = ">=3.12" # dependencies = [ # "fastapi", # "uvicorn", # ] # /// import socket from contextlib import closing from typing import Any, Dict, List from fastapi import FastAPI, Query # type: ignore[reportMissingImports] from fastapi.responses import JSONResponse # type: ignore[reportMissingImports] import uvicorn # type: ignore[reportMissingImports] from converter import Converter def find_free_port() -> int: """使用臨時 socket 綁定到 0 取得可用埠號""" with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: s.bind(("", 0)) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return s.getsockname()[1] app = FastAPI(title="Lunar Calendar Converter API") conv = Converter() @app.get("/solar2lunar") def solar2lunar_endpoint(date: str = Query(None, description="YYYY-MM-DD")): """西曆轉農曆""" if not date: return JSONResponse({"error": "缺少參數 date,格式 YYYY-MM-DD"}, status_code=400) try: info_raw = conv.solar_to_lunar(date) info = { "gregorian": info_raw.get("西曆"), "lunar": info_raw.get("農曆"), "ganzhi": info_raw.get("干支"), "zodiac": info_raw.get("生肖"), "solar_term": info_raw.get("節氣"), } return info except Exception as e: return JSONResponse({"error": str(e)}, status_code=400) @app.get("/lunar2solar") def lunar2solar_endpoint(date: str = Query(None, description="YYYY-MM-DD")): """農曆轉西曆(自動處理閏月,回傳所有可能性)""" if not date: return JSONResponse({"error": "缺少參數 date,格式 YYYY-MM-DD"}, status_code=400) try: y_str, m_str, d_str = date.split("-", 2) y, m, d = int(y_str), int(m_str), int(d_str) except Exception: return JSONResponse({"error": "農曆日期格式應為 YYYY-MM-DD"}, status_code=400) possible_solar_dates: List[str] = conv.lunar_to_solar_all(y, m, d) if not possible_solar_dates: error_msg = f"找不到任何對應農曆 {y}年{m}月{d}日 的西曆日期" return JSONResponse({"error": error_msg}, status_code=404) results_list: List[Dict[str, Any]] = [] for solar_date in possible_solar_dates: try: info_raw = conv.solar_to_lunar(solar_date) results_list.append({ "gregorian": info_raw.get("西曆"), "lunar": info_raw.get("農曆"), "ganzhi": info_raw.get("干支"), "zodiac": info_raw.get("生肖"), "solar_term": info_raw.get("節氣"), }) except Exception: continue return results_list @app.get("/leap-info") def leap_info_endpoint(year: str = Query(None, description="西曆年份,數字")): """查詢指定西曆年份是否有農曆閏月,以及是閏幾月。""" if not year: return JSONResponse({"error": "缺少參數 year"}, status_code=400) try: y = int(year) leap_month = conv.get_leap_month(y) return {"year": y, "leap_month": leap_month} except ValueError: return JSONResponse({"error": "參數 year 必須是有效的整數"}, status_code=400) except Exception as e: return JSONResponse({"error": str(e)}, status_code=500) if __name__ == "__main__": try: free_port = find_free_port() print(f"找到一個可用的 port: {free_port}") uvicorn.run(app, host="0.0.0.0", port=free_port, reload=False) except Exception as e: print(f"啟動服務失敗: {e}")