panchang-ts 4.3.1 → 5.0.0
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/README.md +890 -228
- package/dist/calendar/eclipsesTable.cjs +13 -358
- package/dist/calendar/eclipsesTable.d.cts +33 -23
- package/dist/calendar/eclipsesTable.d.ts +33 -23
- package/dist/calendar/eclipsesTable.js +10 -357
- package/dist/calendar/festivalsTable.cjs +48 -25335
- package/dist/calendar/festivalsTable.d.cts +33 -23
- package/dist/calendar/festivalsTable.d.ts +33 -23
- package/dist/calendar/festivalsTable.js +45 -25334
- package/dist/calendar/moonPhasesTable.cjs +55 -6791
- package/dist/calendar/moonPhasesTable.d.cts +33 -21
- package/dist/calendar/moonPhasesTable.d.ts +33 -21
- package/dist/calendar/moonPhasesTable.js +52 -6790
- package/dist/{eclipsesTableTypes-D-tSQMQd.d.cts → eclipsesTableTypes-BFYWRA44.d.cts} +11 -1
- package/dist/{eclipsesTableTypes-D-tSQMQd.d.ts → eclipsesTableTypes-BFYWRA44.d.ts} +11 -1
- package/dist/festivalsTableTypes-CzDgY4zS.d.cts +100 -0
- package/dist/festivalsTableTypes-CzDgY4zS.d.ts +100 -0
- package/dist/index.cjs +29098 -5938
- package/dist/index.d.cts +1102 -255
- package/dist/index.d.ts +1102 -255
- package/dist/index.js +29081 -5939
- package/dist/moonPhasesTableTypes-9XLibNCZ.d.cts +103 -0
- package/dist/moonPhasesTableTypes-9XLibNCZ.d.ts +103 -0
- package/dist/muhurta/muhurtaTable.cjs +53 -0
- package/dist/muhurta/muhurtaTable.d.cts +47 -0
- package/dist/muhurta/muhurtaTable.d.ts +47 -0
- package/dist/muhurta/muhurtaTable.js +47 -0
- package/dist/muhurtaTableTypes-CgBom297.d.cts +81 -0
- package/dist/muhurtaTableTypes-CgBom297.d.ts +81 -0
- package/package.json +23 -6
- package/dist/festivalsTableTypes-GePaspwd.d.cts +0 -52
- package/dist/festivalsTableTypes-GePaspwd.d.ts +0 -52
- package/dist/moonPhasesTableTypes-TH0T5rVD.d.cts +0 -56
- package/dist/moonPhasesTableTypes-TH0T5rVD.d.ts +0 -56
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Pure TypeScript Hindu Panchang (almanac), Jyotish, and Birth Chart calculations.
|
|
6
6
|
Zero native dependencies. Works offline in React Native (Hermes), Node.js, and browsers.
|
|
7
7
|
|
|
8
|
-
**Fast** (~0.
|
|
8
|
+
**Fast** (~0.25 ms trimmed, ~0.41 ms full) · **Typed** (full TypeScript) · **Offline** (pure JS math) · **8,368 tests across 121 files**
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -31,26 +31,84 @@ const result = getDailyPanchang(
|
|
|
31
31
|
// → DailyPanchangResult | null. Null only at polar latitudes where
|
|
32
32
|
// sunrise can't be computed. Anywhere else, narrow with `if (!result) return;`.
|
|
33
33
|
|
|
34
|
-
console.log(result!.tithis[0].name); // "Krishna Chaturdashi"
|
|
35
|
-
console.log(result!.nakshatras[0].name); // "Mrigashira"
|
|
36
|
-
console.log(result!.vara.name); // "Mangalawara"
|
|
37
|
-
console.log(result!.chandramasa.name); // "Magha"
|
|
38
|
-
console.log(result!.samvat.vikramSamvat); // 2081
|
|
34
|
+
console.log(result!.angas.tithis[0].name); // "Krishna Chaturdashi"
|
|
35
|
+
console.log(result!.angas.nakshatras[0].name); // "Mrigashira"
|
|
36
|
+
console.log(result!.angas.vara.name); // "Mangalawara"
|
|
37
|
+
console.log(result!.calendar.chandramasa.name); // "Magha"
|
|
38
|
+
console.log(result!.calendar.samvat.vikramSamvat); // 2081
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### Reading Output Times
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
Every `Date` in a result is a **real instant** — `.getTime()` is the correct
|
|
44
|
+
epoch millisecond. Every instant has a `*Local` companion: an offset-carrying
|
|
45
|
+
ISO 8601 string, which is what you want for display.
|
|
45
46
|
|
|
46
47
|
```typescript
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
result!.sun.rise; // Date — 2025-01-14T01:39:44.172Z (the actual moment)
|
|
49
|
+
result!.sun.riseLocal; // "2025-01-14T07:09:44.172+05:30"
|
|
50
|
+
result!.inauspicious.rahuKalam.start; // Date
|
|
51
|
+
result!.inauspicious.rahuKalam.startLocal;
|
|
52
|
+
|
|
53
|
+
// Just the wall clock:
|
|
54
|
+
result!.sun.riseLocal.slice(11, 16); // "07:09"
|
|
55
|
+
|
|
56
|
+
// Anything else works too, because the Date is genuinely correct:
|
|
57
|
+
new Intl.DateTimeFormat('en-IN', { timeZone: 'Asia/Kolkata', timeStyle: 'short' })
|
|
58
|
+
.format(result!.sun.rise); // "7:09 am"
|
|
59
|
+
Temporal.Instant.from(result!.sun.riseLocal);
|
|
50
60
|
```
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
For an instant you derive yourself, `formatInZone` renders it the same way:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { formatInZone } from 'panchang-ts';
|
|
66
|
+
const noon = new Date((result!.sun.rise.getTime() + result!.sun.set.getTime()) / 2);
|
|
67
|
+
formatInZone(noon, result!.timezone.offsetMinutes); // "2025-01-14T12:27:31.086+05:30"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Changed in v5 — this is the breaking change most likely to affect you.**
|
|
71
|
+
> Through 4.x every published `Date` was the true instant *shifted* by the UTC
|
|
72
|
+
> offset, and the README told you to read it back with `getUTC*`. That worked
|
|
73
|
+
> only as long as you did nothing else with the value: `JSON.stringify` emitted
|
|
74
|
+
> a wrong instant labelled `Z`, `Intl` with a `timeZone` rendered 12:39 pm for
|
|
75
|
+
> an 07:09 am sunrise, and any comparison, diff, database write, date-fns or
|
|
76
|
+
> Temporal call was off by the offset.
|
|
77
|
+
>
|
|
78
|
+
> Migration is mechanical: `x.getUTCHours()` → read `xLocal`, or format the
|
|
79
|
+
> instant. See [Upgrading from 4.x](#upgrading-from-4x).
|
|
80
|
+
|
|
81
|
+
`moon.rise` / `moon.set` can be `null` — the Moon occasionally does not rise or
|
|
82
|
+
set on a given calendar day, which is normal. `moon.riseLocal` / `moon.setLocal`
|
|
83
|
+
are `null` exactly when they are.
|
|
84
|
+
|
|
85
|
+
### The result is grouped
|
|
86
|
+
|
|
87
|
+
`DailyPanchangResult` has seven groups plus a handful of top-level fields.
|
|
88
|
+
Through 4.x it was ~50 flat fields; the groups are what tell you where to look.
|
|
89
|
+
|
|
90
|
+
| Group | Holds |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `sun` | `rise` / `set` / `nextRise` (+ `*Local`), day and night lengths, the Sun's `siderealLongitude` and `nakshatra` |
|
|
93
|
+
| `moon` | `rise` / `set` (+ `*Local`), the Moon's `siderealLongitude` and `rashi` |
|
|
94
|
+
| `angas` | the five limbs — `tithis`, `nakshatras`, `yogas`, `karanas`, `vara` |
|
|
95
|
+
| `calendar` | `masa` (solar), `chandramasa` (lunar), `samvat` |
|
|
96
|
+
| `muhurtas` | `abhijit`, `brahma`, `vijaya`, `godhuli`, `nishita`, `amritKala`, `madhyahna`, `pratahSandhya`, `sayahnaSandhya`, `doGhati` |
|
|
97
|
+
| `inauspicious` | `rahuKalam`, `gulikaKalam`, `yamaganda`, `durMuhurta`, `varjyam`, `bhadra`, `gandaMula`, `panchaka`, `panchakaInfo`, `panchakaRahita` |
|
|
98
|
+
| `periods` | `choghadiya`, `hora`, `gowri` |
|
|
99
|
+
|
|
100
|
+
Top level: `date`, `location`, `timezone`, `ayanamsa`, `specialYogas`,
|
|
101
|
+
`anandadiYoga`, `festivals`, `eclipse`, `chandraBalam`, `tarabala`.
|
|
102
|
+
|
|
103
|
+
**Nothing is optional.** Every field is always present. A value that does not
|
|
104
|
+
apply is `null`; a collection that does not apply is `[]`. That holds whether
|
|
105
|
+
the reason is the domain (no Bhadra window today) or your options (you did not
|
|
106
|
+
pass `janmaRashi`, so `chandraBalam` is `null`) — the result *shape* never
|
|
107
|
+
depends on what you passed.
|
|
108
|
+
|
|
109
|
+
`getInstantPanchang` uses the same group names for the subset an instant can
|
|
110
|
+
answer: `sun`, `moon`, `angas`, `calendar`, `inauspicious`. There is no
|
|
111
|
+
`muhurtas` or `periods`, because those are properties of a Hindu *day*.
|
|
54
112
|
|
|
55
113
|
### `getDailyPanchang` vs `getInstantPanchang`
|
|
56
114
|
|
|
@@ -66,6 +124,329 @@ Ekadashi split. For reliable festival dating, use `getDailyPanchang`.
|
|
|
66
124
|
|
|
67
125
|
---
|
|
68
126
|
|
|
127
|
+
## Upgrading from 4.x
|
|
128
|
+
|
|
129
|
+
Three changes move numbers that 4.x produced, and one option is gone.
|
|
130
|
+
|
|
131
|
+
### Lahiri ayanamsa corrected by +38″
|
|
132
|
+
|
|
133
|
+
The library's Lahiri constant sat 38 arcseconds behind DrikPanchang's — it used
|
|
134
|
+
`23.853211°` at J2000 (the widely-repeated 23° 51′ 11.6″ figure) where Drik
|
|
135
|
+
computes `23.863801°`. The replacement was solved from Drik's own published
|
|
136
|
+
values across 1950–2050, which agree on it to within 0.01″ — a century-wide
|
|
137
|
+
baseline, so the precession polynomial is pinned too, not just the epoch
|
|
138
|
+
constant. Every sidereal output moves with it:
|
|
139
|
+
|
|
140
|
+
| Output | Effect |
|
|
141
|
+
|---|---|
|
|
142
|
+
| Nakshatra end-times | ~69 s later than 4.x (carries the ayanamsa once) |
|
|
143
|
+
| Yoga end-times | ~129 s later than 4.x (carries it twice) |
|
|
144
|
+
| Planetary longitudes, rashi, pada, lagna, divisionals, dashas | shifted +0.0106° |
|
|
145
|
+
| Tithi / karana end-times | unchanged — Moon − Sun cancels the ayanamsa |
|
|
146
|
+
| Raman / KP / True Chitra / Thirukanitham | moved by the same +38″; their offsets from Lahiri are preserved |
|
|
147
|
+
|
|
148
|
+
Worst-case end-time drift vs Drik dropped from 131 s to 60 s, and the sign split
|
|
149
|
+
by ayanamsa exposure — nakshatra and yoga early, tithi and karana late — is gone.
|
|
150
|
+
If you have snapshot tests or cached charts from 4.x, expect them to need
|
|
151
|
+
re-pinning.
|
|
152
|
+
|
|
153
|
+
### ΔT now uses measurement, so every published time moves ~6 s
|
|
154
|
+
|
|
155
|
+
4.x took ΔT (TT − UT) entirely from Espenak–Meeus. Its post-2005 branches are an
|
|
156
|
+
extrapolation published in 2006, and Earth's rotation did not follow it — by
|
|
157
|
+
2026 the model reads about **5.9 s high**, drifting a further ~0.6 s each year.
|
|
158
|
+
Because this library reports *times*, that lands directly on published values.
|
|
159
|
+
|
|
160
|
+
v5 takes ΔT from the leap-second chain (`32.184 + (TAI − UTC)`, exact, and
|
|
161
|
+
within the 0.9 s band leap seconds maintain) wherever ΔT has actually been
|
|
162
|
+
measured, and resumes Espenak–Meeus beyond it carrying the offset it had
|
|
163
|
+
accrued. Against JPL Horizons the measured era now agrees to **0.005 s** at
|
|
164
|
+
every decade from 1980, where 4.x was seconds out.
|
|
165
|
+
|
|
166
|
+
| Output | Effect |
|
|
167
|
+
|---|---|
|
|
168
|
+
| Tithi / nakshatra / yoga / karana end-times | ~5.7 s later for 2025 dates, growing with the model's drift |
|
|
169
|
+
| Sankranti and other transit instants | same shift — it is one uniform correction, not per-element |
|
|
170
|
+
| Sunrise / sunset / moonrise / moonset | barely moved — the error scales against the 15°/hr sky rotation |
|
|
171
|
+
| Dates a panchang element is *filed under* | unchanged except where a transit sits within seconds of sunrise |
|
|
172
|
+
|
|
173
|
+
That last row is the one to know about. `computeSankrantisForYear` publishes a
|
|
174
|
+
date, and the date is decided by whether the transit precedes sunrise. The 2025
|
|
175
|
+
Tula Sankranti at Reykjavik is such a case: it still falls on Oct 16, but its
|
|
176
|
+
margin narrowed from 7.1 s to 1.4 s. Locations at high latitude with a transit
|
|
177
|
+
near sunrise are where a day could flip.
|
|
178
|
+
|
|
179
|
+
### Instant-mode vara was wrong after ~19:00
|
|
180
|
+
|
|
181
|
+
`getInstantPanchang` located sunrise by searching forward from `date − 12 h`.
|
|
182
|
+
For an evening instant that start point is already past the morning's sunrise,
|
|
183
|
+
so it found *tomorrow's* and rolled the weekday back a day. Any query after
|
|
184
|
+
roughly 7 pm returned the previous vara — and with it the wrong Rahu Kalam,
|
|
185
|
+
Gulika Kalam, Yamaganda, Choghadiya, Hora, Anandadi yoga and special yogas.
|
|
186
|
+
`getDailyPanchang` was never affected. If you cached instant-mode results from
|
|
187
|
+
4.x for evening timestamps, discard them.
|
|
188
|
+
|
|
189
|
+
### `precision` removed
|
|
190
|
+
|
|
191
|
+
`precision: 'standard' | 'high'` and the `Precision` type no longer exist.
|
|
192
|
+
Element transitions are now solved by secant iteration, which converges to the
|
|
193
|
+
root rather than stopping at a fixed tolerance, so there is nothing left for the
|
|
194
|
+
option to select — and the tighter setting no longer buys anything. Removing it
|
|
195
|
+
from your options object is the whole migration; leaving it in is a type error,
|
|
196
|
+
not a silent no-op.
|
|
197
|
+
|
|
198
|
+
### Sunrise is single-valued per location-day
|
|
199
|
+
|
|
200
|
+
Solar rise/set is computed from a canonical anchor and cached per location-day,
|
|
201
|
+
so it no longer depends on which instant the caller happened to start searching
|
|
202
|
+
from. Values shift by ≤108 ms vs 4.x, and two calls for the same day now agree
|
|
203
|
+
exactly instead of differing by up to 109 ms. Windows derived proportionally
|
|
204
|
+
from the day length — Varjyam, Bhadra, the slot systems — move by a little more
|
|
205
|
+
than that. This removes an inconsistency rather than introducing an
|
|
206
|
+
approximation: 4.x returned a different sunrise depending on which caller asked.
|
|
207
|
+
|
|
208
|
+
### Moonrise and moonset are single-valued per location-day
|
|
209
|
+
|
|
210
|
+
The same treatment sunrise received, now applied to the Moon. `getMoonrise` /
|
|
211
|
+
`getMoonset` resolve through a canonical per-UTC-day cache, so an event has one
|
|
212
|
+
timestamp no matter which caller asks or from which instant they searched.
|
|
213
|
+
Published `moon.rise` / `moon.set` shift by **≤182 ms** vs 4.x. Nothing else in the
|
|
214
|
+
result moves — verified over 11,520 daily results across six locations and three
|
|
215
|
+
centuries: zero changes to any index, name, boolean, festival date or other
|
|
216
|
+
timestamp.
|
|
217
|
+
|
|
218
|
+
### `read*` for tables, `compute*` for the engine
|
|
219
|
+
|
|
220
|
+
`getFestivalsForYear` read a pre-built table; `getFestivalsInRange` ran the
|
|
221
|
+
engine. Two near-identical names, completely different inputs and semantics. v5
|
|
222
|
+
settles one convention across all four families — festivals, eclipses, moon
|
|
223
|
+
phases and muhurta:
|
|
224
|
+
|
|
225
|
+
| 4.x | v5 | what it does |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `getFestivalsForYear` | `readFestivalsForYear` | reads a table |
|
|
228
|
+
| `getFestivalsForDate` | `readFestivalsForDate` | reads a table |
|
|
229
|
+
| `getFestivalsYearRange` | `readFestivalsYearRange` | reads a table |
|
|
230
|
+
| `getEclipsesForYear` / `ForDate` / `YearRange` | `readEclipsesForYear` / … | reads a table |
|
|
231
|
+
| `getMoonPhasesForYear` / `ForDate` / `YearRange` | `readMoonPhasesForYear` / … | reads a table |
|
|
232
|
+
| — | `readMuhurtaForYear` / `ForDate` / `YearRange` | **new** — reads a table |
|
|
233
|
+
| — | `readBestMuhurtaDays` | **new** — top-scoring days |
|
|
234
|
+
| `getFestivalsInRange` | `computeFestivalsInRange` | runs the engine |
|
|
235
|
+
| `getEclipsesInRange` | `computeEclipsesInRange` | runs the engine |
|
|
236
|
+
| `getMoonPhasesInRange` | `computeMoonPhasesInRange` | runs the engine |
|
|
237
|
+
| `findAuspiciousDates` | `computeAuspiciousDatesInRange` | runs the engine |
|
|
238
|
+
| `getEkadashiDatesForYear` | `computeEkadashiDatesForYear` | runs the engine |
|
|
239
|
+
| `getSankrantisForYear` | `computeSankrantisForYear` | runs the engine |
|
|
240
|
+
|
|
241
|
+
**Every 4.x name still works** — they are deprecated aliases pointing at the same
|
|
242
|
+
functions, kept through v5. Nothing breaks today; the old names will go in v6.
|
|
243
|
+
|
|
244
|
+
New single-year entry points, the shape most callers reach for first:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
computeFestivalsForYear(2027, location, { timezone: 330 });
|
|
248
|
+
computeEclipsesForYear(2027, location, { timezone: 330 });
|
|
249
|
+
computeMoonPhasesForYear(2027, { timezone: 330 });
|
|
250
|
+
computeAuspiciousDatesForYear(2027, vivahRule, location, { timezone: 330 });
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `buildMuhurtaTable` + the `panchang-ts/muhurta` subpath
|
|
254
|
+
|
|
255
|
+
Festivals, eclipses and moon phases each had a builder and an engine-free
|
|
256
|
+
reader; muhurta had neither, so finding auspicious dates meant running the full
|
|
257
|
+
engine on device for every query. v5 completes the family:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// build once (build time, or first launch), then persist the JSON
|
|
261
|
+
import { buildMuhurtaTable, vivahRule } from 'panchang-ts';
|
|
262
|
+
const table = buildMuhurtaTable({
|
|
263
|
+
rule: vivahRule, location: DELHI, timezoneOffsetMinutes: 330,
|
|
264
|
+
startYear: 2026, endYear: 2031,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// read it back with no astronomy code in the bundle (~1.7 KB)
|
|
268
|
+
import { readBestMuhurtaDays } from 'panchang-ts/muhurta';
|
|
269
|
+
readBestMuhurtaDays(table, 5); // top 5 days, highest score first
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Only days that pass the rule are stored unless you pass `includeFailures: true`.
|
|
273
|
+
|
|
274
|
+
### Built tables are dictionary-encoded and carry `key`
|
|
275
|
+
|
|
276
|
+
`build*Table` now emits a `_dict` of unique entries with each day holding
|
|
277
|
+
indices, rather than repeating every localized string at every occurrence. A
|
|
278
|
+
10-year festival table goes from 315 KB to **89.9 KB (28.5%)**; a 10-year
|
|
279
|
+
moon-phase table from 106 KB to **29.7 KB (28.0%)**. Resolved output is
|
|
280
|
+
identical — verified across every year and both locales.
|
|
281
|
+
|
|
282
|
+
Gzip already hid most of this on the wire, so the win is **parse time and
|
|
283
|
+
resident memory**, which is the constraint that actually bites on Hermes.
|
|
284
|
+
|
|
285
|
+
Festival table entries also gained the stable `key` the engine has been
|
|
286
|
+
returning since 4.x, so a table is no longer both larger *and* less useful than
|
|
287
|
+
engine output.
|
|
288
|
+
|
|
289
|
+
**Tables you already cached still read.** The reader detects the format, so a v1
|
|
290
|
+
table built with 4.x keeps working; only `key` is unavailable from it, and comes
|
|
291
|
+
back as `''`.
|
|
292
|
+
|
|
293
|
+
### Published `Date`s are real instants — the flagship change
|
|
294
|
+
|
|
295
|
+
```diff
|
|
296
|
+
- result.sunrise.getTime() // NOT when sunrise happened (off by the UTC offset)
|
|
297
|
+
- result.sunrise.getUTCHours() // the documented 4.x idiom
|
|
298
|
+
+ result.sun.rise.getTime() // correct epoch ms
|
|
299
|
+
+ result.sun.riseLocal // "2025-01-14T07:09:44.172+05:30"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Applies to **every** `Date` in `DailyPanchangResult` and to every `TimePeriod` —
|
|
303
|
+
`sun.rise`, `sun.set`, `sun.nextRise`, `moon.rise`, `moon.set`, all the muhurtas
|
|
304
|
+
and inauspicious periods, all four slot systems, the element
|
|
305
|
+
`startTime`/`endTime` arrays, the eclipse contacts and sutak window. Each gains
|
|
306
|
+
a `*Local` companion: `sun.riseLocal`, `inauspicious.rahuKalam.startLocal`,
|
|
307
|
+
`angas.tithis[0].endTimeLocal`, and so on.
|
|
308
|
+
|
|
309
|
+
| you had | you now write |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `r.sun.rise.getUTCHours()` | `r.sun.riseLocal.slice(11, 13)` |
|
|
312
|
+
| `` `${h}:${m}` `` from `getUTC*` | `r.sun.riseLocal.slice(11, 16)` |
|
|
313
|
+
| `r.inauspicious.rahuKalam.start.getUTCHours()` | `r.inauspicious.rahuKalam.startLocal.slice(11, 13)` |
|
|
314
|
+
| `r.angas.tithis[0].endTime` for display | `r.angas.tithis[0].endTimeLocal` |
|
|
315
|
+
| a `Date` you derived yourself | `formatInZone(d, r.timezone.offsetMinutes)` |
|
|
316
|
+
|
|
317
|
+
`getInstantPanchang` results carry **no** `*Local` fields: that call takes no
|
|
318
|
+
timezone, so there is no zone to render a wall clock in.
|
|
319
|
+
|
|
320
|
+
**Cost:** rendering the strings adds ~0.04 ms per daily panchang — invisible on
|
|
321
|
+
a cold call (0.7885 → 0.7924 ms) and ~20% of a fully cached warm one
|
|
322
|
+
(0.176 → 0.215 ms). Both pairs are the tree measured against itself when the
|
|
323
|
+
change landed, mid-Phase-36; the release finally warms to **0.17 ms**, against
|
|
324
|
+
published 4.3.1's **6.20 ms**.
|
|
325
|
+
|
|
326
|
+
### `result.timezone` is now an object
|
|
327
|
+
|
|
328
|
+
```diff
|
|
329
|
+
- result.timezone // 330
|
|
330
|
+
+ result.timezone // { offsetMinutes: 330, zone: 'Asia/Kolkata' }
|
|
331
|
+
+ result.timezone.offsetMinutes
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`options.timezone` has always accepted `number | string`, but the result carried
|
|
335
|
+
only a number — so passing `'America/New_York'` produced a result that could not
|
|
336
|
+
say which zone produced it. `zone` is present only when you passed a zone name.
|
|
337
|
+
|
|
338
|
+
**The DST limit, now stated explicitly.** The offset is resolved **once per
|
|
339
|
+
call** from a reference date, so a Hindu day containing a DST transition is
|
|
340
|
+
computed at a single offset throughout. Correct for almost every day; on the one
|
|
341
|
+
or two transition days a year, times after the jump are shifted by its size.
|
|
342
|
+
4.x documented this as a blanket "DST resolves automatically", which was not the
|
|
343
|
+
whole truth.
|
|
344
|
+
|
|
345
|
+
### The result object is grouped
|
|
346
|
+
|
|
347
|
+
`DailyPanchangResult` had ~50 flat top-level fields mixing five categories.
|
|
348
|
+
v5 sorts them into seven groups — see [The result is grouped](#the-result-is-grouped)
|
|
349
|
+
for the full table. This is a large break, and it lands in the same release as
|
|
350
|
+
the `Date` change on purpose: migrating both at once is one pass over your read
|
|
351
|
+
sites, not two.
|
|
352
|
+
|
|
353
|
+
Every rename, in full:
|
|
354
|
+
|
|
355
|
+
| 4.x | v5 |
|
|
356
|
+
|---|---|
|
|
357
|
+
| `sunrise` / `sunset` / `nextSunrise` | `sun.rise` / `sun.set` / `sun.nextRise` |
|
|
358
|
+
| `sunriseLocal` / `sunsetLocal` / `nextSunriseLocal` | `sun.riseLocal` / `sun.setLocal` / `sun.nextRiseLocal` |
|
|
359
|
+
| `dayDurationMinutes` / `nightDurationMinutes` | `sun.dayDurationMinutes` / `sun.nightDurationMinutes` |
|
|
360
|
+
| `dinamanaMinutes` / `ratrimanaMinutes` | `sun.dinamanaMinutes` / `sun.ratrimanaMinutes` |
|
|
361
|
+
| `siderealSunAtSunrise` | `sun.siderealLongitude` |
|
|
362
|
+
| `suryaNakshatra` | `sun.nakshatra` |
|
|
363
|
+
| `moonrise` / `moonset` | `moon.rise` / `moon.set` |
|
|
364
|
+
| `moonriseLocal` / `moonsetLocal` | `moon.riseLocal` / `moon.setLocal` |
|
|
365
|
+
| `siderealMoonAtSunrise` | `moon.siderealLongitude` |
|
|
366
|
+
| `chandraRashi` | `moon.rashi` |
|
|
367
|
+
| `tithis` / `nakshatras` / `yogas` / `karanas` / `vara` | `angas.*` (same names) |
|
|
368
|
+
| `masa` / `chandramasa` / `samvat` | `calendar.*` (same names) |
|
|
369
|
+
| `abhijitMuhurta` / `brahmaMuhurta` / `vijayaMuhurta` | `muhurtas.abhijit` / `muhurtas.brahma` / `muhurtas.vijaya` |
|
|
370
|
+
| `godhuliMuhurta` / `nishitaMuhurta` | `muhurtas.godhuli` / `muhurtas.nishita` |
|
|
371
|
+
| `amritKala` / `madhyahna` / `pratahSandhya` / `sayahnaSandhya` | `muhurtas.*` (same names) |
|
|
372
|
+
| `doGhatiMuhurta` | `muhurtas.doGhati` |
|
|
373
|
+
| `rahuKalam` / `gulikaKalam` / `yamaganda` / `durMuhurta` | `inauspicious.*` (same names) |
|
|
374
|
+
| `varjyam` / `bhadra` / `gandaMula` / `panchaka` / `panchakaRahita` | `inauspicious.*` (same names) |
|
|
375
|
+
| `choghadiya` / `hora` | `periods.choghadiya` / `periods.hora` |
|
|
376
|
+
| `gowriPanchangam` | `periods.gowri` |
|
|
377
|
+
| `eclipse.magnitude` (disc *area*) | `eclipse.obscuration` — same value; the new `eclipse.magnitude` is the *diameter* fraction catalogues publish, and is negative for a penumbral lunar eclipse |
|
|
378
|
+
|
|
379
|
+
Unmoved: `date`, `location`, `timezone`, `ayanamsa`, `specialYogas`,
|
|
380
|
+
`anandadiYoga`, `festivals`, `eclipse`, `chandraBalam`, `tarabala`.
|
|
381
|
+
|
|
382
|
+
For `getInstantPanchang`: `tithi` / `nakshatra` / `yoga` / `karana` / `vara` →
|
|
383
|
+
`angas.*`; `siderealSun` → `sun.siderealLongitude`; `siderealMoon` →
|
|
384
|
+
`moon.siderealLongitude`; `suryaNakshatra` → `sun.nakshatra`; `chandraRashi` →
|
|
385
|
+
`moon.rashi`; `chandramasa` / `samvat` → `calendar.*`; `panchaka` / `gandaMula`
|
|
386
|
+
→ `inauspicious.*`.
|
|
387
|
+
|
|
388
|
+
### One rule for "not applicable": always present, `null` or `[]`
|
|
389
|
+
|
|
390
|
+
4.x used three conventions and you could not predict which you would get —
|
|
391
|
+
`| null` for `bhadra` / `varjyam` / `eclipse`, `?`-optional for `chandraBalam` /
|
|
392
|
+
`tarabala`, and an empty array for `panchakaRahita` / `festivals`. v5 has one
|
|
393
|
+
rule: **every field is always present**, a value that does not apply is `null`,
|
|
394
|
+
and a collection that does not apply is `[]`.
|
|
395
|
+
|
|
396
|
+
```diff
|
|
397
|
+
- if ('chandraBalam' in r) … // 4.x: field absent without janmaRashi
|
|
398
|
+
- r.chandraBalam!.house // and the `!` was mandatory
|
|
399
|
+
+ if (r.chandraBalam !== null) … // v5: always present, null when unasked
|
|
400
|
+
+ r.chandraBalam?.house
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Only `chandraBalam` and `tarabala` changed behaviour; everything else already
|
|
404
|
+
followed the rule. `toBeUndefined()`-style checks against them become
|
|
405
|
+
`toBeNull()`.
|
|
406
|
+
|
|
407
|
+
### `suryaNakshatra` is typed as a nakshatra, not a rashi
|
|
408
|
+
|
|
409
|
+
Now published as `sun.nakshatra`. Its `index` has always been 0..26 (Ashwini …
|
|
410
|
+
Revati). Its *type* said `RashiInfo`, documented "0 = Mesha … 11 = Meena", so
|
|
411
|
+
anyone indexing a 12-element rashi array by it got silent garbage for two thirds
|
|
412
|
+
of the year. The runtime value is unchanged; the type is now
|
|
413
|
+
`NakshatraIndexInfo` and TypeScript will point at the misuse.
|
|
414
|
+
|
|
415
|
+
### `_debug` removed
|
|
416
|
+
|
|
417
|
+
`DailyPanchangResult._debug` was declared in the published type and written
|
|
418
|
+
nowhere in the library. It never carried data. If you referenced it, it was
|
|
419
|
+
always `undefined`.
|
|
420
|
+
|
|
421
|
+
### Alias fields documented rather than removed
|
|
422
|
+
|
|
423
|
+
`sun.dinamanaMinutes` / `sun.dayDurationMinutes` and `sun.ratrimanaMinutes` /
|
|
424
|
+
`sun.nightDurationMinutes` are the same numbers under classical and English
|
|
425
|
+
names. Both pairs stay — consumers use both vocabularies — and the types now say
|
|
426
|
+
plainly that they are aliases, never independently computed.
|
|
427
|
+
|
|
428
|
+
`calendar.chandramasa` keeps its casing beside `moon.rashi` and `sun.nakshatra`.
|
|
429
|
+
Renaming it would break every consumer for a casing preference. The type now
|
|
430
|
+
documents that `calendar.masa` is the **solar** month and `calendar.chandramasa`
|
|
431
|
+
the **lunar** one, which was previously left to guesswork.
|
|
432
|
+
|
|
433
|
+
### Additive, but worth knowing
|
|
434
|
+
|
|
435
|
+
- `EclipseInfo` / `EclipseSubtype` are now exported. 4.x shipped
|
|
436
|
+
`getUpcomingLunarEclipse` and friends without the type they return.
|
|
437
|
+
- `festivals[].key` — stable, language-independent festival id. Match on this,
|
|
438
|
+
never on `name`.
|
|
439
|
+
- `bhadra.locationName` — localized display name; `bhadra.location` stays the
|
|
440
|
+
machine-readable key.
|
|
441
|
+
- `MuhurtaScore.factors` — structured scoring inputs alongside English `reasons`.
|
|
442
|
+
- `BirthChart.byPlanet` — the nine placements keyed by graha.
|
|
443
|
+
- `eclipse.description` is now localized. Under `language: 'hi'` it was
|
|
444
|
+
previously emitted in English, including inside `festivals[].description`.
|
|
445
|
+
- `sections` on `getDailyPanchang` — opt into a narrower, cheaper call. See
|
|
446
|
+
[Performance](#performance).
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
69
450
|
## Features at a Glance
|
|
70
451
|
|
|
71
452
|
| Category | Features |
|
|
@@ -112,16 +493,16 @@ over per-feature helpers.
|
|
|
112
493
|
```typescript
|
|
113
494
|
const r = getDailyPanchang(date, location, { timezone: 330 })!;
|
|
114
495
|
|
|
115
|
-
r.tithis.forEach(t => console.log(t.name, t.paksha, t.completionPercentage, t.endTime));
|
|
116
|
-
r.nakshatras.forEach(n => console.log(n.name, n.pada, n.endTime));
|
|
117
|
-
r.yogas.forEach(y => console.log(y.name, y.endTime));
|
|
118
|
-
r.karanas.forEach(k => console.log(k.name, k.type, k.endTime));
|
|
119
|
-
console.log(r.vara.name, r.vara.englishName); // "Mangalawara", "Tuesday"
|
|
496
|
+
r.angas.tithis.forEach(t => console.log(t.name, t.paksha, t.completionPercentage, t.endTime));
|
|
497
|
+
r.angas.nakshatras.forEach(n => console.log(n.name, n.pada, n.endTime));
|
|
498
|
+
r.angas.yogas.forEach(y => console.log(y.name, y.endTime));
|
|
499
|
+
r.angas.karanas.forEach(k => console.log(k.name, k.type, k.endTime));
|
|
500
|
+
console.log(r.angas.vara.name, r.angas.vara.englishName); // "Mangalawara", "Tuesday"
|
|
120
501
|
|
|
121
502
|
// Single-instant snapshot:
|
|
122
503
|
import { getInstantPanchang } from 'panchang-ts';
|
|
123
504
|
const i = getInstantPanchang(new Date(), location)!;
|
|
124
|
-
console.log(i.tithi.name, i.nakshatra.name, i.yoga.name, i.karana.name, i.vara.name);
|
|
505
|
+
console.log(i.angas.tithi.name, i.angas.nakshatra.name, i.angas.yoga.name, i.angas.karana.name, i.angas.vara.name);
|
|
125
506
|
```
|
|
126
507
|
|
|
127
508
|
## Lunar & Solar Calendar
|
|
@@ -129,16 +510,16 @@ console.log(i.tithi.name, i.nakshatra.name, i.yoga.name, i.karana.name, i.vara.n
|
|
|
129
510
|
```typescript
|
|
130
511
|
const r = getDailyPanchang(date, loc, { timezone: 330, masaSystem: 'purnimanta' })!;
|
|
131
512
|
|
|
132
|
-
r.chandramasa.name; // active system (default: Purnimanta / North Indian)
|
|
133
|
-
r.chandramasa.amantaName; // South Indian
|
|
134
|
-
r.chandramasa.purnimantaName; // North Indian
|
|
135
|
-
r.chandramasa.isAdhika; // true during leap months
|
|
136
|
-
r.samvat.vikramSamvat; // 2081
|
|
137
|
-
r.samvat.shakaSamvat; // 1946
|
|
513
|
+
r.calendar.chandramasa.name; // active system (default: Purnimanta / North Indian)
|
|
514
|
+
r.calendar.chandramasa.amantaName; // South Indian
|
|
515
|
+
r.calendar.chandramasa.purnimantaName; // North Indian
|
|
516
|
+
r.calendar.chandramasa.isAdhika; // true during leap months
|
|
517
|
+
r.calendar.samvat.vikramSamvat; // 2081
|
|
518
|
+
r.calendar.samvat.shakaSamvat; // 1946
|
|
138
519
|
|
|
139
|
-
r.masa.name; // current solar month (Mesha … Meena)
|
|
140
|
-
r.
|
|
141
|
-
r.
|
|
520
|
+
r.calendar.masa.name; // current solar month (Mesha … Meena)
|
|
521
|
+
r.sun.nakshatra.name; // Sun's nakshatra
|
|
522
|
+
r.moon.rashi.name; // Moon sign
|
|
142
523
|
```
|
|
143
524
|
|
|
144
525
|
## Sun, Moon & Muhurta
|
|
@@ -153,36 +534,51 @@ const moonset = getMoonset(localMidnightUtc, loc);
|
|
|
153
534
|
|
|
154
535
|
// Or read off the daily result:
|
|
155
536
|
const r = getDailyPanchang(date, loc, { timezone: 330 })!;
|
|
156
|
-
r.
|
|
157
|
-
r.dayDurationMinutes; r.nightDurationMinutes;
|
|
537
|
+
r.sun.rise; r.sun.set; r.moon.rise; r.moon.set; r.sun.nextRise;
|
|
538
|
+
r.sun.dayDurationMinutes; r.sun.nightDurationMinutes;
|
|
158
539
|
|
|
159
540
|
// Auspicious muhurtas
|
|
160
|
-
r.
|
|
161
|
-
r.
|
|
162
|
-
r.
|
|
163
|
-
r.
|
|
164
|
-
r.
|
|
165
|
-
r.madhyahna; // solar noon ±24 min
|
|
166
|
-
r.pratahSandhya; // dawn twilight, ends at sunrise
|
|
167
|
-
r.sayahnaSandhya; // dusk twilight, starts at sunset
|
|
168
|
-
r.amritKala; // nakshatra-specific window (null when nakshatra has none)
|
|
541
|
+
r.muhurtas.brahma; // two muhurtas before sunrise
|
|
542
|
+
r.muhurtas.abhijit; // 8th day-muhurta; null on Wednesday (Drik convention)
|
|
543
|
+
r.muhurtas.vijaya; // 11th day-muhurta
|
|
544
|
+
r.muhurtas.godhuli; // "cow-dust" sunset muhurta
|
|
545
|
+
r.muhurtas.nishita; // midnight muhurta (Shivaratri)
|
|
546
|
+
r.muhurtas.madhyahna; // solar noon ±24 min
|
|
547
|
+
r.muhurtas.pratahSandhya; // dawn twilight, ends at sunrise
|
|
548
|
+
r.muhurtas.sayahnaSandhya; // dusk twilight, starts at sunset
|
|
549
|
+
r.muhurtas.amritKala; // nakshatra-specific window (null when nakshatra has none)
|
|
169
550
|
```
|
|
170
551
|
|
|
171
|
-
`pratahSandhya` / `sayahnaSandhya` width =
|
|
552
|
+
`muhurtas.pratahSandhya` / `muhurtas.sayahnaSandhya` width =
|
|
553
|
+
`sun.nightDurationMinutes / 10` (~62–81 min).
|
|
172
554
|
|
|
173
555
|
## Inauspicious Periods
|
|
174
556
|
|
|
175
557
|
```typescript
|
|
176
558
|
const r = getDailyPanchang(date, loc, { timezone: 330 })!;
|
|
177
559
|
|
|
178
|
-
r.rahuKalam; // { start, end }
|
|
179
|
-
r.gulikaKalam;
|
|
180
|
-
r.yamaganda;
|
|
181
|
-
r.durMuhurta; // two ~48-min windows
|
|
182
|
-
r.varjyam; // { start, end } | null
|
|
183
|
-
r.gandaMula; // { active, severity: 'mild'|'severe'|null, ... }
|
|
184
|
-
r.bhadra; // { start, end, location: 'earth'|'heaven'|'paatal', isActive } | null
|
|
185
|
-
r.panchaka; // boolean — Moon in last 5 nakshatras
|
|
560
|
+
r.inauspicious.rahuKalam; // { start, end }
|
|
561
|
+
r.inauspicious.gulikaKalam;
|
|
562
|
+
r.inauspicious.yamaganda;
|
|
563
|
+
r.inauspicious.durMuhurta; // two ~48-min windows
|
|
564
|
+
r.inauspicious.varjyam; // { start, end } | null
|
|
565
|
+
r.inauspicious.gandaMula; // { active, severity: 'mild'|'severe'|null, ... }
|
|
566
|
+
r.inauspicious.bhadra; // { start, end, location: 'earth'|'heaven'|'paatal', isActive } | null
|
|
567
|
+
r.inauspicious.panchaka; // boolean — Moon in last 5 nakshatras
|
|
568
|
+
r.inauspicious.panchakaInfo; // which of the five, and whether it's a dosha
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Panchaka is not one undifferentiated affliction. The tradition names five and
|
|
572
|
+
picks between them by **the weekday the spell began on** — so the type belongs
|
|
573
|
+
to the spell, not the day, and two days with identical tithi, nakshatra and
|
|
574
|
+
vara can carry different ones. A spell begun on a Wednesday or Thursday
|
|
575
|
+
(`'samanya'`) carries no named affliction at all:
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
const pk = r.inauspicious.panchakaInfo;
|
|
579
|
+
if (pk.active && pk.isDosha) {
|
|
580
|
+
console.log(pk.name, '— began on vara', pk.onsetVara); // e.g. "Mrityu Panchaka"
|
|
581
|
+
}
|
|
186
582
|
```
|
|
187
583
|
|
|
188
584
|
## Time-Slot Systems
|
|
@@ -191,19 +587,19 @@ r.panchaka; // boolean — Moon in last 5 nakshatras
|
|
|
191
587
|
const r = getDailyPanchang(date, loc, { timezone: 330 })!;
|
|
192
588
|
|
|
193
589
|
// Choghadiya — 8 day + 8 night named, rated slots (Amrit, Kaal, Shubh, Rog, …)
|
|
194
|
-
r.choghadiya.day.forEach(s => console.log(s.name, s.qualityName, s.start, s.end));
|
|
590
|
+
r.periods.choghadiya.day.forEach(s => console.log(s.name, s.qualityName, s.start, s.end));
|
|
195
591
|
|
|
196
592
|
// Gowri Panchangam ("Nalla Neram") — 8 day + 8 night Tamil slots
|
|
197
|
-
r.
|
|
593
|
+
r.periods.gowri.day.forEach(s => console.log(s.name, s.qualityName));
|
|
198
594
|
|
|
199
595
|
// Hora — 12 day + 12 night planetary hours (Chaldean order)
|
|
200
|
-
r.hora.day.forEach(h => console.log(h.planet, h.start, h.end));
|
|
596
|
+
r.periods.hora.day.forEach(h => console.log(h.planet, h.start, h.end));
|
|
201
597
|
|
|
202
598
|
// Do Ghati Muhurta — 15 day + 15 night ~48-min deity-keyed slots (no vara rotation)
|
|
203
|
-
r.
|
|
599
|
+
r.muhurtas.doGhati.day.forEach(g => console.log(g.name, g.start, g.end));
|
|
204
600
|
|
|
205
601
|
// Panchaka Rahita — slices of the day FREE of Panchaka ([] when it pervades)
|
|
206
|
-
r.panchakaRahita.forEach(slice => console.log(slice.start, slice.end));
|
|
602
|
+
r.inauspicious.panchakaRahita.forEach(slice => console.log(slice.start, slice.end));
|
|
207
603
|
```
|
|
208
604
|
|
|
209
605
|
## Special Yogas
|
|
@@ -232,12 +628,20 @@ observances (Masik Shivaratri, Pushya days, Shravan Somvar…).
|
|
|
232
628
|
|
|
233
629
|
```typescript
|
|
234
630
|
r.festivals.forEach(f => {
|
|
631
|
+
// key: stable, language-independent id — 'diwali', 'makar_sankranti', …
|
|
235
632
|
// type: major | minor | ekadashi | smarta_ekadashi | vaishnava_ekadashi
|
|
236
633
|
// | pradosha | sankranti | eclipse
|
|
237
|
-
console.log(f.name, f.type, f.deferralDate);
|
|
634
|
+
console.log(f.key, f.name, f.type, f.deferralDate);
|
|
238
635
|
});
|
|
636
|
+
|
|
637
|
+
// `name` is localized, so match on `key` — never on `name`.
|
|
638
|
+
const hasDiwali = r.festivals.some(f => f.key === 'diwali');
|
|
239
639
|
```
|
|
240
640
|
|
|
641
|
+
`key` is on engine results (`getDailyPanchang`, `getInstantPanchang`,
|
|
642
|
+
`computeFestivalsInRange`). Entries read back out of a `buildFestivalsTable` table
|
|
643
|
+
carry `name` / `type` / `description` only.
|
|
644
|
+
|
|
241
645
|
### Regional scoping
|
|
242
646
|
|
|
243
647
|
`region` scopes regional variants to one Indian state. Pan-Indian festivals
|
|
@@ -262,79 +666,60 @@ getDailyPanchang(jan13, amritsar, { timezone: 330, region: 'punjab' })!
|
|
|
262
666
|
legacy slugs `'tamil'`, `'bengal'`, `'north-india'` are still accepted and
|
|
263
667
|
mapped internally.
|
|
264
668
|
|
|
265
|
-
### Pre-computed table
|
|
266
|
-
|
|
267
|
-
If you want festival *dates* without running the engine, import the static
|
|
268
|
-
table at `panchang-ts/festivals`. It bundles a rolling **2-years-past /
|
|
269
|
-
5-years-future** window pre-computed against Varanasi (IST). Within India
|
|
270
|
-
these dates are essentially universal.
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
import {
|
|
274
|
-
getFestivalsForYear,
|
|
275
|
-
getFestivalsForDate,
|
|
276
|
-
FESTIVALS_META,
|
|
277
|
-
FESTIVALS_YEAR_RANGE,
|
|
278
|
-
} from 'panchang-ts/festivals';
|
|
279
|
-
|
|
280
|
-
const yr = FESTIVALS_YEAR_RANGE.start; // e.g. { start: 2024, end: 2031 }
|
|
281
|
-
getFestivalsForYear(yr)!.length; // ~150 festival days
|
|
282
|
-
const diwali = getFestivalsForYear(yr)!
|
|
283
|
-
.find(d => d.festivals.some(f => f.name === 'Diwali'))!.date;
|
|
284
|
-
getFestivalsForDate(diwali); // [Narak Chaturdashi, Diwali]
|
|
285
|
-
getFestivalsForDate(diwali, 'hi'); // [नरक चतुर्दशी, दिवाली]
|
|
286
|
-
FESTIVALS_META.referenceLocation; // "Varanasi"
|
|
287
|
-
FESTIVALS_META.languages; // ["en", "hi"]
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
This entry point is engine-free — it ships only the JSON + accessors, so
|
|
291
|
-
importing it won't pull the calculation engine into your bundle. Both `en`
|
|
292
|
-
and `hi` are bundled (names *and* descriptions); pass the locale as the
|
|
293
|
-
second argument. Eclipses are excluded here (visibility is location-dependent)
|
|
294
|
-
— they ship as their own bundled table at `panchang-ts/eclipses` (see
|
|
295
|
-
[Eclipses](#eclipses)).
|
|
669
|
+
### Pre-computed table — build your own and cache it
|
|
296
670
|
|
|
297
|
-
|
|
671
|
+
If you want festival *dates* without running the engine in your app, compute a
|
|
672
|
+
table once with `buildFestivalsTable`, cache the JSON, and read it back through
|
|
673
|
+
the engine-free `panchang-ts/festivals` entry point.
|
|
298
674
|
|
|
299
|
-
The
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
For an offline app serving users worldwide, the right pattern is
|
|
306
|
-
**compute-once-then-cache for the user's actual location**. Build a
|
|
307
|
-
location-specific table with `buildFestivalsTable` (from the main entry —
|
|
308
|
-
it uses the engine), persist the returned JSON, then read it back through
|
|
309
|
-
the same accessors via their `source` argument:
|
|
675
|
+
**The library ships no pre-computed table.** Festival dates are
|
|
676
|
+
observer-dependent — canonical times (nishita / pradosha / chandrodaya …) shift
|
|
677
|
+
with the timezone offset, so a table built for one place can be ±1 day wrong
|
|
678
|
+
elsewhere — and any table baked into the package would also go stale. Building
|
|
679
|
+
your own means it is correct for *your* users and covers whatever years you
|
|
680
|
+
want.
|
|
310
681
|
|
|
311
682
|
```typescript
|
|
312
|
-
import { buildFestivalsTable } from 'panchang-ts';
|
|
313
|
-
import {
|
|
683
|
+
import { buildFestivalsTable } from 'panchang-ts'; // uses the engine
|
|
684
|
+
import {
|
|
685
|
+
readFestivalsForYear,
|
|
686
|
+
readFestivalsForDate,
|
|
687
|
+
readFestivalsYearRange,
|
|
688
|
+
} from 'panchang-ts/festivals'; // engine-free
|
|
314
689
|
|
|
315
|
-
//
|
|
316
|
-
// the background / chunk by year), then cache `table` to disk/MMKV.
|
|
690
|
+
// Build once — at your build time, or on first launch in the background.
|
|
317
691
|
const table = buildFestivalsTable({
|
|
318
|
-
location: { latitude:
|
|
319
|
-
timezoneOffsetMinutes:
|
|
692
|
+
location: { latitude: 25.3176, longitude: 82.9739 }, // Varanasi
|
|
693
|
+
timezoneOffsetMinutes: 330, // IST; -300 = US Eastern, 0 = UK
|
|
320
694
|
startYear: 2024,
|
|
321
695
|
endYear: 2031,
|
|
322
|
-
languages: ['en'],
|
|
696
|
+
languages: ['en', 'hi'], // drop 'hi' to halve the size
|
|
697
|
+
referenceLocation: 'Varanasi',
|
|
323
698
|
});
|
|
699
|
+
// …persist `table` as JSON (disk / MMKV / your bundler's asset pipeline).
|
|
324
700
|
|
|
325
|
-
// Later reads are instant lookups
|
|
326
|
-
|
|
327
|
-
|
|
701
|
+
// Later reads are instant lookups — no engine, no ephemeris.
|
|
702
|
+
readFestivalsYearRange(table); // { start: 2024, end: 2031 }
|
|
703
|
+
readFestivalsForYear(table, 2026)!.length; // ~150 festival days
|
|
704
|
+
const diwali = readFestivalsForYear(table, 2026)!
|
|
705
|
+
.find(d => d.festivals.some(f => f.name === 'Diwali'))!.date;
|
|
706
|
+
readFestivalsForDate(table, diwali); // [Narak Chaturdashi, Diwali]
|
|
707
|
+
readFestivalsForDate(table, diwali, 'hi'); // [नरक चतुर्दशी, दिवाली]
|
|
328
708
|
```
|
|
329
709
|
|
|
330
|
-
`
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
710
|
+
`panchang-ts/festivals` imports no astronomy code, so a client bundle that only
|
|
711
|
+
*reads* a table never pulls in the engine. Keep `buildFestivalsTable` on the
|
|
712
|
+
build/server side (or behind a one-time on-device warm-up) and ship only the
|
|
713
|
+
JSON.
|
|
714
|
+
|
|
715
|
+
Eclipses are excluded here — visibility is location-dependent, so they get their
|
|
716
|
+
own table at `panchang-ts/eclipses` (see [Eclipses](#eclipses)).
|
|
334
717
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
718
|
+
`npm run festivals:gen` is a worked example of the whole pattern; it writes a
|
|
719
|
+
rolling 2-past / 5-future window to `./festivals.json` (or a path you pass).
|
|
720
|
+
|
|
721
|
+
**Other notes:** Karva Chauth / Dhanteras / Diwali emit with Purnimanta paksha
|
|
722
|
+
naming.
|
|
338
723
|
|
|
339
724
|
## Eclipses
|
|
340
725
|
|
|
@@ -343,7 +728,9 @@ const r = getDailyPanchang(date, loc, { timezone: 330 })!;
|
|
|
343
728
|
if (r.eclipse) {
|
|
344
729
|
r.eclipse.kind; // 'solar' | 'lunar'
|
|
345
730
|
r.eclipse.subtype; // 'partial' | 'total' | 'annular' | 'penumbral'
|
|
346
|
-
r.eclipse.
|
|
731
|
+
r.eclipse.obscuration; // 0..1 fraction of the disc AREA covered
|
|
732
|
+
r.eclipse.magnitude; // catalogue magnitude — DIAMETER fraction;
|
|
733
|
+
// >1 when total, negative when penumbral
|
|
347
734
|
r.eclipse.visibleFromLocation; // body above horizon at peak?
|
|
348
735
|
r.eclipse.start; r.eclipse.peak; r.eclipse.end;
|
|
349
736
|
r.eclipse.sutakStart; r.eclipse.sutakEnd;
|
|
@@ -354,65 +741,61 @@ import { getUpcomingSolarEclipse, getUpcomingLunarEclipse } from 'panchang-ts';
|
|
|
354
741
|
const next = getUpcomingSolarEclipse(new Date(), loc, 365 /* days */);
|
|
355
742
|
```
|
|
356
743
|
|
|
357
|
-
### Pre-computed table
|
|
744
|
+
### Pre-computed table — build your own and cache it
|
|
745
|
+
|
|
746
|
+
Same pattern as festivals: build a table with `buildEclipsesTable`, cache it,
|
|
747
|
+
read it back through the engine-free `panchang-ts/eclipses` entry point.
|
|
358
748
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
there during any phase** (so an eclipse already in progress at moon/sunrise or
|
|
363
|
-
moon/sunset is included); the `visibleAtPeak` flag tells you whether greatest
|
|
364
|
-
eclipse itself is observable. Within India visibility is essentially uniform.
|
|
749
|
+
**No table is bundled.** Which eclipses are visible — and therefore which carry
|
|
750
|
+
`sutak` — is location-dependent, so a table is only meaningful for the place it
|
|
751
|
+
was built for.
|
|
365
752
|
|
|
366
753
|
```typescript
|
|
754
|
+
import { buildEclipsesTable } from 'panchang-ts'; // uses the engine
|
|
367
755
|
import {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
756
|
+
readEclipsesForYear,
|
|
757
|
+
readEclipsesForDate,
|
|
758
|
+
readEclipsesYearRange,
|
|
759
|
+
} from 'panchang-ts/eclipses'; // engine-free
|
|
760
|
+
|
|
761
|
+
const table = buildEclipsesTable({
|
|
762
|
+
location: { latitude: 25.3176, longitude: 82.9739 }, // Varanasi
|
|
763
|
+
timezoneOffsetMinutes: 330,
|
|
764
|
+
startYear: 2024,
|
|
765
|
+
endYear: 2031,
|
|
766
|
+
languages: ['en', 'hi'],
|
|
767
|
+
// visibleOnly: false → also include eclipses below the horizon (no sutak)
|
|
768
|
+
});
|
|
769
|
+
// …persist `table` as JSON, then:
|
|
373
770
|
|
|
374
|
-
|
|
771
|
+
readEclipsesYearRange(table); // { start: 2024, end: 2031 }
|
|
772
|
+
const e = readEclipsesForYear(table, 2025)![0].eclipses[0];
|
|
375
773
|
e.kind; // 'lunar'
|
|
376
774
|
e.subtype; // 'total'
|
|
377
775
|
e.start; e.peak; e.end; // ISO UTC strings
|
|
378
|
-
e.
|
|
379
|
-
e.
|
|
776
|
+
e.obscuration; // 0..1 disc area covered at peak
|
|
777
|
+
e.magnitude; // catalogue magnitude (diameter); >1 total, <0 penumbral
|
|
778
|
+
e.visibleFromLocation; // visible during any phase?
|
|
380
779
|
e.visibleAtPeak; // is greatest eclipse itself above the horizon?
|
|
381
780
|
e.sutak; // { start, end } — see note below
|
|
382
|
-
|
|
383
|
-
ECLIPSES_META.referenceLocation; // "Varanasi"
|
|
781
|
+
readEclipsesForDate(table, '2025-09-07', 'hi'); // [पूर्ण चंद्र ग्रहण]
|
|
384
782
|
```
|
|
385
783
|
|
|
386
|
-
|
|
387
|
-
**
|
|
388
|
-
`
|
|
389
|
-
and visible **umbral** (partial/total) lunar eclipses; **penumbral** lunar
|
|
390
|
-
eclipses carry no `sutak` and are not religiously observed (drik / pandit
|
|
391
|
-
consensus). For full astronomical detail (e.g. eclipses *not* visible in
|
|
392
|
-
India), use `getUpcomingEclipses` / `getEclipsesInRange` from the main entry.
|
|
784
|
+
By default a table lists every eclipse **visible from the location during any
|
|
785
|
+
phase** (so one already in progress at moon/sunrise or moon/sunset is included);
|
|
786
|
+
`visibleAtPeak` tells you whether greatest eclipse itself is observable.
|
|
393
787
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
788
|
+
Solar eclipses report the subtype seen **locally** (a globally-total eclipse may
|
|
789
|
+
read `partial` from a given place). The `sutak` window is present only where it
|
|
790
|
+
applies — all visible solar eclipses and visible **umbral** (partial/total)
|
|
791
|
+
lunar eclipses; **penumbral** lunar eclipses carry no `sutak` and are not
|
|
792
|
+
religiously observed (drik / pandit consensus).
|
|
399
793
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
import { getEclipsesForYear } from 'panchang-ts/eclipses';
|
|
794
|
+
For one-off astronomical detail without building a table, use
|
|
795
|
+
`getUpcomingEclipses` / `computeEclipsesInRange` from the main entry.
|
|
403
796
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
timezoneOffsetMinutes: 0, // UK / GMT
|
|
407
|
-
startYear: 2024,
|
|
408
|
-
endYear: 2031,
|
|
409
|
-
// visibleOnly: false → also include eclipses below the horizon (no sutak)
|
|
410
|
-
});
|
|
411
|
-
getEclipsesForYear(2025, 'en', table);
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
To regenerate the bundled India table, run `npm run eclipses:gen` (rolling
|
|
415
|
-
window, no constants to edit).
|
|
797
|
+
`npm run eclipses:gen` is a worked example; it writes a rolling 2-past /
|
|
798
|
+
5-future window to `./eclipses.json` (or a path you pass).
|
|
416
799
|
|
|
417
800
|
## Moon Phases
|
|
418
801
|
|
|
@@ -422,48 +805,44 @@ astronomical quarter moments, distinct from the same-named *tithis*, which are
|
|
|
422
805
|
~24h windows.)
|
|
423
806
|
|
|
424
807
|
```typescript
|
|
425
|
-
import {
|
|
426
|
-
const phases =
|
|
808
|
+
import { computeMoonPhasesInRange } from 'panchang-ts';
|
|
809
|
+
const phases = computeMoonPhasesInRange(new Date('2026-01-01'), new Date('2026-12-31'));
|
|
427
810
|
phases.forEach(p => console.log(p.phase, p.time.toISOString())); // ~49 / year
|
|
428
811
|
```
|
|
429
812
|
|
|
430
|
-
### Pre-computed table
|
|
813
|
+
### Pre-computed table — build your own and cache it
|
|
431
814
|
|
|
432
|
-
Same
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
moon at 19:52 UTC on Jan 18 is listed under Jan 19 in
|
|
815
|
+
Same pattern again, at `panchang-ts/moon-phases`. Phases are **global instants**,
|
|
816
|
+
so `buildMoonPhasesTable` takes only a `timezoneOffsetMinutes` (no coordinates)
|
|
817
|
+
— the timezone just decides which calendar date each instant lands on (a new
|
|
818
|
+
moon at 19:52 UTC on Jan 18 is listed under Jan 19 in IST).
|
|
436
819
|
|
|
437
820
|
```typescript
|
|
821
|
+
import { buildMoonPhasesTable } from 'panchang-ts'; // uses the engine
|
|
438
822
|
import {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
} from 'panchang-ts/moon-phases';
|
|
444
|
-
|
|
445
|
-
getMoonPhasesForYear(2026)!.length; // ~49 phase days
|
|
446
|
-
getMoonPhasesForDate('2026-01-03'); // [{ phase: 'full', name: 'Full Moon', ... }]
|
|
447
|
-
getMoonPhasesForDate('2026-01-03', 'hi'); // [{ phase: 'full', name: 'पूर्णिमा', ... }]
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
Each entry carries `phase`, the phase `time` (ISO UTC), and `en` + `hi` text.
|
|
451
|
-
For another timezone, build and cache a table with `buildMoonPhasesTable` (main
|
|
452
|
-
entry) and pass it as the accessors' `source` argument — it takes only a
|
|
453
|
-
`timezoneOffsetMinutes` (no coordinates, since phases are location-independent):
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
import { buildMoonPhasesTable } from 'panchang-ts';
|
|
457
|
-
import { getMoonPhasesForYear } from 'panchang-ts/moon-phases';
|
|
823
|
+
readMoonPhasesForYear,
|
|
824
|
+
readMoonPhasesForDate,
|
|
825
|
+
readMoonPhasesYearRange,
|
|
826
|
+
} from 'panchang-ts/moon-phases'; // engine-free
|
|
458
827
|
|
|
459
828
|
const table = buildMoonPhasesTable({
|
|
460
|
-
timezoneOffsetMinutes:
|
|
461
|
-
startYear: 2024,
|
|
829
|
+
timezoneOffsetMinutes: 330, // IST; -300 = US Eastern
|
|
830
|
+
startYear: 2024,
|
|
831
|
+
endYear: 2031,
|
|
832
|
+
languages: ['en', 'hi'],
|
|
462
833
|
});
|
|
463
|
-
|
|
834
|
+
// …persist `table` as JSON, then:
|
|
835
|
+
|
|
836
|
+
readMoonPhasesYearRange(table); // { start: 2024, end: 2031 }
|
|
837
|
+
readMoonPhasesForYear(table, 2026)!.length; // ~49 phase days
|
|
838
|
+
readMoonPhasesForDate(table, '2026-01-03'); // [{ phase: 'full', name: 'Full Moon', … }]
|
|
839
|
+
readMoonPhasesForDate(table, '2026-01-03', 'hi'); // [{ phase: 'full', name: 'पूर्णिमा', … }]
|
|
464
840
|
```
|
|
465
841
|
|
|
466
|
-
|
|
842
|
+
Each entry carries `phase`, the phase `time` (ISO UTC), and `en` + `hi` text.
|
|
843
|
+
|
|
844
|
+
`npm run moon-phases:gen` is a worked example; it writes a rolling 2-past /
|
|
845
|
+
5-future window to `./moonPhases.json` (or a path you pass).
|
|
467
846
|
|
|
468
847
|
## Planetary Positions
|
|
469
848
|
|
|
@@ -526,8 +905,8 @@ const r = getDailyPanchang(date, loc, {
|
|
|
526
905
|
janmaRashi: 3, // 0 = Mesha … 11 = Meena
|
|
527
906
|
janmaNakshatra: 0, // 0 = Ashwini … 26 = Revati
|
|
528
907
|
})!;
|
|
529
|
-
r.chandraBalam
|
|
530
|
-
r.tarabala
|
|
908
|
+
r.chandraBalam; // { house, quality: 'strong'|'weak', name, englishName } — null without janmaRashi
|
|
909
|
+
r.tarabala; // { taraIndex, name, englishName, quality } — null without janmaNakshatra
|
|
531
910
|
|
|
532
911
|
import { computeSadeSati } from 'panchang-ts';
|
|
533
912
|
const ss = computeSadeSati(natalMoonRashiIndex, new Date());
|
|
@@ -554,6 +933,12 @@ const lagna = computeLagna(birth, loc, 'lahiri', 'en');
|
|
|
554
933
|
// Placidus-KP throws PanchangError('CIRCUMPOLAR') beyond ±66.5°.
|
|
555
934
|
const houses = computeBhava(birth, loc, { houseSystem: 'whole-sign' });
|
|
556
935
|
|
|
936
|
+
// `chart.planets` is the ordered list; `chart.byPlanet` is the same nine
|
|
937
|
+
// placements keyed by graha, for direct lookup without a linear scan.
|
|
938
|
+
const chart = computeRashiChart(birth, loc);
|
|
939
|
+
chart.byPlanet.Mars.house; // instead of chart.planets.find(...)!
|
|
940
|
+
chart.planets.map(p => p.rashi); // iterate the list as before
|
|
941
|
+
|
|
557
942
|
// D1 — full Rashi chart with 9-graha house placement.
|
|
558
943
|
const d1 = computeRashiChart(birth, loc, { houseSystem: 'whole-sign' });
|
|
559
944
|
d1.planets.find(p => p.planet === 'Jupiter')?.house;
|
|
@@ -598,6 +983,14 @@ const richer = computeAshtakoot(
|
|
|
598
983
|
{ rashi: 0, nakshatra: 1, lagnaRashi: 1, navamsaRashi: 5 },
|
|
599
984
|
);
|
|
600
985
|
|
|
986
|
+
// Manglik is a PAIRWISE verdict, not a per-chart one: when both partners are
|
|
987
|
+
// Manglik the two afflictions neutralise each other, so the pair is clean
|
|
988
|
+
// where a Manglik/non-Manglik pair is not.
|
|
989
|
+
const m = computeMangalCompatibility(boyChart, girlChart);
|
|
990
|
+
m.afflicted; // false when neither is Manglik AND when both are
|
|
991
|
+
m.cancellations; // ['both natives Manglik — mutual cancellation']
|
|
992
|
+
m.boy; m.girl; // each native's own MangalDoshaInfo, severity included
|
|
993
|
+
|
|
601
994
|
// Pathu Porutham (Tamil/Kerala, 10-fold) — binary pass/fail per koot.
|
|
602
995
|
// Three vetoes (Yoni, Rajju, Vedha) flip `recommended` regardless of count.
|
|
603
996
|
const tp = computePathuPorutham(
|
|
@@ -802,12 +1195,17 @@ to `computePrashnaChart` for traditional Vedic Prashna.
|
|
|
802
1195
|
## Muhurta Engine
|
|
803
1196
|
|
|
804
1197
|
```typescript
|
|
805
|
-
import { scoreMuhurta,
|
|
1198
|
+
import { scoreMuhurta, computeAuspiciousDatesInRange, vivahRule } from 'panchang-ts';
|
|
806
1199
|
|
|
807
1200
|
const r = scoreMuhurta(new Date('2026-05-12'), DELHI, vivahRule, { timezone: 330 });
|
|
808
|
-
// → { date, score: 0..100, passes: boolean,
|
|
1201
|
+
// → { date, score: 0..100, passes: boolean,
|
|
1202
|
+
// reasons: string[], // diagnostic English
|
|
1203
|
+
// factors: MuhurtaFactor[] } // { code, axis, index?, delta } — stable
|
|
1204
|
+
|
|
1205
|
+
r.factors.filter(f => f.delta < 0); // what cost the day points
|
|
1206
|
+
r.factors.some(f => f.axis === 'exclusion'); // hard-excluded?
|
|
809
1207
|
|
|
810
|
-
const dates =
|
|
1208
|
+
const dates = computeAuspiciousDatesInRange(
|
|
811
1209
|
vivahRule,
|
|
812
1210
|
new Date('2026-05-01'),
|
|
813
1211
|
new Date('2026-05-31'),
|
|
@@ -820,29 +1218,106 @@ const myRule: MuhurtaRule = {
|
|
|
820
1218
|
occasion: 'launch_party',
|
|
821
1219
|
auspiciousVaras: [3, 4, 5],
|
|
822
1220
|
auspiciousNakshatras: [11, 12, 21],
|
|
823
|
-
|
|
1221
|
+
bhadra: 'penalize', // 'ignore' | 'penalize' | 'exclude'
|
|
824
1222
|
excludeEkadashi: true,
|
|
825
1223
|
excludeAdhikaMasa: true,
|
|
826
1224
|
};
|
|
827
1225
|
```
|
|
828
1226
|
|
|
1227
|
+
**Tithi and vara are scored jointly, not just per-anga.** The classical Vara ×
|
|
1228
|
+
Tithi yogas — Siddha, Amrita, Dagdha, Visha, Hutasana, Krakacha, Samvartaka —
|
|
1229
|
+
are applied to every rule, so a Rikta tithi landing on a Saturday is partly
|
|
1230
|
+
redeemed by Siddha yoga rather than flatly penalised. Set
|
|
1231
|
+
`varaTithiYogas: false` for the older per-anga-only scoring, or call
|
|
1232
|
+
`computeVaraTithiYogas(vara, tithi)` directly. Where an auspicious and an
|
|
1233
|
+
inauspicious yoga both fire — a documented ambiguity in the sources — both are
|
|
1234
|
+
surfaced as separate factors and allowed to net out.
|
|
1235
|
+
|
|
1236
|
+
`bhadra` defaults to `'ignore'`; the stock rules use `'penalize'`. A whole-day
|
|
1237
|
+
`'exclude'` is rarely what you want: Vishti karana sits at fixed positions in
|
|
1238
|
+
the tithi cycle, so vetoing the day removes seven tithis outright — among them
|
|
1239
|
+
Shukla Ekadashi, which the same sources list as *preferred* for vivah. Read
|
|
1240
|
+
`panchang.inauspicious.bhadra` for the window and schedule around it.
|
|
1241
|
+
`excludeBhadra: true` still works as an alias for `bhadra: 'exclude'`.
|
|
1242
|
+
|
|
829
1243
|
13 stock rules: vivah, griha pravesh, namakarana, vidyarambh, vahan kharidi,
|
|
830
1244
|
annaprashan, mundan, upanayanam, karnavedha, aksharabhyasam, seemantham, shop
|
|
831
1245
|
opening, travel start.
|
|
832
1246
|
|
|
1247
|
+
### Pre-computed table — build your own and cache it
|
|
1248
|
+
|
|
1249
|
+
Scoring a year of days runs the engine ~365 times. If your app asks the same
|
|
1250
|
+
question repeatedly, compute the answer once and ship the JSON — the same
|
|
1251
|
+
pattern the festival, eclipse and Moon-phase tables use.
|
|
1252
|
+
|
|
1253
|
+
```typescript
|
|
1254
|
+
import { buildMuhurtaTable, vivahRule } from 'panchang-ts';
|
|
1255
|
+
|
|
1256
|
+
const table = buildMuhurtaTable({
|
|
1257
|
+
rule: vivahRule,
|
|
1258
|
+
location: { latitude: 25.3176, longitude: 82.9739 },
|
|
1259
|
+
timezoneOffsetMinutes: 330,
|
|
1260
|
+
startYear: 2026,
|
|
1261
|
+
endYear: 2031,
|
|
1262
|
+
referenceLocation: 'Varanasi',
|
|
1263
|
+
});
|
|
1264
|
+
// persist JSON.stringify(table) — 6 years of vivah dates is ~74 KB
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
Read it back through the engine-free `panchang-ts/muhurta` entry (~1.7 KB, no
|
|
1268
|
+
astronomy code in your bundle):
|
|
1269
|
+
|
|
1270
|
+
```typescript
|
|
1271
|
+
import {
|
|
1272
|
+
readMuhurtaForYear,
|
|
1273
|
+
readMuhurtaForDate,
|
|
1274
|
+
readMuhurtaYearRange,
|
|
1275
|
+
readMuhurtaOccasion,
|
|
1276
|
+
readBestMuhurtaDays,
|
|
1277
|
+
} from 'panchang-ts/muhurta';
|
|
1278
|
+
|
|
1279
|
+
const table = JSON.parse(await (await fetch('/muhurta-vivah.json')).text());
|
|
1280
|
+
|
|
1281
|
+
readMuhurtaOccasion(table); // 'vivah'
|
|
1282
|
+
readMuhurtaYearRange(table); // { start: 2026, end: 2031 }
|
|
1283
|
+
readMuhurtaForYear(table, 2026); // MuhurtaTableDay[] — passing days, by date
|
|
1284
|
+
readMuhurtaForDate(table, '2026-11-11');
|
|
1285
|
+
readBestMuhurtaDays(table, 5); // top 5 across the table, highest first
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
Only days that **pass** the rule are stored by default; pass
|
|
1289
|
+
`includeFailures: true` to keep every day with its score. Scores are location-
|
|
1290
|
+
*and* rule-dependent, so a table built for Varanasi and `vivah` says nothing
|
|
1291
|
+
about another place or occasion.
|
|
1292
|
+
|
|
1293
|
+
`npm run muhurta:gen` is a worked example script
|
|
1294
|
+
([scripts/generate-muhurta-json.ts](scripts/generate-muhurta-json.ts)):
|
|
1295
|
+
|
|
1296
|
+
```bash
|
|
1297
|
+
npm run muhurta:gen -- muhurta-vivah.json vivah
|
|
1298
|
+
```
|
|
1299
|
+
|
|
833
1300
|
Scoring: starts at 50; +10 per matching auspicious axis (tithi / nakshatra /
|
|
834
1301
|
vara / yoga), -15 per inauspicious axis, hard exclusions zero the score.
|
|
835
1302
|
Special yogas (Amrit Siddhi, Sarvartha Siddhi, Ravi/Guru Pushya) add +5;
|
|
836
1303
|
Jwalamukhi subtracts -10. Clamped 0..100; `passes: true` when score ≥ 50.
|
|
837
1304
|
|
|
1305
|
+
Every scoring input appears in both `reasons` (English prose, diagnostic, not a
|
|
1306
|
+
stable format) and `factors` (structured, with a stable `code`). Localize and
|
|
1307
|
+
filter on `factors`.
|
|
1308
|
+
|
|
1309
|
+
`scoreMuhurta` computes only the sections it actually scores against, so it is
|
|
1310
|
+
cheaper than a full `getDailyPanchang`. `computeAuspiciousDatesInRange` does not narrow —
|
|
1311
|
+
each returned day carries its complete `panchang` for callers to drill into.
|
|
1312
|
+
|
|
838
1313
|
## Calendar Conversion
|
|
839
1314
|
|
|
840
1315
|
```typescript
|
|
841
1316
|
import {
|
|
842
1317
|
convertGregorianToHindu, convertHinduToGregorian,
|
|
843
1318
|
getKaliYugaYear, getHinduNewYear,
|
|
844
|
-
|
|
845
|
-
|
|
1319
|
+
computeEkadashiDatesForYear, computeSankrantisForYear,
|
|
1320
|
+
computeFestivalsInRange, getUpcomingEclipses, computeEclipsesInRange,
|
|
846
1321
|
} from 'panchang-ts';
|
|
847
1322
|
|
|
848
1323
|
// Gregorian → Hindu coords at sunrise
|
|
@@ -857,9 +1332,9 @@ const dates = convertHinduToGregorian(
|
|
|
857
1332
|
getKaliYugaYear(new Date('2026-04-01')); // 5127
|
|
858
1333
|
getHinduNewYear(2026, 'tamil-nadu', DELHI, { timezone: 330 }); // Puthandu
|
|
859
1334
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1335
|
+
computeEkadashiDatesForYear(2026, DELHI, { timezone: 330 }); // ~24 Date[]
|
|
1336
|
+
computeSankrantisForYear(2026, DELHI, { timezone: 330 }); // 12 SankrantiEvent[]
|
|
1337
|
+
computeFestivalsInRange(start, end, DELHI, { timezone: 330 }); // FestivalDay[]
|
|
863
1338
|
getUpcomingEclipses(new Date(), DELHI, 5);
|
|
864
1339
|
```
|
|
865
1340
|
|
|
@@ -872,11 +1347,11 @@ tithi (e.g. Ugadi 2026), falls back to the Amanta-Chaitra-masa boundary.
|
|
|
872
1347
|
|
|
873
1348
|
```typescript
|
|
874
1349
|
const hi = getDailyPanchang(date, loc, { timezone: 330, language: 'hi' })!;
|
|
875
|
-
hi.tithis[0].name; // "कृष्ण चतुर्दशी"
|
|
876
|
-
hi.vara.name; // "मंगलवार"
|
|
877
|
-
hi.chandramasa.name; // "माघ"
|
|
878
|
-
hi.choghadiya.day[0].name; // "अमृत"
|
|
879
|
-
hi.vara.englishName; // "Tuesday" — englishName always English
|
|
1350
|
+
hi.angas.tithis[0].name; // "कृष्ण चतुर्दशी"
|
|
1351
|
+
hi.angas.vara.name; // "मंगलवार"
|
|
1352
|
+
hi.calendar.chandramasa.name; // "माघ"
|
|
1353
|
+
hi.periods.choghadiya.day[0].name; // "अमृत"
|
|
1354
|
+
hi.angas.vara.englishName; // "Tuesday" — englishName always English
|
|
880
1355
|
|
|
881
1356
|
// All options:
|
|
882
1357
|
const r = getDailyPanchang(date, loc, {
|
|
@@ -885,10 +1360,10 @@ const r = getDailyPanchang(date, loc, {
|
|
|
885
1360
|
language: 'en', // en | hi
|
|
886
1361
|
masaSystem: 'purnimanta', // purnimanta | amanta
|
|
887
1362
|
region: 'all', // 21 state slugs + 'nepal' + 'all'
|
|
888
|
-
computeEndTimes: true, // false →
|
|
889
|
-
|
|
890
|
-
janmaRashi: undefined, // pass to
|
|
891
|
-
janmaNakshatra: undefined, // pass to
|
|
1363
|
+
computeEndTimes: true, // false → skip transition searches
|
|
1364
|
+
sections: undefined, // undefined = all; see Performance
|
|
1365
|
+
janmaRashi: undefined, // pass to populate r.chandraBalam (else null)
|
|
1366
|
+
janmaNakshatra: undefined, // pass to populate r.tarabala (else null)
|
|
892
1367
|
});
|
|
893
1368
|
```
|
|
894
1369
|
|
|
@@ -899,6 +1374,21 @@ IANA zones.
|
|
|
899
1374
|
|
|
900
1375
|
---
|
|
901
1376
|
|
|
1377
|
+
### Localized vs machine-readable fields
|
|
1378
|
+
|
|
1379
|
+
Every user-facing string follows `language`. Where a value is also meaningful to
|
|
1380
|
+
code, the two are separate fields — the stable key never changes with language:
|
|
1381
|
+
|
|
1382
|
+
| Machine-readable | Localized display |
|
|
1383
|
+
|---|---|
|
|
1384
|
+
| `festival.key` (`'diwali'`) | `festival.name` (`"दिवाली"`) |
|
|
1385
|
+
| `bhadra.location` (`'paatal'`) | `bhadra.locationName` (`"पाताल"`) |
|
|
1386
|
+
| `eclipse.kind` / `eclipse.subtype` | `eclipse.description` |
|
|
1387
|
+
| `muhurtaScore.factors[].code` | `muhurtaScore.reasons` (English only) |
|
|
1388
|
+
|
|
1389
|
+
`MuhurtaScore.reasons` is diagnostic English and not a stable format; use
|
|
1390
|
+
`factors` for anything shown to a user or branched on in code.
|
|
1391
|
+
|
|
902
1392
|
## Types & Exports
|
|
903
1393
|
|
|
904
1394
|
<details>
|
|
@@ -927,7 +1417,8 @@ interface VaraInfo {
|
|
|
927
1417
|
}
|
|
928
1418
|
|
|
929
1419
|
interface FestivalInfo {
|
|
930
|
-
|
|
1420
|
+
key: string; // stable, language-independent id — match on this
|
|
1421
|
+
name: string; // localized — display only
|
|
931
1422
|
type: 'major' | 'minor' | 'ekadashi' | 'smarta_ekadashi' | 'vaishnava_ekadashi'
|
|
932
1423
|
| 'pradosha' | 'sankranti' | 'eclipse';
|
|
933
1424
|
description?: string;
|
|
@@ -958,7 +1449,11 @@ interface EclipseInfo {
|
|
|
958
1449
|
subtype: 'partial' | 'total' | 'annular' | 'penumbral';
|
|
959
1450
|
start: Date; peak: Date; end: Date;
|
|
960
1451
|
visibleFromLocation: boolean;
|
|
961
|
-
|
|
1452
|
+
obscuration: number; // disc AREA covered at peak, [0, 1]
|
|
1453
|
+
magnitude: number; // catalogue magnitude — disc DIAMETER covered.
|
|
1454
|
+
// Not [0, 1]: >1 for a total eclipse, negative
|
|
1455
|
+
// for a penumbral lunar one (the Moon misses
|
|
1456
|
+
// the umbra), exactly as NASA's canon prints it.
|
|
962
1457
|
sutakStart: Date; // 12 h pre-solar / 9 h pre-lunar
|
|
963
1458
|
sutakEnd: Date;
|
|
964
1459
|
description: string;
|
|
@@ -967,6 +1462,7 @@ interface EclipseInfo {
|
|
|
967
1462
|
interface BhadraInfo {
|
|
968
1463
|
start: Date; end: Date;
|
|
969
1464
|
location: 'earth' | 'heaven' | 'paatal'; // 'earth' = malefic for all work
|
|
1465
|
+
locationName: string; // localized display name
|
|
970
1466
|
isActive: boolean;
|
|
971
1467
|
}
|
|
972
1468
|
```
|
|
@@ -1033,6 +1529,7 @@ getSiderealSunLongitude, getSiderealMoonLongitude, getAyanamsa
|
|
|
1033
1529
|
computeRahuKalam, computeGulikaKalam, computeYamaganda
|
|
1034
1530
|
computeVarjyam, computeGandaMula, computeAnandadiYoga
|
|
1035
1531
|
computePanchakaRahita, computeDoGhati, computeGowriPanchangam
|
|
1532
|
+
computePanchaka, classifyPanchaka, isPanchakaDosha, findPanchakaOnset
|
|
1036
1533
|
computeAbhijitMuhurta, computeBrahmaMuhurta, computeVijayaMuhurta
|
|
1037
1534
|
computeGodhuliMuhurta, computeNishitaMuhurta, computeAmritKala
|
|
1038
1535
|
computeMadhyahna, computePratahSandhya, computeSayahnaSandhya
|
|
@@ -1042,7 +1539,7 @@ getUpcomingSolarEclipse, getUpcomingLunarEclipse, getEclipseDuringDay
|
|
|
1042
1539
|
isEclipseVisibleAnyPhase
|
|
1043
1540
|
|
|
1044
1541
|
// Moon phases (new / quarters / full as precise instants)
|
|
1045
|
-
|
|
1542
|
+
computeMoonPhasesInRange
|
|
1046
1543
|
|
|
1047
1544
|
// Jyotish — planets, dashas, transits
|
|
1048
1545
|
computePlanetaryPositions, GRAHA_ABBR
|
|
@@ -1062,14 +1559,15 @@ computeVarshaphala, computeTithiPravesha, computeArudhas, computeUpagrahas, comp
|
|
|
1062
1559
|
|
|
1063
1560
|
// Jyotish — compatibility, doshas
|
|
1064
1561
|
computeAshtakoot, computePathuPorutham
|
|
1065
|
-
computeMangalDosha, computeKaalSarp, computePitruDosha
|
|
1562
|
+
computeMangalDosha, computeMangalCompatibility, computeKaalSarp, computePitruDosha
|
|
1066
1563
|
|
|
1067
1564
|
// KP / Prashna
|
|
1068
1565
|
computeKpSubLord, computeKpCuspalSubLords, computeKpSignificators
|
|
1069
1566
|
computePrashnaChart
|
|
1070
1567
|
|
|
1071
1568
|
// Muhurta engine
|
|
1072
|
-
scoreMuhurta,
|
|
1569
|
+
scoreMuhurta, computeAuspiciousDatesInRange, STOCK_MUHURTA_RULES
|
|
1570
|
+
computeVaraTithiYogas
|
|
1073
1571
|
vivahRule, grihaPraveshRule, namakaranaRule, vidyarambhRule, vahanKharidiRule
|
|
1074
1572
|
annaprashanRule, mundanRule, upanayanamRule, karnavedhaRule
|
|
1075
1573
|
aksharabhyasamRule, seemanthamRule, shopOpeningRule, travelStartRule
|
|
@@ -1077,11 +1575,12 @@ aksharabhyasamRule, seemanthamRule, shopOpeningRule, travelStartRule
|
|
|
1077
1575
|
// Calendar conversion + yearly listings
|
|
1078
1576
|
convertGregorianToHindu, convertHinduToGregorian
|
|
1079
1577
|
getKaliYugaYear, getHinduNewYear, computeSamvat
|
|
1080
|
-
|
|
1081
|
-
getUpcomingEclipses,
|
|
1578
|
+
computeEkadashiDatesForYear, computeSankrantisForYear, computeFestivalsInRange
|
|
1579
|
+
getUpcomingEclipses, computeEclipsesInRange
|
|
1082
1580
|
|
|
1083
|
-
// Static data tables
|
|
1084
|
-
// panchang-ts/festivals
|
|
1581
|
+
// Static data tables — build one, cache the JSON, then read it back through
|
|
1582
|
+
// the engine-free panchang-ts/festivals · /eclipses · /moon-phases entries.
|
|
1583
|
+
// No table ships with the package.
|
|
1085
1584
|
buildFestivalsTable, buildEclipsesTable, buildMoonPhasesTable
|
|
1086
1585
|
|
|
1087
1586
|
// Errors
|
|
@@ -1103,14 +1602,18 @@ Two-pass rendering pattern for smooth UI:
|
|
|
1103
1602
|
import { getDailyPanchang } from 'panchang-ts';
|
|
1104
1603
|
import { InteractionManager } from 'react-native';
|
|
1105
1604
|
|
|
1106
|
-
// Pass 1 —
|
|
1605
|
+
// Pass 1 — cheapest useful result: elements, slots, muhurtas
|
|
1606
|
+
// (~0.25 ms Node on a new date, ~0.14 ms on one already seen).
|
|
1607
|
+
// `sections` is the lever; `computeEndTimes: false` only helps once it is
|
|
1608
|
+
// narrowed, and slightly hurts on a full-section call.
|
|
1107
1609
|
const fast = getDailyPanchang(date, location, {
|
|
1108
1610
|
timezone: 330,
|
|
1611
|
+
sections: [],
|
|
1109
1612
|
computeEndTimes: false,
|
|
1110
1613
|
});
|
|
1111
1614
|
setState(fast);
|
|
1112
1615
|
|
|
1113
|
-
// Pass 2 — background,
|
|
1616
|
+
// Pass 2 — background, everything (~0.41 ms Node)
|
|
1114
1617
|
InteractionManager.runAfterInteractions(() => {
|
|
1115
1618
|
setState(getDailyPanchang(date, location, { timezone: 330 }));
|
|
1116
1619
|
});
|
|
@@ -1120,7 +1623,7 @@ InteractionManager.runAfterInteractions(() => {
|
|
|
1120
1623
|
|
|
1121
1624
|
## Accuracy
|
|
1122
1625
|
|
|
1123
|
-
8,
|
|
1626
|
+
8,368 tests across 121 files, including fixtures cross-verified against reference
|
|
1124
1627
|
panchang calculations spanning 2025–2026 across 10 Indian cities plus New York,
|
|
1125
1628
|
London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
|
|
1126
1629
|
`America/New_York`).
|
|
@@ -1130,8 +1633,8 @@ London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
|
|
|
1130
1633
|
| Sunrise / Sunset | ≤29 s observed vs reference minute-midpoint (±45 s tolerance) |
|
|
1131
1634
|
| Moonrise / Moonset | Meeus apparent-upper-limb (refraction + parallax); ~3–5 min vs simpler-horizon authorities is expected |
|
|
1132
1635
|
| Tithi / Nakshatra / Yoga / Karana names | Exact match vs reference |
|
|
1133
|
-
| Tithi / Nakshatra / Yoga / Karana end-times |
|
|
1134
|
-
| Ayanamsa |
|
|
1636
|
+
| Tithi / Nakshatra / Yoga / Karana end-times | ≤60 s vs Drik across all 20 audited comparisons (tithi 46 s, karana 51 s, nakshatra 24 s, yoga 60 s) |
|
|
1637
|
+
| Ayanamsa (Lahiri) | Reproduces DrikPanchang's published value to ~0.01″ across 1950–2050 |
|
|
1135
1638
|
| Planetary positions (Sun–Saturn) | ±0.02° sidereal |
|
|
1136
1639
|
| Planetary positions (Rahu/Ketu, mean node) | ≤0.5° typical; ±2° tolerance |
|
|
1137
1640
|
| Planetary positions (Rahu/Ketu, true node) | ≤0.6° typical (Meeus periodic correction) |
|
|
@@ -1140,6 +1643,18 @@ London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
|
|
|
1140
1643
|
| Ashtakoot total | ±1 point per pair across 30+ matched pairs |
|
|
1141
1644
|
| Sade Sati arc start/end | ±1–2 days vs authoritative ephemerides |
|
|
1142
1645
|
|
|
1646
|
+
**End-time drift.** Drik publishes end times to the minute, so each comparison
|
|
1647
|
+
above carries ±30 s of quantization — that, not the search, dominates what is
|
|
1648
|
+
left. Two independent checks bound the library's own contribution: the reported
|
|
1649
|
+
value matches an exact bisection of the same index function to ≤24 ms, and Sun
|
|
1650
|
+
and Moon agree with Drik's sidereal positions to well under an arcsecond
|
|
1651
|
+
(`tests/validation/element-endtime-audit.test.ts` carries the working).
|
|
1652
|
+
|
|
1653
|
+
**Ayanamsa.** Only Lahiri is verified against an external reference — Drik
|
|
1654
|
+
publishes no value for the other four. Raman, KP, True Chitrapaksha and
|
|
1655
|
+
Thirukanitham are held at their historical offsets from Lahiri, so correcting
|
|
1656
|
+
Lahiri carried them along rather than silently changing how each relates to it.
|
|
1657
|
+
|
|
1143
1658
|
**Detection notes.** **Aadal / Vidaal** follow the classical Moon-from-Sun
|
|
1144
1659
|
nakshatra-distance rule (AstroShastra, HoraSarvam, Ernst Wilhelm), NOT the
|
|
1145
1660
|
Tamil-Vakya weekday rule used by some online panchangs. **Varjyam** emits the
|
|
@@ -1166,13 +1681,160 @@ Jayanti — matches the canonical date across 2025 and 2026 fixtures.
|
|
|
1166
1681
|
|
|
1167
1682
|
## Performance
|
|
1168
1683
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1684
|
+
Measured at Pune on an Apple M-series laptop under Node 24, median of 11
|
|
1685
|
+
processes per configuration. Treat them as relative guidance, not a spec — they
|
|
1686
|
+
move with hardware, latitude and date.
|
|
1687
|
+
|
|
1688
|
+
Two columns, because they differ and both are real. **Distinct days** is the
|
|
1689
|
+
calendar-scan cost: every call misses the solar rise/set cache. **Same day
|
|
1690
|
+
repeated** is what a UI that re-renders one date sees, and what `npm run bench`
|
|
1691
|
+
reports. The last column is the **published 4.3.1 package**, installed from npm
|
|
1692
|
+
and benchmarked beside this one.
|
|
1693
|
+
|
|
1694
|
+
| `getDailyPanchang` call | Distinct days | Same day repeated | 4.3.1 (distinct) |
|
|
1695
|
+
|---|---|---|---|
|
|
1696
|
+
| Default (all sections + end-times) | **~0.41 ms** | **~0.17 ms** | ~6.06 ms |
|
|
1697
|
+
| `computeEndTimes: false` | ~0.39 ms | ~0.15 ms | ~5.63 ms |
|
|
1698
|
+
| Without `'festivals'` | ~0.39 ms | — | n/a |
|
|
1699
|
+
| `sections: ['festivals', 'eclipse']` | ~0.40 ms | — | n/a |
|
|
1700
|
+
| `sections: []` | ~0.27 ms | — | n/a |
|
|
1701
|
+
| `sections: []` + `computeEndTimes: false` | ~0.25 ms | ~0.14 ms | n/a |
|
|
1702
|
+
| `getInstantPanchang` | ~0.21 ms | ~0.10 ms | ~0.43 ms |
|
|
1703
|
+
|
|
1704
|
+
`sections` did not exist before v5, so the rows using it have no 4.3.1
|
|
1705
|
+
counterpart — passing it to 4.3.1 is silently ignored and you get a full run.
|
|
1706
|
+
|
|
1707
|
+
**A default day is ~15× cheaper than in 4.3.1**, and a repeated day ~37×. Most
|
|
1708
|
+
of that is not tuning: 4.3.1 ran `astronomy-engine`'s full lunar theory inside
|
|
1709
|
+
the eclipse search on every day containing a syzygy, and had no cache that
|
|
1710
|
+
survived a call.
|
|
1711
|
+
|
|
1712
|
+
One surface moved the other way, and it is deliberate: the **raw longitude
|
|
1713
|
+
getters** (`getSiderealSunLongitude`, `getSiderealMoonLongitude`) cost about
|
|
1714
|
+
twice what they did in 4.3.1 per call — ~14 µs against ~7 µs for the Moon,
|
|
1715
|
+
~2.8 µs against ~1.4 µs for the Sun — because the own-ephemeris series keep
|
|
1716
|
+
roughly three times `astronomy-engine`'s accuracy against JPL DE441, and the
|
|
1717
|
+
evaluation is sine-bound, so more terms cost proportionally more. Every
|
|
1718
|
+
documented workflow (`getDailyPanchang`, `getInstantPanchang`, the year/range
|
|
1719
|
+
APIs, the static tables) amortizes those reads through caches and is faster
|
|
1720
|
+
than 4.3.1 by the factors above; the per-call price is only visible to code
|
|
1721
|
+
calling the raw getters in a tight loop over distinct instants. For scanning
|
|
1722
|
+
workloads, prefer the range APIs or `getDailyPanchang` — they read through
|
|
1723
|
+
interpolated longitude blocks precisely so that this cost is paid once per day
|
|
1724
|
+
rather than once per read.
|
|
1725
|
+
|
|
1726
|
+
The same trade surfaces once more in the birth-chart *primitives*:
|
|
1727
|
+
`computeRashiChart` and `computeNavamsa` measure ~0.31 ms against ~0.09 ms on
|
|
1728
|
+
published 4.3.1 — a chart is fifteen full-accuracy planet evaluations (three
|
|
1729
|
+
per planet, for the retrograde probes) and nothing amortizes them. The deeper
|
|
1730
|
+
chart stack inverts it again: `computeShadbala` and `computeBhavaBala` come
|
|
1731
|
+
out ~2× *faster* than 4.3.1, because v5 computes the positions once and reuses
|
|
1732
|
+
them. At a third of a millisecond per chart this is irrelevant interactively;
|
|
1733
|
+
it is visible only to code building thousands of charts in a batch.
|
|
1734
|
+
|
|
1735
|
+
Cost is dominated by ephemeris evaluations, so the lever that matters is the one
|
|
1736
|
+
that avoids them:
|
|
1737
|
+
|
|
1738
|
+
- **`sections`** — skip the optional ephemeris-backed blocks you don't need.
|
|
1739
|
+
Dropping `'festivals'` takes a default call from ~0.41 ms to ~0.39 ms cold,
|
|
1740
|
+
and dropping everything takes it to ~0.27 ms. See
|
|
1741
|
+
[Narrowing the work](#narrowing-the-work).
|
|
1742
|
+
- **`computeEndTimes: false`** — a small win, never a large one. It skips the
|
|
1743
|
+
transition searches, but those read through the same interpolated longitude
|
|
1744
|
+
blocks the rest of the call has already built, so what it saves is arithmetic
|
|
1745
|
+
rather than ephemeris: ~5% cold, ~10% warm. Use it to drop `endTime` fields
|
|
1746
|
+
you don't want, not to go faster.
|
|
1747
|
+
|
|
1748
|
+
*Changed in v5.* Both entry points now always interpolate, so output depends
|
|
1749
|
+
on neither `sections` nor `computeEndTimes` — see `INTERPOLATE_ALWAYS` in
|
|
1750
|
+
`src/core/panchang.ts`. Earlier development builds chose the longitude cache's
|
|
1751
|
+
mode from `computeEndTimes`, which made asking for *less* output cost *more*
|
|
1752
|
+
on a full call; that is gone.
|
|
1753
|
+
|
|
1754
|
+
Repeated calls for the same location-day are cheaper because solar rise/set
|
|
1755
|
+
events are cached process-wide, keyed on `(direction, lat, lon, elevation, UTC
|
|
1756
|
+
day)` and bounded at 20,000 entries. The cache makes sunrise single-valued as
|
|
1757
|
+
well as fast — see [Upgrading from 4.x](#sunrise-is-single-valued-per-location-day).
|
|
1758
|
+
It does not make a *single* cold rise/set call cheaper — against 4.3.1
|
|
1759
|
+
`getSunrise` is +1.8%, `getSunset` +8.8%, `getMoonrise` +5.5% and `getMoonset`
|
|
1760
|
+
+6.6%, i.e. unchanged to slightly worse — what it removes is the second and
|
|
1761
|
+
every later call for the same day.
|
|
1762
|
+
|
|
1763
|
+
Range helpers apply the same narrowing internally:
|
|
1764
|
+
`computeEkadashiDatesForYear` reads only the tithi at sunrise and so runs with
|
|
1765
|
+
every optional section off (**~18 ms** for a full year, against ~2,360 ms in
|
|
1766
|
+
4.3.1); `computeFestivalsInRange` keeps only `'festivals'` and `'eclipse'`
|
|
1767
|
+
(**~131 ms/year**, against ~2,180); `computeSankrantisForYear` needs only the
|
|
1768
|
+
Sun, so it scans one solar longitude per day and bisects the 12 transits rather
|
|
1769
|
+
than building a panchang each day (**~3.3 ms/year**, against ~154).
|
|
1173
1770
|
|
|
1174
1771
|
Birth-chart helpers are independent — calling them does not add work to
|
|
1175
|
-
`getDailyPanchang`.
|
|
1772
|
+
`getDailyPanchang`. Within them, `computeShadbala` and `computeBhavaBala` build
|
|
1773
|
+
the natal positions once and derive all seven charts from them (~0.33 ms each,
|
|
1774
|
+
against ~0.72 in 4.3.1).
|
|
1775
|
+
|
|
1776
|
+
**Charts are the one place v5 is slower.** `computeRashiChart` and
|
|
1777
|
+
`computeNavamsa` cost **~0.32 ms** against ~0.10 in 4.3.1 — 3.3×, entirely the
|
|
1778
|
+
planetary ephemeris, and the deliberate price of an order-of-magnitude accuracy
|
|
1779
|
+
gain against JPL DE441 (Mercury 6.50″ → 0.30″, Venus 19.59″ → 0.86″). If you
|
|
1780
|
+
build many charts and do not need that precision, 4.x was cheaper; nothing else
|
|
1781
|
+
in the library regressed.
|
|
1782
|
+
|
|
1783
|
+
### Narrowing the work
|
|
1784
|
+
|
|
1785
|
+
`PanchangSection` lists the four optional blocks. Everything else a daily
|
|
1786
|
+
panchang returns — the five elements, slot systems, muhurtas, inauspicious
|
|
1787
|
+
periods, masa / samvat / rashi — is arithmetic over the sunrise / sunset /
|
|
1788
|
+
next-sunrise triplet and is always computed, because skipping it would save
|
|
1789
|
+
nothing.
|
|
1790
|
+
|
|
1791
|
+
| Section | Covers | Fields when omitted |
|
|
1792
|
+
|---|---|---|
|
|
1793
|
+
| `'festivals'` | Festival detection — needs the prior day's sunrise/sunset, the next day's transit, per-kala tithi anchors, and the prior day's Chandra Masa | `festivals: []` — but an eclipse entry is still prepended when `'eclipse'` is on |
|
|
1794
|
+
| `'eclipse'` | Eclipse overlapping the Hindu day | `eclipse: null` |
|
|
1795
|
+
| `'moonTimes'` | `moon.rise` / `moon.set` | `null` |
|
|
1796
|
+
| `'lunarWindows'` | Bhadra, Varjyam, Panchaka-Rahita — each binary-searches lunar longitude across the day | `null` / `[]` |
|
|
1797
|
+
|
|
1798
|
+
```typescript
|
|
1799
|
+
// Everything (default).
|
|
1800
|
+
getDailyPanchang(date, loc, { timezone: 330 });
|
|
1801
|
+
|
|
1802
|
+
// Festivals only — no moon times, no Bhadra/Varjyam windows.
|
|
1803
|
+
getDailyPanchang(date, loc, {
|
|
1804
|
+
timezone: 330,
|
|
1805
|
+
sections: ['festivals', 'eclipse'],
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
// Cheapest useful call: elements, slots, muhurtas and inauspicious periods
|
|
1809
|
+
// only. Those are arithmetic on the sunrise triplet and are always computed.
|
|
1810
|
+
getDailyPanchang(date, loc, {
|
|
1811
|
+
timezone: 330,
|
|
1812
|
+
sections: [],
|
|
1813
|
+
computeEndTimes: false,
|
|
1814
|
+
});
|
|
1815
|
+
```
|
|
1816
|
+
|
|
1817
|
+
Omitting a section leaves its fields at their documented empty value (`null`
|
|
1818
|
+
or `[]`) — never a half-filled one.
|
|
1819
|
+
|
|
1820
|
+
Narrowing is **exactly output-neutral**: every field a narrowed call does
|
|
1821
|
+
compute is identical, to the millisecond, to what the full call would have
|
|
1822
|
+
returned. `sections` only decides what is skipped, never what a computed value
|
|
1823
|
+
is. (This is guaranteed by `LongitudeCache` memoizing on the exact instant. It
|
|
1824
|
+
was not true while that memo binned longitudes into 60-second buckets, when
|
|
1825
|
+
narrowing could shift transition times by up to 63 s.)
|
|
1826
|
+
|
|
1827
|
+
### A note on Hermes / React Native
|
|
1828
|
+
|
|
1829
|
+
Earlier versions of this table also quoted Hermes figures. Those were budget
|
|
1830
|
+
targets from the project plan, never measurements: `npm run test:hermes` runs
|
|
1831
|
+
`hermes-parser` over the built bundle to prove the syntax is Hermes-compatible,
|
|
1832
|
+
which is a *parse* check and does not execute anything. Hermes numbers will be
|
|
1833
|
+
published here once they are actually measured on device.
|
|
1834
|
+
|
|
1835
|
+
What does carry over is the shape of the cost: it is dominated by ephemeris
|
|
1836
|
+
math, so the `sections` and `computeEndTimes` levers above have the same
|
|
1837
|
+
proportional effect on any runtime.
|
|
1176
1838
|
|
|
1177
1839
|
---
|
|
1178
1840
|
|