Created
May 2, 2025 16:13
-
-
Save Zayrick/f11937ec34c1099e95519bc6c9f3bd4b to your computer and use it in GitHub Desktop.
基于天文算法的中国传统干支历法(八字)精确计算工具,支持自定义观测地点。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
import ephem | |
from datetime import datetime, timedelta, UTC | |
from typing import Union, Tuple | |
from functools import lru_cache | |
class ChineseCalendar: | |
"""中国传统干支历法计算工具""" | |
# 天干和地支表 | |
GAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"] | |
ZHI = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"] | |
def __init__(self, latitude: Union[str, float] = 34.545811, longitude: Union[str, float] = 108.925825): | |
""" | |
初始化干支历法计算工具 | |
参数: | |
latitude: 观测者纬度,默认为西安纬度 | |
longitude: 观测者经度,默认为西安经度 | |
""" | |
self.latitude = latitude | |
self.longitude = longitude | |
@staticmethod | |
@lru_cache(maxsize=1) | |
def get_timezone_offset() -> float: | |
"""获取当前环境的时区偏移值,舍入到最接近的0.5小时""" | |
try: | |
# 获取当前本地时间和UTC时间 | |
current_time = datetime.now().replace(tzinfo=None) | |
utc_time = datetime.now(UTC).replace(tzinfo=None) | |
# 计算时区差值 | |
hours_diff = (current_time - utc_time).total_seconds() / 3600.0 | |
# 舍入到最接近的0.5小时 | |
if hours_diff >= 0: | |
return math.floor(hours_diff / 0.5 + 0.5) * 0.5 | |
else: | |
return math.ceil(hours_diff / 0.5 - 0.5) * 0.5 | |
except Exception as e: | |
print(f"时区计算错误: {e}") | |
return 8.0 # 默认东八区 | |
def create_observer(self) -> ephem.Observer: | |
"""创建并配置观测者对象""" | |
observer = ephem.Observer() | |
observer.lat = ephem.degrees(str(self.latitude)) | |
observer.lon = ephem.degrees(str(self.longitude)) | |
return observer | |
def get_true_solar_time(self, local_time: datetime) -> datetime: | |
""" | |
计算真太阳时 | |
参数: | |
local_time: 本地时间 | |
返回: | |
datetime: 调整后的真太阳时 | |
""" | |
try: | |
# 获取时区偏移 | |
tz_offset = self.get_timezone_offset() | |
# 创建观测者对象 | |
observer = self.create_observer() | |
# 将本地时间转换为UTC时间 | |
utc_time = local_time - timedelta(hours=tz_offset) | |
observer.date = ephem.Date(utc_time) | |
# 计算太阳位置和日午时间 | |
sun = ephem.Sun(observer) | |
next_noon = observer.next_transit(sun).datetime() + timedelta(hours=tz_offset) | |
# 计算钟表正午时间 | |
clock_noon = next_noon.replace(hour=12, minute=0, second=0, microsecond=0) | |
# 计算时间差值 | |
time_diff = (clock_noon - next_noon).total_seconds() / 3600.0 | |
# 调整本地时间获得真太阳时 | |
return local_time + timedelta(hours=time_diff) | |
except Exception as e: | |
print(f"真太阳时计算错误: {e}") | |
return local_time | |
def get_solar_longitude(self, local_time: datetime) -> float: | |
"""计算太阳视黄经(度)""" | |
try: | |
# 创建观测者对象 | |
observer = self.create_observer() | |
# 计算时区偏移 | |
tz_offset = self.get_timezone_offset() | |
# 将本地时间转换为UTC时间 | |
utc_time = local_time - timedelta(hours=tz_offset) | |
observer.date = ephem.Date(utc_time) | |
# 计算太阳位置 | |
sun = ephem.Sun(observer) | |
# 获取太阳的视黄经 | |
equ = ephem.Equatorial(sun.ra, sun.dec, epoch=observer.date) | |
ecl = ephem.Ecliptic(equ) | |
return (float(ecl.lon) * 180.0 / ephem.pi) % 360.0 | |
except Exception as e: | |
print(f"太阳视黄经计算错误: {e}") | |
return 0.0 | |
def get_year_gan_zhi(self, local_time: datetime) -> str: | |
"""计算年柱""" | |
try: | |
# 2000年立春时间 | |
start_date = datetime(2000, 2, 4, 20, 40, 24) | |
# 计算真太阳时 | |
true_solar_time = self.get_true_solar_time(local_time) | |
# 计算与2000年立春的天数差 | |
diff_days = (true_solar_time - start_date).days | |
# 回归年长度计算 | |
day_per_year = 365.24218968 - 0.0000000616 * (diff_days / 365.24218968 / 2) | |
years = math.floor(diff_days / day_per_year) | |
# 2000年是庚辰年 | |
gan_index = (years + 6) % 10 # 加6位 | |
zhi_index = (years + 4) % 12 # 加4位 | |
return self.GAN[gan_index] + self.ZHI[zhi_index] | |
except Exception as e: | |
print(f"年干支计算错误: {e}") | |
return "未知" | |
def get_month_gan_zhi(self, local_time: datetime) -> str: | |
"""计算月柱""" | |
try: | |
# 获取太阳视黄经 | |
solar_longitude = self.get_solar_longitude(local_time) | |
# 己卯月始于2000年3月5日14:42:40(惊蛰) | |
start_date = datetime(2000, 3, 5, 14, 42, 40) | |
diff_days = (local_time - start_date).days | |
# 计算月的平均长度 | |
day_per_month = (365.24218968 - 0.0000000616 * (diff_days / 365.24218968 / 2)) / 12 | |
# 从己卯月开始的第几个月 | |
moon_num = int(diff_days / day_per_month) | |
# 根据太阳黄经计算月支 | |
solar_month = (int(((solar_longitude - 15) % 360) / 30.0) + 4) % 12 | |
# 近似月支 | |
approx_month = (moon_num + 3) % 12 | |
# 修正值 | |
correction = (solar_month - approx_month + 12) % 12 | |
if correction >= 6: | |
correction -= 12 | |
moon_num += correction | |
# 计算干支索引 | |
gan_index = (moon_num + 5) % 10 # 加5位 | |
zhi_index = (moon_num + 3) % 12 # 加3位 | |
return self.GAN[gan_index] + self.ZHI[zhi_index] | |
except Exception as e: | |
print(f"月干支计算错误: {e}") | |
return "未知" | |
def get_day_gan_zhi(self, local_time: datetime) -> str: | |
"""计算日柱""" | |
try: | |
# 1900年1月1日是甲戌日 | |
start_date = datetime(1900, 1, 1, 0, 0, 0) | |
# 计算真太阳时 | |
true_solar_time = self.get_true_solar_time(local_time) | |
# 计算天数差 | |
diff_days = (true_solar_time - start_date).days | |
# 计算干支索引 | |
gan_index = diff_days % 10 | |
zhi_index = (diff_days + 10) % 12 # 加10位 | |
return self.GAN[gan_index] + self.ZHI[zhi_index] | |
except Exception as e: | |
print(f"日干支计算错误: {e}") | |
return "未知" | |
def get_hour_gan_zhi(self, local_time: datetime) -> str: | |
"""计算时柱""" | |
try: | |
# 计算真太阳时 | |
true_solar_time = self.get_true_solar_time(local_time) | |
# 甲子月甲申日甲子时真太阳时起始点 | |
start_time = datetime(1983, 12, 21, 23, 42, 15) | |
# 计算已经过去的时辰数 | |
hours_diff = (true_solar_time - start_time).total_seconds() / 3600.0 | |
time_periods = round(hours_diff / 2.0) | |
# 子时的起点是真太阳时23:00:00,进行修正 | |
hour_correction = ((((true_solar_time.hour + 1) // 2 - time_periods) % 12) + 12) % 12 | |
if hour_correction >= 6: | |
hour_correction -= 12 | |
time_periods += hour_correction | |
# 计算干支索引 | |
gan_index = time_periods % 10 | |
zhi_index = time_periods % 12 | |
return self.GAN[gan_index] + self.ZHI[zhi_index] | |
except Exception as e: | |
print(f"时干支计算错误: {e}") | |
return "未知" | |
def get_bazi(self, local_time: datetime) -> Tuple[str, str, str, str]: | |
""" | |
计算八字(四柱干支) | |
参数: | |
local_time: 本地时间 | |
返回: | |
Tuple[str, str, str, str]: (年柱, 月柱, 日柱, 时柱) | |
""" | |
year_gz = self.get_year_gan_zhi(local_time) | |
month_gz = self.get_month_gan_zhi(local_time) | |
day_gz = self.get_day_gan_zhi(local_time) | |
hour_gz = self.get_hour_gan_zhi(local_time) | |
return (year_gz, month_gz, day_gz, hour_gz) | |
def parse_datetime(date_str: str) -> datetime: | |
"""解析日期时间字符串""" | |
try: | |
return datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') | |
except ValueError as e: | |
print(f"日期解析错误: {e}") | |
print("使用当前时间代替") | |
return datetime.now() | |
# 使用示例 | |
if __name__ == "__main__": | |
# 解析日期字符串 | |
date_str = '2025-5-2 23:48:30' | |
date = parse_datetime(date_str) | |
# 创建中国历法计算对象(指定观测位置) | |
calendar = ChineseCalendar(latitude='39.9', longitude='110.8333') | |
# 计算八字 | |
year_gz, month_gz, day_gz, hour_gz = calendar.get_bazi(date) | |
# 打印结果 | |
print(f"{date_str} 的八字:") | |
print(f"年柱:{year_gz}") | |
print(f"月柱:{month_gz}") | |
print(f"日柱:{day_gz}") | |
print(f"时柱:{hour_gz}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment