Methodology

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.

sunrisemiddaysunset
Brahma
Abhijit
Madhyahna
Aparahna
Pradosha

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.

WindowDefinitionBasisUsed by
MadhyahnaDay slice 2/5 → 3/5 of dinamanasunset − sunrise, divided in fifthsGanesh Chaturthi
AparahnaDay slice 3/5 → 4/5 of dinamanasunset − sunrise, divided in fifthsVijayadashami
Pradoshasunset → sunset + 2 × (ratrimana / 15)roughly 96 minutes after sunsetDiwali, Dhanteras
Nishita8th of 15 night muhurtasthe midnight muhurtaMaha Shivaratri, Janmashtami
Brahma Muhurtasunrise − 96 min → sunrise − 48 minpre-dawn muhurtaDaily muhurat output
Abhijit8th of 15 day muhurtasthe midday muhurtaDaily 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.

EvaluatorWindow testedExample festival
SUNRISE_VYAPINISunriseLegacy rules; tithi containing sunrise
MADHYAHNA_VYAPINIMidday (2/5–3/5 of day)Ganesh Chaturthi
APARAHNA_VYAPINIAfternoon (3/5–4/5 of day)Vijayadashami
PRADOSHA_VYAPINISunset + ~96 minDiwali, Dhanteras
NISHITA_VYAPINIMidnight muhurtaMaha Shivaratri, Janmashtami
MOONRISELocal moonriseKarwa Chauth, Sankashti
SANKRANTISolar ingressMakar Sankranti
NAKSHATRANakshatra × kalaOnam, Karthigai Deepam
COMBINEDAND of conditionsTithi + nakshatra + kala

The pipeline, per rule

  1. 1

    Scope check

    Month system, target month, paksha and adhika-maas policy. Fails fast with a reason.

  2. 2

    Kala window

    Build the required window for each candidate day (D and D+1 when the tithi spans days).

  3. 3

    Vyapti

    Compute overlap between the tithi interval and the kala interval, per day, in minutes.

  4. 4

    Decision

    TieBreakEngine resolves multi-day qualification, kshaya and vriddhi.

  5. 5

    Pack override

    The resolved rule pack may substitute a regional variant of the winning rule.

  6. 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.

2026-11-08overlap 96 min — selected
2026-11-09overlap 0 min
Tithi interval Required kala Overlap

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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

Nothing in this chain is probabilistic. The same inputs always produce the same day and the same confidence score. If a date changes between two releases, the cause is a rule version change — and the trace will name it.

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.

ScoreLabelMeaning
100FULL_KALA_COVERAGETithi completely covers the required window
60–99PARTIAL_OVERLAPScaled by the fraction of the window covered
55TIE_BREAK_APPLIEDBoth days qualified; the declared strategy decided
40KSHAYA_FALLBACKTithi 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 metadata, embedded in every trace
"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.

PrecedenceSourceExampleNote
1API override?pack=iskconPer-request, explicit
2Account preferenceapi_keys.default_packPer-account setting
3Location defaultDelhi → NORTH_INDIADerived from region
4Global defaultNORTH_INDIAFallback

Honest status

The pack mechanism is implemented and tested, but only the 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

Each new version points at the row it replaces, forming a chain you can walk backwards to the original rule.

valid_from / valid_to

temporal scope

A rule can be scoped to a date range, which is how a genuine historical change in practice is represented without invalidating older answers.

Rule integrity validator

pre-flight

Before any rule reaches the engine, a validator checks that the evaluator exists, the target month is real, the tithi is in range, and no two CURRENT rules collide. On failure the engine falls back to the last known-good snapshot and logs a critical error rather than serving a partially-loaded rule set.

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.

Series resolution — anchor, then members
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.