panchang-ts 2.0.0 → 2.1.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
@@ -139,6 +139,37 @@ console.log(amanta.chandramasa.name); // Amanta month name
139
139
  console.log(amanta.chandramasa.system); // "amanta"
140
140
  ```
141
141
 
142
+ ### Regional Festival Filtering
143
+
144
+ The `region` option scopes regional festival variants to one Indian state.
145
+ Pan-Indian festivals (Diwali, Holi, Raksha Bandhan, the canonical
146
+ `sankranti` event, …) emit regardless.
147
+
148
+ ```typescript
149
+ // Default — every regional variant emits on Makar Sankranti day:
150
+ const all = getDailyPanchang(jan14, chennai, { timezone: 330 });
151
+ all.festivals.map(f => f.name);
152
+ // → ["Sankranti", "Makar Sankranti", "Pongal", "Uttarayan",
153
+ // "Magh Bihu", "Ayyappa Makara Jyothi"]
154
+
155
+ // Scope to Tamil Nadu — drops Bihu/Ayyappa/Uttarayan:
156
+ const tn = getDailyPanchang(jan14, chennai, { timezone: 330, region: 'tamil-nadu' });
157
+ tn.festivals.map(f => f.name);
158
+ // → ["Sankranti", "Makar Sankranti", "Pongal"]
159
+
160
+ // Lohri fires on the Hindu day BEFORE Makara transit, scoped to Punjab/
161
+ // Haryana/Himachal — no extra wiring required, just the region option:
162
+ const lohri = getDailyPanchang(jan13, amritsar, { timezone: 330, region: 'punjab' });
163
+ lohri.festivals.some(f => f.name === 'Lohri'); // true
164
+
165
+ // Pre-v2.1 region values still work but log a one-shot deprecation warning:
166
+ getDailyPanchang(date, loc, { timezone: 330, region: 'tamil' });
167
+ // console.warn: [panchang-ts] FestivalRegion 'tamil' is deprecated;
168
+ // use 'tamil-nadu'. Legacy value will be removed in v3.
169
+ ```
170
+
171
+ See [`FestivalRegion`](#types) for the full state-slug list (21 states + `'nepal'`).
172
+
142
173
  ---
143
174
 
144
175
  ## Features
@@ -158,12 +189,13 @@ Rahu Kalam, Gulika Kalam, Yamaganda, Panchaka detection, Bhadra Kala (Vishti kar
158
189
  ### Special Yogas & Festivals
159
190
  Amrit Siddhi, Sarvartha Siddhi, Ravi Pushya, Guru Pushya yoga detection.
160
191
 
161
- **60+ festivals** spanning pan-Indian, regional, and classical observances:
192
+ **80+ festivals** spanning pan-Indian, regional, and classical observances:
162
193
 
163
194
  - **Ekadashi** — 26 named variants (Putrada, Shat Tila, Nirjala, Devshayani, etc.) with **Smarta / Vaishnava split** via Dashami-viddha rule; Smarta fast emits a `deferralDate` for Dwadashi.
164
195
  - **Pradosha** — 7 weekday-qualified variants (Som Pradosh, Bhauma Pradosh, Shani Pradosh, etc.) firing on both Shukla & Krishna paksha.
165
- - **Sankranti** — transit-based solar-month boundary detection plus regional variants (**Pongal**, **Vishu**, **Baisakhi**, **Magh Bihu**, **Ayyappa Makara Jyothi**) scoped by the `region` option.
196
+ - **Sankranti** — transit-based solar-month boundary detection plus regional variants (**Pongal**, **Vishu**, **Baisakhi**, **Pohela Boishakh**, **Bohag Bihu**, **Magh Bihu**, **Kati Bihu**, **Uttarayan**, **Ayyappa Makara Jyothi**, **Raja Sankranti**, **Harela**, **Sair**, **Singh Sankranti**) scoped by the `region` option. **Lohri** fires on the Hindu day immediately preceding Makara Sankranti under Punjab/Haryana/Himachal scopes.
166
197
  - **Canonical-time classical festivals** — Ganesh Chaturthi (madhyahna), Shivaratri (nishita), Diwali, Holi, Raksha Bandhan (Bhadra-aware, suppressed when Bhadra straddles Purnima), Karva Chauth (chandrodaya), Janmashtami, Dussehra, Navaratri, Ram Navami, Hanuman Jayanti, **Akshaya Tritiya & Parashurama Jayanti** (madhyahna-vyapini, co-emitted on Vaishakha Shukla Tritiya), Makar Sankranti.
198
+ - **Regional festivals (v2.1)** — **Gudi Padwa** (Maharashtra/Goa), **Gangaur** (Rajasthan), **Karaga** (Karnataka), **Bonalu** (Telangana, recurring Sundays in Ashadha), **Varamahalakshmi** (last Friday of Shravana Shukla before Purnima, Karnataka/AP/Telangana/Tamil Nadu), **Bathukamma** (Telangana — Engili Pula + Saddula markers), **Hariyali / Kajari / Hartalika Teej**, **Govardhan Puja**, **Bhai Dooj**, **Phagli** (Himachal), **Jagannath Rath Yatra** (pan-Indian, Ashadha Shukla Dwitiya), **Raja Parba** 3-day arc (Odisha — Pahili / Sankranti / Basi) — all filtered by per-state allow-lists on the rule.
167
199
  - **Regional & seasonal** — Chhath (4-day sequence), Vat Savitri, Upakarma (3 shakha variants via nakshatra+chandraMasa), Onam (nakshatra+solarMasa).
168
200
  - **Monthly observances** — Masik Shivaratri, Vinayaka Chaturthi (suppressed in Maha-month), **Masik Karthigai** (any day Krittika nakshatra prevails — sampled at sunrise / midday / sunset / nishita), Pushya days, Shravan Somvar and other month+weekday patterns.
169
201
  - Adhika (leap) months auto-skipped for tithi-based rules; Purnimanta naming respected.
@@ -332,7 +364,7 @@ Both functions share the same core astronomy, but `getDailyPanchang` operates on
332
364
  | `computeEndTimes` | `boolean` | `true` | Set `false` for ~5x faster, names-only output |
333
365
  | `precision` | `'standard' \| 'high'` | `'standard'` | Binary-search iterations (15 vs 25). High precision is rarely needed. |
334
366
  | `masaSystem` | `'purnimanta' \| 'amanta'` | `'purnimanta'` | Lunar month naming system. Purnimanta (North Indian) or Amanta (South Indian). |
335
- | `region` | `FestivalRegion` | `'all'` | Scopes regional festival variants (Pongal, Vishu, Baisakhi, Bihu, Ayyappa, etc.). See [`FestivalRegion`](#types) for supported values. The canonical pan-Indian `sankranti` event is always emitted regardless. |
367
+ | `region` | `FestivalRegion` | `'all'` | Scopes regional festival variants (Pongal, Vishu, Gudi Padwa, Lohri, Govardhan Puja, Bonalu, …) to a specific Indian state. See [`FestivalRegion`](#types) for the full list. Pre-v2.1 values (`'tamil'`, `'bengal'`, `'north-india'`) are still accepted but emit a deprecation warning; removal in v3. Pan-Indian festivals and the canonical `sankranti` event emit regardless of this setting. |
336
368
  | `janmaRashi` | `number` | _(omitted)_ | Native's birth Moon rashi index (0 = Mesha … 11 = Meena). When provided, the result includes `chandraBalam`. |
337
369
 
338
370
  **`InstantPanchangOptions`** (optional for `getInstantPanchang`): same as above but without `timezone` (instant mode works in UTC).
@@ -582,17 +614,56 @@ interface FestivalInfo {
582
614
  deferralDate?: Date;
583
615
  }
584
616
 
617
+ // State-slug scheme. A caller sets `region` to limit regional variants to
618
+ // their state; pan-Indian festivals (Holi, Diwali, Sankranti itself, …)
619
+ // emit regardless.
585
620
  type FestivalRegion =
586
- | 'all' // default — emits every regional variant
587
- | 'north-india'
588
- | 'tamil'
589
- | 'kerala'
590
- | 'bengal'
591
- | 'punjab'
592
- | 'gujarat'
593
- | 'assam'
594
- | 'maharashtra';
621
+ | 'all' // default — emits every regional variant
622
+ // South
623
+ | 'tamil-nadu' | 'kerala' | 'karnataka' | 'andhra-pradesh' | 'telangana'
624
+ // East
625
+ | 'west-bengal' | 'odisha' | 'assam' | 'bihar' | 'jharkhand'
626
+ // West
627
+ | 'gujarat' | 'maharashtra' | 'goa' | 'rajasthan'
628
+ // North / Central
629
+ | 'punjab' | 'haryana' | 'himachal-pradesh' | 'uttarakhand'
630
+ | 'uttar-pradesh' | 'madhya-pradesh'
631
+ // Neighbour
632
+ | 'nepal';
633
+
634
+ // Pre-v2.1 identifiers. Accepted as input and mapped at call time; a
635
+ // one-shot console warning fires per distinct legacy value. Removal in v3.
636
+ // 'tamil' → 'tamil-nadu'
637
+ // 'bengal' → 'west-bengal'
638
+ // 'north-india' → 'all' (Makar Sankranti is pan-Indian; use state
639
+ // slugs for Lohri / Govardhan / Bhai Dooj)
640
+ type LegacyFestivalRegion = 'tamil' | 'bengal' | 'north-india';
595
641
  ```
642
+
643
+ **Region-scoped festivals** (non-exhaustive — see `src/core/festivals.ts`):
644
+
645
+ | Region | Festival names (keys) |
646
+ |---|---|
647
+ | `tamil-nadu` | pongal, puthandu, varamahalakshmi |
648
+ | `kerala` | vishu, ayyappa_makara_jyothi, onam *(solar-nakshatra)* |
649
+ | `karnataka` | karaga, varamahalakshmi |
650
+ | `andhra-pradesh` | varamahalakshmi |
651
+ | `telangana` | bonalu, varamahalakshmi, bathukamma_start, bathukamma_saddula |
652
+ | `west-bengal` | pohela_boishakh, bhai_dooj |
653
+ | `odisha` | singh_sankranti, raja_pahili, raja_sankranti, raja_basi |
654
+ | `assam` | bohag_bihu, magh_bihu, kati_bihu |
655
+ | `bihar` | singh_sankranti, hariyali_teej, govardhan_puja, bhai_dooj |
656
+ | `gujarat` | uttarayan, govardhan_puja, bhai_dooj |
657
+ | `maharashtra` | gudi_padwa, hartalika_teej, bhai_dooj |
658
+ | `goa` | gudi_padwa |
659
+ | `rajasthan` | gangaur, hariyali_teej, kajari_teej, hartalika_teej, govardhan_puja, bhai_dooj |
660
+ | `punjab` | baisakhi, lohri, govardhan_puja |
661
+ | `haryana` | baisakhi, lohri, govardhan_puja, bhai_dooj |
662
+ | `himachal-pradesh` | sair, phagli, lohri |
663
+ | `uttarakhand` | harela |
664
+ | `uttar-pradesh` | hariyali_teej, kajari_teej, hartalika_teej, govardhan_puja, bhai_dooj |
665
+ | `madhya-pradesh` | hariyali_teej, kajari_teej, hartalika_teej |
666
+ | `nepal` | singh_sankranti, bhai_dooj |
596
667
  </details>
597
668
 
598
669
  <details>
package/dist/index.cjs CHANGED
@@ -3756,28 +3756,47 @@ function computeDurMuhurta(sunrise, sunset, varaIndex) {
3756
3756
  var SANKRANTI_REGIONAL = {
3757
3757
  // Mesha (0) — solar new year across regions
3758
3758
  0: [
3759
- { key: "baisakhi", region: "punjab", type: "major" },
3760
- { key: "vishu", region: "kerala", type: "major" },
3761
- { key: "pohela_boishakh", region: "bengal", type: "major" },
3762
- { key: "puthandu", region: "tamil", type: "major" }
3759
+ { key: "baisakhi", regions: ["punjab", "haryana"], type: "major" },
3760
+ { key: "vishu", regions: ["kerala"], type: "major" },
3761
+ { key: "pohela_boishakh", regions: ["west-bengal"], type: "major" },
3762
+ { key: "puthandu", regions: ["tamil-nadu"], type: "major" },
3763
+ { key: "bohag_bihu", regions: ["assam"], type: "major" }
3763
3764
  ],
3764
3765
  // Karka (3) — Dakshinayana (Sun's southward course begins)
3765
3766
  3: [
3766
- { key: "dakshinayana", region: "all", type: "minor" }
3767
+ { key: "dakshinayana", regions: ["all"], type: "minor" },
3768
+ // Raja Parba (Odisha) — 4-day monsoon festival; we emit a single-day
3769
+ // marker on Karka Sankranti, the most widely cited anchor day.
3770
+ { key: "raja_sankranti", regions: ["odisha"], type: "major" },
3771
+ // Harela (Kumaon / Uttarakhand) — observed on Shravana solar-month start.
3772
+ { key: "harela", regions: ["uttarakhand"], type: "major" }
3767
3773
  ],
3768
- // Simha (4)
3774
+ // Simha (4) — Singh Sankranti is primarily observed in Odisha, Bihar,
3775
+ // and Nepal (as Singhasankranti); not a pan-Indian observance despite
3776
+ // the pre-v2.1 tagging.
3769
3777
  4: [
3770
- { key: "singh_sankranti", region: "all", type: "minor" }
3778
+ { key: "singh_sankranti", regions: ["odisha", "bihar", "jharkhand", "nepal"], type: "minor" }
3779
+ ],
3780
+ // Kanya (5) — Sair (Himachal): first day of the Ashwin solar month.
3781
+ 5: [
3782
+ { key: "sair", regions: ["himachal-pradesh"], type: "minor" }
3783
+ ],
3784
+ // Tula (6) — Kati Bihu (Assam): Kartika solar-month start.
3785
+ 6: [
3786
+ { key: "kati_bihu", regions: ["assam"], type: "minor" }
3771
3787
  ],
3772
3788
  // Makara (9) — Uttarayana / harvest festivals
3773
3789
  9: [
3774
- { key: "makar_sankranti", region: "north-india", type: "major" },
3775
- { key: "pongal", region: "tamil", type: "major" },
3776
- { key: "uttarayan", region: "gujarat", type: "major" },
3777
- { key: "bihu", region: "assam", type: "major" },
3778
- { key: "ayyappa_makara_jyothi", region: "kerala", type: "major" }
3790
+ // Makar Sankranti is pan-Indian (re-scoped from the pre-v2.1 'north-india').
3791
+ { key: "makar_sankranti", regions: ["all"], type: "major" },
3792
+ { key: "pongal", regions: ["tamil-nadu"], type: "major" },
3793
+ { key: "uttarayan", regions: ["gujarat"], type: "major" },
3794
+ { key: "magh_bihu", regions: ["assam"], type: "major" },
3795
+ { key: "ayyappa_makara_jyothi", regions: ["kerala"], type: "major" }
3779
3796
  ]
3780
3797
  };
3798
+ var LOHRI_REGIONS = ["punjab", "haryana", "himachal-pradesh"];
3799
+ var RAJA_REGIONS = ["odisha"];
3781
3800
  var FESTIVAL_REGISTRY = [
3782
3801
  // Chaitra (0)
3783
3802
  { key: "ugadi", masa: 0, tithi: 0, type: "major" },
@@ -3851,7 +3870,144 @@ var FESTIVAL_REGISTRY = [
3851
3870
  // Rig Upakarma — Shravana nakshatra (21) in Shravana chandra masa (4).
3852
3871
  { key: "rig_upakarma", masa: 4, nakshatra: 21, type: "major" },
3853
3872
  // Sama Upakarma — Hasta nakshatra (12) in Bhadrapada chandra masa (5).
3854
- { key: "sama_upakarma", masa: 5, nakshatra: 12, type: "major" }
3873
+ { key: "sama_upakarma", masa: 5, nakshatra: 12, type: "major" },
3874
+ // ── Regional festivals (v2.1 expansion) ─────────────────────
3875
+ // Chaitra (0) — Marathi/Konkani New Year; same tithi as Ugadi but
3876
+ // emitted as a Maharashtra-specific variant name alongside Ugadi.
3877
+ {
3878
+ key: "gudi_padwa",
3879
+ masa: 0,
3880
+ tithi: 0,
3881
+ type: "major",
3882
+ regions: ["maharashtra", "goa"]
3883
+ },
3884
+ // Chaitra (0) Shukla Tritiya — Gangaur (18-day festival concludes here).
3885
+ {
3886
+ key: "gangaur",
3887
+ masa: 0,
3888
+ tithi: 2,
3889
+ type: "major",
3890
+ regions: ["rajasthan"]
3891
+ },
3892
+ // Chaitra (0) Purnima — Karaga festival of Bengaluru.
3893
+ {
3894
+ key: "karaga",
3895
+ masa: 0,
3896
+ tithi: 14,
3897
+ type: "major",
3898
+ regions: ["karnataka"]
3899
+ },
3900
+ // Ashadha (3) — Bonalu, Sundays at Mahakali temples across Telangana.
3901
+ {
3902
+ key: "bonalu",
3903
+ masa: 3,
3904
+ vara: 0,
3905
+ type: "minor",
3906
+ adhikaBehaviour: "observe-in-both",
3907
+ regions: ["telangana"]
3908
+ },
3909
+ // Shravana (4) Shukla Tritiya — Hariyali Teej.
3910
+ {
3911
+ key: "hariyali_teej",
3912
+ masa: 4,
3913
+ tithi: 2,
3914
+ type: "major",
3915
+ regions: ["rajasthan", "uttar-pradesh", "bihar", "haryana", "madhya-pradesh"]
3916
+ },
3917
+ // Shravana (4) Krishna Tritiya — Kajari Teej (tithi 17 = Krishna Tritiya).
3918
+ {
3919
+ key: "kajari_teej",
3920
+ masa: 4,
3921
+ tithi: 17,
3922
+ type: "major",
3923
+ regions: ["rajasthan", "uttar-pradesh", "madhya-pradesh"]
3924
+ },
3925
+ // Bhadrapada (5) Shukla Tritiya — Hartalika Teej.
3926
+ {
3927
+ key: "hartalika_teej",
3928
+ masa: 5,
3929
+ tithi: 2,
3930
+ type: "major",
3931
+ regions: ["rajasthan", "uttar-pradesh", "bihar", "maharashtra", "madhya-pradesh"]
3932
+ },
3933
+ // Amanta Kartika (7) Shukla Pratipada — day after Diwali.
3934
+ // In Purnimanta convention this is also "Kartika Shukla Pratipada" (naming
3935
+ // system aligns), so a namingSystem tag isn't required here.
3936
+ {
3937
+ key: "govardhan_puja",
3938
+ masa: 7,
3939
+ tithi: 0,
3940
+ type: "major",
3941
+ regions: [
3942
+ "uttar-pradesh",
3943
+ "bihar",
3944
+ "haryana",
3945
+ "rajasthan",
3946
+ "gujarat",
3947
+ "madhya-pradesh",
3948
+ "punjab",
3949
+ "jharkhand"
3950
+ ]
3951
+ },
3952
+ // Amanta Kartika (7) Shukla Dwitiya — Bhai Dooj.
3953
+ {
3954
+ key: "bhai_dooj",
3955
+ masa: 7,
3956
+ tithi: 1,
3957
+ type: "major",
3958
+ regions: [
3959
+ "uttar-pradesh",
3960
+ "bihar",
3961
+ "haryana",
3962
+ "maharashtra",
3963
+ "gujarat",
3964
+ "rajasthan",
3965
+ "madhya-pradesh",
3966
+ "west-bengal",
3967
+ "jharkhand",
3968
+ "nepal"
3969
+ ]
3970
+ },
3971
+ // Phalguna (11) Purnima — Phagli (varies by valley; Purnima anchor).
3972
+ {
3973
+ key: "phagli",
3974
+ masa: 11,
3975
+ tithi: 14,
3976
+ type: "minor",
3977
+ regions: ["himachal-pradesh"]
3978
+ },
3979
+ // Ashadha (3) Shukla Dwitiya — Jagannath Rath Yatra (pan-Indian; cultural
3980
+ // epicenter is Puri, Odisha but observed nationwide).
3981
+ { key: "jagannath_rath_yatra", masa: 3, tithi: 1, type: "major" },
3982
+ // Shravana (4) Shukla — Varamahalakshmi: last Friday in Shukla paksha
3983
+ // before Purnima. The (masa+vara) match is gated by tithi ∈ [7,13] so
3984
+ // only the second Friday of the paksha (the one closest to Purnima) fires.
3985
+ {
3986
+ key: "varamahalakshmi",
3987
+ masa: 4,
3988
+ vara: 5,
3989
+ type: "major",
3990
+ tithiRange: [7, 13],
3991
+ regions: ["karnataka", "andhra-pradesh", "telangana", "tamil-nadu"]
3992
+ },
3993
+ // Bathukamma (Telangana) — 9-day floral festival from Bhadrapada
3994
+ // Amavasya through Ashwin Shukla Navami. We emit start (Engili Pula
3995
+ // Bathukamma, masa=5/tithi=29) and climax (Saddula Bathukamma, masa=6/
3996
+ // tithi=8) markers; intermediate days carry no library-level emission.
3997
+ {
3998
+ key: "bathukamma_start",
3999
+ masa: 5,
4000
+ tithi: 29,
4001
+ type: "major",
4002
+ regions: ["telangana"]
4003
+ },
4004
+ {
4005
+ key: "bathukamma_saddula",
4006
+ masa: 6,
4007
+ tithi: 8,
4008
+ type: "major",
4009
+ regions: ["telangana"]
4010
+ }
3855
4011
  ];
3856
4012
  var EKADASHI_NAMES = [
3857
4013
  ["kamada", "papamochani"],
@@ -3928,25 +4084,39 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
3928
4084
  match = masaMatches && rule.nakshatra === ctx.nakshatraIndex;
3929
4085
  } else if (rule.vara !== void 0 && rule.masa !== void 0) {
3930
4086
  const masaMatches = rule.masa === ctx.chandraMasaIndex && (adhikaBehaviour !== "shift-to-nija" || !ctx.isAdhika);
3931
- match = masaMatches && rule.vara === ctx.varaIndex;
4087
+ let inTithiRange = true;
4088
+ if (rule.tithiRange !== void 0) {
4089
+ const dateRule = rule.dateRule ?? "sunrise";
4090
+ const tithi = tithiForRule(dateRule);
4091
+ const [lo, hi2] = rule.tithiRange;
4092
+ inTithiRange = tithi >= lo && tithi <= hi2;
4093
+ }
4094
+ match = masaMatches && rule.vara === ctx.varaIndex && inTithiRange;
3932
4095
  } else if (rule.masa !== void 0 && rule.tithi !== void 0) {
3933
4096
  const masaMatches = rule.masa === ctx.chandraMasaIndex && // `shift-to-nija`: only observe in Nija following an Adhika (but still OK if no prior Adhika,
3934
4097
  // since the normal year has no Adhika and the festival observes as usual).
3935
4098
  (adhikaBehaviour !== "shift-to-nija" || !ctx.isAdhika);
3936
4099
  if (masaMatches) {
3937
4100
  const dateRule = rule.dateRule ?? "sunrise";
3938
- const effectiveTithi = tithiForRule(dateRule);
3939
- if (effectiveTithi === rule.tithi) {
3940
- const startTithi = tithiForRuleStart(dateRule);
3941
- const priorEndTithi = priorDayTithiForRule(dateRule);
3942
- if (dateRule !== "sunrise" && startTithi !== void 0 && startTithi !== rule.tithi && priorEndTithi === rule.tithi) {
3943
- continue;
3944
- }
4101
+ const endTithi = tithiForRule(dateRule);
4102
+ const startTithi = tithiForRuleStart(dateRule);
4103
+ const priorEndTithi = priorDayTithiForRule(dateRule);
4104
+ const matchedByEnd = endTithi === rule.tithi;
4105
+ const matchedByStart = dateRule !== "sunrise" && startTithi !== void 0 && startTithi === rule.tithi && endTithi !== rule.tithi;
4106
+ if (matchedByEnd || matchedByStart) {
4107
+ if (dateRule !== "sunrise" && startTithi !== void 0 && startTithi !== rule.tithi && priorEndTithi === rule.tithi) continue;
4108
+ if (matchedByStart && priorEndTithi === rule.tithi) continue;
3945
4109
  match = true;
3946
4110
  }
3947
4111
  }
3948
4112
  }
3949
4113
  if (!match) continue;
4114
+ if (rule.regions !== void 0) {
4115
+ const region2 = ctx.region ?? "all";
4116
+ if (region2 !== "all" && !rule.regions.includes("all") && !rule.regions.includes(region2)) {
4117
+ continue;
4118
+ }
4119
+ }
3950
4120
  const festival = { name: nameResolver(rule.key), type: rule.type };
3951
4121
  if (rule.namingSystem === "purnimanta" && ctx.purnimantaMasaName && ctx.amantaMasaName) {
3952
4122
  festival.description = `Purnimanta: ${ctx.purnimantaMasaName} Krishna Paksha`;
@@ -4001,26 +4171,32 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4001
4171
  if (tithiForRule("chandrodaya") === 18) {
4002
4172
  results.push({ name: nameResolver("sankashti_chaturthi"), type: "major" });
4003
4173
  }
4004
- const nishitaTithi = tithiForRule("nishita");
4005
- if (nishitaTithi === 28) {
4006
- const isMahaShivaratriMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 10;
4007
- if (!isMahaShivaratriMonth) {
4008
- const startTithi = ctx.tithiByRuleStart?.nishita;
4009
- const priorEndTithi = ctx.priorDayTithiByRule?.nishita;
4010
- const suppressed = startTithi !== void 0 && startTithi !== 28 && priorEndTithi === 28;
4011
- if (!suppressed) {
4174
+ {
4175
+ const endTithi = tithiForRule("nishita");
4176
+ const startTithi = ctx.tithiByRuleStart?.nishita;
4177
+ const priorEndTithi = ctx.priorDayTithiByRule?.nishita;
4178
+ const matchedByEnd = endTithi === 28;
4179
+ const matchedByStart = startTithi !== void 0 && startTithi === 28 && endTithi !== 28;
4180
+ if (matchedByEnd || matchedByStart) {
4181
+ const isMahaShivaratriMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 10;
4182
+ const dedupeA = startTithi !== void 0 && startTithi !== 28 && priorEndTithi === 28;
4183
+ const dedupeB = matchedByStart && priorEndTithi === 28;
4184
+ if (!isMahaShivaratriMonth && !dedupeA && !dedupeB) {
4012
4185
  results.push({ name: nameResolver("masik_shivaratri"), type: "minor" });
4013
4186
  }
4014
4187
  }
4015
4188
  }
4016
- const madhyahnaTithi = tithiForRule("madhyahna");
4017
- if (madhyahnaTithi === 3) {
4018
- const isGaneshChaturthiMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 5;
4019
- if (!isGaneshChaturthiMonth) {
4020
- const startTithi = ctx.tithiByRuleStart?.madhyahna;
4021
- const priorEndTithi = ctx.priorDayTithiByRule?.madhyahna;
4022
- const suppressed = startTithi !== void 0 && startTithi !== 3 && priorEndTithi === 3;
4023
- if (!suppressed) {
4189
+ {
4190
+ const endTithi = tithiForRule("madhyahna");
4191
+ const startTithi = ctx.tithiByRuleStart?.madhyahna;
4192
+ const priorEndTithi = ctx.priorDayTithiByRule?.madhyahna;
4193
+ const matchedByEnd = endTithi === 3;
4194
+ const matchedByStart = startTithi !== void 0 && startTithi === 3 && endTithi !== 3;
4195
+ if (matchedByEnd || matchedByStart) {
4196
+ const isGaneshChaturthiMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 5;
4197
+ const dedupeA = startTithi !== void 0 && startTithi !== 3 && priorEndTithi === 3;
4198
+ const dedupeB = matchedByStart && priorEndTithi === 3;
4199
+ if (!isGaneshChaturthiMonth && !dedupeA && !dedupeB) {
4024
4200
  results.push({ name: nameResolver("vinayaka_chaturthi"), type: "minor" });
4025
4201
  }
4026
4202
  }
@@ -4045,6 +4221,7 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4045
4221
  description: nameResolver(variantKey)
4046
4222
  });
4047
4223
  }
4224
+ const region = ctx.region ?? "all";
4048
4225
  if (ctx.sankrantiRashi !== void 0 && ctx.sankrantiRashi !== null) {
4049
4226
  const rashiName = rashiNameResolver ? rashiNameResolver(ctx.sankrantiRashi) : `Rashi ${ctx.sankrantiRashi}`;
4050
4227
  results.push({
@@ -4052,11 +4229,10 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4052
4229
  type: "sankranti",
4053
4230
  description: rashiName
4054
4231
  });
4055
- const region = ctx.region ?? "all";
4056
4232
  const regionalRules = SANKRANTI_REGIONAL[ctx.sankrantiRashi];
4057
4233
  if (regionalRules) {
4058
4234
  for (const r of regionalRules) {
4059
- if (region !== "all" && r.region !== "all" && r.region !== region) continue;
4235
+ if (region !== "all" && !r.regions.includes("all") && !r.regions.includes(region)) continue;
4060
4236
  results.push({
4061
4237
  name: nameResolver(r.key),
4062
4238
  type: "sankranti",
@@ -4065,9 +4241,47 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4065
4241
  }
4066
4242
  }
4067
4243
  }
4244
+ if (ctx.nextDaySankrantiRashi === 9) {
4245
+ if (region === "all" || LOHRI_REGIONS.includes(region)) {
4246
+ results.push({ name: nameResolver("lohri"), type: "major" });
4247
+ }
4248
+ }
4249
+ if (ctx.nextDaySankrantiRashi === 3) {
4250
+ if (region === "all" || RAJA_REGIONS.includes(region)) {
4251
+ results.push({ name: nameResolver("raja_pahili"), type: "major" });
4252
+ }
4253
+ }
4254
+ if (ctx.prevDaySankrantiRashi === 3) {
4255
+ if (region === "all" || RAJA_REGIONS.includes(region)) {
4256
+ results.push({ name: nameResolver("raja_basi"), type: "major" });
4257
+ }
4258
+ }
4068
4259
  return results;
4069
4260
  }
4070
4261
 
4262
+ // src/core/regionAlias.ts
4263
+ var LEGACY_REGION_ALIASES = {
4264
+ "tamil": "tamil-nadu",
4265
+ "bengal": "west-bengal",
4266
+ "north-india": "all"
4267
+ };
4268
+ var warned = /* @__PURE__ */ new Set();
4269
+ function resolveRegionAlias(region) {
4270
+ if (region === void 0) return "all";
4271
+ if (region in LEGACY_REGION_ALIASES) {
4272
+ const legacy = region;
4273
+ const canonical = LEGACY_REGION_ALIASES[legacy];
4274
+ if (!warned.has(legacy)) {
4275
+ warned.add(legacy);
4276
+ console.warn(
4277
+ `[panchang-ts] FestivalRegion '${legacy}' is deprecated; use '${canonical}'. Legacy value will be removed in v3.`
4278
+ );
4279
+ }
4280
+ return canonical;
4281
+ }
4282
+ return region;
4283
+ }
4284
+
4071
4285
  // src/core/bhadra.ts
4072
4286
  function isVishtiKarana(karanaIndex) {
4073
4287
  if (karanaIndex <= 0 || karanaIndex >= 57) return false;
@@ -4343,13 +4557,36 @@ var en = {
4343
4557
  vishu: "Vishu",
4344
4558
  pohela_boishakh: "Pohela Boishakh",
4345
4559
  puthandu: "Puthandu",
4560
+ bohag_bihu: "Bohag Bihu",
4346
4561
  makar_sankranti: "Makar Sankranti",
4347
4562
  pongal: "Pongal",
4348
4563
  uttarayan: "Uttarayan",
4349
- bihu: "Magh Bihu",
4564
+ magh_bihu: "Magh Bihu",
4350
4565
  ayyappa_makara_jyothi: "Ayyappa Makara Jyothi",
4351
4566
  dakshinayana: "Dakshinayana",
4567
+ raja_sankranti: "Raja Sankranti",
4568
+ raja_pahili: "Pahili Raja",
4569
+ raja_basi: "Basi Raja",
4570
+ harela: "Harela",
4352
4571
  singh_sankranti: "Singh Sankranti",
4572
+ sair: "Sair",
4573
+ kati_bihu: "Kati Bihu",
4574
+ lohri: "Lohri",
4575
+ // Regional festivals (v2.1 expansion)
4576
+ gudi_padwa: "Gudi Padwa",
4577
+ gangaur: "Gangaur",
4578
+ karaga: "Karaga",
4579
+ bonalu: "Bonalu",
4580
+ hariyali_teej: "Hariyali Teej",
4581
+ kajari_teej: "Kajari Teej",
4582
+ hartalika_teej: "Hartalika Teej",
4583
+ govardhan_puja: "Govardhan Puja",
4584
+ bhai_dooj: "Bhai Dooj",
4585
+ phagli: "Phagli",
4586
+ jagannath_rath_yatra: "Jagannath Rath Yatra",
4587
+ varamahalakshmi: "Varamahalakshmi Vratam",
4588
+ bathukamma_start: "Bathukamma \u2014 Engili Pula",
4589
+ bathukamma_saddula: "Bathukamma \u2014 Saddula",
4353
4590
  // Chhath Puja (4-day, Kartika Shukla Chaturthi→Saptami)
4354
4591
  chhath_nahay_khay: "Chhath \u2014 Nahay Khay",
4355
4592
  chhath_kharna: "Chhath \u2014 Kharna",
@@ -4585,13 +4822,36 @@ var hi = {
4585
4822
  vishu: "\u0935\u093F\u0937\u0941",
4586
4823
  pohela_boishakh: "\u092A\u094B\u0939\u0947\u0932\u093E \u092C\u094B\u0907\u0936\u093E\u0916",
4587
4824
  puthandu: "\u092A\u0941\u0925\u093E\u0902\u0921\u0941",
4825
+ bohag_bihu: "\u092C\u094B\u0939\u093E\u0917 \u092C\u093F\u0939\u0942",
4588
4826
  makar_sankranti: "\u092E\u0915\u0930 \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4589
4827
  pongal: "\u092A\u094B\u0902\u0917\u0932",
4590
4828
  uttarayan: "\u0909\u0924\u094D\u0924\u0930\u093E\u092F\u0923",
4591
- bihu: "\u092E\u093E\u0918 \u092C\u093F\u0939\u0942",
4829
+ magh_bihu: "\u092E\u093E\u0918 \u092C\u093F\u0939\u0942",
4592
4830
  ayyappa_makara_jyothi: "\u0905\u092F\u094D\u092F\u092A\u094D\u092A\u093E \u092E\u0915\u0930 \u091C\u094D\u092F\u094B\u0924\u093F",
4593
4831
  dakshinayana: "\u0926\u0915\u094D\u0937\u093F\u0923\u093E\u092F\u0928",
4832
+ raja_sankranti: "\u0930\u093E\u091C\u093E \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4833
+ raja_pahili: "\u092A\u0939\u093F\u0932\u0940 \u0930\u093E\u091C\u093E",
4834
+ raja_basi: "\u092C\u093E\u0938\u0940 \u0930\u093E\u091C\u093E",
4835
+ harela: "\u0939\u0930\u0947\u0932\u093E",
4594
4836
  singh_sankranti: "\u0938\u093F\u0902\u0939 \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4837
+ sair: "\u0938\u0948\u0930",
4838
+ kati_bihu: "\u0915\u093E\u0924\u0940 \u092C\u093F\u0939\u0942",
4839
+ lohri: "\u0932\u094B\u0939\u0921\u093C\u0940",
4840
+ // Regional festivals (v2.1 expansion)
4841
+ gudi_padwa: "\u0917\u0941\u0921\u093C\u0940 \u092A\u0921\u093C\u0935\u093E",
4842
+ gangaur: "\u0917\u0923\u0917\u094C\u0930",
4843
+ karaga: "\u0915\u0930\u0917\u093E",
4844
+ bonalu: "\u092C\u094B\u0928\u093E\u0932\u0941",
4845
+ hariyali_teej: "\u0939\u0930\u093F\u092F\u093E\u0932\u0940 \u0924\u0940\u091C",
4846
+ kajari_teej: "\u0915\u091C\u0930\u0940 \u0924\u0940\u091C",
4847
+ hartalika_teej: "\u0939\u0930\u0924\u093E\u0932\u093F\u0915\u093E \u0924\u0940\u091C",
4848
+ govardhan_puja: "\u0917\u094B\u0935\u0930\u094D\u0927\u0928 \u092A\u0942\u091C\u093E",
4849
+ bhai_dooj: "\u092D\u093E\u0908 \u0926\u0942\u091C",
4850
+ phagli: "\u092B\u093E\u0917\u094D\u0932\u0940",
4851
+ jagannath_rath_yatra: "\u091C\u0917\u0928\u094D\u0928\u093E\u0925 \u0930\u0925 \u092F\u093E\u0924\u094D\u0930\u093E",
4852
+ varamahalakshmi: "\u0935\u0930\u092E\u0939\u093E\u0932\u0915\u094D\u0937\u094D\u092E\u0940 \u0935\u094D\u0930\u0924",
4853
+ bathukamma_start: "\u092C\u0924\u0941\u0915\u092E\u094D\u092E\u093E \u2014 \u090F\u0902\u0917\u093F\u0932\u0940 \u092A\u0941\u0932\u093E",
4854
+ bathukamma_saddula: "\u092C\u0924\u0941\u0915\u092E\u094D\u092E\u093E \u2014 \u0938\u0926\u094D\u0926\u0941\u0932\u093E",
4595
4855
  // Chhath Puja
4596
4856
  chhath_nahay_khay: "\u091B\u0920 \u2014 \u0928\u0939\u093E\u092F \u0916\u093E\u092F",
4597
4857
  chhath_kharna: "\u091B\u0920 \u2014 \u0916\u0930\u0928\u093E",
@@ -4821,7 +5081,7 @@ function getInstantPanchang(date, location, options) {
4821
5081
  isAdhika: chandramasa.isAdhika,
4822
5082
  varaIndex: vara.index,
4823
5083
  solarMasaIndex: Math.floor(siderealSun / 30) % 12,
4824
- region: options?.region ?? "all"
5084
+ region: resolveRegionAlias(options?.region)
4825
5085
  },
4826
5086
  (key) => t.festivalNames[key] ?? t.misc[key] ?? key,
4827
5087
  (idx) => resolveMasaName(idx, lang)
@@ -5006,6 +5266,21 @@ function getDailyPanchang(date, location, options) {
5006
5266
  const rashiAtSunrise = Math.floor(siderealSunAtSunrise / 30) % 12;
5007
5267
  const rashiAtNextSunrise = Math.floor(getSun(nextSunriseUtc) / 30) % 12;
5008
5268
  const sankrantiRashi = rashiAtSunrise !== rashiAtNextSunrise ? rashiAtNextSunrise : null;
5269
+ let nextDaySankrantiRashi = null;
5270
+ try {
5271
+ const tomorrowSunsetUtc = computeSunset(nextSunriseUtc, location);
5272
+ const dayAfterSunriseUtc = computeSunrise(tomorrowSunsetUtc, location);
5273
+ const rashiAtDayAfterSunrise = Math.floor(getSun(dayAfterSunriseUtc) / 30) % 12;
5274
+ if (rashiAtNextSunrise !== rashiAtDayAfterSunrise) {
5275
+ nextDaySankrantiRashi = rashiAtDayAfterSunrise;
5276
+ }
5277
+ } catch (e) {
5278
+ if (!(e instanceof PanchangError && (e.code === "NO_SUNRISE" || e.code === "NO_SUNSET"))) {
5279
+ throw e;
5280
+ }
5281
+ }
5282
+ const rashiAtYesterdaySunrise = Math.floor(getSun(yesterdaySunriseUtc) / 30) % 12;
5283
+ const prevDaySankrantiRashi = rashiAtYesterdaySunrise !== rashiAtSunrise ? rashiAtSunrise : null;
5009
5284
  let ekadashiDashamiViddha = false;
5010
5285
  if (tithiAtSunrise.index === 10 || tithiAtSunrise.index === 25) {
5011
5286
  const tithiAtArunodaya = tithiAt(arunodayaUtc);
@@ -5055,6 +5330,8 @@ function getDailyPanchang(date, location, options) {
5055
5330
  priorDayTithiByRule,
5056
5331
  nakshatraIndicesInDay,
5057
5332
  sankrantiRashi,
5333
+ nextDaySankrantiRashi,
5334
+ prevDaySankrantiRashi,
5058
5335
  ekadashiDashamiViddha,
5059
5336
  smartaDwadashiToday,
5060
5337
  moonriseInDay: moonriseInDayUtc,
@@ -5063,7 +5340,7 @@ function getDailyPanchang(date, location, options) {
5063
5340
  end: utcToLocalDisplay(bhadraUtc.end, offsetMinutes)
5064
5341
  } : null,
5065
5342
  formatClock,
5066
- region: options.region ?? "all"
5343
+ region: resolveRegionAlias(options.region)
5067
5344
  },
5068
5345
  (key) => t.festivalNames[key] ?? t.misc[key] ?? key,
5069
5346
  (idx) => resolveMasaName(idx, lang)
package/dist/index.d.cts CHANGED
@@ -12,13 +12,31 @@ type Language = 'en' | 'hi';
12
12
  type Precision = 'standard' | 'high';
13
13
  type MasaSystem = 'purnimanta' | 'amanta';
14
14
  /**
15
- * Regional scope for region-specific festival variants (primarily regional
16
- * Sankranti names Pongal, Vishu, Baisakhi, Bihu, Ayyappa Makara Jyothi,
17
- * etc.). Default `'all'` emits every regional variant; narrower values
18
- * filter the regional variants to that region. The canonical pan-Indian
15
+ * Regional scope for region-specific festival variants. Default `'all'` emits
16
+ * every regional variant; narrower values filter to festivals scoped to that
17
+ * region (or pan-Indian festivals, which always emit). The canonical
19
18
  * `sankranti` event is always emitted regardless of this setting.
19
+ *
20
+ * State-slug naming: each value corresponds to an Indian state (or `nepal`
21
+ * for Nepali panchang usage). The single pre-v2.1 direction-based value
22
+ * `'north-india'` and the ethno-linguistic slugs `'tamil'` and `'bengal'`
23
+ * are still accepted as inputs ({@link LegacyFestivalRegion}) and mapped
24
+ * to their canonical equivalents at entry-point resolution time.
25
+ */
26
+ type FestivalRegion = 'all' | 'tamil-nadu' | 'kerala' | 'karnataka' | 'andhra-pradesh' | 'telangana' | 'west-bengal' | 'odisha' | 'assam' | 'bihar' | 'jharkhand' | 'gujarat' | 'maharashtra' | 'goa' | 'rajasthan' | 'punjab' | 'haryana' | 'himachal-pradesh' | 'uttarakhand' | 'uttar-pradesh' | 'madhya-pradesh' | 'nepal';
27
+ /**
28
+ * Pre-v2.1 region identifiers accepted for back-compat. These are mapped
29
+ * to canonical {@link FestivalRegion} values by `resolveRegionAlias` before
30
+ * festival filtering. Slated for removal in v3.
31
+ *
32
+ * - `'tamil'` → `'tamil-nadu'`
33
+ * - `'bengal'` → `'west-bengal'`
34
+ * - `'north-india'` → `'all'` (Makar Sankranti is pan-Indian; use explicit
35
+ * state slugs for Lohri / Govardhan / Bhai Dooj)
36
+ *
37
+ * @deprecated Use the equivalent {@link FestivalRegion} value. Removal in v3.
20
38
  */
21
- type FestivalRegion = 'all' | 'north-india' | 'tamil' | 'kerala' | 'bengal' | 'punjab' | 'gujarat' | 'assam' | 'maharashtra';
39
+ type LegacyFestivalRegion = 'tamil' | 'bengal' | 'north-india';
22
40
  interface PanchangOptions {
23
41
  timezone: number | string;
24
42
  ayanamsa?: AyanamsaType;
@@ -35,9 +53,10 @@ interface PanchangOptions {
35
53
  janmaRashi?: number;
36
54
  /**
37
55
  * Regional scope for region-specific festival variants. Default `'all'`.
38
- * See {@link FestivalRegion} for supported regions.
56
+ * See {@link FestivalRegion} for supported regions; pre-v2.1 values
57
+ * ({@link LegacyFestivalRegion}) are accepted and mapped internally.
39
58
  */
40
- region?: FestivalRegion;
59
+ region?: FestivalRegion | LegacyFestivalRegion;
41
60
  }
42
61
  interface InstantPanchangOptions {
43
62
  ayanamsa?: AyanamsaType;
@@ -54,9 +73,10 @@ interface InstantPanchangOptions {
54
73
  janmaRashi?: number;
55
74
  /**
56
75
  * Regional scope for region-specific festival variants. Default `'all'`.
57
- * See {@link FestivalRegion} for supported regions.
76
+ * See {@link FestivalRegion} for supported regions; pre-v2.1 values
77
+ * ({@link LegacyFestivalRegion}) are accepted and mapped internally.
58
78
  */
59
- region?: FestivalRegion;
79
+ region?: FestivalRegion | LegacyFestivalRegion;
60
80
  }
61
81
 
62
82
  interface TimePeriod {
@@ -964,4 +984,4 @@ declare class PanchangError extends Error {
964
984
  constructor(message: string, code: PanchangErrorCode);
965
985
  }
966
986
 
967
- export { type AntarDasha, type AyanamsaType, type BhadraInfo, type ChandraBalamInfo, type ChandraMasaInfo, type ChoghadiyaInfo, type ChoghadiyaQuality, type ChoghadiyaSlot, type DailyKaranaInfo, type DailyNakshatraInfo, type DailyPanchangResult, type DailyTithiInfo, type DailyYogaInfo, type DashaLord, type FestivalInfo, type FestivalRegion, GRAHA_ABBR, type GeoLocation, type GowriInfo, type GowriSlot, type GrahaName, type GrahaPosition, type HoraInfo, type HoraSlot, type InstantPanchangOptions, type InstantPanchangResult, type KaranaInfo, type Language, type MahaDasha, type MasaInfo, type MasaSystem, type NakshatraInfo, PanchangError, type PanchangErrorCode, type PanchangOptions, type PlanetaryPositions, type Precision, type RashiInfo, type SamvatInfo, type SpecialYogaInfo, type TimePeriod, type TithiInfo, type VaraInfo, type VimshottariDashaResult, type YogaInfo, computeAbhijitMuhurta, computeAmritKala, computeBrahmaMuhurta, computeChandraBalam, computeGodhuliMuhurta, computeGowriPanchangam, computeGulikaKalam, computeNishitaMuhurta, computePlanetaryPositions, computeRahuKalam, computeVijayaMuhurta, computeVimshottariDasha, computeVimshottariDashaFromBirth, computeYamaganda, computeAyanamsa as getAyanamsa, getDailyPanchang, getEclipseDuringDay, getInstantPanchang, getMoonrise, getMoonset, getSiderealMoonLongitude, getSiderealSunLongitude, computeSunrise as getSunrise, computeSunset as getSunset, getUpcomingLunarEclipse, getUpcomingSolarEclipse };
987
+ export { type AntarDasha, type AyanamsaType, type BhadraInfo, type ChandraBalamInfo, type ChandraMasaInfo, type ChoghadiyaInfo, type ChoghadiyaQuality, type ChoghadiyaSlot, type DailyKaranaInfo, type DailyNakshatraInfo, type DailyPanchangResult, type DailyTithiInfo, type DailyYogaInfo, type DashaLord, type FestivalInfo, type FestivalRegion, GRAHA_ABBR, type GeoLocation, type GowriInfo, type GowriSlot, type GrahaName, type GrahaPosition, type HoraInfo, type HoraSlot, type InstantPanchangOptions, type InstantPanchangResult, type KaranaInfo, type Language, type LegacyFestivalRegion, type MahaDasha, type MasaInfo, type MasaSystem, type NakshatraInfo, PanchangError, type PanchangErrorCode, type PanchangOptions, type PlanetaryPositions, type Precision, type RashiInfo, type SamvatInfo, type SpecialYogaInfo, type TimePeriod, type TithiInfo, type VaraInfo, type VimshottariDashaResult, type YogaInfo, computeAbhijitMuhurta, computeAmritKala, computeBrahmaMuhurta, computeChandraBalam, computeGodhuliMuhurta, computeGowriPanchangam, computeGulikaKalam, computeNishitaMuhurta, computePlanetaryPositions, computeRahuKalam, computeVijayaMuhurta, computeVimshottariDasha, computeVimshottariDashaFromBirth, computeYamaganda, computeAyanamsa as getAyanamsa, getDailyPanchang, getEclipseDuringDay, getInstantPanchang, getMoonrise, getMoonset, getSiderealMoonLongitude, getSiderealSunLongitude, computeSunrise as getSunrise, computeSunset as getSunset, getUpcomingLunarEclipse, getUpcomingSolarEclipse };
package/dist/index.d.ts CHANGED
@@ -12,13 +12,31 @@ type Language = 'en' | 'hi';
12
12
  type Precision = 'standard' | 'high';
13
13
  type MasaSystem = 'purnimanta' | 'amanta';
14
14
  /**
15
- * Regional scope for region-specific festival variants (primarily regional
16
- * Sankranti names Pongal, Vishu, Baisakhi, Bihu, Ayyappa Makara Jyothi,
17
- * etc.). Default `'all'` emits every regional variant; narrower values
18
- * filter the regional variants to that region. The canonical pan-Indian
15
+ * Regional scope for region-specific festival variants. Default `'all'` emits
16
+ * every regional variant; narrower values filter to festivals scoped to that
17
+ * region (or pan-Indian festivals, which always emit). The canonical
19
18
  * `sankranti` event is always emitted regardless of this setting.
19
+ *
20
+ * State-slug naming: each value corresponds to an Indian state (or `nepal`
21
+ * for Nepali panchang usage). The single pre-v2.1 direction-based value
22
+ * `'north-india'` and the ethno-linguistic slugs `'tamil'` and `'bengal'`
23
+ * are still accepted as inputs ({@link LegacyFestivalRegion}) and mapped
24
+ * to their canonical equivalents at entry-point resolution time.
25
+ */
26
+ type FestivalRegion = 'all' | 'tamil-nadu' | 'kerala' | 'karnataka' | 'andhra-pradesh' | 'telangana' | 'west-bengal' | 'odisha' | 'assam' | 'bihar' | 'jharkhand' | 'gujarat' | 'maharashtra' | 'goa' | 'rajasthan' | 'punjab' | 'haryana' | 'himachal-pradesh' | 'uttarakhand' | 'uttar-pradesh' | 'madhya-pradesh' | 'nepal';
27
+ /**
28
+ * Pre-v2.1 region identifiers accepted for back-compat. These are mapped
29
+ * to canonical {@link FestivalRegion} values by `resolveRegionAlias` before
30
+ * festival filtering. Slated for removal in v3.
31
+ *
32
+ * - `'tamil'` → `'tamil-nadu'`
33
+ * - `'bengal'` → `'west-bengal'`
34
+ * - `'north-india'` → `'all'` (Makar Sankranti is pan-Indian; use explicit
35
+ * state slugs for Lohri / Govardhan / Bhai Dooj)
36
+ *
37
+ * @deprecated Use the equivalent {@link FestivalRegion} value. Removal in v3.
20
38
  */
21
- type FestivalRegion = 'all' | 'north-india' | 'tamil' | 'kerala' | 'bengal' | 'punjab' | 'gujarat' | 'assam' | 'maharashtra';
39
+ type LegacyFestivalRegion = 'tamil' | 'bengal' | 'north-india';
22
40
  interface PanchangOptions {
23
41
  timezone: number | string;
24
42
  ayanamsa?: AyanamsaType;
@@ -35,9 +53,10 @@ interface PanchangOptions {
35
53
  janmaRashi?: number;
36
54
  /**
37
55
  * Regional scope for region-specific festival variants. Default `'all'`.
38
- * See {@link FestivalRegion} for supported regions.
56
+ * See {@link FestivalRegion} for supported regions; pre-v2.1 values
57
+ * ({@link LegacyFestivalRegion}) are accepted and mapped internally.
39
58
  */
40
- region?: FestivalRegion;
59
+ region?: FestivalRegion | LegacyFestivalRegion;
41
60
  }
42
61
  interface InstantPanchangOptions {
43
62
  ayanamsa?: AyanamsaType;
@@ -54,9 +73,10 @@ interface InstantPanchangOptions {
54
73
  janmaRashi?: number;
55
74
  /**
56
75
  * Regional scope for region-specific festival variants. Default `'all'`.
57
- * See {@link FestivalRegion} for supported regions.
76
+ * See {@link FestivalRegion} for supported regions; pre-v2.1 values
77
+ * ({@link LegacyFestivalRegion}) are accepted and mapped internally.
58
78
  */
59
- region?: FestivalRegion;
79
+ region?: FestivalRegion | LegacyFestivalRegion;
60
80
  }
61
81
 
62
82
  interface TimePeriod {
@@ -964,4 +984,4 @@ declare class PanchangError extends Error {
964
984
  constructor(message: string, code: PanchangErrorCode);
965
985
  }
966
986
 
967
- export { type AntarDasha, type AyanamsaType, type BhadraInfo, type ChandraBalamInfo, type ChandraMasaInfo, type ChoghadiyaInfo, type ChoghadiyaQuality, type ChoghadiyaSlot, type DailyKaranaInfo, type DailyNakshatraInfo, type DailyPanchangResult, type DailyTithiInfo, type DailyYogaInfo, type DashaLord, type FestivalInfo, type FestivalRegion, GRAHA_ABBR, type GeoLocation, type GowriInfo, type GowriSlot, type GrahaName, type GrahaPosition, type HoraInfo, type HoraSlot, type InstantPanchangOptions, type InstantPanchangResult, type KaranaInfo, type Language, type MahaDasha, type MasaInfo, type MasaSystem, type NakshatraInfo, PanchangError, type PanchangErrorCode, type PanchangOptions, type PlanetaryPositions, type Precision, type RashiInfo, type SamvatInfo, type SpecialYogaInfo, type TimePeriod, type TithiInfo, type VaraInfo, type VimshottariDashaResult, type YogaInfo, computeAbhijitMuhurta, computeAmritKala, computeBrahmaMuhurta, computeChandraBalam, computeGodhuliMuhurta, computeGowriPanchangam, computeGulikaKalam, computeNishitaMuhurta, computePlanetaryPositions, computeRahuKalam, computeVijayaMuhurta, computeVimshottariDasha, computeVimshottariDashaFromBirth, computeYamaganda, computeAyanamsa as getAyanamsa, getDailyPanchang, getEclipseDuringDay, getInstantPanchang, getMoonrise, getMoonset, getSiderealMoonLongitude, getSiderealSunLongitude, computeSunrise as getSunrise, computeSunset as getSunset, getUpcomingLunarEclipse, getUpcomingSolarEclipse };
987
+ export { type AntarDasha, type AyanamsaType, type BhadraInfo, type ChandraBalamInfo, type ChandraMasaInfo, type ChoghadiyaInfo, type ChoghadiyaQuality, type ChoghadiyaSlot, type DailyKaranaInfo, type DailyNakshatraInfo, type DailyPanchangResult, type DailyTithiInfo, type DailyYogaInfo, type DashaLord, type FestivalInfo, type FestivalRegion, GRAHA_ABBR, type GeoLocation, type GowriInfo, type GowriSlot, type GrahaName, type GrahaPosition, type HoraInfo, type HoraSlot, type InstantPanchangOptions, type InstantPanchangResult, type KaranaInfo, type Language, type LegacyFestivalRegion, type MahaDasha, type MasaInfo, type MasaSystem, type NakshatraInfo, PanchangError, type PanchangErrorCode, type PanchangOptions, type PlanetaryPositions, type Precision, type RashiInfo, type SamvatInfo, type SpecialYogaInfo, type TimePeriod, type TithiInfo, type VaraInfo, type VimshottariDashaResult, type YogaInfo, computeAbhijitMuhurta, computeAmritKala, computeBrahmaMuhurta, computeChandraBalam, computeGodhuliMuhurta, computeGowriPanchangam, computeGulikaKalam, computeNishitaMuhurta, computePlanetaryPositions, computeRahuKalam, computeVijayaMuhurta, computeVimshottariDasha, computeVimshottariDashaFromBirth, computeYamaganda, computeAyanamsa as getAyanamsa, getDailyPanchang, getEclipseDuringDay, getInstantPanchang, getMoonrise, getMoonset, getSiderealMoonLongitude, getSiderealSunLongitude, computeSunrise as getSunrise, computeSunset as getSunset, getUpcomingLunarEclipse, getUpcomingSolarEclipse };
package/dist/index.js CHANGED
@@ -3754,28 +3754,47 @@ function computeDurMuhurta(sunrise, sunset, varaIndex) {
3754
3754
  var SANKRANTI_REGIONAL = {
3755
3755
  // Mesha (0) — solar new year across regions
3756
3756
  0: [
3757
- { key: "baisakhi", region: "punjab", type: "major" },
3758
- { key: "vishu", region: "kerala", type: "major" },
3759
- { key: "pohela_boishakh", region: "bengal", type: "major" },
3760
- { key: "puthandu", region: "tamil", type: "major" }
3757
+ { key: "baisakhi", regions: ["punjab", "haryana"], type: "major" },
3758
+ { key: "vishu", regions: ["kerala"], type: "major" },
3759
+ { key: "pohela_boishakh", regions: ["west-bengal"], type: "major" },
3760
+ { key: "puthandu", regions: ["tamil-nadu"], type: "major" },
3761
+ { key: "bohag_bihu", regions: ["assam"], type: "major" }
3761
3762
  ],
3762
3763
  // Karka (3) — Dakshinayana (Sun's southward course begins)
3763
3764
  3: [
3764
- { key: "dakshinayana", region: "all", type: "minor" }
3765
+ { key: "dakshinayana", regions: ["all"], type: "minor" },
3766
+ // Raja Parba (Odisha) — 4-day monsoon festival; we emit a single-day
3767
+ // marker on Karka Sankranti, the most widely cited anchor day.
3768
+ { key: "raja_sankranti", regions: ["odisha"], type: "major" },
3769
+ // Harela (Kumaon / Uttarakhand) — observed on Shravana solar-month start.
3770
+ { key: "harela", regions: ["uttarakhand"], type: "major" }
3765
3771
  ],
3766
- // Simha (4)
3772
+ // Simha (4) — Singh Sankranti is primarily observed in Odisha, Bihar,
3773
+ // and Nepal (as Singhasankranti); not a pan-Indian observance despite
3774
+ // the pre-v2.1 tagging.
3767
3775
  4: [
3768
- { key: "singh_sankranti", region: "all", type: "minor" }
3776
+ { key: "singh_sankranti", regions: ["odisha", "bihar", "jharkhand", "nepal"], type: "minor" }
3777
+ ],
3778
+ // Kanya (5) — Sair (Himachal): first day of the Ashwin solar month.
3779
+ 5: [
3780
+ { key: "sair", regions: ["himachal-pradesh"], type: "minor" }
3781
+ ],
3782
+ // Tula (6) — Kati Bihu (Assam): Kartika solar-month start.
3783
+ 6: [
3784
+ { key: "kati_bihu", regions: ["assam"], type: "minor" }
3769
3785
  ],
3770
3786
  // Makara (9) — Uttarayana / harvest festivals
3771
3787
  9: [
3772
- { key: "makar_sankranti", region: "north-india", type: "major" },
3773
- { key: "pongal", region: "tamil", type: "major" },
3774
- { key: "uttarayan", region: "gujarat", type: "major" },
3775
- { key: "bihu", region: "assam", type: "major" },
3776
- { key: "ayyappa_makara_jyothi", region: "kerala", type: "major" }
3788
+ // Makar Sankranti is pan-Indian (re-scoped from the pre-v2.1 'north-india').
3789
+ { key: "makar_sankranti", regions: ["all"], type: "major" },
3790
+ { key: "pongal", regions: ["tamil-nadu"], type: "major" },
3791
+ { key: "uttarayan", regions: ["gujarat"], type: "major" },
3792
+ { key: "magh_bihu", regions: ["assam"], type: "major" },
3793
+ { key: "ayyappa_makara_jyothi", regions: ["kerala"], type: "major" }
3777
3794
  ]
3778
3795
  };
3796
+ var LOHRI_REGIONS = ["punjab", "haryana", "himachal-pradesh"];
3797
+ var RAJA_REGIONS = ["odisha"];
3779
3798
  var FESTIVAL_REGISTRY = [
3780
3799
  // Chaitra (0)
3781
3800
  { key: "ugadi", masa: 0, tithi: 0, type: "major" },
@@ -3849,7 +3868,144 @@ var FESTIVAL_REGISTRY = [
3849
3868
  // Rig Upakarma — Shravana nakshatra (21) in Shravana chandra masa (4).
3850
3869
  { key: "rig_upakarma", masa: 4, nakshatra: 21, type: "major" },
3851
3870
  // Sama Upakarma — Hasta nakshatra (12) in Bhadrapada chandra masa (5).
3852
- { key: "sama_upakarma", masa: 5, nakshatra: 12, type: "major" }
3871
+ { key: "sama_upakarma", masa: 5, nakshatra: 12, type: "major" },
3872
+ // ── Regional festivals (v2.1 expansion) ─────────────────────
3873
+ // Chaitra (0) — Marathi/Konkani New Year; same tithi as Ugadi but
3874
+ // emitted as a Maharashtra-specific variant name alongside Ugadi.
3875
+ {
3876
+ key: "gudi_padwa",
3877
+ masa: 0,
3878
+ tithi: 0,
3879
+ type: "major",
3880
+ regions: ["maharashtra", "goa"]
3881
+ },
3882
+ // Chaitra (0) Shukla Tritiya — Gangaur (18-day festival concludes here).
3883
+ {
3884
+ key: "gangaur",
3885
+ masa: 0,
3886
+ tithi: 2,
3887
+ type: "major",
3888
+ regions: ["rajasthan"]
3889
+ },
3890
+ // Chaitra (0) Purnima — Karaga festival of Bengaluru.
3891
+ {
3892
+ key: "karaga",
3893
+ masa: 0,
3894
+ tithi: 14,
3895
+ type: "major",
3896
+ regions: ["karnataka"]
3897
+ },
3898
+ // Ashadha (3) — Bonalu, Sundays at Mahakali temples across Telangana.
3899
+ {
3900
+ key: "bonalu",
3901
+ masa: 3,
3902
+ vara: 0,
3903
+ type: "minor",
3904
+ adhikaBehaviour: "observe-in-both",
3905
+ regions: ["telangana"]
3906
+ },
3907
+ // Shravana (4) Shukla Tritiya — Hariyali Teej.
3908
+ {
3909
+ key: "hariyali_teej",
3910
+ masa: 4,
3911
+ tithi: 2,
3912
+ type: "major",
3913
+ regions: ["rajasthan", "uttar-pradesh", "bihar", "haryana", "madhya-pradesh"]
3914
+ },
3915
+ // Shravana (4) Krishna Tritiya — Kajari Teej (tithi 17 = Krishna Tritiya).
3916
+ {
3917
+ key: "kajari_teej",
3918
+ masa: 4,
3919
+ tithi: 17,
3920
+ type: "major",
3921
+ regions: ["rajasthan", "uttar-pradesh", "madhya-pradesh"]
3922
+ },
3923
+ // Bhadrapada (5) Shukla Tritiya — Hartalika Teej.
3924
+ {
3925
+ key: "hartalika_teej",
3926
+ masa: 5,
3927
+ tithi: 2,
3928
+ type: "major",
3929
+ regions: ["rajasthan", "uttar-pradesh", "bihar", "maharashtra", "madhya-pradesh"]
3930
+ },
3931
+ // Amanta Kartika (7) Shukla Pratipada — day after Diwali.
3932
+ // In Purnimanta convention this is also "Kartika Shukla Pratipada" (naming
3933
+ // system aligns), so a namingSystem tag isn't required here.
3934
+ {
3935
+ key: "govardhan_puja",
3936
+ masa: 7,
3937
+ tithi: 0,
3938
+ type: "major",
3939
+ regions: [
3940
+ "uttar-pradesh",
3941
+ "bihar",
3942
+ "haryana",
3943
+ "rajasthan",
3944
+ "gujarat",
3945
+ "madhya-pradesh",
3946
+ "punjab",
3947
+ "jharkhand"
3948
+ ]
3949
+ },
3950
+ // Amanta Kartika (7) Shukla Dwitiya — Bhai Dooj.
3951
+ {
3952
+ key: "bhai_dooj",
3953
+ masa: 7,
3954
+ tithi: 1,
3955
+ type: "major",
3956
+ regions: [
3957
+ "uttar-pradesh",
3958
+ "bihar",
3959
+ "haryana",
3960
+ "maharashtra",
3961
+ "gujarat",
3962
+ "rajasthan",
3963
+ "madhya-pradesh",
3964
+ "west-bengal",
3965
+ "jharkhand",
3966
+ "nepal"
3967
+ ]
3968
+ },
3969
+ // Phalguna (11) Purnima — Phagli (varies by valley; Purnima anchor).
3970
+ {
3971
+ key: "phagli",
3972
+ masa: 11,
3973
+ tithi: 14,
3974
+ type: "minor",
3975
+ regions: ["himachal-pradesh"]
3976
+ },
3977
+ // Ashadha (3) Shukla Dwitiya — Jagannath Rath Yatra (pan-Indian; cultural
3978
+ // epicenter is Puri, Odisha but observed nationwide).
3979
+ { key: "jagannath_rath_yatra", masa: 3, tithi: 1, type: "major" },
3980
+ // Shravana (4) Shukla — Varamahalakshmi: last Friday in Shukla paksha
3981
+ // before Purnima. The (masa+vara) match is gated by tithi ∈ [7,13] so
3982
+ // only the second Friday of the paksha (the one closest to Purnima) fires.
3983
+ {
3984
+ key: "varamahalakshmi",
3985
+ masa: 4,
3986
+ vara: 5,
3987
+ type: "major",
3988
+ tithiRange: [7, 13],
3989
+ regions: ["karnataka", "andhra-pradesh", "telangana", "tamil-nadu"]
3990
+ },
3991
+ // Bathukamma (Telangana) — 9-day floral festival from Bhadrapada
3992
+ // Amavasya through Ashwin Shukla Navami. We emit start (Engili Pula
3993
+ // Bathukamma, masa=5/tithi=29) and climax (Saddula Bathukamma, masa=6/
3994
+ // tithi=8) markers; intermediate days carry no library-level emission.
3995
+ {
3996
+ key: "bathukamma_start",
3997
+ masa: 5,
3998
+ tithi: 29,
3999
+ type: "major",
4000
+ regions: ["telangana"]
4001
+ },
4002
+ {
4003
+ key: "bathukamma_saddula",
4004
+ masa: 6,
4005
+ tithi: 8,
4006
+ type: "major",
4007
+ regions: ["telangana"]
4008
+ }
3853
4009
  ];
3854
4010
  var EKADASHI_NAMES = [
3855
4011
  ["kamada", "papamochani"],
@@ -3926,25 +4082,39 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
3926
4082
  match = masaMatches && rule.nakshatra === ctx.nakshatraIndex;
3927
4083
  } else if (rule.vara !== void 0 && rule.masa !== void 0) {
3928
4084
  const masaMatches = rule.masa === ctx.chandraMasaIndex && (adhikaBehaviour !== "shift-to-nija" || !ctx.isAdhika);
3929
- match = masaMatches && rule.vara === ctx.varaIndex;
4085
+ let inTithiRange = true;
4086
+ if (rule.tithiRange !== void 0) {
4087
+ const dateRule = rule.dateRule ?? "sunrise";
4088
+ const tithi = tithiForRule(dateRule);
4089
+ const [lo, hi2] = rule.tithiRange;
4090
+ inTithiRange = tithi >= lo && tithi <= hi2;
4091
+ }
4092
+ match = masaMatches && rule.vara === ctx.varaIndex && inTithiRange;
3930
4093
  } else if (rule.masa !== void 0 && rule.tithi !== void 0) {
3931
4094
  const masaMatches = rule.masa === ctx.chandraMasaIndex && // `shift-to-nija`: only observe in Nija following an Adhika (but still OK if no prior Adhika,
3932
4095
  // since the normal year has no Adhika and the festival observes as usual).
3933
4096
  (adhikaBehaviour !== "shift-to-nija" || !ctx.isAdhika);
3934
4097
  if (masaMatches) {
3935
4098
  const dateRule = rule.dateRule ?? "sunrise";
3936
- const effectiveTithi = tithiForRule(dateRule);
3937
- if (effectiveTithi === rule.tithi) {
3938
- const startTithi = tithiForRuleStart(dateRule);
3939
- const priorEndTithi = priorDayTithiForRule(dateRule);
3940
- if (dateRule !== "sunrise" && startTithi !== void 0 && startTithi !== rule.tithi && priorEndTithi === rule.tithi) {
3941
- continue;
3942
- }
4099
+ const endTithi = tithiForRule(dateRule);
4100
+ const startTithi = tithiForRuleStart(dateRule);
4101
+ const priorEndTithi = priorDayTithiForRule(dateRule);
4102
+ const matchedByEnd = endTithi === rule.tithi;
4103
+ const matchedByStart = dateRule !== "sunrise" && startTithi !== void 0 && startTithi === rule.tithi && endTithi !== rule.tithi;
4104
+ if (matchedByEnd || matchedByStart) {
4105
+ if (dateRule !== "sunrise" && startTithi !== void 0 && startTithi !== rule.tithi && priorEndTithi === rule.tithi) continue;
4106
+ if (matchedByStart && priorEndTithi === rule.tithi) continue;
3943
4107
  match = true;
3944
4108
  }
3945
4109
  }
3946
4110
  }
3947
4111
  if (!match) continue;
4112
+ if (rule.regions !== void 0) {
4113
+ const region2 = ctx.region ?? "all";
4114
+ if (region2 !== "all" && !rule.regions.includes("all") && !rule.regions.includes(region2)) {
4115
+ continue;
4116
+ }
4117
+ }
3948
4118
  const festival = { name: nameResolver(rule.key), type: rule.type };
3949
4119
  if (rule.namingSystem === "purnimanta" && ctx.purnimantaMasaName && ctx.amantaMasaName) {
3950
4120
  festival.description = `Purnimanta: ${ctx.purnimantaMasaName} Krishna Paksha`;
@@ -3999,26 +4169,32 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
3999
4169
  if (tithiForRule("chandrodaya") === 18) {
4000
4170
  results.push({ name: nameResolver("sankashti_chaturthi"), type: "major" });
4001
4171
  }
4002
- const nishitaTithi = tithiForRule("nishita");
4003
- if (nishitaTithi === 28) {
4004
- const isMahaShivaratriMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 10;
4005
- if (!isMahaShivaratriMonth) {
4006
- const startTithi = ctx.tithiByRuleStart?.nishita;
4007
- const priorEndTithi = ctx.priorDayTithiByRule?.nishita;
4008
- const suppressed = startTithi !== void 0 && startTithi !== 28 && priorEndTithi === 28;
4009
- if (!suppressed) {
4172
+ {
4173
+ const endTithi = tithiForRule("nishita");
4174
+ const startTithi = ctx.tithiByRuleStart?.nishita;
4175
+ const priorEndTithi = ctx.priorDayTithiByRule?.nishita;
4176
+ const matchedByEnd = endTithi === 28;
4177
+ const matchedByStart = startTithi !== void 0 && startTithi === 28 && endTithi !== 28;
4178
+ if (matchedByEnd || matchedByStart) {
4179
+ const isMahaShivaratriMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 10;
4180
+ const dedupeA = startTithi !== void 0 && startTithi !== 28 && priorEndTithi === 28;
4181
+ const dedupeB = matchedByStart && priorEndTithi === 28;
4182
+ if (!isMahaShivaratriMonth && !dedupeA && !dedupeB) {
4010
4183
  results.push({ name: nameResolver("masik_shivaratri"), type: "minor" });
4011
4184
  }
4012
4185
  }
4013
4186
  }
4014
- const madhyahnaTithi = tithiForRule("madhyahna");
4015
- if (madhyahnaTithi === 3) {
4016
- const isGaneshChaturthiMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 5;
4017
- if (!isGaneshChaturthiMonth) {
4018
- const startTithi = ctx.tithiByRuleStart?.madhyahna;
4019
- const priorEndTithi = ctx.priorDayTithiByRule?.madhyahna;
4020
- const suppressed = startTithi !== void 0 && startTithi !== 3 && priorEndTithi === 3;
4021
- if (!suppressed) {
4187
+ {
4188
+ const endTithi = tithiForRule("madhyahna");
4189
+ const startTithi = ctx.tithiByRuleStart?.madhyahna;
4190
+ const priorEndTithi = ctx.priorDayTithiByRule?.madhyahna;
4191
+ const matchedByEnd = endTithi === 3;
4192
+ const matchedByStart = startTithi !== void 0 && startTithi === 3 && endTithi !== 3;
4193
+ if (matchedByEnd || matchedByStart) {
4194
+ const isGaneshChaturthiMonth = !ctx.isAdhika && ctx.chandraMasaIndex === 5;
4195
+ const dedupeA = startTithi !== void 0 && startTithi !== 3 && priorEndTithi === 3;
4196
+ const dedupeB = matchedByStart && priorEndTithi === 3;
4197
+ if (!isGaneshChaturthiMonth && !dedupeA && !dedupeB) {
4022
4198
  results.push({ name: nameResolver("vinayaka_chaturthi"), type: "minor" });
4023
4199
  }
4024
4200
  }
@@ -4043,6 +4219,7 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4043
4219
  description: nameResolver(variantKey)
4044
4220
  });
4045
4221
  }
4222
+ const region = ctx.region ?? "all";
4046
4223
  if (ctx.sankrantiRashi !== void 0 && ctx.sankrantiRashi !== null) {
4047
4224
  const rashiName = rashiNameResolver ? rashiNameResolver(ctx.sankrantiRashi) : `Rashi ${ctx.sankrantiRashi}`;
4048
4225
  results.push({
@@ -4050,11 +4227,10 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4050
4227
  type: "sankranti",
4051
4228
  description: rashiName
4052
4229
  });
4053
- const region = ctx.region ?? "all";
4054
4230
  const regionalRules = SANKRANTI_REGIONAL[ctx.sankrantiRashi];
4055
4231
  if (regionalRules) {
4056
4232
  for (const r of regionalRules) {
4057
- if (region !== "all" && r.region !== "all" && r.region !== region) continue;
4233
+ if (region !== "all" && !r.regions.includes("all") && !r.regions.includes(region)) continue;
4058
4234
  results.push({
4059
4235
  name: nameResolver(r.key),
4060
4236
  type: "sankranti",
@@ -4063,9 +4239,47 @@ function computeFestivals(ctx, nameResolver, rashiNameResolver) {
4063
4239
  }
4064
4240
  }
4065
4241
  }
4242
+ if (ctx.nextDaySankrantiRashi === 9) {
4243
+ if (region === "all" || LOHRI_REGIONS.includes(region)) {
4244
+ results.push({ name: nameResolver("lohri"), type: "major" });
4245
+ }
4246
+ }
4247
+ if (ctx.nextDaySankrantiRashi === 3) {
4248
+ if (region === "all" || RAJA_REGIONS.includes(region)) {
4249
+ results.push({ name: nameResolver("raja_pahili"), type: "major" });
4250
+ }
4251
+ }
4252
+ if (ctx.prevDaySankrantiRashi === 3) {
4253
+ if (region === "all" || RAJA_REGIONS.includes(region)) {
4254
+ results.push({ name: nameResolver("raja_basi"), type: "major" });
4255
+ }
4256
+ }
4066
4257
  return results;
4067
4258
  }
4068
4259
 
4260
+ // src/core/regionAlias.ts
4261
+ var LEGACY_REGION_ALIASES = {
4262
+ "tamil": "tamil-nadu",
4263
+ "bengal": "west-bengal",
4264
+ "north-india": "all"
4265
+ };
4266
+ var warned = /* @__PURE__ */ new Set();
4267
+ function resolveRegionAlias(region) {
4268
+ if (region === void 0) return "all";
4269
+ if (region in LEGACY_REGION_ALIASES) {
4270
+ const legacy = region;
4271
+ const canonical = LEGACY_REGION_ALIASES[legacy];
4272
+ if (!warned.has(legacy)) {
4273
+ warned.add(legacy);
4274
+ console.warn(
4275
+ `[panchang-ts] FestivalRegion '${legacy}' is deprecated; use '${canonical}'. Legacy value will be removed in v3.`
4276
+ );
4277
+ }
4278
+ return canonical;
4279
+ }
4280
+ return region;
4281
+ }
4282
+
4069
4283
  // src/core/bhadra.ts
4070
4284
  function isVishtiKarana(karanaIndex) {
4071
4285
  if (karanaIndex <= 0 || karanaIndex >= 57) return false;
@@ -4341,13 +4555,36 @@ var en = {
4341
4555
  vishu: "Vishu",
4342
4556
  pohela_boishakh: "Pohela Boishakh",
4343
4557
  puthandu: "Puthandu",
4558
+ bohag_bihu: "Bohag Bihu",
4344
4559
  makar_sankranti: "Makar Sankranti",
4345
4560
  pongal: "Pongal",
4346
4561
  uttarayan: "Uttarayan",
4347
- bihu: "Magh Bihu",
4562
+ magh_bihu: "Magh Bihu",
4348
4563
  ayyappa_makara_jyothi: "Ayyappa Makara Jyothi",
4349
4564
  dakshinayana: "Dakshinayana",
4565
+ raja_sankranti: "Raja Sankranti",
4566
+ raja_pahili: "Pahili Raja",
4567
+ raja_basi: "Basi Raja",
4568
+ harela: "Harela",
4350
4569
  singh_sankranti: "Singh Sankranti",
4570
+ sair: "Sair",
4571
+ kati_bihu: "Kati Bihu",
4572
+ lohri: "Lohri",
4573
+ // Regional festivals (v2.1 expansion)
4574
+ gudi_padwa: "Gudi Padwa",
4575
+ gangaur: "Gangaur",
4576
+ karaga: "Karaga",
4577
+ bonalu: "Bonalu",
4578
+ hariyali_teej: "Hariyali Teej",
4579
+ kajari_teej: "Kajari Teej",
4580
+ hartalika_teej: "Hartalika Teej",
4581
+ govardhan_puja: "Govardhan Puja",
4582
+ bhai_dooj: "Bhai Dooj",
4583
+ phagli: "Phagli",
4584
+ jagannath_rath_yatra: "Jagannath Rath Yatra",
4585
+ varamahalakshmi: "Varamahalakshmi Vratam",
4586
+ bathukamma_start: "Bathukamma \u2014 Engili Pula",
4587
+ bathukamma_saddula: "Bathukamma \u2014 Saddula",
4351
4588
  // Chhath Puja (4-day, Kartika Shukla Chaturthi→Saptami)
4352
4589
  chhath_nahay_khay: "Chhath \u2014 Nahay Khay",
4353
4590
  chhath_kharna: "Chhath \u2014 Kharna",
@@ -4583,13 +4820,36 @@ var hi = {
4583
4820
  vishu: "\u0935\u093F\u0937\u0941",
4584
4821
  pohela_boishakh: "\u092A\u094B\u0939\u0947\u0932\u093E \u092C\u094B\u0907\u0936\u093E\u0916",
4585
4822
  puthandu: "\u092A\u0941\u0925\u093E\u0902\u0921\u0941",
4823
+ bohag_bihu: "\u092C\u094B\u0939\u093E\u0917 \u092C\u093F\u0939\u0942",
4586
4824
  makar_sankranti: "\u092E\u0915\u0930 \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4587
4825
  pongal: "\u092A\u094B\u0902\u0917\u0932",
4588
4826
  uttarayan: "\u0909\u0924\u094D\u0924\u0930\u093E\u092F\u0923",
4589
- bihu: "\u092E\u093E\u0918 \u092C\u093F\u0939\u0942",
4827
+ magh_bihu: "\u092E\u093E\u0918 \u092C\u093F\u0939\u0942",
4590
4828
  ayyappa_makara_jyothi: "\u0905\u092F\u094D\u092F\u092A\u094D\u092A\u093E \u092E\u0915\u0930 \u091C\u094D\u092F\u094B\u0924\u093F",
4591
4829
  dakshinayana: "\u0926\u0915\u094D\u0937\u093F\u0923\u093E\u092F\u0928",
4830
+ raja_sankranti: "\u0930\u093E\u091C\u093E \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4831
+ raja_pahili: "\u092A\u0939\u093F\u0932\u0940 \u0930\u093E\u091C\u093E",
4832
+ raja_basi: "\u092C\u093E\u0938\u0940 \u0930\u093E\u091C\u093E",
4833
+ harela: "\u0939\u0930\u0947\u0932\u093E",
4592
4834
  singh_sankranti: "\u0938\u093F\u0902\u0939 \u0938\u0902\u0915\u094D\u0930\u093E\u0902\u0924\u093F",
4835
+ sair: "\u0938\u0948\u0930",
4836
+ kati_bihu: "\u0915\u093E\u0924\u0940 \u092C\u093F\u0939\u0942",
4837
+ lohri: "\u0932\u094B\u0939\u0921\u093C\u0940",
4838
+ // Regional festivals (v2.1 expansion)
4839
+ gudi_padwa: "\u0917\u0941\u0921\u093C\u0940 \u092A\u0921\u093C\u0935\u093E",
4840
+ gangaur: "\u0917\u0923\u0917\u094C\u0930",
4841
+ karaga: "\u0915\u0930\u0917\u093E",
4842
+ bonalu: "\u092C\u094B\u0928\u093E\u0932\u0941",
4843
+ hariyali_teej: "\u0939\u0930\u093F\u092F\u093E\u0932\u0940 \u0924\u0940\u091C",
4844
+ kajari_teej: "\u0915\u091C\u0930\u0940 \u0924\u0940\u091C",
4845
+ hartalika_teej: "\u0939\u0930\u0924\u093E\u0932\u093F\u0915\u093E \u0924\u0940\u091C",
4846
+ govardhan_puja: "\u0917\u094B\u0935\u0930\u094D\u0927\u0928 \u092A\u0942\u091C\u093E",
4847
+ bhai_dooj: "\u092D\u093E\u0908 \u0926\u0942\u091C",
4848
+ phagli: "\u092B\u093E\u0917\u094D\u0932\u0940",
4849
+ jagannath_rath_yatra: "\u091C\u0917\u0928\u094D\u0928\u093E\u0925 \u0930\u0925 \u092F\u093E\u0924\u094D\u0930\u093E",
4850
+ varamahalakshmi: "\u0935\u0930\u092E\u0939\u093E\u0932\u0915\u094D\u0937\u094D\u092E\u0940 \u0935\u094D\u0930\u0924",
4851
+ bathukamma_start: "\u092C\u0924\u0941\u0915\u092E\u094D\u092E\u093E \u2014 \u090F\u0902\u0917\u093F\u0932\u0940 \u092A\u0941\u0932\u093E",
4852
+ bathukamma_saddula: "\u092C\u0924\u0941\u0915\u092E\u094D\u092E\u093E \u2014 \u0938\u0926\u094D\u0926\u0941\u0932\u093E",
4593
4853
  // Chhath Puja
4594
4854
  chhath_nahay_khay: "\u091B\u0920 \u2014 \u0928\u0939\u093E\u092F \u0916\u093E\u092F",
4595
4855
  chhath_kharna: "\u091B\u0920 \u2014 \u0916\u0930\u0928\u093E",
@@ -4819,7 +5079,7 @@ function getInstantPanchang(date, location, options) {
4819
5079
  isAdhika: chandramasa.isAdhika,
4820
5080
  varaIndex: vara.index,
4821
5081
  solarMasaIndex: Math.floor(siderealSun / 30) % 12,
4822
- region: options?.region ?? "all"
5082
+ region: resolveRegionAlias(options?.region)
4823
5083
  },
4824
5084
  (key) => t.festivalNames[key] ?? t.misc[key] ?? key,
4825
5085
  (idx) => resolveMasaName(idx, lang)
@@ -5004,6 +5264,21 @@ function getDailyPanchang(date, location, options) {
5004
5264
  const rashiAtSunrise = Math.floor(siderealSunAtSunrise / 30) % 12;
5005
5265
  const rashiAtNextSunrise = Math.floor(getSun(nextSunriseUtc) / 30) % 12;
5006
5266
  const sankrantiRashi = rashiAtSunrise !== rashiAtNextSunrise ? rashiAtNextSunrise : null;
5267
+ let nextDaySankrantiRashi = null;
5268
+ try {
5269
+ const tomorrowSunsetUtc = computeSunset(nextSunriseUtc, location);
5270
+ const dayAfterSunriseUtc = computeSunrise(tomorrowSunsetUtc, location);
5271
+ const rashiAtDayAfterSunrise = Math.floor(getSun(dayAfterSunriseUtc) / 30) % 12;
5272
+ if (rashiAtNextSunrise !== rashiAtDayAfterSunrise) {
5273
+ nextDaySankrantiRashi = rashiAtDayAfterSunrise;
5274
+ }
5275
+ } catch (e) {
5276
+ if (!(e instanceof PanchangError && (e.code === "NO_SUNRISE" || e.code === "NO_SUNSET"))) {
5277
+ throw e;
5278
+ }
5279
+ }
5280
+ const rashiAtYesterdaySunrise = Math.floor(getSun(yesterdaySunriseUtc) / 30) % 12;
5281
+ const prevDaySankrantiRashi = rashiAtYesterdaySunrise !== rashiAtSunrise ? rashiAtSunrise : null;
5007
5282
  let ekadashiDashamiViddha = false;
5008
5283
  if (tithiAtSunrise.index === 10 || tithiAtSunrise.index === 25) {
5009
5284
  const tithiAtArunodaya = tithiAt(arunodayaUtc);
@@ -5053,6 +5328,8 @@ function getDailyPanchang(date, location, options) {
5053
5328
  priorDayTithiByRule,
5054
5329
  nakshatraIndicesInDay,
5055
5330
  sankrantiRashi,
5331
+ nextDaySankrantiRashi,
5332
+ prevDaySankrantiRashi,
5056
5333
  ekadashiDashamiViddha,
5057
5334
  smartaDwadashiToday,
5058
5335
  moonriseInDay: moonriseInDayUtc,
@@ -5061,7 +5338,7 @@ function getDailyPanchang(date, location, options) {
5061
5338
  end: utcToLocalDisplay(bhadraUtc.end, offsetMinutes)
5062
5339
  } : null,
5063
5340
  formatClock,
5064
- region: options.region ?? "all"
5341
+ region: resolveRegionAlias(options.region)
5065
5342
  },
5066
5343
  (key) => t.festivalNames[key] ?? t.misc[key] ?? key,
5067
5344
  (idx) => resolveMasaName(idx, lang)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panchang-ts",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Pure TypeScript Hindu Panchang calculations. Tithi, Nakshatra, Yoga, Karana, Vara, and more. Offline-first, React Native compatible.",
5
5
  "author": "Ishank",
6
6
  "license": "MIT",