Skip to content

Instantly share code, notes, and snippets.

@Zayrick
Created May 2, 2025 16:13
Show Gist options
  • Save Zayrick/f11937ec34c1099e95519bc6c9f3bd4b to your computer and use it in GitHub Desktop.
Save Zayrick/f11937ec34c1099e95519bc6c9f3bd4b to your computer and use it in GitHub Desktop.
基于天文算法的中国传统干支历法(八字)精确计算工具,支持自定义观测地点。
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