Skip to content
Documentation · all sections

Muhurta Engine

panchang · v5.4.0 · MIT

Score any date against 13 stock occasions or your own rule, understand the scoring model and Vara × Tithi yogas, and ship pre-computed muhurta tables.

Scoring a date

scoreMuhurta scores one date for one occasion at one place. computeAuspiciousDatesInRange and computeAuspiciousDatesForYear run the same scoring over a span and give you the days back, highest score first.

import {
  scoreMuhurta, computeAuspiciousDatesInRange, computeAuspiciousDatesForYear, vivahRule,
} from 'panchang-ts';

const DELHI = { latitude: 28.6139, longitude: 77.2090 };
const opts = { timezone: 330 };

const r = scoreMuhurta(new Date('2026-05-12'), DELHI, vivahRule, opts);
console.log(r.score, r.passes);                            // 10 false
console.log(r.factors.filter(f => f.delta < 0).length);    // 3 — what cost the day points
console.log(r.factors.some(f => f.axis === 'exclusion'));  // false — hard-excluded?
console.log(r.factors[0].code, r.factors[0].axis, r.factors[0].delta);
// inauspicious_vara vara -15

const days = computeAuspiciousDatesInRange(
  vivahRule, new Date('2026-05-01'), new Date('2026-05-31'), DELHI, opts);
console.log(days.length, days[0].panchang.sun.riseLocal.slice(0, 10), days[0].score);
// 7 2026-05-07 80
console.log(days[0].panchang.angas.tithis[0].name);        // Krishna Panchami

console.log(computeAuspiciousDatesForYear(2027, vivahRule, DELHI, opts).length);  // 166

Every day starts at 50. A matching auspicious tithi, nakshatra, vara or yoga adds 10, and an inauspicious one subtracts 15. Each Vara × Tithi yoga does the same, 10 up or 15 down, and bhadra: 'penalize' subtracts 15 on a day with a Bhadra window. The special yogas (Amrit Siddhi, Sarvartha Siddhi, Ravi/Guru Pushya) add 5, and Jwalamukhi subtracts 10. So 12 May above is 50, less 15 each for the weekday, a Krakacha yoga and Bhadra, plus 5 for Sarvartha Siddhi: 10. A hard exclusion drops the score to 0. The final score is clamped to 0..100, and passes is true at 50 or above.

Each factor carries a code, an axis, a delta and, where the axis has one, an index.

scoreMuhurta works out only the parts of the day it scores against, so it costs less than a full getDailyPanchang. computeAuspiciousDatesInRange trims nothing: every day it returns carries its whole panchang for you to drill into. In Go, ComputeAuspiciousDatesInRangeContext and ComputeAuspiciousDatesForYearContext take a context.Context and stop between days when it is cancelled.

A listed day's date is the instant it was scored at: local midnight from the year call, the start's local time of day from the range call. Read it in the location's zone, or take the day from panchang.sun.riseLocal as the example does. The best day of 2027 above is dated 2027-09-16T18:30:00.000Z, which is midnight on 17 September in Delhi.

Both calls step one civil day of timezone at a time. Since 5.4 that holds across daylight-saving changes: a 2026 year for New York with includeFailures: true holds 365 days, 1 January to 31 December, each once. Before, both stepped a fixed 24 hours and the year call started from the offset in force on 1 July, so the same call began on 31 December 2025, skipped 8 March, listed 1 November twice and left out 31 December. Numeric offsets and zones without daylight saving, IST included, give the same days as before.

The year call and buildMuhurtaTable take any year from 1900 to 2100, at any offset; before 5.4, 1900 failed east of UTC and 2100 west of it. A year outside that range throws INVALID_DATE, and that now includes 0 to 99: 5.3 read 99 as 1999.

Rules — 13 stock occasions, or your own

Thirteen rules ship with the library: vivahRule, grihaPraveshRule, namakaranaRule, vidyarambhRule, vahanKharidiRule, annaprashanRule, mundanRule, upanayanamRule, karnavedhaRule, aksharabhyasamRule, seemanthamRule, shopOpeningRule and travelStartRule. Import them all at once as STOCK_MUHURTA_RULES. A rule is plain data, so you can write your own.

import { scoreMuhurta, type MuhurtaRule } from 'panchang-ts';

const myRule: MuhurtaRule = {
  occasion: 'launch_party',
  auspiciousVaras: [3, 4, 5],
  auspiciousNakshatras: [11, 12, 21],
  bhadra: 'penalize',        // 'ignore' | 'penalize' | 'exclude'
  excludeEkadashi: true,
  excludeAdhikaMasa: true,
};

const r = scoreMuhurta(new Date('2026-05-14'), DELHI, myRule, { timezone: 330 });
console.log(r.score, r.passes);                  // 65 true
console.log(r.factors.map(f => f.code).join(',')); // auspicious_vara,sarvartha_siddhi

In Go the mode is a types.BhadraMode — types.BhadraIgnore, types.BhadraPenalize or types.BhadraExclude — and Bhadra holds a pointer to one, so you assign the mode to a variable and take its address, as the Go tab does. A factor is a types.MuhurtaFactor carrying a types.MuhurtaFactorAxis, so a helper can be typed on one, and panchang.AllMuhurtaFactorAxes() returns the eight axes for an exhaustive switch.

Tithi and vara are scored jointly

The classical Vara × Tithi yogas (Siddha, Amrita, Dagdha, Visha, Hutasana, Krakacha, Samvartaka) apply to every rule. So a Rikta tithi landing on a Saturday is partly redeemed by Siddha yoga instead of being penalised outright. Set varaTithiYogas: false to go back to scoring each anga on its own, or call computeVaraTithiYogas(vara, tithi) to get the yogas for a pair directly.

Some pairs fire an auspicious and an inauspicious yoga at the same time. The sources disagree there, so both show up as separate factors and the deltas net out.

Handling Bhadra

bhadra defaults to 'ignore', and the stock rules set 'penalize'. 'exclude' vetoes every day whose span from sunrise to the next sunrise touches any Bhadra, which is usually too blunt. Vishti karana is 8 of the 60 half-tithis of each lunar month, so at Delhi it rules out 149 of the 365 days of 2026, across 20 different sunrise tithis. Shukla Ekadashi is among them, and the same sources list it as preferred for vivah. Read panchang.inauspicious.bhadra for the window and schedule around it instead. excludeBhadra: true still works as an alias for bhadra: 'exclude'.

Pre-computed table — build your own and cache it

Scoring a year of days runs the engine about 365 times. If your app asks the same question over and over, work the answer out once and ship the JSON. In Go, BuildMuhurtaTableContext is the same builder taking a context.Context, so a long build can be cancelled. The festival, eclipse and moon-phase tables use the same pattern.

import { buildMuhurtaTable, vivahRule } from 'panchang-ts';
import { writeFileSync } from 'node:fs';

const table = buildMuhurtaTable({
  rule: vivahRule,
  location: { latitude: 25.3176, longitude: 82.9739 },
  timezoneOffsetMinutes: 330,
  startYear: 2026,
  endYear: 2031,
  referenceLocation: 'Varanasi',   // a label stamped into table._meta
});
const json = JSON.stringify(table);
writeFileSync('muhurta-vivah.json', json);
console.log(json.length);   // 50036 — six years of vivah dates are ~50 KB
console.log(table._meta.occasion, table._meta.referenceLocation);  // vivah Varanasi

Read the file back through the panchang-ts/muhurta entry point. It has no astronomy code in it, so it adds ~1.6 KB to your bundle.

import {
  readMuhurtaForYear, readMuhurtaForDate, readMuhurtaYearRange,
  readMuhurtaOccasion, readBestMuhurtaDays,
} from 'panchang-ts/muhurta';

const table = JSON.parse(await (await fetch('/muhurta-vivah.json')).text());

console.log(readMuhurtaOccasion(table));    // vivah
console.log(readMuhurtaYearRange(table));   // { start: 2026, end: 2031 }
console.log(readMuhurtaForYear(table, 2026)!.length);   // 142 — passing days only
const d = readMuhurtaForDate(table, '2026-11-11')!;
console.log(d.date, d.score, d.passes, d.factors.length);   // 2026-11-11 70 true 8
console.log(readBestMuhurtaDays(table, 5).map(x => `${x.date}:${x.score}`).join(' '));
// 2026-03-20:100 2030-09-13:100 2026-04-29:95 2026-12-30:95 2027-01-20:95

Only days that pass the rule are stored by default. Pass includeFailures: true to keep every day with its score. A score depends on both the location and the rule, so a table built for Varanasi and vivah tells you nothing about another place or another occasion.

The file format is shared between the two languages. A table Go writes reads back through the TypeScript readers, and the other way round. Since 5.4 Go has the readers too, as package functions in panchang. ReadMuhurtaForDate takes a time.Time and ReadMuhurtaForDateKey the YYYY-MM-DD string, and ReadBestMuhurtaDays takes its limit explicitly where TypeScript defaults it to 10.

FunctionReturns
computePanchaka(siderealMoon)whether that sidereal Moon longitude, in degrees from 0 up to 360, falls in Panchaka: the last five nakshatras, from Dhanishtha’s 3rd pada (300°) to 360°. The value is used as given, not wrapped, so bring it into that range first
classifyPanchaka(onsetVaraIndex)the named Panchaka type, taken from the vara the spell began on, 0 = Sunday; an index outside 0 to 6 throws a RangeError
isPanchakaDosha(type)whether that type carries a dosha — false only for samanya
findPanchakaOnset(referenceUtc, getMoon)the instant the running spell began, or null when none was active
computeVaraTithiYogas(vara, tithi)the Vara × Tithi yogas firing for that pair; a vara outside 0 to 6 or a tithi outside 0 to 29 throws a RangeError