基于 Flask + lunar-python 的农历转换 API 实战(公历 ↔ 农历 / 干支 / 生肖 / 节日)

在国内业务系统中,农历(阴历) 仍然被广泛使用,例如:

  • 农历生日、纪念日
  • 传统节日(春节、除夕、中秋)
  • 日历 / 黄历 / 命理类应用
  • 本地化系统展示

本文基于 lunar-python 农历算法库,使用 Flask 封装了一套 完整、可直接部署的农历转换 REST API,支持:

  • ✅ 公历 → 农历
  • ✅ 农历 → 公历(支持闰月)
  • ✅ 当前农历日期查询
  • ✅ 干支、生肖、节气、节日
  • ✅ 完整中文描述

支持时间范围:1900 -- 2100 年


一、技术选型说明

1️⃣ lunar-python

选择 lunar-python 的原因:

  • 农历算法成熟、权威
  • API 设计清晰
  • 支持干支、生肖、节气、节日
  • 支持闰月(负数月份表示)
  • 纯 Python,无外部依赖
bash 复制代码
pip install lunar-python

2️⃣ Flask

  • 轻量
  • 易于部署
  • 非常适合工具型 API / 内部服务
bash 复制代码
pip install flask

二、API 设计说明

本示例实现了 4 个接口:

接口 说明
/api/to_lunar 公历 → 农历
/api/to_solar 农历 → 公历(支持闰月)
/api/today_lunar 获取当前农历
/ 服务状态 & 接口说明

三、核心设计:统一农历数据结构

为了方便前端或其他系统使用,先对 lunar-python 的返回结果进行统一格式化

农历信息格式化函数

python 复制代码
def format_lunar(lunar):
    """统一格式化农历信息(完全适配 lunar_python 最新版)"""
    month = lunar.getMonth()  # 可能为负数(闰月)
    is_leap_month = month < 0
    abs_month = abs(month)

    festivals = lunar.getFestivals()          # 标准农历节日
    other_festivals = lunar.getOtherFestivals()  # 其他扩展节日

    return {
        "lunar_year": lunar.getYear(),
        "lunar_month": abs_month,
        "lunar_day": lunar.getDay(),
        "is_leap_month": is_leap_month,
        "chinese_string": lunar.toFullString(),
        "full_info": lunar.toFullString(),
        "ganzhi_year": lunar.getYearInChinese(),
        "ganzhi_month": lunar.getMonthInChinese(),
        "ganzhi_day": lunar.getDayInChinese(),
        "shengxiao": lunar.getYearShengXiao(),
        "jieqi": lunar.getJieQi() if lunar.getJieQi() else None,
        "festivals": festivals if festivals else None,
        "other_festivals": other_festivals if other_festivals else None
    }

设计说明

  • 闰月判断month < 0

  • 节日区分

    • getFestivals():传统节日(春节、除夕)
    • getOtherFestivals():扩展节日
  • 输出结构稳定,方便前端直接使用


四、公历 → 农历 API

接口定义

复制代码
GET /api/to_lunar

参数

参数 说明
year 公历年
month 公历月
day 公历日

代码实现

python 复制代码
@app.route('/api/to_lunar', methods=['GET'])
def to_lunar():
    try:
        year = int(request.args.get('year'))
        month = int(request.args.get('month'))
        day = int(request.args.get('day'))

        solar = Solar.fromYmd(year, month, day)
        lunar = solar.getLunar()

        return jsonify({
            "success": True,
            "solar_date": f"{year}-{month:02d}-{day:02d}",
            "lunar": format_lunar(lunar)
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "error": str(e)
        }), 400

示例请求

复制代码
/api/to_lunar?year=2025&month=2&day=1

五、农历 → 公历 API(支持闰月)

接口定义

复制代码
GET /api/to_solar

参数

参数 说明
year 农历年
month 农历月
day 农历日
leap 是否闰月(true / false)

关键点:闰月处理

python 复制代码
lunar_month = -month if leap else month

这是 lunar-python 的标准用法。

代码实现

python 复制代码
@app.route('/api/to_solar', methods=['GET'])
def to_solar():
    try:
        year = int(request.args.get('year'))
        month = int(request.args.get('month'))
        day = int(request.args.get('day'))
        leap = request.args.get('leap', 'false').lower() in ('true', '1', 'yes')

        lunar_month = -month if leap else month
        lunar = Lunar.fromYmd(year, lunar_month, day)
        solar = lunar.getSolar()

        return jsonify({
            "success": True,
            "lunar": format_lunar(lunar),
            "solar_date": solar.toYmd()
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "error": str(e)
        }), 400

六、获取当前农历日期 API

接口定义

复制代码
GET /api/today_lunar

代码实现

python 复制代码
@app.route('/api/today_lunar', methods=['GET'])
def today_lunar():
    try:
        now = datetime.now()
        today_solar = Solar.fromDate(now)
        today_lunar = today_solar.getLunar()

        return jsonify({
            "success": True,
            "solar_date": today_solar.toYmd(),
            "solar_datetime": today_solar.toYmdHms(),
            "timestamp": now.isoformat(),
            "lunar": format_lunar(today_lunar)
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "error": str(e)
        }), 500

适合:

  • 首页日历
  • 今日农历展示
  • 黄历 / 日签应用

七、服务启动与运行

python 复制代码
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5006, debug=True)

启动后访问:

复制代码
http://localhost:5006/

即可看到接口说明。


八、返回 JSON 示例(简化)

json 复制代码
{
  "lunar_year": 2024,
  "lunar_month": 12,
  "lunar_day": 4,
  "is_leap_month": false,
  "chinese_string": "甲辰年 腊月初四",
  "shengxiao": "龙",
  "festivals": ["春节"]
}

完整代码

bash 复制代码
from flask import Flask, request, jsonify
from lunar_python import Solar, Lunar
from datetime import datetime
import traceback

app = Flask(__name__)

def format_lunar(lunar):
    """统一格式化农历信息(完全适配 lunar_python 最新版)"""
    month = lunar.getMonth()  # 可能为负数(闰月)
    is_leap_month = month < 0
    abs_month = abs(month)

    festivals = lunar.getFestivals()  # 标准农历节日(如春节、除夕)
    other_festivals = lunar.getOtherFestivals()  # 其他扩展节日

    return {
        "lunar_year": lunar.getYear(),
        "lunar_month": abs_month,
        "lunar_day": lunar.getDay(),
        "is_leap_month": is_leap_month,
        "chinese_string": lunar.toFullString(),
        "full_info": lunar.toFullString(),
        "ganzhi_year": lunar.getYearInChinese(),
        "ganzhi_month": lunar.getMonthInChinese(),
        "ganzhi_day": lunar.getDayInChinese(),
        "shengxiao": lunar.getYearShengXiao(),
        "jieqi": lunar.getJieQi() if lunar.getJieQi() else None,
        "festivals": festivals if festivals else None,  # 传统农历节日列表
        "other_festivals": other_festivals if other_festivals else None  # 其他节日
    }

@app.route('/api/to_lunar', methods=['GET'])
def to_lunar():
    try:
        year = int(request.args.get('year'))
        month = int(request.args.get('month'))
        day = int(request.args.get('day'))

        solar = Solar.fromYmd(year, month, day)
        lunar = solar.getLunar()

        return jsonify({
            "success": True,
            "solar_date": f"{year}-{month:02d}-{day:02d}",
            "lunar": format_lunar(lunar)
        })
    except Exception as e:
        app.logger.error(f"Error in to_lunar: {str(e)}\n{traceback.format_exc()}")
        return jsonify({
            "success": False,
            "error": str(e),
            "detail": str(e)
        }), 400

@app.route('/api/to_solar', methods=['GET'])
def to_solar():
    try:
        year = int(request.args.get('year'))
        month = int(request.args.get('month'))
        day = int(request.args.get('day'))
        leap = request.args.get('leap', 'false').lower() in ('true', '1', 'yes')

        lunar_month = -month if leap else month
        lunar = Lunar.fromYmd(year, lunar_month, day)
        solar = lunar.getSolar()

        return jsonify({
            "success": True,
            "lunar": format_lunar(lunar),
            "solar_date": solar.toYmd()
        })
    except Exception as e:
        app.logger.error(f"Error in to_solar: {str(e)}\n{traceback.format_exc()}")
        return jsonify({
            "success": False,
            "error": str(e),
            "detail": str(e)
        }), 400

@app.route('/api/today_lunar', methods=['GET'])
def today_lunar():
    try:
        app.logger.info("Calling /api/today_lunar")
        
        now = datetime.now()
        today_solar = Solar.fromDate(now)
        app.logger.info(f"Today solar: {today_solar.toYmd()} {today_solar.toYmdHms()}")

        today_lunar = today_solar.getLunar()

        return jsonify({
            "success": True,
            "solar_date": today_solar.toYmd(),
            "solar_datetime": today_solar.toYmdHms(),
            "timestamp": now.isoformat(),
            "lunar": format_lunar(today_lunar)
        })

    except Exception as e:
        error_msg = str(e)
        tb = traceback.format_exc()
        app.logger.error(f"Error in today_lunar: {error_msg}\n{tb}")

        return jsonify({
            "success": False,
            "error": "Internal Server Error",
            "detail": error_msg,
            "traceback": tb
        }), 500

@app.route('/', methods=['GET'])
def home():
    return jsonify({
        "message": "Lunar Calendar API is running! (lunar_python fully adapted)",
        "current_time": datetime.now().isoformat(),
        "endpoints": {
            "to_lunar": "/api/to_lunar?year=2025&month=12&day=24",
            "to_solar": "/api/to_solar?year=2025&month=11&day=5&leap=false",
            "today_lunar": "/api/today_lunar"
        }
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5006, debug=True)

九、适用场景

  • 📅 日历 / 黄历系统
  • 🎂 农历生日管理
  • 🧧 中国本土化应用
  • 🌐 Web / 小程序后端
  • 🧰 内部工具 API

十、总结

本文基于 lunar-python + Flask 实现了一套:

功能完整、结构清晰、可直接部署的农历转换 API

特点:

  • 不依赖外部命令
  • 支持闰月
  • 返回结构友好
  • 易于扩展

非常适合用于实际生产项目

相关推荐
Dxy12393102165 分钟前
Python基于BERT的上下文纠错详解
开发语言·python·bert
SiYuanFeng1 小时前
Colab复现 NanoChat:从 Tokenizer(CPU)、Base Train(CPU) 到 SFT(GPU) 的完整踩坑实录
python·colab
炸炸鱼.2 小时前
Python 操作 MySQL 数据库
android·数据库·python·adb
_深海凉_3 小时前
LeetCode热题100-颜色分类
python·算法·leetcode
AC赳赳老秦3 小时前
OpenClaw email技能:批量发送邮件、自动回复,高效处理工作邮件
运维·人工智能·python·django·自动化·deepseek·openclaw
zhaoshuzhaoshu3 小时前
Python 语法之数据结构详细解析
python
AI问答工程师4 小时前
Meta Muse Spark 的"思维压缩"到底是什么?我用 Python 复现了核心思路(附代码)
人工智能·python
zfan5205 小时前
python对Excel数据处理(1)
python·excel·pandas
小饕5 小时前
我从零搭建 RAG 学到的 10 件事
python
老歌老听老掉牙5 小时前
PyQt5+Qt Designer实战:可视化设计智能参数配置界面,告别手动布局时代!
python·qt