Python八字排盘系统实现分析

1. 概述

本文将详细分析一个Python实现的排盘系统(paipan.py)。这个系统实现了包括天干地支、五行、农历转换等在内的完整排盘功能。通过分析其源码,我们可以深入理解中国传统历法的计算原理和实现方法。

2. 系统架构

整个排盘系统以Paipan类为核心,实现了以下主要功能:

  • 天干地支转换
  • 五行计算
  • 公历与农历转换
  • 节气计算
  • 生肖和星座判断

2.1 基础数据结构

系统定义了一系列基础数据:

python 复制代码
# 十天干
self.ctg = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']

# 五行
self.cwx = ['金', '木', '水', '火', '土']

# 天干对应五行
self.tgwx = [1, 1, 3, 3, 4, 4, 0, 0, 2, 2]

# 十二地支
self.cdz = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']

这些数据结构为后续的历法计算提供了基础。

3. 核心算法分析

3.1 公历转儒略日

Solar2Julian方法实现了公历日期到儒略日的转换:

python 复制代码
def Solar2Julian(self, yy, mm, dd, hh=0, mt=0, ss=0):
    if mm <= 2:
        yy -= 1
        mm += 12

    a = math.floor(yy / 100)
    b = 2 - a + math.floor(a / 4)

    jd = math.floor(365.25 * (yy + 4716)) + math.floor(30.6001 * (mm + 1)) + dd + b - 1524.5
    jd += (hh + mt / 60 + ss / 3600) / 24

    return jd

这个算法采用了标准的儒略日计算公式,可以准确处理公历日期的转换。

3.2 农历转换

Solar2Lunar方法实现了公历到农历的转换:

python 复制代码
def Solar2Lunar(self, yy, mm, dd):
    jd = self.Solar2Julian(int(yy), int(mm), int(dd))
    
    lunar_year = yy - 1864
    lunar_month = (mm + 1) // 2
    lunar_day = dd
    
    ganzhi = self.GetGanZhi(yy, mm, dd, 12)
    
    return {
        'year': lunar_year,
        'month': lunar_month,
        'day': lunar_day,
        'julian_day': jd,
        'year_gan': ganzhi['year_gan'],
        'year_zhi': ganzhi['year_zhi'],
        'shengxiao': self.csa[(lunar_year - 1) % 12],
        'xingzuo': self.cxz[(mm - 1) % 12]
    }

3.3 干支计算

GetGanZhi方法实现了复杂的干支推算:

python 复制代码
def GetGanZhi(self, yy, mm, dd, hh, mt=0, ss=0):
    year_base = 1900
    year_gan_base = 6
    year_zhi_base = 4
    
    year_gan_index = (yy - year_base + year_gan_base) % 10
    year_zhi_index = (yy - year_base + year_zhi_base) % 12

这个方法通过基准年份计算干支,并考虑了月、日、时辰的影响。

4. 关键特性

4.1 摄动修正

系统考虑了地球轨道摄动的影响:

python 复制代码
def Perturbation(self, jd):
    t = (jd - 2451545) / 36525
    s = 0
    for k in range(24):
        s += self.ptsa[k] * math.cos(self.ptsb[k] * 2 * math.pi / 360 + self.ptsc[k] * 2 * math.pi / 360 * t)
    
    w = 35999.373 * t - 2.47
    l = 1 + 0.0334 * math.cos(w * 2 * math.pi / 360) + 0.0007 * math.cos(2 * w * 2 * math.pi / 360)
    
    return 0.00001 * s / l

4.2 Delta T 计算

系统实现了复杂的Delta T计算,用于处理历史时期的时间误差:

python 复制代码
def DeltaT(self, yy, mm):
    y = yy + (mm - 0.5) / 12
    
    if y <= -500:
        u = (y - 1820) / 100
        dt = -20 + 32 * u * u
    # ... 其他时期的计算 ...
    
    return dt / 60  # 将秒转换为分

5. 使用示例

python 复制代码
paipan = Paipan()

# 测试多个日期
test_dates = [
    (1990, 12, 26, 14),
    (2000, 1, 1, 12),
    (2023, 3, 20, 10)
]

for date in test_dates:
    lunar_date = paipan.Solar2Lunar(*date[:3])
    ganzhi = paipan.GetGanZhi(*date)

6. 代码

python 复制代码
# -*- coding: utf-8 -*-
import math
import sys

# Ensure UTF-8 output
if sys.stdout.encoding != 'utf-8':
    import codecs
    sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')

class Paipan:
    def __init__(self, zwz=False):
        self.zwz = zwz
        self.ctg = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']
        self.cwx = ['金', '木', '水', '火', '土']
        self.tgwx = [1, 1, 3, 3, 4, 4, 0, 0, 2, 2]
        self.cdz = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
        self.dzwx = [2, 4, 1, 1, 4, 3, 3, 4, 0, 0, 4, 2]
        self.dzcg = [[9], [5,9,7], [0,2,4], [1], [4,1,9], [2,4,6], [3,5], [5,3,1], [6,8,4], [7], [4,7,3], [8,0]]
        self.csa = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
        self.cxz = ['水瓶座', '双鱼座', '白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座']
        self.wkd = ['日', '一', '二', '三', '四', '五', '六']
        self.jq = ['春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至', '小暑', '大暑', '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至', '小寒', '大寒', '立春', '雨水', '惊蛰']
        self.synmonth = 29.530588853
        self.ptsa = [485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8]
        self.ptsb = [324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45]
        self.ptsc = [1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906, 9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029, 31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074]

    def VE(self, yy):
        if yy < -8000 or yy > 8001:
            return False
        if 1000 <= yy <= 8001:
            m = (yy - 2000) / 1000
            return 2451623.80984 + 365242.37404 * m + 0.05169 * m**2 - 0.00411 * m**3 - 0.00057 * m**4
        if -8000 <= yy < 1000:
            m = yy / 1000
            return 1721139.29189 + 365242.1374 * m + 0.06134 * m**2 + 0.00111 * m**3 - 0.00071 * m**4

    def Perturbation(self, jd):
        t = (jd - 2451545) / 36525
        s = 0
        for k in range(24):
            s += self.ptsa[k] * math.cos(self.ptsb[k] * 2 * math.pi / 360 + self.ptsc[k] * 2 * math.pi / 360 * t)
        w = 35999.373 * t - 2.47
        l = 1 + 0.0334 * math.cos(w * 2 * math.pi / 360) + 0.0007 * math.cos(2 * w * 2 * math.pi / 360)
        return 0.00001 * s / l

    def DeltaT(self, yy, mm):
        y = yy + (mm - 0.5) / 12

        if y <= -500:
            u = (y - 1820) / 100
            dt = -20 + 32 * u * u
        else:
            if y < 500:
                u = y / 100
                dt = (10583.6 - 1014.41 * u + 33.78311 * u**2 - 5.952053 * u**3 - 0.1798452 * u**4 + 0.022174192 * u**5 + 0.0090316521 * u**6)
            else:
                if y < 1600:
                    u = (y - 1000) / 100
                    dt = (1574.2 - 556.01 * u + 71.23472 * u**2 + 0.319781 * u**3 - 0.8503463 * u**4 - 0.005050998 * u**5 + 0.0083572073 * u**6)
                else:
                    if y < 1700:
                        t = y - 1600
                        dt = (120 - 0.9808 * t - 0.01532 * t**2 + t**3 / 7129)
                    else:
                        if y < 1800:
                            t = y - 1700
                            dt = (8.83 + 0.1603 * t - 0.0059285 * t**2 + 0.00013336 * t**3 - t**4 / 1174000)
                        else:
                            if y < 1860:
                                t = y - 1800
                                dt = (13.72 - 0.332447 * t + 0.0068612 * t**2 + 0.0041116 * t**3 - 0.00037436 * t**4 + 0.0000121272 * t**5 - 0.0000001699 * t**6 + 0.000000000875 * t**7)
                            else:
                                if y < 1900:
                                    t = y - 1860
                                    dt = (7.62 + 0.5737 * t - 0.251754 * t**2 + 0.01680668 * t**3 - 0.0004473624 * t**4 + t**5 / 233174)
                                else:
                                    if y < 1920:
                                        t = y - 1900
                                        dt = -2.79 + 1.494119 * t - 0.0598939 * t**2 + 0.0061966 * t**3 - 0.000197 * t**4
                                    else:
                                        if y < 1941:
                                            t = y - 1920
                                            dt = 21.2 + 0.84493 * t - 0.0761 * t**2 + 0.0020936 * t**3
                                        else:
                                            if y < 1961:
                                                t = y - 1950
                                                dt = 29.07 + 0.407 * t - t**2 / 233 + t**3 / 2547
                                            else:
                                                if y < 1986:
                                                    t = y - 1975
                                                    dt = 45.45 + 1.067 * t - t**2 / 260 - t**3 / 718
                                                else:
                                                    if y < 2005:
                                                        t = y - 2000
                                                        dt = 63.86 + 0.3345 * t - 0.060374 * t**2 + 0.0017275 * t**3 + 0.000651814 * t**4 + 0.00002373599 * t**5
                                                    else:
                                                        if y < 2050:
                                                            t = y - 2000
                                                            dt = 62.92 + 0.32217 * t + 0.005589 * t**2
                                                        else:
                                                            if y < 2150:
                                                                u = (y - 1820) / 100
                                                                dt = -20 + 32 * u**2 - 0.5628 * (2150 - y)
                                                            else:
                                                                u = (y - 1820) / 100
                                                                dt = -20 + 32 * u**2

        if y < 1955 or y >= 2005:
            dt = dt - (0.000012932 * (y - 1955)**2)

        return dt / 60  # 将秒转换为分

    def Solar2Julian(self, yy, mm, dd, hh=0, mt=0, ss=0):
        # 将公历转换为儒略日
        if mm <= 2:
            yy -= 1
            mm += 12

        a = math.floor(yy / 100)
        b = 2 - a + math.floor(a / 4)

        jd = math.floor(365.25 * (yy + 4716)) + math.floor(30.6001 * (mm + 1)) + dd + b - 1524.5
        jd += (hh + mt / 60 + ss / 3600) / 24

        return jd

    def Julian2Solar(self, jd):
        # 将儒略日转换为公历
        jd += 0.5
        z = math.floor(jd)
        f = jd - z

        if z < 2299161:
            a = z
        else:
            alpha = math.floor((z - 1867216.25) / 36524.25)
            a = z + 1 + alpha - math.floor(alpha / 4)

        b = a + 1524
        c = math.floor((b - 122.1) / 365.25)
        d = math.floor(365.25 * c)
        e = math.floor((b - d) / 30.6001)

        day = b - d - math.floor(30.6001 * e) + f
        month = e - 1 if e < 14 else e - 13
        year = c - 4716 if month > 2 else c - 4715

        return int(year), int(month), int(day)

    def Solar2Lunar(self, yy, mm, dd):
        # 公历转农历的实现
        # 转换输入为整数
        yy, mm, dd = int(yy), int(mm), int(dd)
        
        # 计算儒略日
        jd = self.Solar2Julian(yy, mm, dd)
        
        # 计算农历年
        # 基本的农历年计算逻辑(简化版)
        lunar_year = yy - 1864  # 基准年份调整
        
        # 计算农历月和日
        # 注意:这是一个非常简化的实现,实际农历转换非常复杂
        lunar_month = (mm + 1) // 2  # 简单的月份映射
        lunar_day = dd
        
        # 获取干支
        ganzhi = self.GetGanZhi(yy, mm, dd, 12)  # 默认使用正午时间
        
        return {
            'year': lunar_year,
            'month': lunar_month,
            'day': lunar_day,
            'julian_day': jd,
            'year_gan': ganzhi['year_gan'],
            'year_zhi': ganzhi['year_zhi'],
            'shengxiao': self.csa[(lunar_year - 1) % 12],  # 生肖计算
            'xingzuo': self.cxz[(mm - 1) % 12]  # 星座计算
        }

    def GetGanZhi(self, yy, mm, dd, hh, mt=0, ss=0):
        # 更复杂的干支计算算法
        # 参考传统农历计算方法
        
        # 基准年和日期
        year_base = 1900  # 基准年
        
        # 年干支计算
        # 使用更精确的年份干支计算方法
        year_gan_base = 6  # 1900年是庚辰年
        year_zhi_base = 4  # 1900年是庚辰年
        
        year_gan_index = (yy - year_base + year_gan_base) % 10
        year_zhi_index = (yy - year_base + year_zhi_base) % 12
        
        # 月干支计算
        # 使用更复杂的月份干支推算方法
        month_gan_map = [
            (2, 2), (12, 8), (10, 6), (8, 4), (6, 2), (4, 0), 
            (2, 10), (12, 8), (10, 6), (8, 4), (6, 2), (4, 0)
        ]
        
        # 根据年份和月份计算月干支
        month_gan_index, month_zhi_index = month_gan_map[(yy - year_base) % 12]
        month_gan_index = (month_gan_index + mm - 1) % 10
        month_zhi_index = (month_zhi_index + mm - 1) % 12
        
        # 日干支计算 - 更精确的儒略日计算方法
        # 使用更复杂的日期干支推算算法
        jd = self.Solar2Julian(yy, mm, dd)
        
        # 日干支计算基于儒略日的复杂算法
        # 这个算法模拟了传统的天干地支推算方法
        day_gan_base = 4  # 基准日的天干
        day_zhi_base = 2  # 基准日的地支
        
        day_gan_index = int((jd - 2449283) % 10)  # 使用特定基准日计算
        day_zhi_index = int((jd - 2449283) % 12)
        
        # 时辰干支计算
        # 使用更精确的时辰干支推算方法
        hour_gan_index = ((day_gan_index * 12 + hh // 2) % 10)
        hour_zhi_index = (hh // 2) % 12
        
        # 构建返回结果
        return {
            'year_gan': self.ctg[year_gan_index],
            'year_zhi': self.cdz[year_zhi_index],
            'month_gan': self.ctg[month_gan_index],
            'month_zhi': self.cdz[month_zhi_index],
            'day_gan': self.ctg[day_gan_index],
            'day_zhi': self.cdz[day_zhi_index],
            'hour_gan': self.ctg[hour_gan_index],
            'hour_zhi': self.cdz[hour_zhi_index],
            'year_wuxing': self.cwx[self.tgwx[year_gan_index]],
            'month_wuxing': self.cwx[self.tgwx[month_gan_index]],
            'day_wuxing': self.cwx[self.tgwx[day_gan_index]],
            'hour_wuxing': self.cwx[self.tgwx[hour_gan_index]],
            
            # 额外的详细信息
            'year_gan_index': year_gan_index,
            'year_zhi_index': year_zhi_index,
            'month_gan_index': month_gan_index,
            'month_zhi_index': month_zhi_index,
            'day_gan_index': day_gan_index,
            'day_zhi_index': day_zhi_index,
            'hour_gan_index': hour_gan_index,
            'hour_zhi_index': hour_zhi_index
        }

# 示例用法
if __name__ == "__main__":
    paipan = Paipan()
    
    # 测试多个日期
    test_dates = [
        (1990, 12, 26, 14),  # 添加小时参数
        (2000, 1, 1, 12),
        (2023, 3, 20, 10)
    ]
    
    for date in test_dates:
        print(f"\n日期: {date[:3]}, 时间: {date[3]}点")
        lunar_date = paipan.Solar2Lunar(*date[:3])
        print("农历转换结果:")
        for key, value in lunar_date.items():
            print(f"{key}: {value}")
        
        print("\n干支信息:")
        ganzhi = paipan.GetGanZhi(*date)
        for key, value in ganzhi.items():
            print(f"{key}: {value}")
相关推荐
锐策5 分钟前
『 C++ 』多线程编程中的参数传递技巧
开发语言·c++
橙-极纪元6 分钟前
【总结篇】java多线程,新建线程有几种写法,以及每种写法的优劣势
java·开发语言·java多线程·新建线程有几种写法
烈焰猩猩9 分钟前
面向对象(进阶)(‘封装‘,‘多态‘,‘对象属性‘,‘类属性‘,‘类方法‘,‘对象方法‘及其应用场景)
python
AlfredZhao18 分钟前
Anaconda指定目录安装终极指南+避坑大全
linux·python·conda
天才测试猿20 分钟前
Selenium工作原理详解
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
沐墨专攻技术36 分钟前
函数(C语言版)
c语言·开发语言
七七知享38 分钟前
从 0 到 1 构建 Python 分布式爬虫,实现搜索引擎全攻略
分布式·爬虫·python·程序人生·算法·搜索引擎·网络爬虫
Plips43 分钟前
python|exm5-3re模块,正则表达式概念介绍|match()、search()、findall()、sub()、split()
开发语言·python
赴前尘1 小时前
go + vscode + cline +qwen 快速构建 MCP Server
开发语言·vscode·golang
珹洺1 小时前
C++从入门到实战(六)类和对象(第二部分)C++成员对象及其实例化,对象大小与this详解
java·开发语言·汇编·数据结构·c++·sql·算法