Skip to content
Documentation · all sections

Festivals

panchang · v5.4.0 · MIT

80+ festivals with stable keys, Smarta/Vaishnava Ekadashi handling, regional scoping across 20 states + Nepal, and the cacheable pre-computed festivals table.

Every daily result carries the festivals for that day. About 80 are covered:

  • Ekadashi: 26 variants, with the Smarta and Vaishnava split.
  • Sankranti and its regional forms: Pongal, Vishu, Baisakhi, Pohela Boishakh, Bihu, Uttarayan, Lohri.
  • Classical festivals dated by canonical times: Janmashtami, Shivaratri, Ganesh Chaturthi, Diwali, Holika Dahan (with Holi the next day), Raksha Bandhan, Karva Chauth, Akshaya Tritiya.
  • Regional observances: Gudi Padwa, Gangaur, Teej, Onam, Chhath, Karthigai Deepam.
  • Recurring days: Pradosha, Masik Shivaratri, Masik Karthigai, Pushya days, Shravan Somvar.

Reading festivals off a daily result

getDailyPanchang puts the day's festivals in festivals. Each entry has a stable key, a localized name, a type and an optional description.

import { getDailyPanchang } from 'panchang-ts';

const loc = { latitude: 25.3176, longitude: 82.9739 };   // Varanasi
const r = getDailyPanchang(new Date('2026-11-08T12:00:00Z'), loc, { timezone: 330 })!;

for (const f of r.festivals) {
  // key: stable id. type: major | minor | ekadashi | smarta_ekadashi
  //      | vaishnava_ekadashi | pradosha | sankranti | eclipse
  console.log(f.key, f.name, f.type, f.description);
}
// narak_chaturdashi Narak Chaturdashi major Purnimanta: Kartika Krishna Paksha
// diwali Diwali major Purnimanta: Kartika Krishna Paksha

// name is localized, so match on key.
console.log(r.festivals.some(f => f.key === 'diwali'));   // true

Smarta and Vaishnava Ekadashi

On most Ekadashis both fasts fall on the same day, so you get smarta_ekadashi, vaishnava_ekadashi and the generic ekadashi together. When the reference almanac prints the fast on two days, the earlier day is the Smarta fast and the later one the Vaishnava fast. Five geometries split this way:

  • Dashami-viddha: the tithi begins between arunodaya and sunrise.
  • Vriddha (Pakshavardhini) Dwadashi: the Dwadashi spans two sunrises.
  • Vriddha Ekadashi, kshaya Dwadashi: the Ekadashi spans two sunrises and the Dwadashi touches none. The first Ekadashi day is the Smarta fast and the second the Vaishnava fast. Before 5.4 the festival list missed that Smarta fast, although computeEkadashiDatesForYear already gave it.
  • Kshaya: the tithi touches no sunrise at all.
  • Trisprisha: the Ekadashi does touch a sunrise, but the Dwadashi is kshaya. The Smarta fast moves back to the Dashami day and the Vaishnava fast keeps the Ekadashi.

In the last two the Smarta day is Dashami at sunrise, not Ekadashi, so a filter on the sunrise tithi misses that fast. Match on the festival type instead.

import { computeFestivalsInRange, formatInZone } from 'panchang-ts';

const varanasi = { latitude: 25.3176, longitude: 82.9739 };
const show = (a: string, b: string) =>
  computeFestivalsInRange(new Date(a), new Date(b), varanasi, { timezone: 330 })
    .filter(d => d.festival.type.includes('ekadashi'))
    .forEach(d => console.log(formatInZone(d.date, 330).slice(0, 10), d.festival.type));

show('2027-10-25', '2027-10-26');   // Dashami-viddha  (Indira Ekadashi)
show('2026-08-23', '2026-08-24');   // vriddha Dwadashi (Shravana Putrada)
show('2030-03-15', '2030-03-16');   // vriddha Ekadashi, kshaya Dwadashi (Amalaki)
show('2027-07-29', '2027-07-30');   // kshaya          (Yogini)
show('2026-07-10', '2026-07-11');   // trisprisha      (Apara Ekadashi)
// 2027-10-25 smarta_ekadashi / 2027-10-25 ekadashi / 2027-10-26 vaishnava_ekadashi
// 2026-08-23 smarta_ekadashi / 2026-08-23 ekadashi / 2026-08-24 vaishnava_ekadashi
// 2030-03-15 smarta_ekadashi / 2030-03-15 ekadashi / 2030-03-16 vaishnava_ekadashi
// 2027-07-29 smarta_ekadashi / 2027-07-29 ekadashi / 2027-07-30 vaishnava_ekadashi
// 2026-07-10 smarta_ekadashi / 2026-07-10 ekadashi / 2026-07-11 vaishnava_ekadashi

Any other vriddha Ekadashi, where the tithi is current at two consecutive sunrises and the Dwadashi still reaches one, does not split. Nothing emits on the first day and everything on the second (the reference almanac's Unmilini Mahadwadashi). computeEkadashiDatesForYear lists the Smarta dates only.

Festivals over a range of dates

computeFestivalsInRange(start, end, loc, opts) lists festivals without a daily loop, and computeFestivalsForYear(year, loc, opts) does a whole year. You get one entry per festival, so a day with three festivals gives three entries. Both helpers are documented in Calendar Conversion.

import { computeFestivalsInRange, formatInZone } from 'panchang-ts';

const varanasi = { latitude: 25.3176, longitude: 82.9739 };
const days = computeFestivalsInRange(
  new Date('2026-03-01T00:00:00Z'),
  new Date('2026-03-31T00:00:00Z'),
  varanasi,
  { timezone: 330 },
);
console.log(days.length);   // 23  (one entry per festival, not per day)

for (const d of days.filter(d => d.festival.type === 'major')) {
  console.log(formatInZone(d.date, 330).slice(0, 10), d.festival.key);
}
// 2026-03-03 holika_dahan
// 2026-03-04 holi
// 2026-03-06 sankashti_chaturthi
// 2026-03-19 ugadi
// 2026-03-19 gudi_padwa
// 2026-03-21 gangaur
// 2026-03-26 rama_navami

Read each date in the listing's zone, as formatInZone does here, and never with toISOString(). From computeFestivalsForYear it is the local midnight itself: Akshaya Tritiya 2020 at Delhi comes back as 2020-04-25T18:30:00.000Z, which is 26 April 00:00 IST and which toISOString() reads as the 25th. Since 5.4 that holds across a daylight-saving change too: Diwali 2026 in America/New_York is 2026-11-08T05:00:00.000Z. From computeFestivalsInRange the date carries the range start's local time of day.

Regional scoping

region narrows the regional variants to one Indian state. Pan-Indian festivals emit whatever you pass.

const chennai  = { latitude: 13.0827, longitude: 80.2707 };
const amritsar = { latitude: 31.6340, longitude: 74.8723 };
const jan14 = new Date('2025-01-14T12:00:00Z');
const jan13 = new Date('2025-01-13T12:00:00Z');
const names = (r: DailyPanchangResult) => r.festivals.map(f => f.name);

// All regional variants (default):
names(getDailyPanchang(jan14, chennai, { timezone: 330 })!);
// → ["Sankranti","Makar Sankranti","Pongal","Uttarayan","Magh Bihu","Ayyappa Makara Jyothi"]

// Tamil Nadu only:
names(getDailyPanchang(jan14, chennai, { timezone: 330, region: 'tamil-nadu' })!);
// → ["Sankranti","Makar Sankranti","Pongal"]

// Lohri fires on the Hindu day before the Makara transit, in Punjab scope:
getDailyPanchang(jan13, amritsar, { timezone: 330, region: 'punjab' })!
  .festivals.some(f => f.key === 'lohri');   // true

FestivalRegion covers 20 Indian states plus 'nepal' and 'all' (the default). The older slugs 'tamil', 'bengal' and 'north-india' still work. The full union is in Types & Exports.

karthigai_deepam is the one key that displaces another by region. It emits for 'all' and 'tamil-nadu' only, and on its day it takes the place of masik_karthigai. Every other region still lists masik_karthigai on that day: at Chennai on 4 December 2025, 'tamil-nadu' gives karthigai_deepam and 'kerala' gives masik_karthigai. A table built for 'all' and filtered by region afterwards therefore loses that month's Masik Karthigai outside Tamil Nadu, so build the table for the region you serve.

Pre-computed table

You can get festival dates without running the engine in your app. Compute a table once with buildFestivalsTable, cache the JSON, and read it back through the engine-free panchang-ts/festivals entry point.

Build the table once
import { buildFestivalsTable } from 'panchang-ts';            // uses the engine

// Build at your build time, or on first launch in the background.
const table = buildFestivalsTable({
  location: { latitude: 25.3176, longitude: 82.9739 },   // Varanasi
  timezoneOffsetMinutes: 330,    // IST; -300 = US Eastern, 0 = UK
  startYear: 2024,
  endYear: 2031,
  languages: ['en', 'hi'],       // drop 'hi' for ~15% less, not half
  referenceLocation: 'Varanasi',
});
// …persist `table` as JSON (disk / MMKV / your bundler's asset pipeline).
Read it back
import {
  readFestivalsForYear,
  readFestivalsForDate,
  readFestivalsYearRange,
} from 'panchang-ts/festivals';                                // engine-free

// Reads are lookups — no engine, no ephemeris.
readFestivalsYearRange(table);                    // { start: 2024, end: 2031 }
readFestivalsForYear(table, 2026)!.length;        // 146 festival days
const diwali = readFestivalsForYear(table, 2026)!
  .find(d => d.festivals.some(f => f.key === 'diwali'))!.date;   // '2026-11-08'
readFestivalsForDate(table, diwali).map(f => f.name);
// → ["Narak Chaturdashi","Diwali"]
readFestivalsForDate(table, diwali, 'hi').map(f => f.name);
// → ["नरक चतुर्दशी","दिवाली"]

panchang-ts/festivals imports no astronomy code, so a client bundle that only reads a table never pulls in the engine. Keep buildFestivalsTable on the build or server side (or behind a one-time on-device warm-up) and ship only the JSON. The eight-year bilingual table above is about 82 KB, and the same table run through 2033 about 95 KB. Tables written by v1 (4.x) still read, with key empty on their entries.

In Go both sides work. BuildFestivalsTableContext is the same build under a context.Context, and stops early when it is cancelled. The readers, new in the 5.4 module, are package functions that need no Session: ReadFestivalsForYear, ReadFestivalsForDate (a time.Time, dated at the table's offset), ReadFestivalsForDateKey (a YYYY-MM-DD string) and ReadFestivalsYearRange. They take a types.AnyFestivalsFile, which decodes a v1 (4.x) table too, filling Raw instead of Dict and Packed. Wrap a table you just built with its AsAny method. A program that decodes a table with types alone links no engine code. Calling the readers imports panchang, which links some engine code with it, though a read computes nothing.

Eclipses are excluded here. Visibility is location-dependent, so they get their own table at panchang-ts/eclipses (see Eclipses & Moon Phases).

Dating notes

Each festival lands on a calendar day by the kala its tradition uses: madhyahna for Ganesh Chaturthi, pradosha for Diwali, nishita for Maha Shivaratri, moonrise for Karva Chauth. Anything without a canonical-time rule uses the tithi at sunrise.

The windows are the reference almanac's. Daytime runs from sunrise to sunset and the night from sunset to the next sunrise. Madhyahna is the third fifth of the daytime and aparahna the fourth. Pradosha is the first fifth of the night, nishita the 8th of its 15 muhurtas, and arunodaya the 96 minutes before sunrise. The day is chosen from the tithi's exact span against those windows, by one of these rules:

RuleFestivalsDelhi example
The day whose window holds more of the tithi. On a tie, or when both windows are full, the earlier day.Ganesh and Vinayaka Chaturthi (madhyahna); Dhanteras, Diwali and Parashurama Jayanti (pradosha); Govardhan Puja (the whole daytime)Ganesh Chaturthi 2033: 28 Aug
Nishita ladder: a day whose nishita is all Chaturdashi wins, the later of two such days; otherwise the larger overlap.Maha Shivaratri, Masik ShivaratriMaha Shivaratri 2018: 13 Feb
Aparahna ladder: the first full aparahna, otherwise the larger overlap. The next day then takes over if it holds Dashami and Shravana at sunrise and Shravana in its aparahna, and the chosen day’s aparahna lacks Shravana.Dussehra2019: 8 Oct
The last day whose window touches the tithi.Rama Navami (madhyahna); Bhai Dooj and Vat Savitri Amavasya (aparahna)Rama Navami 2032: 19 Apr
The first day whose arunodaya touches Chaturdashi, else the day it begins.Narak Chaturdashi2021: 4 Nov
The first day on which Panchami has begun by two fifths of the daytime.Vasant Panchami2020: 29 Jan
The first sunrise day on which the tithi lasts at least 3 muhurtas (3/15 of the daytime) after sunrise, else the day it begins.Raksha Bandhan, Yajur Upakarma, Nag Panchami, Akshaya TritiyaAkshaya Tritiya 2020: 26 Apr
The first day on which Navami has begun before sunset less two muhurtas.Maha Navami2015: 21 Oct
Moonrise; the sunrise day when the tithi touches no moonrise.Karva Chauth, Sankashti ChaturthiSankashti Chaturthi 2025: 10 Oct, by the fallback
The tithi at sunrise. When it holds two sunrises, the first.Most other tithi festivals, among them Ugadi, Gudi Padwa, Navaratri, Durga Ashtami, Anant Chaturdashi, Hanuman Jayanti, and Guru, Sharad and Kartika PurnimaAnant Chaturdashi 2018: 23 Sep
The tithi at sunrise. When it holds two sunrises, the second.Hariyali, Kajari and Hartalika Teej, Gangaur, Jagannath Rath YatraHartalika Teej 2006: 27 Aug

Krishna Janmashtami keeps its own nishita rule. The full mapping of festivals to kalas is in Accuracy. Karva Chauth, Dhanteras, Narak Chaturdashi and Diwali emit with Purnimanta paksha naming.

A kshaya tithi begins and ends between two sunrises, so it is current at no sunrise at all. A rule keyed on the sunrise tithi then matched on no day of the year and the festival dropped out of that year entirely. The Hindu day that wholly contains the tithi now claims it, which is what the reference almanac publishes.

Only phagli still opts out of containment (see the deferred rules below). Holi, Narak Chaturdashi and Chhath Usha Arghya opted out before 5.4 and now have anchors of their own. With these rules every once-a-year festival except Phagli is listed exactly once a year at Delhi from 2000 to 2035.

Pradosh vrat

pradosha goes to the day whose pradosha window overlaps Trayodashi most, the earlier day winning a tie or when both windows are full. Its description names the weekday form. At Delhi that gives 24 dates in 2025, among them Ravi Pradosha on 9 February, and 25 in 2027, matching the reference almanac's lists for both years.

Holika Dahan and Holi

holika_dahan is the Holika Dahan evening, and holi is the day after it, Rangwali Holi, which is how the reference almanac publishes them. At Delhi in 2026, holika_dahan falls on 3 March and holi on 4 March. The evening comes from the Nirnaya Sindhu ladder, applied to the pradosha of the day Purnima begins and of the day after, with Bhadra taken as the first half of Purnima:

  • If both pradoshas hold Purnima, the first that holds some of it after Bhadra has ended, and otherwise the first.
  • If one does, that evening. It moves to the next evening only when that pradosha holds no Purnima free of Bhadra, Bhadra lasts past the middle of the night, and Purnima still fills at least seven eighths of the next day's daylight (three quarters when the Pratipada that follows is longer than Purnima).
  • If neither does, the day Purnima begins, or the next day when it begins after sunset.

Before 5.4 holi was the Purnima sunrise day, and there was no holika_dahan.

Chhath, Maha Navami and Varamahalakshmi

Chhath is anchored on one day. chhath_sandhya_arghya is the Kartika Shukla Shashthi sunrise day, and chhath_nahay_khay, chhath_kharna and chhath_usha_arghya fall two days before, one day before and one day after it. The four always run on consecutive days: 25 to 28 October 2025 at Delhi.

maha_navami takes the first day on which Navami has begun before sunset less two muhurtas, so it can share a day with durga_ashtami: both fall on 21 October 2015 at Delhi. bathukamma_saddula is the Durgashtami day, 30 September 2025. varamahalakshmi is the Friday in the seven days that end on the Shravana Purnima sunrise day: 8 August 2025, and 28 August 2026, when the Purnima day is itself a Friday.

Masik Karthigai and Karthigai Deepam

masik_karthigai falls once per Krittika transit, on the first day whose sunrise or sunset is in Krittika. karthigai_deepam is the Masik Karthigai day of the Tamil month Karthigai nearest the full moon, and replaces masik_karthigai on that day for the regions in Regional scoping. Chennai 2025 has 13 masik_karthigai days and karthigai_deepam on 4 December.

Onam

onam is one day a year, the Thiruvonam of the solar month Chingam. When Thiruvonam holds two Chingam sunrises it takes the first, when it holds none the day that contains it, and when Chingam has two Thiruvonams the later: 15 September 2024 and 16 September 2013 at Kochi. An adhika lunar month no longer suppresses it.

Monday, Tuesday and Saturday vratas

shravan_somvar, mangala_gauri, kartik_somvar and magha_shanivar count their month in your masaSystem. The default, 'purnimanta', gives the North Indian days, 'amanta' the southern ones, and region 'nepal' the solar month. Adhika months still count, and bonalu is not affected.

import { computeFestivalsForYear, formatInZone } from 'panchang-ts';

const delhi = { latitude: 28.6139, longitude: 77.2090 };
const mondays = (opts: { masaSystem?: 'amanta'; region?: 'nepal' } = {}) =>
  computeFestivalsForYear(2025, delhi, { timezone: 330, ...opts })
    .filter(d => d.festival.key === 'shravan_somvar')
    .map(d => formatInZone(d.date, 330).slice(0, 10));

mondays();                          // purnimanta, the default
// → ["2025-07-14","2025-07-21","2025-07-28","2025-08-04"]
mondays({ masaSystem: 'amanta' });
// → ["2025-07-28","2025-08-04","2025-08-11","2025-08-18"]
mondays({ region: 'nepal' });       // the solar month
// → ["2025-07-21","2025-07-28","2025-08-04","2025-08-11"]

The purnimanta list is the default, so since 5.4 a caller that never sets masaSystem gets the North Indian dates. Before, all four always counted the amanta month, which is the list 'amanta' still gives.

Rules still waiting on a reference

Three rules are deferred until there is a reference capture to check them against. Until then they behave like this:

  • Phagli takes every day whose sunrise holds Phalguna Purnima. Delhi lists it on both 15 and 16 March 2033, and not at all in 2018.
  • Pradosh has no fallback. A Trayodashi that touches neither day's pradosha gives that paksha no Pradosh, as between 24 January and 23 February 2013 at Delhi, one of three such gaps there from 2000 to 2035.
  • Guru Purnima keeps the plain sunrise rule. A refinement that would take the previous day when Purnima lasts under about one muhurta after sunrise is not applied.