panchanga 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hindu Society of North America
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # panchanga
2
+
3
+ Drik Panchanga (Smārta, pūrṇimānta) engine for the [HSNA](https://hsna.ca) website.
4
+
5
+ A dependency-light TypeScript library that computes the Hindu lunisolar calendar —
6
+ the **five aṅgas** (vāra, tithi, nakṣatra, yoga, karaṇa), the lunar month, and
7
+ sidereal / ayanāṁśa values — and resolves **Hindu festival dates**, all from
8
+ astronomical first principles via
9
+ [astronomy-engine](https://github.com/cosinekitty/astronomy). No lookup tables, no
10
+ remote API.
11
+
12
+ > **Status:** pre-1.0 (`0.1.0`), under active development. The grammar and public API
13
+ > may still change.
14
+
15
+ ## Features
16
+
17
+ - **The five aṅgas** — vāra (sunrise-to-sunrise weekday), tithi, nakṣatra, yoga, and
18
+ karaṇa (including Viṣṭi / Bhadra, with its Mukha/Pucchā split and Vāsa), plus the
19
+ pūrṇimānta / amānta lunar month with adhika (leap) and kṣaya (lost) month detection.
20
+ - **Daily pañcāṅga** — `dailyPanchanga(date, loc)` bundles all five aṅgas (each resolved
21
+ at sunrise, with its end-time) and the day's sun/moon instants into one record.
22
+ - **Grahaṇa (eclipses)** — solar & lunar eclipse type, contact timings, local visibility,
23
+ and sūtak windows (9h lunar / 12h solar) for any year and place.
24
+ - **Astronomy primitives** — Lahiri ayanāṁśa (IAU 1976 precession), sidereal longitudes
25
+ and Sun rāśi, new moons, solar ingress (saṅkrānti).
26
+ - **Time & kāla windows** — timezone/DST-safe sunrise, sunset and moonrise, the muhūrta
27
+ windows (pūrvāhna, madhyāhna, aparāhna, pradoṣa, niśīta, brahma-muhūrta, …), and the
28
+ weekday day-part periods **Rāhu Kāla, Yamaganda, Gulika, and Abhijit**.
29
+ - **Festival engine** — a compact rule grammar (`Observance`) and a **pure, testable**
30
+ pervasion-day selector that resolves ~195 observances to civil dates and **never
31
+ silently drops**: every miss is explained in `diagnostics`. Selection conventions are
32
+ first-class — `udaya` vs window-fraction precedence, a `nearest-window` fallback, an
33
+ `adhika:"prefer-adhika"` leap-month policy, Bhadra exclusion, and nakṣatra- and
34
+ weekday-anchored festivals (Onam, Varalakṣmī).
35
+ - **Validated at two well-separated longitudes** against Drik Panchang fixtures (2026):
36
+ **New Delhi** and **Calgary** (the HSNA temple's city). All 24 core festivals and all
37
+ 24 in-year Ekādaśīs match Drik's Calgary calendar exactly, including the −1-day
38
+ localisation shifts; the handful of remaining differences are convention edges, pinned
39
+ and documented in the tests.
40
+
41
+ ## Requirements
42
+
43
+ - **ESM only** (`"type": "module"`) — use `import`, not `require`.
44
+ - **Node.js ≥ 18** to consume (the build targets ES2022). Building/testing from source
45
+ needs **Node ≥ 22.12** (Vitest 4).
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ npm install panchanga
51
+ ```
52
+
53
+ ## Quick start
54
+
55
+ ### The full pañcāṅga for a day
56
+
57
+ ```ts
58
+ import { dailyPanchanga } from "panchanga";
59
+ import type { GeoLocation } from "panchanga";
60
+
61
+ const newDelhi: GeoLocation = {
62
+ latitude: 28.6139,
63
+ longitude: 77.209,
64
+ timeZone: "Asia/Kolkata", // IANA tz id
65
+ };
66
+
67
+ const p = dailyPanchanga(new Date("2026-01-23"), newDelhi);
68
+
69
+ console.log(p.date, p.vara.name); // 2026-01-23 Shukravara
70
+ console.log(p.tithi.paksha, p.tithi.name); // shukla Panchami
71
+ console.log(p.nakshatra.name); // Purva Bhadrapada
72
+ console.log(p.yoga.name); // Parigha
73
+ console.log(p.karana.name); // Bava
74
+ console.log(p.month.purnimanta); // Magha
75
+ console.log(p.muhurta.rahuKala); // { start: "...Z", end: "...Z" } — Rāhu Kāla
76
+ ```
77
+
78
+ Each running aṅga (tithi, nakṣatra, yoga, karaṇa) is the one **prevailing at sunrise**
79
+ and carries an `endsAt` (ISO-UTC) marking when it gives way to the next; the record also
80
+ includes `sunrise`, `sunset`, `moonrise`, and the day's `muhurta` windows (Rāhu Kāla,
81
+ Yamaganda, Gulika, Abhijit).
82
+
83
+ ### Compute festival dates for a year and location
84
+
85
+ ```ts
86
+ import { computeFestivals, allRules } from "panchanga";
87
+ import type { GeoLocation } from "panchanga";
88
+
89
+ const newDelhi: GeoLocation = {
90
+ latitude: 28.6139,
91
+ longitude: 77.209,
92
+ timeZone: "Asia/Kolkata", // IANA tz id
93
+ };
94
+
95
+ const { results, diagnostics } = computeFestivals(2026, newDelhi, {
96
+ rules: allRules(2026),
97
+ });
98
+
99
+ for (const f of results) {
100
+ if (f.date) console.log(f.date, f.id, "—", f.monthLabel.purnimanta);
101
+ }
102
+ // 2026-01-14 makar-sankranti — Magha
103
+ // 2026-03-04 holi — Chaitra
104
+ // 2026-04-02 hanuman-jayanti — Vaishakha
105
+ // …
106
+
107
+ // Anything that could not be resolved is explained, never dropped:
108
+ diagnostics.forEach((d) => console.warn(d));
109
+ ```
110
+
111
+ Each [`FestivalResult`](src/types.ts) carries the civil `date` (`YYYY-MM-DD` in `loc`'s
112
+ timezone), a map of key `instants` (tithi start/end, window start/end, …) as ISO-UTC
113
+ strings, both month labels, and per-rule `diagnostics`.
114
+
115
+ ### Read raw calendar elements at an instant
116
+
117
+ ```ts
118
+ import {
119
+ tithiBoundaries, nakshatraAt, NAKSHATRA_NAMES, karanaAt, lunarMonth,
120
+ } from "panchanga";
121
+
122
+ const when = new Date("2026-01-23T12:00:00Z");
123
+
124
+ const tithi = tithiBoundaries(when); // { number: 1..30, start: Date, end: Date }
125
+ const nak = NAKSHATRA_NAMES[nakshatraAt(when)];
126
+ const kar = karanaAt(when); // karaṇa name
127
+ const month = lunarMonth(when, { system: "purnimanta" });
128
+
129
+ console.log(tithi.number, nak, kar, month.purnimantaLabel, month.adhika);
130
+ ```
131
+
132
+ ### Sidereal / ayanāṁśa
133
+
134
+ ```ts
135
+ import { ayanamsha, siderealSunRashi } from "panchanga";
136
+
137
+ const t = new Date("2026-06-24T00:00:00Z");
138
+ ayanamsha(t); // Lahiri ayanāṁśa in degrees (≈ 24.2° in 2026)
139
+ siderealSunRashi(t); // sidereal rāśi index of the Sun: 0 = Mesha … 11 = Mīna
140
+ ```
141
+
142
+ ## API overview
143
+
144
+ The public surface (see [`src/index.ts`](src/index.ts)) is layered:
145
+
146
+ **1. Ayanāṁśa & sidereal** — `ayanamsha`, `siderealLongitude`, `siderealSunRashi`,
147
+ `normalize360`, `LAHIRI_ANCHOR_J2000_DEG`.
148
+
149
+ **2. Time, vāra & kāla windows** — `riseSet`, `moonrise`, `sunset`, `varaAt`,
150
+ `sunriseWindow` / `pratahkala`, `purvahna`, `madhyahna`, `aparahna`, `pradosha`,
151
+ `nishita`, `brahmaMuhurta`, `arunodaya`, `rahuKala`, `yamaganda`, `gulikaKala`,
152
+ `abhijitMuhurta`, `sankrantiPunyaKala`, plus timezone helpers (`localDayString`,
153
+ `startOfLocalDayUTC`, `nextLocalDayStartUTC`) and `VARA_NAMES`. Types: `GeoLocation`,
154
+ `TimeWindow`, `Vara`.
155
+
156
+ **3. Calendar elements** — `tithiAt`, `tithiBoundaries`, `nakshatraAt`,
157
+ `nakshatraBoundaries`, `yogaAt`, `yogaBoundaries`, `karanaAt`, `karanaIndexAt`,
158
+ `karanaName`, `karanaBoundaries`, `bhadraIntervals`, `bhadraSplit`, `elongation`,
159
+ `newMoons`, `solarIngress`, `lunarMonth`. Name tables: `TITHI_NAMES`, `NAKSHATRA_NAMES`,
160
+ `YOGA_NAMES`, `MOVABLE_KARANAS`, `LUNAR_MONTH_NAMES`.
161
+
162
+ **4. Daily aggregator** — `dailyPanchanga(date, loc)` → `DailyPanchanga` (the five aṅgas
163
+ at sunrise + sun/moon instants + month label + the day's Rāhu/Yama/Gulika/Abhijit
164
+ muhūrtas).
165
+
166
+ **4b. Grahaṇa (eclipses)** — `lunarEclipses(year, loc?)`, `solarEclipses(year, loc?)` →
167
+ eclipse type, contact phases (ISO-UTC `IsoWindow`s), local visibility, and sūtak.
168
+ Types: `LunarEclipse`, `SolarEclipse`, `GrahanKind`.
169
+
170
+ **5. Festival engine** — `computeFestivals`, `computeFestival`, `selectDayByPervasion`
171
+ (the pure selector), and the rule data: `CORE_RULES` plus the generators
172
+ `ekadashiRules`, `sankashtiRules`, `pradoshRules`, `masikShivaratriRules`,
173
+ `purnimaVratRules`, `purnimaSnanaRules`, `amavasyaRules`, `sankrantiRules`, `oneOffFestivalRules`,
174
+ `regionalFestivalRules` (each
175
+ `(year)`), `CHHATH_RULE`, and `allRules(year)`. Grammar types: `Observance`,
176
+ `FestivalRule`, `FestivalResult`, `Kala`, `TithiRef`, `Paksha`. Observance kinds
177
+ include `tithi-pervades` (with `udaya` / `max-window-fraction` precedence, an
178
+ `adhika:"prefer-adhika"` leap-month policy, and a `nearest-window` fallback),
179
+ `solar-ingress`, `moonrise`, `solar-arghya`, `derived`, `nakshatra-pervades`
180
+ (Onam), and `weekday-relative` (Varalakṣmī).
181
+
182
+ `allRules(year)` covers ~195 observances: the 24 major festivals, every Ekādaśī,
183
+ Sankaṣṭī Caturthī, Pradoṣa Vrata, Masik Śivarātri, Pūrṇimā Vrata (vrat) and Pūrṇimā
184
+ snāna-dāna, Amāvāsyā, all 12 Sankrāntis, Chhath, the HSNA one-offs (Ugadi, the Teej
185
+ trio, Nag Panchami, Rath Yatra, Tulsi Vivah, Anant Chaturdashi, …), and ~21 further
186
+ regional festivals & jayantis (Ratha Saptamī, Narasimha/Paraśurāma/Sītā/Gaṅgā jayantis,
187
+ Vat Sāvitrī, Vishwakarma, Kālī Chaudas, Onam, Varalakṣmī, …) — validated against Drik
188
+ Panchang's New Delhi and Calgary calendars (residual cases are ±1-day convention edges,
189
+ pinned in the tests).
190
+
191
+ ## The rule grammar
192
+
193
+ A festival is authored as a [`FestivalRule`](src/types.ts) whose `observance.kind`
194
+ declares how its civil date is resolved:
195
+
196
+ | `kind` | Resolves by | Examples |
197
+ |---|---|---|
198
+ | `tithi-pervades` | A tithi pervading a kāla window on the chosen day, picked by a `precedence` policy (`max-window-fraction`, `udaya`, `first`, `second`). Optional `nakshatra` filter, `avoidKarana: "vishti"` (Bhadra), `adhika: "prefer-adhika"` (leap-month policy), and a `fallback` (`previous-day` / `next-day` / `nearest-window`) | most lunar festivals |
199
+ | `solar-ingress` | The Sun's sidereal ingress into a rāśi | Makar Saṅkrānti, Vishwakarma |
200
+ | `moonrise` | Tithi live at moonrise | Karva Chauth, Sankaṣṭī |
201
+ | `solar-arghya` | Tithi at sunset and the next sunrise | Chhath |
202
+ | `derived` | An offset from another festival | Holi = Holikā + 1 |
203
+ | `nakshatra-pervades` | The day a named nakṣatra is at sunrise while the Sun is in a given rāśi | Onam (Śravaṇa in Siṃha) |
204
+ | `weekday-relative` | The latest weekday before another festival's date | Varalakṣmī (Friday before Śrāvaṇa Pūrṇimā) |
205
+
206
+ `allRules(year)` returns `CORE_RULES` plus every recurring and regional observance
207
+ generated for that year (see the generators listed under **API overview → 5**).
208
+
209
+ ## Scope of validation
210
+
211
+ This package computes panchanga values via `astronomy-engine` and is validated for
212
+ conformance to Drik Panchang (Smārta, pūrṇimānta) for the locations and years covered by
213
+ `test/fixtures`. It has **not** been independently verified by a traditional pandit or
214
+ Jyotisha authority — verify computed values against your local authority before ritual
215
+ use.
216
+
217
+ ## Development
218
+
219
+ ```sh
220
+ npm install # install dependencies
221
+ npm test # vitest run — unit suites + Drik-Panchang conformance
222
+ npm run build # tsc → dist/ (ESM .js + .d.ts + source/declaration maps)
223
+ ```
224
+
225
+ Tests live in `test/` (~370 cases): per-module unit suites plus the Drik-Panchang
226
+ conformance checks — `conformance.test.ts` (2026 New Delhi) and **five Calgary suites**
227
+ (the HSNA temple's city) whose EXPECTED dates are transcribed from Drik Panchang's Calgary
228
+ calendar (geoname-id 5913490): `conformance-calgary.test.ts` (24 core festivals),
229
+ `-vratas` (Ekādaśī, Saṅkaṣṭī, Pūrṇimā vrat & snāna, Amāvāsyā, minor Saṅkrāntis), `-oneoff`
230
+ (HSNA regional festivals) and `-regional` (21 further festivals/jayantis incl. Onam &
231
+ Varalakṣmī). All 24 core festivals and all 24 in-year Ekādaśīs match Drik Calgary exactly;
232
+ the few remaining ±1 convention edges and definitional differences are pinned and
233
+ documented. The suites also assert the **localisation invariant** — every Calgary date is
234
+ within ±1 day of New Delhi — which is what catches a wrong convention (a single locale
235
+ can't, since many conventions agree there).
236
+
237
+ ## Known convention edges
238
+
239
+ Most differences from Drik Panchang are not errors — they are points where the tradition
240
+ itself admits more than one reckoning, or where the engine intentionally follows HSNA. The
241
+ ones to be aware of:
242
+
243
+ - **Pūrṇimā / Amāvāsyā — vrat vs snāna.** `purnima-vrat-*` is the *moonrise* vrat day;
244
+ `purnima-snana-*` is the next-morning *snāna-dāna* day Drik lists as "X Purnima". At
245
+ far-western longitudes these are usually one civil day apart — both are correct, for
246
+ different observances.
247
+ - **Gauṇa (Vaiṣṇava) Ekādaśī — not yet emitted.** When an Ekādaśī is Daśamī-viddha,
248
+ Vaiṣṇavas fast the next day ("Gauṇa"). The engine emits only the Smārta date; the
249
+ second-day reckoning is future work.
250
+ - **Ganga Dussehra, Balram Jayanti — definitional, follow HSNA.** Ganga Dussehra uses the
251
+ adhika Jyeṣṭha when present (matching Drik); Balram Jayanti follows HSNA's Kṛṣṇa-Ṣaṣṭhī
252
+ rather than Drik's Śukla-Ṣaṣṭhī.
253
+ - **Maha Navami (Durga) — Sandhi rule, +1 known diff.** Drik can place Navamī on the
254
+ Aṣṭamī-udaya day via the Sandhi-Pūjā convention, which the generic tithi grammar does
255
+ not express.
256
+ - **Pinned ±1 edges:** Kārtika Pūrṇimā snāna, Sarva-Pitṛ Amāvāsyā, Mithuna Saṅkrānti,
257
+ Phulera Dooj, Hariyali Teej / Nag Panchami / Kansh Vadh (New Delhi). Each is a two-day
258
+ tithi straddling sunrise where the day-attribution convention is genuinely borderline;
259
+ the tests pin the produced date so any change is surfaced consciously.
260
+
261
+ ## Tech stack
262
+
263
+ - **Language:** TypeScript 6 (`strict`), ES2022 target, NodeNext modules, ESM-only.
264
+ - **Runtime dependency:** [`astronomy-engine`](https://github.com/cosinekitty/astronomy)
265
+ — the only one; provides ephemeris (Sun/Moon position, rise/set, moon-phase search,
266
+ precession).
267
+ - **Tooling:** `tsc` for the build, [Vitest](https://vitest.dev) 4 for tests. No bundler,
268
+ linter, or CI.
269
+
270
+ ## License
271
+
272
+ MIT © 2026 Hindu Society of North America
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Lahiri (Chitrapakṣa) ayanāṁśa and sidereal longitudes.
3
+ *
4
+ * MODEL
5
+ * -----
6
+ * Lahiri ayanāṁśa = a canonical constant anchor at J2000.0 plus the accumulated
7
+ * **general precession in longitude** from J2000.0 to the date, computed with the
8
+ * **IAU 1976 (Lieske et al. 1977)** precession series. This is the same polynomial
9
+ * given by Meeus, *Astronomical Algorithms* (2nd ed.), ch. 21 ("Precession"), for
10
+ * the precession of ecliptical coordinates — Meeus' general-precession-in-longitude
11
+ * polynomial IS the IAU 1976 series.
12
+ *
13
+ * ayanāṁśa(T) = 23.853222° + pA(T)
14
+ *
15
+ * where T is the time in Julian centuries of Terrestrial Time (TT) from J2000.0 and
16
+ * pA(T) is the accumulated general precession in longitude in degrees.
17
+ *
18
+ * ANCHOR
19
+ * ------
20
+ * At J2000.0 the mean Lahiri ayanāṁśa is **23.853222°**. This is the canonical
21
+ * Swiss Ephemeris `SE_SIDM_LAHIRI` / ICRC value, expressed as the Swiss Ephemeris
22
+ * user-defined mode `swe_set_sid_mode(SE_SIDM_USER, 2451545.0, 23.853222)`. Official
23
+ * Lahiri is "an arbitrary value at an arbitrary epoch propagated by IAU 1976
24
+ * precession" — it is NOT a distinct precession series. There is no festival-date
25
+ * tuning here; the anchor and series are fixed.
26
+ *
27
+ * PRECESSION SERIES (IAU 1976 / Meeus ch. 21), referred to J2000.0 (arcseconds):
28
+ *
29
+ * pA(T) = 5029.0966″·T + 1.11113″·T² − 0.000006″·T³
30
+ *
31
+ * The leading coefficient, 5029.0966″/Julian century, is the IAU 1976 rate of
32
+ * general precession in longitude at J2000.0 (Lieske et al. 1977,
33
+ * A&A 58, 1). (In Meeus' general two-epoch form the T² and T³ coefficients also
34
+ * carry T₀ terms; with the fixed reference epoch J2000.0, T₀ = 0 and they drop out.)
35
+ *
36
+ * MEAN vs TRUE
37
+ * ------------
38
+ * The **mean** ayanāṁśa (precession only) is the core. It matches published Lahiri
39
+ * tables and is what the frozen reference values are checked against.
40
+ *
41
+ * Drik Panchang and most published "display" panchāngas use the **true** ("with
42
+ * nutation") ayanāṁśa: mean + nutation in longitude Δψ. Pass `{ nutation: true }`
43
+ * to get it. The nutation amplitude is ≤ ~17″ (< 0.3 arcmin), so either value
44
+ * satisfies the < 1 arcmin reference tolerance, but the two are distinct and the
45
+ * caller chooses. Festival-date code will pass `{ nutation: true }`.
46
+ */
47
+ import { Body, type FlexibleDateTime } from "astronomy-engine";
48
+ /** Canonical Lahiri anchor: mean ayanāṁśa at J2000.0, in degrees. */
49
+ export declare const LAHIRI_ANCHOR_J2000_DEG = 23.853222;
50
+ export interface AyanamshaOptions {
51
+ /**
52
+ * When true, add nutation in longitude Δψ to obtain the **true** ("with
53
+ * nutation") ayanāṁśa used for display (e.g. Drik Panchang). Default false
54
+ * (mean ayanāṁśa, precession only).
55
+ */
56
+ nutation?: boolean;
57
+ }
58
+ /**
59
+ * Lahiri (Chitrapakṣa) ayanāṁśa in degrees for the given instant.
60
+ *
61
+ * Mean by default (precession only). Pass `{ nutation: true }` for the true
62
+ * ("with nutation") value.
63
+ */
64
+ export declare function ayanamsha(date: FlexibleDateTime, options?: AyanamshaOptions): number;
65
+ /** Normalize an angle in degrees into the range [0, 360). */
66
+ export declare function normalize360(deg: number): number;
67
+ /**
68
+ * Sidereal (Lahiri) ecliptic longitude of a body, in degrees, normalized to
69
+ * [0, 360).
70
+ *
71
+ * sidereal = tropical-of-date − ayanāṁśa(date)
72
+ *
73
+ * Both the tropical longitude (from astronomy-engine) and the ayanāṁśa here use
74
+ * the **true** equinox of date: the tropical longitudes are apparent
75
+ * (true-ecliptic-of-date), so we subtract the **true** ayanāṁśa (with nutation)
76
+ * to keep the equinox definition consistent on both sides.
77
+ */
78
+ export declare function siderealLongitude(date: FlexibleDateTime, body: Body): number;
79
+ /**
80
+ * Sidereal solar rāśi (zodiac sign) index, 0..11.
81
+ * 0 = Meṣa (Aries), 1 = Vṛṣabha, …, 11 = Mīna (Pisces).
82
+ */
83
+ export declare function siderealSunRashi(date: FlexibleDateTime): number;
84
+ //# sourceMappingURL=ayanamsha.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ayanamsha.d.ts","sourceRoot":"","sources":["../src/ayanamsha.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EAML,IAAI,EACJ,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAE1B,qEAAqE;AACrE,eAAO,MAAM,uBAAuB,YAAY,CAAC;AAKjD,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAaD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CAWR;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,IAAI,GACT,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAE/D"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Lahiri (Chitrapakṣa) ayanāṁśa and sidereal longitudes.
3
+ *
4
+ * MODEL
5
+ * -----
6
+ * Lahiri ayanāṁśa = a canonical constant anchor at J2000.0 plus the accumulated
7
+ * **general precession in longitude** from J2000.0 to the date, computed with the
8
+ * **IAU 1976 (Lieske et al. 1977)** precession series. This is the same polynomial
9
+ * given by Meeus, *Astronomical Algorithms* (2nd ed.), ch. 21 ("Precession"), for
10
+ * the precession of ecliptical coordinates — Meeus' general-precession-in-longitude
11
+ * polynomial IS the IAU 1976 series.
12
+ *
13
+ * ayanāṁśa(T) = 23.853222° + pA(T)
14
+ *
15
+ * where T is the time in Julian centuries of Terrestrial Time (TT) from J2000.0 and
16
+ * pA(T) is the accumulated general precession in longitude in degrees.
17
+ *
18
+ * ANCHOR
19
+ * ------
20
+ * At J2000.0 the mean Lahiri ayanāṁśa is **23.853222°**. This is the canonical
21
+ * Swiss Ephemeris `SE_SIDM_LAHIRI` / ICRC value, expressed as the Swiss Ephemeris
22
+ * user-defined mode `swe_set_sid_mode(SE_SIDM_USER, 2451545.0, 23.853222)`. Official
23
+ * Lahiri is "an arbitrary value at an arbitrary epoch propagated by IAU 1976
24
+ * precession" — it is NOT a distinct precession series. There is no festival-date
25
+ * tuning here; the anchor and series are fixed.
26
+ *
27
+ * PRECESSION SERIES (IAU 1976 / Meeus ch. 21), referred to J2000.0 (arcseconds):
28
+ *
29
+ * pA(T) = 5029.0966″·T + 1.11113″·T² − 0.000006″·T³
30
+ *
31
+ * The leading coefficient, 5029.0966″/Julian century, is the IAU 1976 rate of
32
+ * general precession in longitude at J2000.0 (Lieske et al. 1977,
33
+ * A&A 58, 1). (In Meeus' general two-epoch form the T² and T³ coefficients also
34
+ * carry T₀ terms; with the fixed reference epoch J2000.0, T₀ = 0 and they drop out.)
35
+ *
36
+ * MEAN vs TRUE
37
+ * ------------
38
+ * The **mean** ayanāṁśa (precession only) is the core. It matches published Lahiri
39
+ * tables and is what the frozen reference values are checked against.
40
+ *
41
+ * Drik Panchang and most published "display" panchāngas use the **true** ("with
42
+ * nutation") ayanāṁśa: mean + nutation in longitude Δψ. Pass `{ nutation: true }`
43
+ * to get it. The nutation amplitude is ≤ ~17″ (< 0.3 arcmin), so either value
44
+ * satisfies the < 1 arcmin reference tolerance, but the two are distinct and the
45
+ * caller chooses. Festival-date code will pass `{ nutation: true }`.
46
+ */
47
+ import { MakeTime, SunPosition, GeoVector, Ecliptic, e_tilt, Body, } from "astronomy-engine";
48
+ /** Canonical Lahiri anchor: mean ayanāṁśa at J2000.0, in degrees. */
49
+ export const LAHIRI_ANCHOR_J2000_DEG = 23.853222;
50
+ /** Julian days per Julian century. */
51
+ const DAYS_PER_CENTURY = 36525;
52
+ /**
53
+ * Accumulated general precession in longitude pA(T), IAU 1976 / Meeus ch. 21,
54
+ * referred to J2000.0.
55
+ *
56
+ * @param T time in Julian centuries (TT) from J2000.0
57
+ * @returns pA in arcseconds
58
+ */
59
+ function accumulatedPrecessionArcsec(T) {
60
+ return 5029.0966 * T + 1.11113 * T * T - 0.000006 * T * T * T;
61
+ }
62
+ /**
63
+ * Lahiri (Chitrapakṣa) ayanāṁśa in degrees for the given instant.
64
+ *
65
+ * Mean by default (precession only). Pass `{ nutation: true }` for the true
66
+ * ("with nutation") value.
67
+ */
68
+ export function ayanamsha(date, options = {}) {
69
+ const time = MakeTime(date);
70
+ const T = time.tt / DAYS_PER_CENTURY; // Julian centuries TT from J2000.0
71
+ let result = LAHIRI_ANCHOR_J2000_DEG + accumulatedPrecessionArcsec(T) / 3600;
72
+ if (options.nutation) {
73
+ // e_tilt(...).dpsi is nutation in longitude Δψ in arcseconds (IAU 2000B).
74
+ result += e_tilt(time).dpsi / 3600;
75
+ }
76
+ return result;
77
+ }
78
+ /** Normalize an angle in degrees into the range [0, 360). */
79
+ export function normalize360(deg) {
80
+ const r = deg % 360;
81
+ return r < 0 ? r + 360 : r;
82
+ }
83
+ /**
84
+ * Tropical ecliptic longitude **of date** (true equinox of date, apparent) of a
85
+ * body, in degrees.
86
+ *
87
+ * - Sun: `SunPosition(date).elon` — apparent geocentric true-ecliptic-of-date
88
+ * longitude (precession + nutation applied).
89
+ * - Other bodies: `Ecliptic(GeoVector(body, date, true)).elon` — geocentric
90
+ * apparent (aberration-corrected) vector rotated to the true ecliptic of date.
91
+ *
92
+ * Both are referred to the **true** (nutating) equinox of date, so they pair
93
+ * consistently with the **true** ayanāṁśa.
94
+ */
95
+ function tropicalLongitudeOfDate(date, body) {
96
+ if (body === Body.Sun) {
97
+ return SunPosition(date).elon;
98
+ }
99
+ return Ecliptic(GeoVector(body, date, /* aberration */ true)).elon;
100
+ }
101
+ /**
102
+ * Sidereal (Lahiri) ecliptic longitude of a body, in degrees, normalized to
103
+ * [0, 360).
104
+ *
105
+ * sidereal = tropical-of-date − ayanāṁśa(date)
106
+ *
107
+ * Both the tropical longitude (from astronomy-engine) and the ayanāṁśa here use
108
+ * the **true** equinox of date: the tropical longitudes are apparent
109
+ * (true-ecliptic-of-date), so we subtract the **true** ayanāṁśa (with nutation)
110
+ * to keep the equinox definition consistent on both sides.
111
+ */
112
+ export function siderealLongitude(date, body) {
113
+ const tropical = tropicalLongitudeOfDate(date, body);
114
+ const ayan = ayanamsha(date, { nutation: true });
115
+ return normalize360(tropical - ayan);
116
+ }
117
+ /**
118
+ * Sidereal solar rāśi (zodiac sign) index, 0..11.
119
+ * 0 = Meṣa (Aries), 1 = Vṛṣabha, …, 11 = Mīna (Pisces).
120
+ */
121
+ export function siderealSunRashi(date) {
122
+ return Math.floor(siderealLongitude(date, Body.Sun) / 30);
123
+ }
124
+ //# sourceMappingURL=ayanamsha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ayanamsha.js","sourceRoot":"","sources":["../src/ayanamsha.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,QAAQ,EACR,MAAM,EACN,IAAI,GAEL,MAAM,kBAAkB,CAAC;AAE1B,qEAAqE;AACrE,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAEjD,sCAAsC;AACtC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAW/B;;;;;;GAMG;AACH,SAAS,2BAA2B,CAAC,CAAS;IAC5C,OAAO,SAAS,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,IAAsB,EACtB,UAA4B,EAAE;IAE9B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,gBAAgB,CAAC,CAAC,mCAAmC;IACzE,IAAI,MAAM,GAAG,uBAAuB,GAAG,2BAA2B,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAE7E,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,0EAA0E;QAC1E,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;IACrC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC;IACpB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,uBAAuB,CAAC,IAAsB,EAAE,IAAU;IACjE,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IACD,OAAO,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACrE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAsB,EACtB,IAAU;IAEV,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO,YAAY,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAsB;IACrD,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * src/eclipses.ts — grahaṇa (solar & lunar eclipses).
3
+ *
4
+ * Wraps astronomy-engine's eclipse search to enumerate the eclipses of a given
5
+ * year, with their contact timings, type, local visibility, and sūtak window.
6
+ *
7
+ * Conventions:
8
+ * • A grahaṇa "counts" for a location only when it is VISIBLE there — a lunar
9
+ * eclipse when the Moon is above the horizon during the umbral/penumbral
10
+ * phase, a solar eclipse when the Sun is above the horizon and the disc is
11
+ * obscured. Sūtak (the abstention period) applies only to a visible eclipse.
12
+ * • Sūtak begins before first contact: 9 hours (3 prahara) for a Chandra
13
+ * Grahaṇa, 12 hours (4 prahara) for a Sūrya Grahaṇa, and runs to mokṣa (last
14
+ * contact). This is the widely-used Smārta convention.
15
+ *
16
+ * Timings are UTC instants. Lunar contact times are derived from the eclipse
17
+ * peak and astronomy-engine's semi-durations (sd_*, in minutes).
18
+ */
19
+ import { type GeoLocation, type IsoWindow } from "./time.js";
20
+ /**
21
+ * Eclipse type (grahaṇa kind), as a local string union so consumers can name
22
+ * the discriminant without reaching into the `astronomy-engine` dependency.
23
+ */
24
+ export type GrahanKind = "penumbral" | "partial" | "total" | "annular";
25
+ export interface LunarEclipse {
26
+ kind: GrahanKind;
27
+ /** ISO-UTC instant of greatest eclipse. */
28
+ peak: string;
29
+ penumbral: IsoWindow;
30
+ /** Umbral partial phase; null for a penumbral-only eclipse. */
31
+ partial: IsoWindow | null;
32
+ /** Totality; null unless the eclipse is total. */
33
+ total: IsoWindow | null;
34
+ /** Whether the Moon is above the horizon at peak in `loc` (null if no loc). */
35
+ visible: boolean | null;
36
+ /** Sūtak window (9h before umbral first contact → mokṣa) when visible; else null. */
37
+ sutak: IsoWindow | null;
38
+ }
39
+ export interface SolarEclipse {
40
+ kind: GrahanKind;
41
+ /** ISO-UTC instant of greatest eclipse (global). */
42
+ peak: string;
43
+ /** Local circumstances at `loc` when the eclipse is seen there; else null. */
44
+ local: {
45
+ partialStart: string;
46
+ peak: string;
47
+ partialEnd: string;
48
+ obscuration: number;
49
+ } | null;
50
+ visible: boolean | null;
51
+ /** Sūtak window (12h before local first contact → last contact); else null. */
52
+ sutak: IsoWindow | null;
53
+ }
54
+ /** All lunar eclipses whose peak falls in calendar `year` (UTC). */
55
+ export declare function lunarEclipses(year: number, loc?: GeoLocation): LunarEclipse[];
56
+ /** All solar eclipses whose greatest-eclipse instant falls in calendar `year`. */
57
+ export declare function solarEclipses(year: number, loc?: GeoLocation): SolarEclipse[];
58
+ //# sourceMappingURL=eclipses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eclipses.d.ts","sourceRoot":"","sources":["../src/eclipses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAeH,OAAO,EAAoB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAQ/E;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAIvE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,+DAA+D;IAC/D,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,qFAAqF;IACrF,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9F,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,+EAA+E;IAC/E,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CACzB;AA4BD,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,CA6C7E;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,CAuC7E"}