How a festival date is actually decided
This page documents the reasoning the engine applies, in the order it applies it. If you disagree with one of our dates, this is the page that lets you say precisely where.
The premise
Sunrise is not the question
The simplest way to build a festival calendar is to ask which tithi was running at sunrise and match it against a table. It is also wrong often enough to matter.
Classical practice does not ask what was running at sunrise. It asks whether the required tithi prevails — vyapti — during a particular window of the day. Which window depends on the festival. Diwali is decided at Pradosha, just after sunset. Ganesh Chaturthi at Madhyahna, around midday. Maha Shivaratri at Nishita, around midnight.
A tithi is an interval, not a label. Once you model it as an interval and compare it against a window, the awkward cases — a tithi that spans two days, or one so short it touches no sunrise at all — stop being special cases and become arithmetic.
Kala windows are fractions of the day, not clock times
Every window is derived from dinamana (sunset − sunrise) and ratrimana (next sunrise − sunset), so all of them move with latitude and season.
Kala vyapini
The windows, and how they are defined
Every window is a fraction of the local day or night, derived from dinamana (sunset − sunrise) and ratrimana (next sunrise − sunset). None of them is a fixed clock time.
| Window | Definition | Basis | Used by |
|---|---|---|---|
| Madhyahna | Day slice 2/5 → 3/5 of dinamana | sunset − sunrise, divided in fifths | Ganesh Chaturthi |
| Aparahna | Day slice 3/5 → 4/5 of dinamana | sunset − sunrise, divided in fifths | Vijayadashami |
| Pradosha | sunset → sunset + 2 × (ratrimana / 15) | roughly 96 minutes after sunset | Diwali, Dhanteras |
| Nishita | 8th of 15 night muhurtas | the midnight muhurta | Maha Shivaratri, Janmashtami |
| Brahma Muhurta | sunrise − 96 min → sunrise − 48 min | pre-dawn muhurta | Daily muhurat output |
| Abhijit | 8th of 15 day muhurtas | the midday muhurta | Daily muhurat output |
Why this matters at scale
Because the windows scale with day length, the same festival can fall on different days in Chennai and Kathmandu in the same year. An engine using fixed clock times cannot represent that, and will quietly serve the wrong date to one of them.
Evaluation
Nine evaluators, one interface
Each evaluator implements a single question. Adding a tradition later means adding an evaluator class and rule rows — never modifying the pipeline.
| Evaluator | Window tested | Example festival |
|---|---|---|
| SUNRISE_VYAPINI | Sunrise | Legacy rules; tithi containing sunrise |
| MADHYAHNA_VYAPINI | Midday (2/5–3/5 of day) | Ganesh Chaturthi |
| APARAHNA_VYAPINI | Afternoon (3/5–4/5 of day) | Vijayadashami |
| PRADOSHA_VYAPINI | Sunset + ~96 min | Diwali, Dhanteras |
| NISHITA_VYAPINI | Midnight muhurta | Maha Shivaratri, Janmashtami |
| MOONRISE | Local moonrise | Karwa Chauth, Sankashti |
| SANKRANTI | Solar ingress | Makar Sankranti |
| NAKSHATRA | Nakshatra × kala | Onam, Karthigai Deepam |
| COMBINED | AND of conditions | Tithi + nakshatra + kala |
The pipeline, per rule
- 1
Scope check
Month system, target month, paksha and adhika-maas policy. Fails fast with a reason.
- 2
Kala window
Build the required window for each candidate day (D and D+1 when the tithi spans days).
- 3
Vyapti
Compute overlap between the tithi interval and the kala interval, per day, in minutes.
- 4
Decision
TieBreakEngine resolves multi-day qualification, kshaya and vriddhi.
- 5
Pack override
The resolved rule pack may substitute a regional variant of the winning rule.
- 6
Trace
An EvaluationTrace is emitted whether the rule matched or not.
Worked example
Diwali 2026, start to finish
The clearest way to explain vyapti is to run it once, in full, with real numbers.
Vyapti — does Amavasya prevail during Pradosha?
Diwali 2026. Amavasya runs 08 Nov 11:28 → 09 Nov 13:01. Pradosha is ~96 minutes after sunset on each day.
Scope check. The rule targets Amavasya of Kartika in the amanta reckoning, Krishna paksha. Both 8 and 9 November are candidates because the Amavasya interval spans them.
Kala window. Pradosha is built for each day from that day's sunset — 17:31 to 19:07 on both.
Vyapti. Amavasya runs from 8 Nov 11:28 to 9 Nov 13:01. On 8 November it covers the full 96-minute window. On 9 November it has already ended four and a half hours before Pradosha opens — overlap zero.
Decision. Full overlap on exactly one day, so the tie-break engine never has to run. Selected: 8 November, confidence 100, label FULL_KALA_COVERAGE.
Trace. The reasoning above is emitted as structured JSON, not reconstructed afterwards. It is what /v1/festivals/explain returns.
Tie-breaking
When both days qualify, or neither does
Vriddhi and kshaya tithis are the hard part of any Hindu calendar engine. The resolution order below is applied identically to every rule.
- Step 1
Full overlap on exactly one day
If the target tithi completely covers the required kala on one day and not the other, that day wins outright. Confidence 100.
- Step 2
Both days partial → larger overlap
Compare overlap in minutes and take the greater. Confidence scales with the overlap fraction, between 60 and 99.
- Step 3
Equal, or both full → declared strategy
The rule’s own tie_break value decides: FIRST_DAY, SECOND_DAY, PAIRED_WITH_NEXT (e.g. Navami linked to Dashami), or a festival-specific strategy. Confidence 55.
- Step 4
Kshaya — tithi touches the kala on no day
Falls back to the day where the tithi exists at any point, per the Dharmasindhu default. The trace records that the fallback fired. Confidence 40.
Deterministic, not heuristic
Confidence
How certain the engine is, and why
Confidence is computed from the overlap fraction and which tie-break branch fired. It is not a model output and carries no uncertainty of its own.
| Score | Label | Meaning |
|---|---|---|
| 100 | FULL_KALA_COVERAGE | Tithi completely covers the required window |
| 60–99 | PARTIAL_OVERLAP | Scaled by the fraction of the window covered |
| 55 | TIE_BREAK_APPLIED | Both days qualified; the declared strategy decided |
| 40 | KSHAYA_FALLBACK | Tithi never met the window; classical fallback used |
A confidence of 40 does not mean the date is likely wrong. It means the tithi never met the required window on any candidate day, so the engine applied the classical fallback rather than a direct match. That is a legitimate answer — but you deserve to know it was a fallback.
This is why confidence is available on every plan including Free. Withholding it would mean serving a date that looks equally certain whether it came from a full overlap or a kshaya fallback, which defeats the purpose of the whole architecture.
Authority
Every rule cites its source
A derivation without a source is an assertion. Rules carry the shastric texts they are drawn from, and traces echo them.
"authority": ["Dharmasindhu", "Nirnaya Sindhu"],
"authority_notes":
"Lakshmi Puja follows Amavasya prevailing during Pradosha."Kala definitions follow Dharmasindhu and Nirnayasindhu conventions — the same conventions DrikPanchang uses, which is why Tier A comparison is meaningful rather than circular.
Where authorities genuinely differ, the difference is represented as a rule pack rather than resolved silently in code. A Smarta and a Vaishnava rule for Janmashtami are two rows, not one row with a branch in it.
Rule packs
Regional and sectarian variation
Traditions differ. The engine models that as data — a pack chain with well-defined precedence — instead of embedding one tradition as the default and treating the rest as exceptions.
| Precedence | Source | Example | Note |
|---|---|---|---|
| 1 | API override | ?pack=iskcon | Per-request, explicit |
| 2 | Account preference | api_keys.default_pack | Per-account setting |
| 3 | Location default | Delhi → NORTH_INDIA | Derived from region |
| 4 | Global default | NORTH_INDIA | Fallback |
Honest status
NORTH_INDIA pack is currently populated. Requesting another pack resolves to the North Indian rules today. Authoring Tamil, Gujarati and ISKCON packs is scheduled work, and we list it as a limitation rather than implying coverage we do not have.Versioning
Rules move through a lifecycle
A rule is never edited in place. Changes insert a new version and mark the previous row deprecated, so every past answer stays reproducible.
DRAFT
Nobody
Authored but invisible to the engine. Reviewable in the database.
EXPERIMENTAL
Opt-in only
Loadable via ?rules=experimental. Never served by default.
CURRENT
Production
The only status the engine loads under normal operation.
DEPRECATED
History
Superseded by a newer version. Retained for reproducibility.
ARCHIVED
History
Retired entirely. Past traces still resolve against it.
supersedes_rule_id
lineage
valid_from / valid_to
temporal scope
Rule integrity validator
pre-flight
Dependency graph
Multi-day observances are anchored series
Navratri and the Diwali cluster are not independent rules that happen to land next to each other. They are a graph with an anchor.
The anchor rule fixes day one — Ghatasthapana for Navratri, Lakshmi Puja for the Diwali cluster. Members are then placed relative to the anchor, each optionally refined by its own evaluator. Durga Ashtami still checks ashtami vyapti; Vijayadashami still uses aparahna.
A member can never land outside the anchor's maximum span. That constraint is what keeps an eight-day Navratri coherent in years where a tithi is lost, instead of producing a calendar with two Ashtamis or none.
The graph is data, evaluated topologically — anchors first, members second — not hardcoded sequencing.
SHARAD_NAVRATRI
├─ anchor Ghatasthapana
│ Ashwin Shukla Pratipada, sunrise-vyapini
├─ member Durga Ashtami +7 ashtami-vyapti
├─ member Maha Navami +8 navami-vyapti
└─ member Vijayadashami +9 APARAHNA_VYAPINI
constraint: member ∈ [anchor, anchor + max_span]Found a date you disagree with?
Send us the request and the value you expected. Accuracy reports are triaged ahead of everything else, and if you are right, the correction ships as a new rule version with the reasoning published.