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:
110
api.py
Normal file
110
api.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/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}")
|
||||
Reference in New Issue
Block a user