Celestial Calendar
Astronomical calculations and Gregorian / Chinese Lunar date conversion, with Jieqi (节气, the 24 solar terms), Sun/Moon positions, and sunrise/sunset. The core is a C++23 header-only library, also available through Python, JavaScript / TypeScript, and a C ABI.
中文 · Python · JavaScript / TypeScript · C++ · C / FFI · Features and algorithms · Building · Validation · Design
This guide describes the current source, including unreleased changes. See the migration guide when moving from 0.x. The install commands below get the latest published version on PyPI or npm; use its matching documentation. To build the current source yourself, use the local Python wheel recipe or the WASM/npm recipe.
For Ganzhi (干支) calculations, use bazi; this library has no Ganzhi API.
Install and examples
Pick a language. Each section runs one standalone query for the 2026 winter solstice. The package guides carry more examples and the full contracts.
Python
Python 3.11 or newer is supported. Install in your application's virtual environment:
python -m pip install celestial-calendar
Each wheel contains its platform's native library, so using it requires no compiler or separate CelestialCalendar installation. Importing neither searches system libraries nor downloads a fallback. See the Python package guide for available wheel targets and the full contract.
Each Python block is a standalone program for example.py in your application directory. Run it with
python example.py in the environment where the package is installed.
Query winter solstice, Dongzhi (冬至), and print its UT1 calendar date:
import celestial_calendar as celestial
event = celestial.jieqi_moment(2026, celestial.Jieqi.DONGZHI)
moment = event.moment_ut1
print(f"{celestial.jieqi_name(event.jieqi)} (UT1): {moment.year:04d}-{moment.month:02d}-{moment.day:02d}")
Expected output:
冬至 (UT1): 2026-12-21
moment_ut1 is UT1, not UTC or a UTC+8 wall clock. For local-time display, follow the
UTC input → Jieqi → UTC+8 guide.
Jieqi queries accept years in [401, 32766], but an in-range calculation can still fail if it cannot produce
a unique moment. A returned UT1 year can differ from the query year.
Today's lunar date, Gregorian / lunar round trips and the complete chart snapshot are in the Python package guide.
JavaScript / TypeScript
Node 22 or newer is supported. Browser consumers are tested on Chrome and Firefox. Install in your application directory:
npm install @0xf3cd/celestial
Each JavaScript block is a standalone ES module for example.mjs in that directory; run it with
node example.mjs. TypeScript declarations ship with the package. For browser deployment, keep the emitted .wasm
URL available and serve the asset with Content-Type: application/wasm.
Call and await init() before using calculation methods, which then run synchronously. Concurrent and repeated
calls share one initialization promise, resolving to undefined. A failed load preserves the loader's original
rejection, and a later explicit call may retry.
The same winter-solstice query produces the expected output shown above:
import * as celestial from "@0xf3cd/celestial";
await celestial.init();
const event = celestial.jieqi.moment(2026, celestial.Jieqi.DONGZHI);
const { year, month, day } = event.momentUt1;
const date = [year, month, day].map((value, index) => String(value).padStart(index === 0 ? 4 : 2, "0")).join("-");
console.log(`${celestial.jieqi.name(event.jieqi)} (UT1): ${date}`);
momentUt1 is UT1, not UTC; the query-year domain and failure conditions are the same as in Python.
Use the UTC/Jieqi guide for UTC+8 display.
Today's lunar date, the fixed-offset date round trip and the chart snapshot are in the JavaScript package guide.
C++
Use a source checkout or source archive. The library is header-only: compile its headers with your application, without building or linking a separate CelestialCalendar library. Native release ZIPs do not contain the C++ header tree.
This complete quickstart.cpp queries the UT1 moment of winter solstice, Dongzhi (冬至):
#include <iostream>
#include "jieqi.hpp"
int main() {
using namespace calendar::jieqi;
const Jieqi jq = Jieqi::冬至;
const auto moment = jieqi_ut1_moment(2026, jq);
std::cout << name_of(jq) << " 2026 (UT1): " << moment.year() << '-' << moment.month() << '-'
<< moment.day() << ", day fraction " << moment.fraction() << '\n';
}
Save it as build/quickstart.cpp (create build/ if needed; Git ignores it) and run from the source root with a
C++23-capable clang++ installation:
# Headers include each other by basename; all three directories are needed.
clang++ -std=c++23 -I src/astro -I src/calendar -I src/util \
build/quickstart.cpp -o build/quickstart
./build/quickstart
The Jieqi API is in src/calendar/jieqi.hpp, including jieqi_ut1_moment, jieqi_jde,
and JieqiGenerator. Other self-contained headers are organized under src/astro/, src/calendar/, and src/util/.
The headers for lunar nodes, longitude rates, charts, planetary illumination and planetary events are described
in the C++ guide of the API reference. Of these, the chart snapshot is also available
from C, Python and JavaScript, the interval longitude events from C and Python, and the operation capabilities
and model identities from C and Python (capabilities).
ephemeris_events.hpp adds C++ interval searches for a supplied longitude,
stationary instants and directed longitude differences; see Longitude events
for half-open ownership, numerical contacts and the floating-point premises.
Selected-target apparent ecliptic or equatorial positions at one JDE(TT) instant and house geometry at a civil
instant are available without a full snapshot, from C, Python and C++
(positions / equatorial_positions / house_query).
Position queries use each selected target's provider window; the civil house query uses the shared civil policy,
without the snapshot's joint target/rate window. C and Python results record resolved model/source identities.
C and FFI
The C ABI in celestial.h supports C, ctypes, and other FFIs. This is a complete
quickstart.c for the same query:
#include <stdio.h>
#include <stdlib.h>
#include "celestial.h"
int main(void) {
const uint8_t index = 21; /* 冬至; index 0 is 立春. */
const uint32_t size = celestial_get_jieqi_name(index, NULL, 0);
if (celestial_last_error_kind() != CELESTIAL_ERROR_NONE || size == 0) {
fprintf(stderr, "%s\n", celestial_last_error());
return 1;
}
char *name = malloc(size);
if (name == NULL) return 1;
const uint32_t required = celestial_get_jieqi_name(index, name, size);
if (celestial_last_error_kind() != CELESTIAL_ERROR_NONE || required != size) {
fprintf(stderr, "%s\n", celestial_last_error());
free(name);
return 1;
}
const CelestialJieqiMomentQuery m = celestial_query_jieqi_moment(2026, index);
if (celestial_last_error_kind() != CELESTIAL_ERROR_NONE || !m.valid) {
fprintf(stderr, "%s\n", celestial_last_error());
free(name);
return 1;
}
printf("%s (UT1): %d-%02u-%02u, day fraction %.6f\n", name, m.y, m.m, m.d, m.frac);
free(name);
return 0;
}
Open a chosen version on Releases and download the native ZIP for your platform. Public downloads need no GitHub token. Check the native runtime table before downloading.
| Platform | Release file | Files needed to link and run |
|---|---|---|
| Linux x86_64 | linux_amd64.zip |
lib/libcelestial_calendar.so and its versioned files |
| Linux arm64 | linux_arm64.zip |
lib/libcelestial_calendar.so and its versioned files |
| macOS arm64 | macos_arm64.zip |
lib/libcelestial_calendar.dylib and its versioned files |
| Windows x86_64 | windows_x86_64.zip |
link-time lib/celestial_calendar.lib; runtime bin/celestial_calendar.dll |
Every ZIP includes include/celestial.h. Extract the entire ZIP into native/ in the application directory,
preserving all library filenames: the runtime loader also uses the versioned name. Unix uploads dereference
installed symlinks, so the downloaded versioned libraries are ordinary files.
On Linux, run from the directory containing quickstart.c and native/, recording an absolute runtime search path:
prefix="$PWD/native"
cc -std=c11 quickstart.c -I "$prefix/include" -L "$prefix/lib" \
-lcelestial_calendar -Wl,-rpath,"$prefix/lib" -o quickstart_c
./quickstart_c
On macOS, link the .dylib from lib/ and make that directory available to the runtime loader. On Windows, use
an MSVC-compatible toolchain, link lib/celestial_calendar.lib, and put bin/celestial_calendar.dll beside the
executable. Consumers do not define CELESTIAL_BUILDING_DLL.
If you built the shared library yourself, save quickstart.c as build/quickstart.c; the
corresponding Linux command from the source root is:
cc -std=c11 build/quickstart.c -I src/shared_lib -L build/shared_lib \
-lcelestial_calendar -Wl,-rpath,"$PWD/build/shared_lib" -o build/quickstart_c
./build/quickstart_c
Check valid before reading a returned result structure. A zero count can be a legitimate empty result;
celestial_last_error_kind() is authoritative even if the message is empty. Both it and celestial_last_error()
preserve error state; every other export clears or records the calling thread's kind and message. Read or copy the
library-owned message before another recording call on that thread. Sized strings count the terminating NUL;
NULL, 0 queries the required size, and positive capacity truncates and terminates. Jieqi moments use fixed Algo5
to return UT1, not UTC or a UTC+8 wall date. See the C API guide for ABI evolution.
Native runtime
Starting with 1.1.0, prebuilt macOS arm64 libraries require macOS 15 or newer.
The Supported column is the compatibility promise; the Measured column only records properties of the built
artifact and never lowers that promise. Linux records the highest GLIBC / GLIBCXX requirement, macOS the Mach-O
deployment target, and Windows how the Visual C++ runtime is linked. CI writes the same values to
build_info.json, rejects an artifact whose measured requirement exceeds the promise, and checks release files
against them. Linux assumes the system provides the standard libstdc++.so.6 and libgcc_s.so.1; no Windows
OS version floor is declared.
| Artifact | Supported runtime | Measured artifact property |
|---|---|---|
linux_amd64 |
glibc=2.28, glibcxx=3.4.21 |
glibc=2.26, glibcxx=3.4.21 |
linux_arm64 |
glibc=2.28, glibcxx=3.4.21 |
glibc=2.17, glibcxx=3.4.21 |
macos_arm64 |
macos=15.0 |
macos=15.0 |
windows_x86_64 |
windows=not_declared |
msvc_runtime=static |
Features and algorithms
- Gregorian / lunar date conversion.
- Exact Jieqi moments, with offline JSON export.
- Sunrise, sunset, transit, twilight, polar day and polar night; differences from the USNO / NOAA / JPL DE external references are within ±2 minutes.
- C++ headers provide the Ascendant, Descendant, Midheaven and Imum Coeli, and Equal, Whole Sign and Placidus
house cusps; site longitude is east-positive, and the instant is passed as both UT1 and TT. All three systems
reject the geographic poles and an Ascendant direction unresolved at floating-point round-off; Placidus also
rejects
abs(latitude) >= 90° - true_obliquityand never switches system. - C++ headers provide apparent geocentric ecliptic and equatorial positions of the Sun, Moon, Mercury through Pluto
and the lunar nodes, planetary illumination geometry and retrograde markers, and new-moon
moments. Mercury through Neptune use VSOP87D;
Planet::PLUTOuses the lower-precision Meeus Chapter 37 model, whose apparent position and illumination geometry accept[2409543.5, 2488069.5)JDE(TT) and whose centred retrograde check accepts[2409544.0, 2488069.0). - C++ headers return true-ecliptic, true-equinox-of-date longitudes of the mean and true ascending and descending lunar nodes from JDE(TT); true nodes use the Meeus five-term correction, latitude is exactly zero by definition, and no physical distance exists.
- C++
ephemeris.hppprovides one instantaneous ecliptic longitude rate for the Sun, Moon, planets and lunar nodes, in degrees per TT day, by a locally unwrapped seven-point central difference over the existing apparent position models. Near a station its sign can differ from the one-day retrograde marker; seeis_retrogradeinplanet.hpp. - A chart snapshot at one instant: from an explicitly scaled UTC/UT1 civil input, a ΔT model and a site/house
system, it returns positions and rates of fourteen bodies/points, the four axes and the twelve cusps. It is
available as C++
chart.hpp, Ccelestial_chart_snapshot, Pythonchart_snapshotand JavaScriptchart.snapshot. The C ABI and both packages still expose instantaneous longitude-rate queries only through this snapshot; C and Python also provide interval events and independent position/house queries, which JavaScript does not expose. - Interval longitude crossings, stations and directed two-body longitude differences: C++ provides
ephemeris_events.hpp, C exposescelestial_longitude_events/celestial_longitude_stations/celestial_longitude_difference_events, and Python provideslongitude_eventsand related queries. See the event guide; JavaScript exposes no event API. - Operation input bounds and model identities: C exposes
celestial_capabilities,celestial_house_capability,celestial_model_identitiesand operation/model descriptions. Python providescapabilitiesand related queries; C++ providescapability.hpp. JavaScript exposes no capability API. - Independent position and house queries: select a nonempty target subset for apparent geocentric ecliptic or
equatorial positions at one JDE(TT), without rates or the chart's joint window. Civil-house queries apply the
shared civil policy independently of that joint target/rate window. C provides
celestial_positions,celestial_equatorial_positionsandcelestial_house_query; Python providespositions,equatorial_positionsandhouse_query. Results carry resolved model/source identities. C++ providesephemeris::position,equatorial_positionand the numeric operations inhouse.hpp. JavaScript exposes none of these queries. - C++ yearly searches for planetary conjunction, opposition, quadrature, greatest elongation and exact stations
return events ordered by JDE(TT). Events belong to
[Jan 1, next Jan 1)UTC, with the library's UT1 substitute before 1972. The[1, 32766]input year domain is not a guarantee of successful evaluation or modern ephemeris precision in remote eras; Pluto provides outer-planet events only, over complete Gregorian years[1886, 2098]. - Equation of time and local apparent solar time.
- UT1 / UTC / TT conversion, leap seconds and ΔT, Julian Days, sidereal time, obliquity, nutation.
- A C ABI shared library, a native Python wheel and a JavaScript / TypeScript WASM package.
Choosing inputs
Gregorian / lunar conversions operate on calendar-date labels, not instants, and each algorithm has its own
lunar-year domain and date basis. Choose by source and compatibility role, not by treating the algorithm number as
an accuracy ranking: Algo1 keeps Hong Kong Observatory labels, Algo2 computes dates astronomically over a wider
window, and Algo3 combines the two. Read a domain from the library rather than copying it:
celestial_get_supported_lunar_year_range in C, supported_lunar_year_range() in Python, lunar.supportedYearRange() in
JavaScript, and START_YEAR / END_YEAR of each calendar::lunar::algoN in C++. Each algorithm's source, domain
and date basis are described in Lunar algorithms.
For ΔT (TT minus UT1, in seconds), use the default, Algo5 (the current project model); select one of the frozen Algo1 to Algo4 only to compare with that earlier model. Sources and hard year domains are listed in the Delta T models.
Keep time scales separate. JD UT1 and JDE TT are not interchangeable; Jieqi results are UT1, whereas
celestial_apparent_solar_time() / Python apparent_solar_time() / JavaScript sun.apparentSolarTime() take civil UTC.
The pure /date bridge supplies no UTC-to-UT1
or Date-to-JDE conversion. The library does not use observed DUT1 (UT1 − UTC), so a UT1 result is not a UTC time;
Time scales describes the library's UTC model and how large the gap is in each era.
In the C ABI and both packages, geographic longitude is east-positive and solar and lunar angles are degrees. Solar distance is AU. The standalone Moon coordinate gives kilometres, while a chart snapshot gives every distance in AU, the Moon's included. The equation of time is degrees of hour angle, so multiply by 240 for seconds of time.
References
- ERFA v2.0.1
- Definitions of Systems of Time
- USNO Delta T Values
- SOFA Library (ANSI C)
- Morrison, Stephenson, Hohenkerk & Zawilski, 2021 addendum to "Measurement of the Earth's rotation"
- vsop87c
- PyMeeus
- Sonia Keys / meeus v3 at bfbd9ac
- Swiss Ephemeris v2.10.3bfinal
- meeus-elp82
- AA+ v2.55 A class framework for Computational Astronomy
- Xu Jianwei, 寿星万年历2008版(V1.3.2)
- Fred Espenak, Thousand Year Canon of Solar Eclipses 1501 to 2500 (2014)
- 算法系列之十八:用天文方法计算二十四节气(上)
- 算法系列之十八:用天文方法计算二十四节气(下)
- 算法系列之十九:用天文方法计算日月合朔(新月)
- 历书科普问题解答 - 中国科学院紫金山天文台
- 农历编算法则
- ytliu0 / ChineseCalendar
- JPL Horizons
- Hong Kong Observatory — 24 Solar Terms
License
Project-authored material is licensed under the MIT License; see LICENSE.
The MIT grant covers the material this project authored: the C++ and C sources and headers, the shared-library implementation and C ABI, the JavaScript and Python binding sources and type declarations, the build, packaging, release, and validation automation, the documentation written here, and the project-generated build and release evidence files.
The MIT grant does not extend to third-party material this project redistributes. It keeps its source terms, with notices and attribution carried where applicable:
- Retained blocks inside project-maintained files — coefficient tables, encoded datasets, formula transcriptions, and validation rows keep their source terms and carry a pinned in-file or adjacent attribution boundary.
- Third-party toolchain material embedded by the build — the Emscripten, musl, and LLVM-runtime
portions inside
celestial-jieqi.wasmand its generated glue, and the Microsoft C/C++ runtime portions statically linked into the Windows DLL and the native library inside the Windows wheel. These are governed by their own terms and are accompanied by the canonical third-party notice bundle where one is required. - Standalone third-party files and data — the vendored
run-clang-tidy.py, upstream licence texts underthird_party/, frozen source captures, stored golden datasets, crawler outputs, notebook inputs, and book-derived anchors keep their own source attribution and terms. They reach the tagged tree and GitHub-generated source archives, but not the native archives, wheels, npm tarball, or WASM artifact as standalone files.
Retained third-party material is not relicensed by sitting beside MIT-licensed code, and this
project asserts no ownership of an upstream source's data merely because this project's code reads
or compares against it. See THIRD_PARTY_NOTICES.txt and the inline or adjacent attribution records
for the applicable exceptions.