panchang-ts 0.7.0 → 1.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 CHANGED
@@ -163,7 +163,7 @@ Rahu Kalam, Gulika Kalam, Yamaganda, Panchaka detection.
163
163
  Amrit Siddhi, Sarvartha Siddhi, Ravi Pushya, Guru Pushya yoga detection. 24 major pan-Indian festivals, recurring Ekadashi & Pradosha Vrata, Sankranti — Adhika months auto-skipped.
164
164
 
165
165
  ### Jyotish (Vedic Astrology)
166
- All 9 graha positions (geocentric, sidereal) with rashi, nakshatra, pada, and retrograde status. Vimshottari Dasha with Antardasha breakdown.
166
+ All 9 graha positions (geocentric, sidereal) with rashi, nakshatra, pada, and retrograde status. Vimshottari Dasha with Antardasha breakdown — from a birth moment alone or from an explicit Moon longitude. Chandra Balam (transit-Moon favorability relative to janma rashi).
167
167
 
168
168
  ### Astronomy
169
169
  Sunrise, Sunset, Moonrise, Moonset, Chandra Rashi (Moon sign), Surya Nakshatra.
@@ -310,7 +310,9 @@ import {
310
310
  computeAbhijitMuhurta, computeBrahmaMuhurta,
311
311
  computeGowriPanchangam,
312
312
  // Jyotish
313
- computePlanetaryPositions, computeVimshottariDasha,
313
+ computePlanetaryPositions,
314
+ computeVimshottariDasha, computeVimshottariDashaFromBirth,
315
+ computeChandraBalam,
314
316
  GRAHA_ABBR,
315
317
  } from 'panchang-ts';
316
318
 
@@ -342,17 +344,27 @@ const brahma = computeBrahmaMuhurta(sunrise, sunset); // { start, end }
342
344
  **Jyotish (Vedic Astrology):**
343
345
 
344
346
  ```typescript
345
- // All 9 graha positions (sidereal)
347
+ // All 9 graha positions (sidereal — Rahu/Ketu use mean node)
346
348
  const grahas = computePlanetaryPositions(birthDate, 'lahiri');
347
349
  console.log(grahas.jupiter.rashi.name); // "Dhanu"
348
350
  console.log(grahas.saturn.isRetrograde); // true/false
349
351
  console.log(GRAHA_ABBR['Jupiter']); // "Ju"
350
352
 
351
- // Vimshottari Dasha — pass birth date and Moon's sidereal longitude
352
- const moonLon = getSiderealMoonLongitude(birthDate, 'lahiri');
353
- const dasha = computeVimshottariDasha(birthDate, moonLon);
353
+ // Vimshottari Dasha — convenience form: birth date only (Moon longitude derived)
354
+ const dasha = computeVimshottariDashaFromBirth(birthDate, 'lahiri');
354
355
  console.log(dasha.currentMahaDashaLord); // "Rahu"
355
356
  console.log(dasha.mahaDashas[0]!.antarDashas[0]!.lord); // "Rahu"
357
+
358
+ // Or pass an explicit Moon sidereal longitude (useful when you already have one)
359
+ const moonLon = getSiderealMoonLongitude(birthDate, 'lahiri');
360
+ const dasha2 = computeVimshottariDasha(birthDate, moonLon);
361
+
362
+ // Chandra Balam — transit Moon's favorability vs. janma rashi
363
+ // janmaRashi and transitMoonRashi are 0-indexed (0 = Mesha ... 11 = Meena)
364
+ const cb = computeChandraBalam(3 /* Karka */, 6 /* Tula */);
365
+ console.log(cb.house); // 4
366
+ console.log(cb.quality); // "weak"
367
+ console.log(cb.englishName); // "Ashubha"
356
368
  ```
357
369
 
358
370
  ---
@@ -559,6 +571,13 @@ interface VimshottariDashaResult {
559
571
  currentIndex: number;
560
572
  mahaDashas: MahaDasha[]; // 9-entry sequence starting from birth
561
573
  }
574
+
575
+ interface ChandraBalamInfo {
576
+ house: number; // 1 = janma rashi; 12 = rashi before janma
577
+ quality: 'strong' | 'weak'; // Shubha houses = 1,3,6,7,10,11
578
+ englishName: string; // "Shubha" | "Ashubha"
579
+ name: string; // localized
580
+ }
562
581
  ```
563
582
  </details>
564
583
 
@@ -594,16 +613,42 @@ InteractionManager.runAfterInteractions(() => {
594
613
 
595
614
  ## Accuracy
596
615
 
597
- Validated against [DrikPanchang.com](https://www.drikpanchang.com) for 19+ date/city combinations across India and New York.
598
-
599
- | Element | Accuracy |
600
- |---------|----------|
601
- | Sunrise / Sunset | ±2 minutes |
602
- | Moonrise / Moonset | ±2 minutes |
603
- | Tithi, Nakshatra, Yoga, Karana names | Exact match |
604
- | Element end-times | ±5 minutes |
605
- | Ayanamsa | ±0.005° vs Swiss Ephemeris |
606
- | Choghadiya / Hora / Gowri slots | Derived from sunrise/sunset inherits ±2 min |
616
+ 4,864 tests passing, including Drik-verified fixtures against
617
+ [DrikPanchang.com](https://www.drikpanchang.com) spanning 2025–2026 across
618
+ Delhi, Chennai, and New York.
619
+
620
+ | Element | Accuracy | Validation |
621
+ |---------|----------|------------|
622
+ | Sunrise / Sunset | **≤29 s observed vs Drik minute-midpoint** (±45 s tolerance) | 16 assertions |
623
+ | Moonrise / Moonset | ±2 min vs Drik | Strict fixtures |
624
+ | Tithi, Nakshatra, Yoga, Karana names | Exact match vs Drik | Strict fixtures |
625
+ | Tithi / Nakshatra / Yoga / Karana end-times | **±3 min tolerance, max 2.01 min observed** | 20 assertions |
626
+ | Ayanamsa | ±0.005° vs Swiss Ephemeris | Unit tests |
627
+ | Planetary positions (Sun–Saturn) | **±0.02° vs Drik sidereal** | Drik fixtures |
628
+ | Planetary positions (Rahu/Ketu, mean node) | ≤0.5° typical; ±2° tolerance to absorb mean-vs-true drift | Drik fixtures |
629
+ | Rashi / Nakshatra / Retrograde flag | Exact match vs Drik | Drik fixtures |
630
+ | Festival dates | 12 Drik-verified festivals (2025–2026) — see caveats below | Drik fixtures |
631
+ | Choghadiya / Hora / Gowri slots | Derived from sunrise/sunset — inherits ±2 min | — |
632
+
633
+ ### Festival Detection — Documented Tradeoff
634
+
635
+ Library uses **tithi-at-sunrise** to resolve a festival to a calendar day.
636
+ DrikPanchang applies several other traditional rules depending on the
637
+ festival; where those rules pick a different day, our output can drift
638
+ ±1 day vs Drik. This is a rule-choice tradeoff, not a computation bug —
639
+ it is documented and deliberately surfaced rather than hidden.
640
+
641
+ | Resolution rule Drik uses | Festivals affected |
642
+ |---------------------------|--------------------|
643
+ | Tithi-at-midnight | Krishna Janmashtami, Maha Shivaratri, Diwali / Lakshmi Puja |
644
+ | Madhyahna-vyapini (tithi overlapping noon) | Ganesh Chaturthi on edge years, Akshaya Tritiya 2026 |
645
+ | Kshaya-tithi handling (tithi never at sunrise) | Ugadi 2026-03-19 (Pratipad is Kshaya) |
646
+
647
+ If exact Drik parity matters for your use case, cross-check the above
648
+ festival set against the Drik site for the target year. Everything else
649
+ — Holi, Ugadi (non-Kshaya years), Rama Navami, Raksha Bandhan, Ganesh
650
+ Chaturthi (normal years), Navaratri, Dussehra, Karva Chauth, Hanuman
651
+ Jayanti — matches Drik's canonical date across 2025 and 2026 fixtures.
607
652
 
608
653
  ---
609
654
 
package/dist/index.cjs CHANGED
@@ -3262,28 +3262,6 @@ function computeFestivals(tithiIndex, _nakshatraIndex, chandraMasaIndex, isAdhik
3262
3262
  return results;
3263
3263
  }
3264
3264
 
3265
- // src/astronomy/moonrise.ts
3266
- function getMoonrise(searchFromUtc, location, limitDays = 2) {
3267
- const observer = new Observer(
3268
- location.latitude,
3269
- location.longitude,
3270
- location.elevation ?? 0
3271
- );
3272
- const astroTime = MakeTime(searchFromUtc);
3273
- const result = SearchRiseSet(Body.Moon, observer, 1, astroTime, limitDays);
3274
- return result ? result.date : null;
3275
- }
3276
- function getMoonset(searchFromUtc, location, limitDays = 2) {
3277
- const observer = new Observer(
3278
- location.latitude,
3279
- location.longitude,
3280
- location.elevation ?? 0
3281
- );
3282
- const astroTime = MakeTime(searchFromUtc);
3283
- const result = SearchRiseSet(Body.Moon, observer, -1, astroTime, limitDays);
3284
- return result ? result.date : null;
3285
- }
3286
-
3287
3265
  // src/i18n/en.ts
3288
3266
  var en = {
3289
3267
  tithiNames: [
@@ -3412,6 +3390,7 @@ var en = {
3412
3390
  ravi_pushya: "Ravi Pushya Yoga",
3413
3391
  guru_pushya: "Guru Pushya Yoga"
3414
3392
  },
3393
+ chandraBalamNames: { shubha: "Shubha", ashubha: "Ashubha" },
3415
3394
  festivalNames: {
3416
3395
  ugadi: "Ugadi",
3417
3396
  rama_navami: "Rama Navami",
@@ -3576,6 +3555,7 @@ var sa = {
3576
3555
  ravi_pushya: "\u0930\u0935\u093F \u092A\u0941\u0937\u094D\u092F \u092F\u094B\u0917",
3577
3556
  guru_pushya: "\u0917\u0941\u0930\u0941 \u092A\u0941\u0937\u094D\u092F \u092F\u094B\u0917"
3578
3557
  },
3558
+ chandraBalamNames: { shubha: "\u0936\u0941\u092D", ashubha: "\u0905\u0936\u0941\u092D" },
3579
3559
  festivalNames: {
3580
3560
  ugadi: "\u092F\u0941\u0917\u093E\u0926\u093F",
3581
3561
  rama_navami: "\u0930\u093E\u092E \u0928\u0935\u092E\u0940",
@@ -3740,6 +3720,7 @@ var hi = {
3740
3720
  ravi_pushya: "\u0930\u0935\u093F \u092A\u0941\u0937\u094D\u092F \u092F\u094B\u0917",
3741
3721
  guru_pushya: "\u0917\u0941\u0930\u0941 \u092A\u0941\u0937\u094D\u092F \u092F\u094B\u0917"
3742
3722
  },
3723
+ chandraBalamNames: { shubha: "\u0936\u0941\u092D", ashubha: "\u0905\u0936\u0941\u092D" },
3743
3724
  festivalNames: {
3744
3725
  ugadi: "\u0909\u0917\u093E\u0926\u0940",
3745
3726
  rama_navami: "\u0930\u093E\u092E \u0928\u0935\u092E\u0940",
@@ -3814,6 +3795,45 @@ function resolveChandraMasaName(index, lang, adhika = false) {
3814
3795
  return adhika ? `${t.misc.adhika} ${name}` : name;
3815
3796
  }
3816
3797
 
3798
+ // src/jyotish/chandraBalam.ts
3799
+ var STRONG_HOUSES = /* @__PURE__ */ new Set([1, 3, 6, 7, 10, 11]);
3800
+ function computeChandraBalam(janmaRashiIndex, transitMoonRashiIndex, lang = "en") {
3801
+ if (!Number.isInteger(janmaRashiIndex) || janmaRashiIndex < 0 || janmaRashiIndex > 11) {
3802
+ throw new RangeError(`janmaRashiIndex must be integer in [0, 11], got ${janmaRashiIndex}`);
3803
+ }
3804
+ if (!Number.isInteger(transitMoonRashiIndex) || transitMoonRashiIndex < 0 || transitMoonRashiIndex > 11) {
3805
+ throw new RangeError(`transitMoonRashiIndex must be integer in [0, 11], got ${transitMoonRashiIndex}`);
3806
+ }
3807
+ const house = (transitMoonRashiIndex - janmaRashiIndex + 12) % 12 + 1;
3808
+ const quality = STRONG_HOUSES.has(house) ? "strong" : "weak";
3809
+ const englishName = quality === "strong" ? "Shubha" : "Ashubha";
3810
+ const t = getTranslations(lang);
3811
+ const name = quality === "strong" ? t.chandraBalamNames.shubha : t.chandraBalamNames.ashubha;
3812
+ return { house, quality, englishName, name };
3813
+ }
3814
+
3815
+ // src/astronomy/moonrise.ts
3816
+ function getMoonrise(searchFromUtc, location, limitDays = 2) {
3817
+ const observer = new Observer(
3818
+ location.latitude,
3819
+ location.longitude,
3820
+ location.elevation ?? 0
3821
+ );
3822
+ const astroTime = MakeTime(searchFromUtc);
3823
+ const result = SearchRiseSet(Body.Moon, observer, 1, astroTime, limitDays);
3824
+ return result ? result.date : null;
3825
+ }
3826
+ function getMoonset(searchFromUtc, location, limitDays = 2) {
3827
+ const observer = new Observer(
3828
+ location.latitude,
3829
+ location.longitude,
3830
+ location.elevation ?? 0
3831
+ );
3832
+ const astroTime = MakeTime(searchFromUtc);
3833
+ const result = SearchRiseSet(Body.Moon, observer, -1, astroTime, limitDays);
3834
+ return result ? result.date : null;
3835
+ }
3836
+
3817
3837
  // src/core/panchang.ts
3818
3838
  function getInstantPanchang(date, location, options) {
3819
3839
  validateDate(date);
@@ -3917,6 +3937,7 @@ function getInstantPanchang(date, location, options) {
3917
3937
  (key) => t.festivalNames[key] ?? t.misc[key] ?? key,
3918
3938
  (idx) => resolveMasaName(idx, lang)
3919
3939
  );
3940
+ const chandraBalam = options?.janmaRashi !== void 0 ? computeChandraBalam(options.janmaRashi, chandraRashi.index, lang) : void 0;
3920
3941
  return {
3921
3942
  timestamp: date,
3922
3943
  location,
@@ -3934,7 +3955,8 @@ function getInstantPanchang(date, location, options) {
3934
3955
  suryaNakshatra,
3935
3956
  panchaka: computePanchaka(siderealMoon),
3936
3957
  specialYogas,
3937
- festivals
3958
+ festivals,
3959
+ ...chandraBalam !== void 0 ? { chandraBalam } : {}
3938
3960
  };
3939
3961
  }
3940
3962
  function getDailyPanchang(date, location, options) {
@@ -4136,6 +4158,7 @@ function getDailyPanchang(date, location, options) {
4136
4158
  k.endTime = toLocalOrNull(k.endTime);
4137
4159
  k.startTime = toLocalOrNull(k.startTime);
4138
4160
  }
4161
+ const chandraBalam = options.janmaRashi !== void 0 ? computeChandraBalam(options.janmaRashi, chandraRashi.index, lang) : void 0;
4139
4162
  return {
4140
4163
  date,
4141
4164
  location,
@@ -4180,7 +4203,8 @@ function getDailyPanchang(date, location, options) {
4180
4203
  gowriPanchangam: {
4181
4204
  day: gowriPanchangam.day.map((s) => ({ ...s, ...convertTimePeriod(s) })),
4182
4205
  night: gowriPanchangam.night.map((s) => ({ ...s, ...convertTimePeriod(s) }))
4183
- }
4206
+ },
4207
+ ...chandraBalam !== void 0 ? { chandraBalam } : {}
4184
4208
  };
4185
4209
  }
4186
4210
 
@@ -4209,15 +4233,11 @@ function isRetrograde(body, date) {
4209
4233
  if (delta < -180) delta += 360;
4210
4234
  return delta < 0;
4211
4235
  }
4212
- function getTrueRahuLongitudeTropical(date) {
4236
+ function getMeanRahuLongitudeTropical(date) {
4213
4237
  const T = (dateToJulianDay(date) - 2451545) / 36525;
4214
- const omega = 125.04455501 - 1934.13626197 * T + 207765e-8 * T * T;
4215
- const F = normalize360(
4216
- 93.27191028 + 483202.0175233 * T - 36825e-7 * T * T + 3083e-9 * T * T * T
4238
+ return normalize360(
4239
+ 125.0445479 - 1934.1362891 * T + 20754e-7 * T * T + T * T * T / 467441 - T * T * T * T / 60616e3
4217
4240
  );
4218
- const F_rad = F * Math.PI / 180;
4219
- const correction = -1.4979 * Math.sin(2 * F_rad) - 0.15 * Math.sin(0 * F_rad + Math.PI * 2 * (357.5 / 360)) - 0.1226 * Math.sin(2 * (omega * Math.PI / 180)) + 0.1176 * Math.sin(2 * F_rad - 2 * (omega * Math.PI / 180));
4220
- return normalize360(omega + correction / 60);
4221
4241
  }
4222
4242
  function buildGrahaPosition(planet, siderealLon, isRetro, nakshatraNameFn, rashiNameFn) {
4223
4243
  const rashiIndex = Math.floor(siderealLon / 30);
@@ -4243,7 +4263,7 @@ function computePlanetaryPositions(date, ayanamsaType, nakshatraName = identity,
4243
4263
  const jupTrop = getTropicalPlanetLongitude(Body.Jupiter, date);
4244
4264
  const venTrop = getTropicalPlanetLongitude(Body.Venus, date);
4245
4265
  const satTrop = getTropicalPlanetLongitude(Body.Saturn, date);
4246
- const rahuTrop = getTrueRahuLongitudeTropical(date);
4266
+ const rahuTrop = getMeanRahuLongitudeTropical(date);
4247
4267
  const ketuTrop = normalize360(rahuTrop + 180);
4248
4268
  const marsRetro = isRetrograde(Body.Mars, date);
4249
4269
  const mercRetro = isRetrograde(Body.Mercury, date);
@@ -4349,6 +4369,10 @@ function computeVimshottariDasha(birthDate, moonSiderealLon) {
4349
4369
  mahaDashas
4350
4370
  };
4351
4371
  }
4372
+ function computeVimshottariDashaFromBirth(birthDate, ayanamsaType = "lahiri") {
4373
+ const moonSid = getSiderealMoonLongitude(birthDate, ayanamsaType);
4374
+ return computeVimshottariDasha(birthDate, moonSid);
4375
+ }
4352
4376
  function buildAntarDashas(mahaLord, mahaStart, mahaDurationMs) {
4353
4377
  const mahaIdx = DASHA_ORDER.indexOf(mahaLord);
4354
4378
  const antarDashas = [];
@@ -4407,11 +4431,13 @@ exports.GRAHA_ABBR = GRAHA_ABBR;
4407
4431
  exports.PanchangError = PanchangError;
4408
4432
  exports.computeAbhijitMuhurta = computeAbhijitMuhurta;
4409
4433
  exports.computeBrahmaMuhurta = computeBrahmaMuhurta;
4434
+ exports.computeChandraBalam = computeChandraBalam;
4410
4435
  exports.computeGowriPanchangam = computeGowriPanchangam;
4411
4436
  exports.computeGulikaKalam = computeGulikaKalam;
4412
4437
  exports.computePlanetaryPositions = computePlanetaryPositions;
4413
4438
  exports.computeRahuKalam = computeRahuKalam;
4414
4439
  exports.computeVimshottariDasha = computeVimshottariDasha;
4440
+ exports.computeVimshottariDashaFromBirth = computeVimshottariDashaFromBirth;
4415
4441
  exports.computeYamaganda = computeYamaganda;
4416
4442
  exports.getAyanamsa = computeAyanamsa;
4417
4443
  exports.getDailyPanchang = getDailyPanchang;