CelestialCalendar Documentation
On this page

Migrating to 1.0

This guide describes migration from 0.x to the 1.0 API. Rebuild C/FFI consumers against the matching header and library; update Python/JavaScript callers with the matching package. There are no 0.x forwarding exports. No accepted numerical value changes, but invalid-input acceptance, error classes and some messages intentionally change.

C and FFI names and layouts

Every public function uses celestial_, every record Celestial, and every public code CELESTIAL_. Initial declarations have no universal _v1 / V1 suffix. Existing layouts and signatures stay fixed within a major; a genuinely incompatible change needs an explicitly named parallel function/record or a new major. The fixed chart roster/array is part of that layout, even if a selected-target query later supports more targets.

Previous name or family 1.0 name or action
ut1_to_jd, query_jieqi_moment, other unprefixed exports celestial_ut1_to_jd, celestial_query_jieqi_moment, corresponding celestial_ exports
chart_snapshot_v1, ChartSnapshotV1 celestial_chart_snapshot, CelestialChartSnapshot
ChartBodyV1, ChartHousesV1 CelestialChartBody, CelestialChartHouses
position_query_v1, PositionRecordV1 celestial_positions, CelestialPositionRecord
house_query_v1, HouseQueryResultV1 celestial_house_query, CelestialHouseQueryResult
CHART_TARGET_*, EVENT_TARGET_*, capability target codes One CELESTIAL_TARGET_* family
Six Delta T exports: default and algo1–algo5 celestial_delta_t(year, model_code)
set_log_verbosity Removed; the library does not print errors

Read celestial.h for the complete declarations. Do not retain a copied 0.x layout and merely rename its symbols: the snapshot now contains resolved Delta T/house-nutation identities and per-body source identities. CelestialDeltaT also records the resolved model. CelestialPositionRecord is a separate shape from a chart body, and equatorial records have distinct RA/declination fields.

A model key is the exact (kind_code, identity_code) pair, not a flat registry index. Explicit Delta T values are 1–5, Meeus/IAU 1980 nutation values are 1/2, and position sources are 1–6. Selectable-model identity values are also valid explicit selectors within the matching kind. DEFAULT (0) is input-only where supported, never a returned identity; its meaning belongs to the operation. Use celestial_model_identity_text(kind, identity, buffer, capacity) for model text, not celestial_capability_text with a registry ordinal. The latter describes an operation; target operations need a concrete target, while HOUSES, CHART and DELTA_T use CELESTIAL_TARGET_ALL.

Errors and caller-owned buffers

Check celestial_last_error_kind() after each recording call, including count and fill passes. It is authoritative even if allocating a message failed. celestial_last_error() and celestial_last_error_kind() both preserve state; every other export clears or records the calling thread's kind/message. A legal empty result has kind NONE. Check valid before reading a returned record; invalid payload and padding bytes have no contract.

Failure C kind / binding exception
Wrong binding type or record shape Python/JavaScript TypeError
Malformed or out-of-declared-domain input, wherever detected CELESTIAL_ERROR_INVALID_INPUT; Python ValueError, JavaScript RangeError
Computation, resource or internal failure Corresponding C error kind; binding CelestialError
Malformed native success, unknown returned codes or inconsistent fill totals Binding CelestialError

For example, an out-of-domain native sidereal query or a date label outside the selected lunar algorithm's coverage now raises ValueError / RangeError, not the library error used by 0.x. After a finite Sun/Moon input, an internal non-finite angle or invalid computed coordinate is a computation failure; a malformed/non-finite input remains invalid input. Applications should branch on the error kind/class, not match message text or test whether the message is nonempty.

Returned Gregorian/civil labels use astronomical years in [-32767, 32767], including year 0 (1 BCE). A valid year-1 solar-time input can cross into year 0. The JavaScript /date bridge keeps its separate [1, 32767] civil domain; Python's GregorianDate.to_date() still requires [1, 9999].

Sized UTF-8 calls return the required byte count including NUL. Pass a null buffer and zero capacity to size; with positive capacity, calls write at most capacity minus one payload bytes and a terminating NUL, truncating if needed. Allocate the reported size and check the error kind after both passes. Jieqi-name callers must not depend on a fixed 16-byte allocation. The complete C quickstart shows dynamic allocation.

Count/fill queries report the total separately from the number written. A truncated fill is a successful prefix, not a partial result from a failed computation. Check errors after both passes and reject changed totals when replaying count then fill. Buffers are caller-owned; result values copied by the packages remain owned values.

Python

Use Target in place of ChartTarget, EventTarget and CapabilitySubject. Positions, charts, events and TT capabilities share that enum. Position/body .source values and snapshot/house .delta_t_model / .nutation_model values are ModelKey(kind, identity) pairs. ModelIdentity.key supplies the same pair; model_identity_text(key) looks it up without depending on registry order. Matching-kind keys can be reused as model selections. PositionSource and registry-wide default-alias fields are removed.

positions(targets, jde_tt) remains ecliptic. equatorial_positions(targets, jde_tt) returns distinct EquatorialPosition values with right_ascension_deg, declination_deg, optional distance_au and .source. Both preserve caller order, reject empty/duplicate/unknown selections, evaluate only selected targets, and fail the entire query if a target fails. They evaluate no rates, civil conversion or houses. All fourteen targets are supported; mathematical nodes have no distance. Read civil policy through the separate house_capability(delta_t_model=...), not a TT target-domain query.

import celestial_calendar as celestial

targets = (celestial.Target.SUN, celestial.Target.TRUE_ASCENDING)
ecliptic = celestial.positions(targets, 2451545.0)
equatorial = celestial.equatorial_positions(targets, 2451545.0)
print(equatorial[0].right_ascension_deg, equatorial[0].declination_deg)
print(celestial.model_identity_text(equatorial[0].source))

when = celestial.CivilDateTime(2026, 1, 1, 0.5)
site = celestial.GeoLocation(51.5, 0.0)
houses = celestial.house_query(when, celestial.CivilScale.UTC, site, celestial.HouseSystem.EQUAL)
policy = celestial.house_capability(delta_t_model=houses.delta_t_model)
again = celestial.house_query(
  when,
  celestial.CivilScale.UTC,
  site,
  celestial.HouseSystem.EQUAL,
  delta_t_model=houses.delta_t_model,
  nutation_model=houses.nutation_model,
)
print(policy.civil_start_year, policy.civil_end_year_exclusive, again.times)

house_query keeps delta_t_model= and nutation_model=; delta_t and chart_snapshot keep model=. delta_t still returns a scalar. Reusing resolved keys preserves the chosen model, not coefficients across library versions. Python set_log_verbosity and LogVerbosity are removed.

SunCoordinate.radius_au becomes SunCoordinate.distance_au, still in AU. The standalone Moon coordinate keeps distance_km; chart distances remain distance_au, with None for nodes. The chart snapshot and interval-event APIs introduced in 0.8.0 and 0.9.0 remain available under the current names.

JavaScript and TypeScript

Remove config, logging types/setters and initialization configuration. Use await init() without arguments. The public namespaces remain time, sun, moon, jieqi, lunar and chart; no independent position, equatorial, house, event, capability or model-text query is added. The Sun coordinate's radiusAu becomes distanceAu; Moon distance remains distanceKm, and chart distances remain distanceAu (null for nodes). The celestial-calendar npm alias forwards the same API; use matching primary/alias versions.

Wrong types/shapes use TypeError; invalid input found by JavaScript or native code uses RangeError. Computation/resource/internal failures and corrupt native success records use CelestialError. Initialization and loader retry behavior are unchanged. In both bindings, CelestialError.recorded is true when a recognized native error kind was reported, even if its message is empty; binding-local failures set it to false.

C++ ownership, time and model conventions

Use astro::ephemeris::position instead of astro::chart::position; use its distinct equatorial_position for RA/declination. ephemeris::TARGET_FACTS owns targets, sources and provider domains. The C++ capability facade uses typed enums and optional intervals; raw C codes, NaN encoding, model registries and runtime text stay in shared_lib. Adapters use public core operations, not detail helpers.

astro::julian_day owns Scale, Times, civil_to_tt and the general resolve_times. Replace chart::Scale / chart::Times uses with the Julian-day types. The general resolver has no chart/house admission policy; model guards apply at the evaluation instant, UT1 forward and TT reverse. Algo4 UT1 input can resolve to TT beyond its TT-evaluation end, then fail if that TT is reversed. UTC-to-TT alone does not evaluate Delta T.

chart::resolve_civil_times applies the shared chart/civil-house policy: [1885, 2100) civil years, strict UTC from 1972-01-01 and Algo4 derived JDE(TT) < 2464328.5 before reverse conversion. This deliberately excludes the last roughly 71.5 UT1 seconds of 2034 under Algo4. Chart adds its joint target/rate window; house does not. Common time/model rejections may now precede a chart joint-window rejection, so multiply-invalid messages can change. Legacy convenience conversions keep their pre-1972 UT1 substitute and fixed Algo5; Jieqi TT-to-UT1 also stays fixed Algo5 even if the selectable default changes.

house::axes(jd_ut1, jde_tt, location, model) is a standalone C++ operation without cusps; no C axes export is added. Numeric axes/houses have no civil-year or chart target/rate window. House nutation follows house::DEFAULT_NUTATION_MODEL, currently IAU 1980. Nutation/true-obliquity primitives, omitted sidereal model arguments, positions, projection, solar time and rise/set retain fixed IAU 1980 conventions. A snapshot's house-nutation key does not describe its body corrections.

For the duration constructor, replace Datetime{day, hh_mm_ss{elapsed}} with Datetime{day, elapsed}; an existing hh_mm_ss can supply .to_duration(). Fraction and time-point constructors remain.

C++ numerical helpers and generators

The annual event APIs keep their definitions and domains; they do not become wrappers around the interval engine. Choose an API by its event definition and endpoint policy, not by the similarity of its result names.

moon_phase::new_moon::moments, phase_moments::moments, their RootGenerator names and phase_moments::PhaseKind remain. Both generators now offer peek() const, which returns the saved root without advancing or refining its successor. next() still computes the successor before returning the pending root; failure leaves that pending root unchanged. Copy and move assignment remain available.

The duplicated low-level Moon solvers and constants move to moon_phase::detail. new_moon::longitude_diff and phase_moments::longitude_diff become the single moon_phase::longitude_diff. Code using phase_moments::phase_diff(jde, target_deg) can express the same signed fold as toolbox::normalize_pm180(moon_phase::longitude_diff(jde) - target_deg). There are no forwarding aliases for the removed low-level names.

Prefer the annual operations or generators for consumers. Code that integrates the internal solver must select its numerical policy explicitly: conjunction for the former new_moon route, phase for the former phase_moments route. The internal namespace is not a new stable solver API. In particular, an old conjunction call (left, right, cap) must not keep the same argument list: the shared solver's third argument is the target elongation in degrees. Pass 0.0 there and move cap to the fourth position:

const double root = astro::moon_phase::detail::newton_method<
  astro::moon_phase::detail::NumericPolicy::CONJUNCTION
>(left_jde, right_jde, 0.0, cap);

JieqiGenerator::peek() const does not advance, but it evaluates the pending Jieqi and can throw. Its constructor retains the linear first-match search, including at remote years where roots may be nonchronological. LunarMonthGenerator retains its assembled-month cache. Construction still evaluates the first pending Jieqi and new moon, but defers the following new moon until the first month is read. Failure to compute that following root can therefore occur later.

Scientific limits that still apply

This upgrade changes no numerical goldens, tolerances or Pluto/node hard windows. Current sources, frames, units and operation-specific limits are in the API reference; time-scale and failure conventions are in Design. Choose lunar algorithms and ΔT models using Algorithms, and consult Validation for reference coverage and its limits.

The new equatorial tests separate same-input projection/node conformance from physical Horizons regression checks; their thresholds and sample limits are in Validation. Release artifacts are validated separately on supported platforms.