CelestialCalendar Documentation
On this page

CelestialCalendar Python 包

celestial-calendar 提供 CelestialCalendar 的 Python 绑定,用于天文计算、公历/阴历转换和节气时刻查询。 wheel 内含目标平台的原生库,运行时不需要编译器,也不必另行安装 CelestialCalendar。支持 Python 3.11 或更新版本。

English

本文描述当前源码,含尚未发布的改动。安装命令取得最新已发布版本,请以所装版本的文档为准。 运行当前源码示例,可先构建本地 wheel。从 0.x 升级见迁移指南(英文)。

安装

在应用的 Python 环境中,从 PyPI 安装适合当前平台的 wheel:

python -m pip install celestial-calendar

以下每个 Python 代码块均可在该环境中独立作为 example.py,用 python example.py 运行; 除本包外,只用到 Python 标准库。

示例

今天的阴历日期

先取当前时刻在固定 UTC+8 下的公历日期,再用 ALGO3 把这个日期标签转换为阴历;这里的“今天”不取决于主机时区。 这一步不涉及 UT1,也没有将时钟 UTC 当作 UT1。

from datetime import datetime, timedelta, timezone

import celestial_calendar as celestial

east_eight = timezone(timedelta(hours=8))
today = datetime.now(east_eight).date()
gregorian = celestial.GregorianDate.from_date(today)
lunar = celestial.gregorian_to_lunar(celestial.LunarAlgorithm.ALGO3, gregorian)
print(today, lunar)

GregorianDate.from_date() 只接受日期,不接受 datetime;示例先选固定偏移,再显式调用 .date(), 库不会替调用者丢弃时间或偏移。各阴历算法的日期基准与年域不同,见阴历算法。

UTC 与 JDE(TT) 转换

1.1.0 起提供 utc_to_jde() 和 jde_to_utc()。1972 年前按 UT1 处理 UTC 字段,使用固定的 ΔT 模型 Algo5; 星盘查询仍拒绝 1972 年前的 UTC。闰秒边界不能保证可逆;超过闰秒表最后一项的日期保留该项的 TT−UTC 偏移。

import celestial_calendar as celestial

utc = celestial.CivilDateTime(2026, 10, 28, (19 * 3600 + 35 * 60 + 37) / 86400)
jde_tt = celestial.utc_to_jde(utc)
print(celestial.jde_to_utc(jde_tt))

fraction 是日小数(0.5 表示正午)。JDE 用浮点数计日,转换回来的民用时刻可能与输入有微小差异。

从 UTC 输入查询下一个节气、再显示为东八区时间,见 附 Python 和 JavaScript 可运行程序的指南。 该指南也列出了正反向各自可接受的输入范围与不可逆边界。

下一个节气,还剩多少天

以明确给定的 2026-12-31 00:00 UT1 为起点,不读取当前时钟。查询 2026 和 2027 的全部节气, 按实际 UT1 时刻排序,取严格晚于起点的第一个。枚举从立春开始,而不是从公历年内的第一个节气开始。

import celestial_calendar as celestial

cutoff_ut1 = celestial.CivilDateTime(2026, 12, 31, 0)
cutoff_jd = celestial.ut1_to_jd(cutoff_ut1)
events = [celestial.jieqi_moment(year, jieqi) for year in (2026, 2027) for jieqi in celestial.Jieqi]
ordered = sorted(events, key=lambda event: celestial.ut1_to_jd(event.moment_ut1))
upcoming = next(event for event in ordered if celestial.ut1_to_jd(event.moment_ut1) > cutoff_jd)
remaining_days = celestial.ut1_to_jd(upcoming.moment_ut1) - cutoff_jd
print(celestial.jieqi_name(upcoming.jieqi))
print("UT1:", upcoming.moment_ut1)
print("Remaining UT1 days:", remaining_days)

剩余天数保留小数,是两个 UT1 儒略日之差,不是公历日期之差。此例只演示这次跨年查询, 不是覆盖整个 API 年域的通用搜索。节气查询的年域与失败边界见 API 契约。

CivilDateTime 在上面的 UT1 示例中明确表示 UT1,最后一个字段是日小数,0.5 表示半日; ut1_to_jde() 把同一 UT1 输入转换为 TT 下的 JDE。moment_ut1 不是 UTC 或东八区时间, 显示为这两种时间前须先确定时标转换。

同一瞬间的完整星盘快照

这里明确输入 2026-01-01 12:00 UTC,不读取主机时区,也不把 UTC 当作 UT1。

import celestial_calendar as celestial

snapshot = celestial.chart_snapshot(
  celestial.CivilDateTime(2026, 1, 1, 0.5),
  celestial.CivilScale.UTC,
  celestial.GeoLocation(51.5, 0.0),
  celestial.HouseSystem.PLACIDUS,
)
print(snapshot.times.jd_ut1, snapshot.times.jde_tt)
for body in snapshot.bodies:
  print(body.target.name, body.longitude_deg, body.longitude_rate_deg_per_tt_day, body.distance_au)
print(snapshot.houses.cusps_deg)

结果是持有自身数据的冻结 dataclass,天体和宫头集合为元组。十四项顺序为日、月、水星至冥王星、 平均升/降交点、真升/降交点;每项有视地心黄经、纬度和带符号黄经速率。 黄经使用真黄道与日期分点,角度单位为度。所有天体的距离都用 AU,月球也一样; 独立的 moon_apparent_geocentric_coordinate() 则以千米给出月地距离,即 MoonCoordinate.distance_km。 黄经速率是瞬时值,单位为度/TT 日;负值表示该瞬间黄经在减小(逆行)。节点纬度是已知的零,距离为 None。 四轴与十二宫头均为 [0, 360) 度,宫头元组从第一宫开始。经度东正西负,纬度北正南负;地点只影响宫位。完整数值见共享重放示例。

能力与模型身份

capabilities() 查询 POSITION、LONGITUDE_RATE、EVENTS 和 EQUATORIAL 四类 TT 目标定义域; HOUSES、CHART 和 DELTA_T 会抛出 ValueError。model_identities() 与 capability_text() 提供模型与操作说明。 每个定义域边界都由该操作自身的定义常量算出,所以能力记录接受与拒绝的输入和计算本身完全一致; NaN 边界意为「无显式界」;定义域是接受边界,不是精度承诺。

import celestial_calendar as celestial

records = celestial.capabilities(celestial.CapabilityOperation.EVENTS)
pluto = next(r for r in records if r.subject is celestial.Target.PLUTO)
print(pluto.domain_start_jde_tt, pluto.domain_end_jde_tt, pluto.maximum_span_tt_days)
print(celestial.capability_text(celestial.CapabilityOperation.EVENTS, celestial.Target.PLUTO))
identities = celestial.model_identities()
algo5 = next(i for i in identities if i.key == celestial.ModelKey(celestial.ModelKind.DELTA_T, 5))
print(celestial.model_identity_text(algo5.key))

独立位置与宫位查询

positions() 只计算你选择的目标在单一 JDE(TT) 的视地心位置——不评估速率、不套快照联合窗口——所以 目标子集能在完整快照拒绝被排除目标窗口的地方成功。house_query() 走快照的民用换算但不受联合窗口 限制,并记录它实际解析出的模型身份。两者任一错误即整体失败,不存在半份结果。

import celestial_calendar as celestial

selected = celestial.positions(
  [celestial.Target.PLUTO, celestial.Target.SUN],
  2409543.5,  # Pluto's window start
)
for position in selected:
  print(position.target, position.longitude_deg, position.latitude_deg, position.distance_au, position.source)

location = celestial.GeoLocation(latitude_deg=51.5, longitude_deg=0.0)
civil = celestial.CivilDateTime(year=1885, month=1, day=1, fraction=0.0)  # before the snapshot window
houses = celestial.house_query(civil, celestial.CivilScale.UT1, location, celestial.HouseSystem.PLACIDUS)
print(houses.times, houses.houses.ascendant_deg, houses.delta_t_model, houses.nutation_model)
again = celestial.house_query(
  civil,
  celestial.CivilScale.UT1,
  location,
  celestial.HouseSystem.PLACIDUS,
  delta_t_model=houses.delta_t_model,
  nutation_model=houses.nutation_model,
)
equatorial = celestial.equatorial_positions([celestial.Target.SUN, celestial.Target.TRUE_ASCENDING], 2451545.0)
for position in equatorial:
  print(position.right_ascension_deg, position.declination_deg, position.distance_au, position.source)

位置、事件、能力查询和快照统一使用 Target。 equatorial_positions(targets, jde_tt) 单独返回不可变的赤道坐标:赤经 [0, 360) 度、赤纬 [-90, 90] 度, 地心、当日坐标系,使用固定 IAU1980 黄经章动与真黄赤交角;物理距离为 AU,数学交点无距离。 它保持选择顺序,拒绝空选择和重复目标,不计算速率、民用时刻或宫位。

不可变 ModelKey(kind, identity) 标明模型种类和非零身份码。注册记录用 .key,位置和快照天体用 .source, 宫位与快照用 .delta_t_model、.nutation_model。模型键可传回相应种类的选择参数;DEFAULT 只用于输入, 由原生侧解析。model_identity_text(key) 按模型键查询文字,不按注册表位置查询。delta_t() 仍返回秒数标量, 但会校验原生结果的模型键。太阳距离 distance_au 的单位是 AU,独立月球坐标的 distance_km 单位是千米。

house_capability(delta_t_model=...) 返回独立的 CivilHouseCapability,包含民用年域、允许时标、UTC 起始日期、 已解析的 Delta T 键和可选的 TT 上界,不伪造 TT 区间,也不保证几何求解成功。民用宫位政策只通过 house_capability() 查询。 capability_text() 的目标操作必须提供具体 Target;HOUSES、CHART 和 DELTA_T 必须使用 subject=None, 不能传入一个会被忽略的目标。 FINITE_INPUT_EVALUATION 表示没有声明硬输入区间;有限时刻仍可能计算失败,坐标有效性不证明远年代精度或物理合理性。

区间黄经事件

longitude_events()、longitude_stations() 与 longitude_difference_events() 提供 C++ 区间搜索(黄经事件指南):供定黄经的过宫与精确切触、留站、 以及带方向的两体黄经差。角度不带任何名称与解释;把精确的目标角度映射到你自己的术语即可。

import celestial_calendar as celestial

crossings = celestial.longitude_events(celestial.Target.MERCURY, 0.0, 2460735.5, 2460785.5)
for event in crossings:
  print(event.jde_tt, event.longitude_deg, event.direction)

stations = celestial.longitude_stations(celestial.Target.MERCURY, 2460735.5, 2460785.5)
for station in stations:
  print(station.jde_tt, station.motion_before.value, "->", station.motion_after.value)

differences = celestial.longitude_difference_events(
  celestial.Target.MOON, celestial.Target.SUN, -60.0, 2460676.5, 2460707.5
)

时间为 JDE(TT),落在公开的 2^-30 日报告格点上;速率为公开七点算子的度/TT 日。 区间 [start, end) 必须非空、不超过 3660 TT 日,且落在目标黄经速率域内;空元组是合法的 「无事件」结果,数值歧义或工作量超限则抛 CelestialError,绝不返回部分列表。

平台与本地构建

正式发布使用下列四种平台的 wheel;PyPI 与对应 GitHub Release 使用逐字节相同的四个 wheel:

系统 架构 wheel 文件名中必需的标签
Linux (manylinux 2.28) x86_64 manylinux_2_28_x86_64
Linux (manylinux 2.28) aarch64 manylinux_2_28_aarch64
macOS 15 或更新版本 arm64 macosx_15_0_arm64
Windows AMD64 win_amd64

Linux wheel 文件名可能包含其他兼容的 manylinux 标签。每个都是面向相应平台上 Python 3.11 或更新版本的 py3 wheel。 不发布其他平台的 wheel,例如 Intel macOS、Windows on ARM 或基于 musl 的 Linux。

构建本地 wheel

使用完整源码 checkout,不能只复制 bindings/python。下面的 Linux shell 示例需要 Python 3.11 或更新版本、 系统 CMake 3.22 或更新版本,以及已安装的 C/C++23 工具链。示例选用 clang-22 和 clang++-22; 若本机编译器名称不同,请相应设置 CC 和 CXX。现有的哈希锁定依赖提供构建前端、后端和 Ninja。 从仓库根目录运行,使用尚未占用的构建、虚拟环境和输出目录:

python3 -m venv build/local-wheel-env
. build/local-wheel-env/bin/activate
python -m pip install --require-hashes -r bindings/python/requirements-build.txt
CC=clang-22 CXX=clang++-22 python -m build \
  --wheel --no-isolation \
  --outdir build/local-wheelhouse \
  --config-setting=build-dir="$PWD/build/local-wheel-build" \
  bindings/python
deactivate

将 build/local-wheelhouse 中生成的 .whl 安装到应用环境。这只构建 wheel,不构建 sdist。 该 wheel 使用本机原生库,不是经过修复的官方 wheel,也不是可移植的发行 wheel;它不扩展上表的支持平台。

API 契约

扁平的公开 API 使用不可变 dataclass、枚举、普通标量与元组,不暴露 ctypes 协议。CivilDateTime 有四个字段: year、month、day,以及 [0, 1) 内的有限日小数 fraction,没有单独的时、分、秒字段。 时间尺度由函数名、字段名或 CivilScale 标明;民用时刻不会隐式转换成 Python 的 datetime 类型。 各函数的契约见 __init__.py 中的英文 docstring。

错误输入类型(包括错误枚举的成员)抛出 TypeError。未通过有限性、范围或定义域校验的值抛出 ValueError; 属于对应枚举类、却不是其已声明成员的对象同样抛出 ValueError。 原生无效输入同样抛出 ValueError;计算、资源或内部失败以及损坏的成功载荷抛出 CelestialError。 其 operation 属性为公开函数名;recorded 表示原生侧报告了已知的非零错误种类,即使消息分配失败而为空也为真。 合法的“无结果”仍以 None 或 () 表示。错误不会自动打印。

local_apparent_sidereal_time(jd_ut1, longitude_deg) 接受有限的 UT1 下 JD,其公历年份须在 [401, 32766] 内; 地理经度须有限、东正西负,范围为 [-180, 180] 度。结果为 [0, 360) 度。 年份范围由原生边界校验,超出年域的 JD 抛出 ValueError。 apparent_solar_time() 同样接受 [-180, 180] 度范围内、东正西负的地理经度,但其民用时刻输入是 UTC。

jieqi_moment(year, jieqi) 返回 JieqiMoment(jieqi, moment_ut1)。其中的 CivilDateTime 是 UT1, 不是 UTC 或东八区钟表时间;同一瞬间显示为 UTC+8 时,日期可能改变。显示为 UTC 或固定偏移时间前,须先确定时标转换。 返回的 UT1 年份可能与请求年份不同。输入年域为 [401, 32766],但即使输入在范围内, 原生计算无法得到唯一时刻时仍会抛出 CelestialError。

moon_phase_moments(year, phase)、sun_longitude_crossings(year, longitude_deg) 和 new_moons_in_year(year) 接受 [1, 32766] 内的公历年份。

sun_longitude_crossings() 返回太阳到达指定视地心黄经时的 TT 下 JDE 元组。 longitude_deg 须有限,且在 [0, 360) 度内。原生失败使用 CelestialError.operation == "sun_longitude_crossings"。

new_moons_after(jde, count) 接受 [0, 4096] 内的 count,零返回 ()。 上限使单个原生输出缓冲区不超过 32 KiB。 变长结果与文本缓冲区另有单缓冲区 64 MiB 的绑定层资源上限,超限会在分配前抛出 CelestialError。 原生返回结果不一致时也抛出 CelestialError。

星盘输入与失败边界

chart_snapshot(civil_dt, scale, location, system, model=DeltaTModel.DEFAULT) 接受 CivilDateTime、 CivilScale.UTC / UT1、GeoLocation,以及 HouseSystem.EQUAL / WHOLE_SIGN / PLACIDUS。 DEFAULT 由原生侧解析,目前为 ALGO5;其他模型用 DeltaTModel 或相应种类的 ModelKey 显式选择。它不接受标准库 datetime, 本地钟表时间须由调用者先转换成声明的输入时标,不能丢弃时区后直接传入。

Python 在原生计算前检查类型、有限性和 ABI 整数宽度,日期、年份和地理定义域由原生侧检查。类型错误抛 TypeError,包括给 scale、system 或 model 传普通字符串或其他枚举的成员,以及在数值字段里放枚举成员。属于对应枚举类、却不是其已声明成员的对象, 与无效公历日期、非有限数和原始值越界一样抛 ValueError。原始值的范围是:公历年 [1885, 2100), 日小数 [0, 1),UTC 从 1972-01-01 开始(更早的输入请选 CivilScale.UT1),纬度 (-90, 90), 经度 [-180, 180]。原生核心负责转换后 TT 的共同域 [2409543.875, 2488069.125) JDE、 Algo4 的额外限制 JDE < 2464328.5、所有宫制在浮点舍入精度下无法确定的上升点方向, 以及 Placidus 随日期变化的极区限制。原生定义域和几何拒绝抛 ValueError;计算失败抛 CelestialError。 不返回部分结果,也不替换宫制。民用宫位查询共用转换后 TT 的 Algo4 严格上界,2034 年末部分 UT1 输入因此被拒绝。 原始民用年份合法不保证转换后的完整快照可计算。

输入为 UTC 时,结果里的 UT1 是模型值,不是观测 DUT1;UTC 如何换算到 TT 与 UT1,见 Time scales。 插入的闰秒不在民用日小数输入内。Python 结果复制自 C 协议,不保留原生缓冲区; 不兼容的字段或天体集合扩展需要显式新形状或新主版本。

纯日期桥接与阴历往返

GregorianDate(year, month, day) 是前推公历日期,与 CivilDateTime、LunarDate 分开。 计算调用会校验公历日期和民用时刻输入的年份是否在 [1, 32767] 内,以及月、日是否合法。 本项目日期类型的范围仍宽于标准库日期类型,构造函数不以 9999 年为上限。

from datetime import date

import celestial_calendar as celestial

day = date(2026, 8, 15)
gregorian = celestial.GregorianDate.from_date(day)
print(gregorian.to_date())

algorithm = celestial.LunarAlgorithm.ALGO3
lunar = celestial.gregorian_to_lunar(algorithm, day)
print(celestial.lunar_to_gregorian(algorithm, lunar).to_date())

GregorianDate.from_date(value) 是静态工厂:即使通过 GregorianDate 子类调用,也总是返回确切的 GregorianDate 类型。它接受标准库年域 [1, 9999] 内的 datetime.date 实例及其子类,但拒绝所有 datetime.datetime 实例及其子类, 无论是否带时区,均抛出 TypeError。 gregorian_to_lunar(algorithm, date) 接受 GregorianDate,也接受上述标准库纯日期输入; 不接受 datetime,也不会丢弃其时间或偏移。

GregorianDate.to_date() 校验字段后返回标准库 datetime.date。字段类型错误抛出 TypeError; 年份超出 [1, 9999] 或公历月/日无效时抛出 ValueError。 两个桥接方法都保留年/月/日标签。用于阴历转换时,这个标签属于下文所选算法的日期基准; 两者都不进行时区或 UT1/UTC/TT 转换。

阴历算法

用 LunarAlgorithm.ALGO1、ALGO2 或 ALGO3 选择算法。公历输入与输出是日期标签,不是瞬间。 各算法的来源、阴历年域、选用场景与日期基准见阴历算法(英文)。

supported_lunar_year_range(algorithm) 查询原生算法包含两端的阴历年范围,返回 LunarYearRange(start, end)。 公历日期覆盖范围从阴历 start 年首日到阴历 end 年末日,而非这些公历年份的一月至十二月。 lunar_year_info(algorithm, year) 返回公历 first_day、传统 leap_month 月号或 None, 以及按历法顺序排列的 month_lengths,闰月紧随同名普通月份。 lunar_to_gregorian() 接受 LunarDate,使用 [1, 12] 内的传统月号和显式 is_leap 布尔值, 而非月份的位置索引。

lunar_year_info() 和 lunar_to_gregorian() 直接由原生计算检查日期与阴历年范围。 Python 包不另存范围表或缓存,不在导入或计算前查询范围。

有效公历标签超出所选算法覆盖范围、阴历年越界,或阴历闰月/日组合不存在,均抛出 ValueError。

Delta T 模型

delta_t(year, model=DeltaTModel.DEFAULT) 对有限的十进制公历年返回 TT − UT1 的秒数。 可选 DeltaTModel.ALGO1 至 ALGO5 或对应的 ModelKey;默认解析为 Algo5。 各模型的来源、维护状态、硬年域与选用场景见 Delta T 模型(英文)。

许可证

许可证范围与第三方例外以英文版的 License 节为准。