panchang-ts 4.1.1 → 4.3.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 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.1 ms names-only, ~0.5 ms full) · **Typed** (full TypeScript) · **Offline** (pure JS math) · **8,103 tests across 96 files**
8
+ **Fast** (~0.1 ms names-only, ~0.5 ms full) · **Typed** (full TypeScript) · **Offline** (pure JS math) · **8,164 tests across 100 files**
9
9
 
10
10
  ---
11
11
 
@@ -262,6 +262,80 @@ getDailyPanchang(jan13, amritsar, { timezone: 330, region: 'punjab' })!
262
262
  legacy slugs `'tamil'`, `'bengal'`, `'north-india'` are still accepted and
263
263
  mapped internally.
264
264
 
265
+ ### Pre-computed table (bundled, India / IST)
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)).
296
+
297
+ ### Festivals outside India — build a location table and cache it
298
+
299
+ The bundled table is **IST-only**. Elsewhere (Europe, North America, rest
300
+ of world) festival dates can shift by ±1 day, because canonical times
301
+ (nishita / pradosha / chandrodaya …) are observer-dependent — and the shift
302
+ tracks the timezone offset, not the "region", so a single per-continent
303
+ table would mis-date boundary-day festivals.
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:
310
+
311
+ ```typescript
312
+ import { buildFestivalsTable } from 'panchang-ts';
313
+ import { getFestivalsForYear, getFestivalsForDate } from 'panchang-ts/festivals';
314
+
315
+ // On first use at the user's location (a few seconds on-device — run it in
316
+ // the background / chunk by year), then cache `table` to disk/MMKV.
317
+ const table = buildFestivalsTable({
318
+ location: { latitude: 40.7128, longitude: -74.006 },
319
+ timezoneOffsetMinutes: -300, // US Eastern (EST); 0 = UK, 330 = IST
320
+ startYear: 2024,
321
+ endYear: 2031,
322
+ languages: ['en'], // omit hi to halve the size
323
+ });
324
+
325
+ // Later reads are instant lookups against the cached table:
326
+ getFestivalsForYear(2026, 'en', table);
327
+ getFestivalsForDate('2026-11-08', 'en', table); // key is in the table's tz
328
+ ```
329
+
330
+ `buildFestivalsTable` returns the same `FestivalsFile` shape as the bundled
331
+ data, so a cached table and the bundled India table are interchangeable as
332
+ the `source` argument. India-majority apps can lean on the bundled table for
333
+ zero first-load latency and only compute-and-cache for non-IST users.
334
+
335
+ **Other notes:** Karva Chauth / Dhanteras / Diwali emit with Purnimanta
336
+ paksha naming. To regenerate the bundled India table after a registry
337
+ change, run `npm run festivals:gen` (rolling window, no constants to edit).
338
+
265
339
  ## Eclipses
266
340
 
267
341
  ```typescript
@@ -280,6 +354,117 @@ import { getUpcomingSolarEclipse, getUpcomingLunarEclipse } from 'panchang-ts';
280
354
  const next = getUpcomingSolarEclipse(new Date(), loc, 365 /* days */);
281
355
  ```
282
356
 
357
+ ### Pre-computed table (bundled, India / IST)
358
+
359
+ Like the festivals table, eclipse data ships as a static, engine-free entry
360
+ at `panchang-ts/eclipses` — a rolling **2-years-past / 5-years-future** window
361
+ pre-computed against Varanasi (IST). It lists every eclipse **visible from
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.
365
+
366
+ ```typescript
367
+ import {
368
+ getEclipsesForYear,
369
+ getEclipsesForDate,
370
+ ECLIPSES_META,
371
+ ECLIPSES_YEAR_RANGE,
372
+ } from 'panchang-ts/eclipses';
373
+
374
+ const e = getEclipsesForYear(2025)![0].eclipses[0];
375
+ e.kind; // 'lunar'
376
+ e.subtype; // 'total'
377
+ e.start; e.peak; e.end; // ISO UTC strings
378
+ e.magnitude; // 0..1 obscuration at peak
379
+ e.visibleFromLocation; // visible during any phase? (always true in bundled table)
380
+ e.visibleAtPeak; // is greatest eclipse itself above the horizon?
381
+ e.sutak; // { start, end } — see note below
382
+ getEclipsesForDate('2025-09-07', 'hi'); // [पूर्ण चंद्र ग्रहण]
383
+ ECLIPSES_META.referenceLocation; // "Varanasi"
384
+ ```
385
+
386
+ Each entry carries `en` + `hi` text. Solar eclipses report the subtype seen
387
+ **locally** (a globally-total eclipse may read `partial` from Varanasi). The
388
+ `sutak` window is present only where it applies — all visible solar eclipses
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.
393
+
394
+ **Outside India:** which eclipses are visible — and thus carry `sutak` —
395
+ differs by location. Build and cache a location-specific table with
396
+ `buildEclipsesTable` (main entry, uses the engine), then read it back via the
397
+ same accessors' `source` argument — the same compute-once-then-cache pattern
398
+ as festivals:
399
+
400
+ ```typescript
401
+ import { buildEclipsesTable } from 'panchang-ts';
402
+ import { getEclipsesForYear } from 'panchang-ts/eclipses';
403
+
404
+ const table = buildEclipsesTable({
405
+ location: { latitude: 51.5074, longitude: -0.1278 },
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).
416
+
417
+ ## Moon Phases
418
+
419
+ The four principal lunar phases — **new** (Amavasya), **first quarter**,
420
+ **full** (Purnima), **last quarter** — as precise instants. (These are the
421
+ astronomical quarter moments, distinct from the same-named *tithis*, which are
422
+ ~24h windows.)
423
+
424
+ ```typescript
425
+ import { getMoonPhasesInRange } from 'panchang-ts';
426
+ const phases = getMoonPhasesInRange(new Date('2026-01-01'), new Date('2026-12-31'));
427
+ phases.forEach(p => console.log(p.phase, p.time.toISOString())); // ~49 / year
428
+ ```
429
+
430
+ ### Pre-computed table (bundled, India / IST)
431
+
432
+ Same engine-free pattern as festivals and eclipses, at `panchang-ts/moon-phases`
433
+ — a rolling **2-years-past / 5-years-future** window. Phases are global
434
+ instants; the bundled table maps each onto its **IST** calendar date (so a new
435
+ moon at 19:52 UTC on Jan 18 is listed under Jan 19 in India).
436
+
437
+ ```typescript
438
+ import {
439
+ getMoonPhasesForYear,
440
+ getMoonPhasesForDate,
441
+ MOON_PHASES_META,
442
+ MOON_PHASES_YEAR_RANGE,
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';
458
+
459
+ const table = buildMoonPhasesTable({
460
+ timezoneOffsetMinutes: -300, // US Eastern
461
+ startYear: 2024, endYear: 2031,
462
+ });
463
+ getMoonPhasesForYear(2026, 'en', table);
464
+ ```
465
+
466
+ Regenerate the bundled India table with `npm run moon-phases:gen`.
467
+
283
468
  ## Planetary Positions
284
469
 
285
470
  ```typescript
@@ -657,7 +842,7 @@ import {
657
842
  convertGregorianToHindu, convertHinduToGregorian,
658
843
  getKaliYugaYear, getHinduNewYear,
659
844
  getEkadashiDatesForYear, getSankrantisForYear,
660
- getFestivalsInRange, getUpcomingEclipses,
845
+ getFestivalsInRange, getUpcomingEclipses, getEclipsesInRange,
661
846
  } from 'panchang-ts';
662
847
 
663
848
  // Gregorian → Hindu coords at sunrise
@@ -854,6 +1039,10 @@ computeMadhyahna, computePratahSandhya, computeSayahnaSandhya
854
1039
 
855
1040
  // Eclipses
856
1041
  getUpcomingSolarEclipse, getUpcomingLunarEclipse, getEclipseDuringDay
1042
+ isEclipseVisibleAnyPhase
1043
+
1044
+ // Moon phases (new / quarters / full as precise instants)
1045
+ getMoonPhasesInRange
857
1046
 
858
1047
  // Jyotish — planets, dashas, transits
859
1048
  computePlanetaryPositions, GRAHA_ABBR
@@ -888,7 +1077,12 @@ aksharabhyasamRule, seemanthamRule, shopOpeningRule, travelStartRule
888
1077
  // Calendar conversion + yearly listings
889
1078
  convertGregorianToHindu, convertHinduToGregorian
890
1079
  getKaliYugaYear, getHinduNewYear, computeSamvat
891
- getEkadashiDatesForYear, getSankrantisForYear, getFestivalsInRange, getUpcomingEclipses
1080
+ getEkadashiDatesForYear, getSankrantisForYear, getFestivalsInRange
1081
+ getUpcomingEclipses, getEclipsesInRange
1082
+
1083
+ // Static data tables (engine-using runtime builders; bundled JSON at
1084
+ // panchang-ts/festivals, panchang-ts/eclipses, panchang-ts/moon-phases)
1085
+ buildFestivalsTable, buildEclipsesTable, buildMoonPhasesTable
892
1086
 
893
1087
  // Errors
894
1088
  PanchangError
@@ -926,7 +1120,7 @@ InteractionManager.runAfterInteractions(() => {
926
1120
 
927
1121
  ## Accuracy
928
1122
 
929
- 8,103 tests across 96 files, including fixtures cross-verified against reference
1123
+ 8,164 tests across 100 files, including fixtures cross-verified against reference
930
1124
  panchang calculations spanning 2025–2026 across 10 Indian cities plus New York,
931
1125
  London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
932
1126
  `America/New_York`).
@@ -0,0 +1,404 @@
1
+ 'use strict';
2
+
3
+ // src/data/eclipses.json
4
+ var eclipses_default = {
5
+ _meta: {
6
+ referenceLocation: "Varanasi",
7
+ latitude: 25.3176,
8
+ longitude: 82.9739,
9
+ timezoneOffsetMinutes: 330,
10
+ visibleOnly: true,
11
+ languages: [
12
+ "en",
13
+ "hi"
14
+ ],
15
+ startYear: 2024,
16
+ endYear: 2031,
17
+ generatedAt: "2026-05-30T14:11:40.371Z",
18
+ note: "Eclipses pre-computed for Varanasi (IST), visible from there during any phase (eclipsed body above the horizon between first and last contact \u2014 so an eclipse in progress at moon/sunrise or moon/sunset is included). visibleAtPeak flags whether greatest eclipse itself is observable. Within India visibility is essentially uniform; elsewhere it differs, so build a location-specific table at runtime with buildEclipsesTable and cache it. Solar eclipses carry the subtype seen from Varanasi (a globally-total eclipse may show as partial). Times are ISO UTC. Sutak: 4 prahara (12h) before first contact for solar, 3 prahara (9h) for umbral (partial/total) lunar, ending at moksha; penumbral lunar eclipses carry no sutak and are not religiously observed (drik / pandit consensus). Each entry carries both en and hi text."
19
+ },
20
+ years: {
21
+ "2024": [],
22
+ "2025": [
23
+ {
24
+ date: "2025-09-07",
25
+ eclipses: [
26
+ {
27
+ name: {
28
+ en: "Total Lunar Eclipse",
29
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
30
+ },
31
+ kind: "lunar",
32
+ subtype: "total",
33
+ start: "2025-09-07T15:28:02.515Z",
34
+ peak: "2025-09-07T18:11:41.501Z",
35
+ end: "2025-09-07T20:55:20.486Z",
36
+ magnitude: 1,
37
+ visibleFromLocation: true,
38
+ visibleAtPeak: true,
39
+ description: {
40
+ en: "Total Lunar Eclipse \u2014 100% obscuration.",
41
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 100% \u0917\u094D\u0930\u093E\u0938\u0964"
42
+ },
43
+ sutak: {
44
+ start: "2025-09-07T06:28:02.515Z",
45
+ end: "2025-09-07T20:55:20.486Z"
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ ],
51
+ "2026": [
52
+ {
53
+ date: "2026-03-03",
54
+ eclipses: [
55
+ {
56
+ name: {
57
+ en: "Total Lunar Eclipse",
58
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
59
+ },
60
+ kind: "lunar",
61
+ subtype: "total",
62
+ start: "2026-03-03T08:44:01.948Z",
63
+ peak: "2026-03-03T11:33:40.289Z",
64
+ end: "2026-03-03T14:23:18.629Z",
65
+ magnitude: 1,
66
+ visibleFromLocation: true,
67
+ visibleAtPeak: false,
68
+ description: {
69
+ en: "Total Lunar Eclipse \u2014 100% obscuration.",
70
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 100% \u0917\u094D\u0930\u093E\u0938\u0964"
71
+ },
72
+ sutak: {
73
+ start: "2026-03-02T23:44:01.948Z",
74
+ end: "2026-03-03T14:23:18.629Z"
75
+ }
76
+ }
77
+ ]
78
+ }
79
+ ],
80
+ "2027": [
81
+ {
82
+ date: "2027-02-21",
83
+ eclipses: [
84
+ {
85
+ name: {
86
+ en: "Penumbral Lunar Eclipse",
87
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
88
+ },
89
+ kind: "lunar",
90
+ subtype: "penumbral",
91
+ start: "2027-02-20T21:11:52.109Z",
92
+ peak: "2027-02-20T23:12:44.142Z",
93
+ end: "2027-02-21T01:13:36.174Z",
94
+ magnitude: 0,
95
+ visibleFromLocation: true,
96
+ visibleAtPeak: true,
97
+ description: {
98
+ en: "Penumbral Lunar Eclipse \u2014 penumbral shadow only; no sutak.",
99
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 \u0915\u0947\u0935\u0932 \u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091B\u093E\u092F\u093E; \u0938\u0942\u0924\u0915 \u0928\u0939\u0940\u0902\u0964"
100
+ }
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ date: "2027-07-18",
106
+ eclipses: [
107
+ {
108
+ name: {
109
+ en: "Penumbral Lunar Eclipse",
110
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
111
+ },
112
+ kind: "lunar",
113
+ subtype: "penumbral",
114
+ start: "2027-07-18T15:50:13.585Z",
115
+ peak: "2027-07-18T16:02:54.880Z",
116
+ end: "2027-07-18T16:15:36.174Z",
117
+ magnitude: 0,
118
+ visibleFromLocation: true,
119
+ visibleAtPeak: true,
120
+ description: {
121
+ en: "Penumbral Lunar Eclipse \u2014 penumbral shadow only; no sutak.",
122
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 \u0915\u0947\u0935\u0932 \u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091B\u093E\u092F\u093E; \u0938\u0942\u0924\u0915 \u0928\u0939\u0940\u0902\u0964"
123
+ }
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ date: "2027-08-02",
129
+ eclipses: [
130
+ {
131
+ name: {
132
+ en: "Partial Solar Eclipse",
133
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923"
134
+ },
135
+ kind: "solar",
136
+ subtype: "partial",
137
+ start: "2027-08-02T10:35:33.763Z",
138
+ peak: "2027-08-02T11:07:37.251Z",
139
+ end: "2027-08-02T11:38:16.999Z",
140
+ magnitude: 0.06923317826751327,
141
+ visibleFromLocation: true,
142
+ visibleAtPeak: true,
143
+ description: {
144
+ en: "Partial Solar Eclipse \u2014 7% obscuration.",
145
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923 \u2014 7% \u0917\u094D\u0930\u093E\u0938\u0964"
146
+ },
147
+ sutak: {
148
+ start: "2027-08-01T22:35:33.763Z",
149
+ end: "2027-08-02T11:38:16.999Z"
150
+ }
151
+ }
152
+ ]
153
+ }
154
+ ],
155
+ "2028": [
156
+ {
157
+ date: "2028-07-06",
158
+ eclipses: [
159
+ {
160
+ name: {
161
+ en: "Partial Lunar Eclipse",
162
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
163
+ },
164
+ kind: "lunar",
165
+ subtype: "partial",
166
+ start: "2028-07-06T15:43:55.421Z",
167
+ peak: "2028-07-06T18:19:38.379Z",
168
+ end: "2028-07-06T20:55:21.336Z",
169
+ magnitude: 0.3322998217792101,
170
+ visibleFromLocation: true,
171
+ visibleAtPeak: true,
172
+ description: {
173
+ en: "Partial Lunar Eclipse \u2014 33% obscuration.",
174
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 33% \u0917\u094D\u0930\u093E\u0938\u0964"
175
+ },
176
+ sutak: {
177
+ start: "2028-07-06T06:43:55.421Z",
178
+ end: "2028-07-06T20:55:21.336Z"
179
+ }
180
+ }
181
+ ]
182
+ },
183
+ {
184
+ date: "2028-12-31",
185
+ eclipses: [
186
+ {
187
+ name: {
188
+ en: "Total Lunar Eclipse",
189
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
190
+ },
191
+ kind: "lunar",
192
+ subtype: "total",
193
+ start: "2028-12-31T14:03:27.574Z",
194
+ peak: "2028-12-31T16:51:54.549Z",
195
+ end: "2028-12-31T19:40:21.523Z",
196
+ magnitude: 1,
197
+ visibleFromLocation: true,
198
+ visibleAtPeak: true,
199
+ description: {
200
+ en: "Total Lunar Eclipse \u2014 100% obscuration.",
201
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 100% \u0917\u094D\u0930\u093E\u0938\u0964"
202
+ },
203
+ sutak: {
204
+ start: "2028-12-31T05:03:27.574Z",
205
+ end: "2028-12-31T19:40:21.523Z"
206
+ }
207
+ }
208
+ ]
209
+ }
210
+ ],
211
+ "2029": [
212
+ {
213
+ date: "2029-12-21",
214
+ eclipses: [
215
+ {
216
+ name: {
217
+ en: "Total Lunar Eclipse",
218
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
219
+ },
220
+ kind: "lunar",
221
+ subtype: "total",
222
+ start: "2029-12-20T19:42:34.436Z",
223
+ peak: "2029-12-20T22:41:54.610Z",
224
+ end: "2029-12-21T01:41:14.783Z",
225
+ magnitude: 1,
226
+ visibleFromLocation: true,
227
+ visibleAtPeak: true,
228
+ description: {
229
+ en: "Total Lunar Eclipse \u2014 100% obscuration.",
230
+ hi: "\u092A\u0942\u0930\u094D\u0923 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 100% \u0917\u094D\u0930\u093E\u0938\u0964"
231
+ },
232
+ sutak: {
233
+ start: "2029-12-20T10:42:34.436Z",
234
+ end: "2029-12-21T01:41:14.783Z"
235
+ }
236
+ }
237
+ ]
238
+ }
239
+ ],
240
+ "2030": [
241
+ {
242
+ date: "2030-06-01",
243
+ eclipses: [
244
+ {
245
+ name: {
246
+ en: "Partial Solar Eclipse",
247
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923"
248
+ },
249
+ kind: "solar",
250
+ subtype: "partial",
251
+ start: "2030-06-01T05:29:15.459Z",
252
+ peak: "2030-06-01T06:26:07.220Z",
253
+ end: "2030-06-01T07:23:32.918Z",
254
+ magnitude: 0.03201876826173855,
255
+ visibleFromLocation: true,
256
+ visibleAtPeak: true,
257
+ description: {
258
+ en: "Partial Solar Eclipse \u2014 3% obscuration.",
259
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923 \u2014 3% \u0917\u094D\u0930\u093E\u0938\u0964"
260
+ },
261
+ sutak: {
262
+ start: "2030-05-31T17:29:15.459Z",
263
+ end: "2030-06-01T07:23:32.918Z"
264
+ }
265
+ }
266
+ ]
267
+ },
268
+ {
269
+ date: "2030-06-16",
270
+ eclipses: [
271
+ {
272
+ name: {
273
+ en: "Partial Lunar Eclipse",
274
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
275
+ },
276
+ kind: "lunar",
277
+ subtype: "partial",
278
+ start: "2030-06-15T16:13:48.134Z",
279
+ peak: "2030-06-15T18:33:14.732Z",
280
+ end: "2030-06-15T20:52:41.329Z",
281
+ magnitude: 0.4708077570686115,
282
+ visibleFromLocation: true,
283
+ visibleAtPeak: true,
284
+ description: {
285
+ en: "Partial Lunar Eclipse \u2014 47% obscuration.",
286
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 47% \u0917\u094D\u0930\u093E\u0938\u0964"
287
+ },
288
+ sutak: {
289
+ start: "2030-06-15T07:13:48.134Z",
290
+ end: "2030-06-15T20:52:41.329Z"
291
+ }
292
+ }
293
+ ]
294
+ },
295
+ {
296
+ date: "2030-12-10",
297
+ eclipses: [
298
+ {
299
+ name: {
300
+ en: "Penumbral Lunar Eclipse",
301
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923"
302
+ },
303
+ kind: "lunar",
304
+ subtype: "penumbral",
305
+ start: "2030-12-09T20:07:30.214Z",
306
+ peak: "2030-12-09T22:27:32.805Z",
307
+ end: "2030-12-10T00:47:35.395Z",
308
+ magnitude: 0,
309
+ visibleFromLocation: true,
310
+ visibleAtPeak: true,
311
+ description: {
312
+ en: "Penumbral Lunar Eclipse \u2014 penumbral shadow only; no sutak.",
313
+ hi: "\u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091A\u0902\u0926\u094D\u0930 \u0917\u094D\u0930\u0939\u0923 \u2014 \u0915\u0947\u0935\u0932 \u0909\u092A\u091A\u094D\u091B\u093E\u092F\u093E \u091B\u093E\u092F\u093E; \u0938\u0942\u0924\u0915 \u0928\u0939\u0940\u0902\u0964"
314
+ }
315
+ }
316
+ ]
317
+ }
318
+ ],
319
+ "2031": [
320
+ {
321
+ date: "2031-05-21",
322
+ eclipses: [
323
+ {
324
+ name: {
325
+ en: "Partial Solar Eclipse",
326
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923"
327
+ },
328
+ kind: "solar",
329
+ subtype: "partial",
330
+ start: "2031-05-21T06:16:35.159Z",
331
+ peak: "2031-05-21T07:56:20.750Z",
332
+ end: "2031-05-21T09:26:36.965Z",
333
+ magnitude: 0.3939059024675881,
334
+ visibleFromLocation: true,
335
+ visibleAtPeak: true,
336
+ description: {
337
+ en: "Partial Solar Eclipse \u2014 39% obscuration.",
338
+ hi: "\u0906\u0902\u0936\u093F\u0915 \u0938\u0942\u0930\u094D\u092F \u0917\u094D\u0930\u0939\u0923 \u2014 39% \u0917\u094D\u0930\u093E\u0938\u0964"
339
+ },
340
+ sutak: {
341
+ start: "2031-05-20T18:16:35.159Z",
342
+ end: "2031-05-21T09:26:36.965Z"
343
+ }
344
+ }
345
+ ]
346
+ }
347
+ ]
348
+ }
349
+ };
350
+
351
+ // src/calendar/eclipsesTable.ts
352
+ var bundled = eclipses_default;
353
+ var ECLIPSES_META = bundled._meta;
354
+ var ECLIPSES_YEAR_RANGE = {
355
+ start: bundled._meta.startYear,
356
+ end: bundled._meta.endYear
357
+ };
358
+ function flatten(raw, lang) {
359
+ const name = raw.name[lang] ?? Object.values(raw.name)[0] ?? "";
360
+ const out = {
361
+ name,
362
+ kind: raw.kind,
363
+ subtype: raw.subtype,
364
+ start: raw.start,
365
+ peak: raw.peak,
366
+ end: raw.end,
367
+ magnitude: raw.magnitude,
368
+ visibleFromLocation: raw.visibleFromLocation,
369
+ visibleAtPeak: raw.visibleAtPeak
370
+ };
371
+ if (raw.sutak) out.sutak = raw.sutak;
372
+ if (raw.description) {
373
+ out.description = raw.description[lang] ?? Object.values(raw.description)[0] ?? "";
374
+ }
375
+ return out;
376
+ }
377
+ function getEclipsesForYear(year, lang = "en", source = bundled) {
378
+ const days = source.years[String(year)];
379
+ if (!days) return null;
380
+ return days.map((d) => ({
381
+ date: d.date,
382
+ eclipses: d.eclipses.map((e) => flatten(e, lang))
383
+ }));
384
+ }
385
+ function getEclipsesForDate(date, lang = "en", source = bundled) {
386
+ const key = typeof date === "string" ? date : toDateKey(date, source._meta.timezoneOffsetMinutes);
387
+ const yearKey = key.slice(0, 4);
388
+ const days = source.years[yearKey];
389
+ if (!days) return [];
390
+ const day = days.find((d) => d.date === key);
391
+ return day ? day.eclipses.map((e) => flatten(e, lang)) : [];
392
+ }
393
+ function toDateKey(d, offsetMinutes) {
394
+ const shifted = new Date(d.getTime() + offsetMinutes * 6e4);
395
+ const y = shifted.getUTCFullYear();
396
+ const m = String(shifted.getUTCMonth() + 1).padStart(2, "0");
397
+ const day = String(shifted.getUTCDate()).padStart(2, "0");
398
+ return `${y}-${m}-${day}`;
399
+ }
400
+
401
+ exports.ECLIPSES_META = ECLIPSES_META;
402
+ exports.ECLIPSES_YEAR_RANGE = ECLIPSES_YEAR_RANGE;
403
+ exports.getEclipsesForDate = getEclipsesForDate;
404
+ exports.getEclipsesForYear = getEclipsesForYear;
@@ -0,0 +1,40 @@
1
+ import { g as EclipseTableMeta, E as EclipsesTableLanguage, a as EclipsesFile, d as EclipseTableEntry, c as EclipseTableDay } from '../eclipsesTableTypes-D-tSQMQd.cjs';
2
+ export { b as EclipseSutak, e as EclipseTableEntryRaw, f as EclipseTableKind, h as EclipseTableSubtype, R as RawEclipseTableDay } from '../eclipsesTableTypes-D-tSQMQd.cjs';
3
+
4
+ /** Metadata describing what the bundled table was generated from. */
5
+ declare const ECLIPSES_META: EclipseTableMeta;
6
+ /** Inclusive year range covered by the bundled table. */
7
+ declare const ECLIPSES_YEAR_RANGE: {
8
+ readonly start: number;
9
+ readonly end: number;
10
+ };
11
+ /**
12
+ * Eclipse days for a Gregorian year, flattened to the requested locale.
13
+ *
14
+ * Returns `null` if `year` is outside the table's range, or an empty array if
15
+ * the year is in range but had no (visible) eclipses.
16
+ *
17
+ * @param year Gregorian year.
18
+ * @param lang `'en'` (default) or `'hi'`.
19
+ * @param source Table to read from. Defaults to the bundled Varanasi table;
20
+ * pass a {@link buildEclipsesTable} result to read a
21
+ * location-specific table you built and cached.
22
+ */
23
+ declare function getEclipsesForYear(year: number, lang?: EclipsesTableLanguage, source?: EclipsesFile): EclipseTableDay[] | null;
24
+ /**
25
+ * Eclipses on a specific date, flattened to the requested locale.
26
+ *
27
+ * Returns an empty array if the date has no eclipse or is outside the table's
28
+ * range. The date is matched against the eclipse peak's local date.
29
+ *
30
+ * @param date Either an ISO `YYYY-MM-DD` string (interpreted in the table's
31
+ * reference timezone) or a `Date` (its local calendar date in the
32
+ * table's reference timezone is used).
33
+ * @param lang `'en'` (default) or `'hi'`.
34
+ * @param source Table to read from. Defaults to the bundled Varanasi table;
35
+ * pass a {@link buildEclipsesTable} result for a
36
+ * location-specific table.
37
+ */
38
+ declare function getEclipsesForDate(date: string | Date, lang?: EclipsesTableLanguage, source?: EclipsesFile): EclipseTableEntry[];
39
+
40
+ export { ECLIPSES_META, ECLIPSES_YEAR_RANGE, EclipseTableDay, EclipseTableEntry, EclipseTableMeta, EclipsesFile, EclipsesTableLanguage, getEclipsesForDate, getEclipsesForYear };