Skip to content
Documentation · all sections

Upgrading 4.x → 5

panchang · v5.4.0 · MIT

Real Date instants and their *Local companions, the grouped result object, the corrected Lahiri ayanamsa, measured ΔT, and the read*/compute* naming convention.

Three changes move numbers that 4.x produced, and one option is gone. Everything else is renaming, and every rename is listed below.

Published Dates are real instants

- result.sunrise.getTime()      // NOT when sunrise happened (off by the UTC offset)
- result.sunrise.getUTCHours()  // the documented 4.x idiom
+ result.sun.rise.getTime()     // correct epoch ms
+ result.sun.riseLocal          // "2025-01-14T07:09:44.172+05:30"

In 4.x every published Date was the true instant shifted by the UTC offset, and the docs told you to read it back with getUTC*. That worked only if you did nothing else with the value. JSON.stringify wrote a wrong instant labelled Z. Intl with a timeZone rendered 12:39 pm for an 07:09 am sunrise. Any comparison, diff, database write, date-fns call or Temporal call was off by the offset.

v5 fixes every Date in DailyPanchangResult and every TimePeriod: sun and moon events, all muhurtas and inauspicious periods, all four slot systems, the element startTime/endTime arrays, the eclipse contacts and the sutak window. Each one gains a *Local string beside it for display.

You hadYou now write
r.sun.rise.getUTCHours()r.sun.riseLocal.slice(11, 13)
`${h}:${m}` from getUTC*r.sun.riseLocal.slice(11, 16)
r.inauspicious.rahuKalam.start.getUTCHours()r.inauspicious.rahuKalam.startLocal.slice(11, 13)
r.angas.tithis[0].endTime for displayr.angas.tithis[0].endTimeLocal
a Date you derived yourselfformatInZone(d, r.timezone.offsetMinutes)

getInstantPanchang results carry no *Local fields. That call takes no timezone, so there is no zone to render a wall clock in.

Building those strings costs about 0.04 ms per daily panchang. A warm call still finishes in 0.17 ms, against 6.20 ms for the published 4.3.1.

The result object is grouped

DailyPanchangResult had roughly 50 flat top-level fields mixing five categories. v5 sorts them into seven groups. See the group table for the shape. This is a large break, and it ships in the same release as the Date change on purpose: migrating both at once is one pass over your read sites instead of two.

Every rename, in full:

4.xv5
sunrise / sunset / nextSunrisesun.rise / sun.set / sun.nextRise
sunriseLocal / sunsetLocal / nextSunriseLocalsun.riseLocal / sun.setLocal / sun.nextRiseLocal
dayDurationMinutes / nightDurationMinutessun.dayDurationMinutes / sun.nightDurationMinutes
dinamanaMinutes / ratrimanaMinutessun.dinamanaMinutes / sun.ratrimanaMinutes
siderealSunAtSunrisesun.siderealLongitude
suryaNakshatrasun.nakshatra
moonrise / moonsetmoon.rise / moon.set
moonriseLocal / moonsetLocalmoon.riseLocal / moon.setLocal
siderealMoonAtSunrisemoon.siderealLongitude
chandraRashimoon.rashi
tithis / nakshatras / yogas / karanas / varaangas.* (same names)
masa / chandramasa / samvatcalendar.* (same names)
abhijitMuhurta / brahmaMuhurta / vijayaMuhurta / godhuliMuhurta / nishitaMuhurtamuhurtas.abhijit / .brahma / .vijaya / .godhuli / .nishita
amritKala / madhyahna / pratahSandhya / sayahnaSandhyamuhurtas.* (same names)
doGhatiMuhurtamuhurtas.doGhati
rahuKalam / gulikaKalam / yamaganda / durMuhurta / varjyam / bhadra / gandaMula / panchaka / panchakaRahitainauspicious.* (same names)
choghadiya / horaperiods.choghadiya / periods.hora
gowriPanchangamperiods.gowri
eclipse.magnitude (disc area)eclipse.obscuration holds that same value. The new eclipse.magnitude is the diameter fraction catalogues publish, and is negative for a penumbral lunar eclipse.

These did not move: date, location, timezone, ayanamsa, specialYogas, anandadiYoga, festivals, eclipse, chandraBalam, tarabala.

For getInstantPanchang: tithi / nakshatra / yoga / karana / vara → angas.*; siderealSun → sun.siderealLongitude; siderealMoon → moon.siderealLongitude; suryaNakshatra → sun.nakshatra; chandraRashi → moon.rashi; chandramasa / samvat → calendar.*; panchaka / gandaMula → inauspicious.*.

Lahiri ayanamsa corrected by +38″

The old Lahiri constant sat 38 arcseconds behind the reference almanac's. The replacement was solved from the almanac's own published values across 1950–2050, which agree on it to within 0.01″. Every sidereal output moves with it:

OutputEffect
Nakshatra end-times~69 s later than 4.x (carries the ayanamsa once)
Yoga end-times~129 s later than 4.x (carries it twice)
Planetary longitudes, rashi, pada, lagna, divisionals, dashasshifted +0.0106°
Tithi / karana end-timesunchanged — Moon − Sun cancels the ayanamsa
Raman / KP / True Chitra / Thirukanithammoved by the same +38″; their offsets from Lahiri are preserved

Worst-case end-time drift against the reference almanac dropped from 131 s to 63 s. If you have snapshot tests or cached charts from 4.x, expect to re-pin them.

ΔT comes from measurement, so every published time moves ~6 s

4.x took ΔT (TT − UT) entirely from Espenak–Meeus, whose post-2005 branches are a 2006 extrapolation. By 2026 that reads about 5.9 s high. v5 takes ΔT from the leap-second chain for the years where ΔT has actually been measured, then resumes Espenak–Meeus beyond that, carrying the accrued offset. Against JPL Horizons the measured era now agrees to 0.005 s at every decade from 1980.

OutputEffect
Tithi / nakshatra / yoga / karana end-times~5.7 s later for 2025 dates
Sankranti and other transit instantssame shift — one uniform correction, not per-element
Sunrise / sunset / moonrise / moonsetbarely moved
Dates a panchang element is filed underunchanged except where a transit sits within seconds of sunrise

Instant-mode vara was wrong after ~19:00

4.x getInstantPanchang looked for sunrise by searching forward from date − 12 h. For an evening instant that found tomorrow's sunrise and rolled the weekday back a day. Any query after roughly 7 pm returned the previous vara, and with it the wrong Rahu Kalam, Gulika Kalam, Yamaganda, Choghadiya, Hora and special yogas. getDailyPanchang was never affected. If you cached instant-mode results from 4.x for evening timestamps, throw them away.

precision removed

precision: 'standard' | 'high' and the Precision type are gone. Element transitions are now solved by secant iteration, which converges on the root, so the option has nothing left to select. Delete it from your options object. Leaving it in is a type error, not a silent no-op.

Sunrise, moonrise and moonset have one value per day

An event now has a single timestamp for a location and a day, whichever call asks for it. In 4.x you could get a different sunrise depending on which instant the caller searched from. Sunrise values shift by up to 108 ms against 4.x, and published moon.rise / moon.set by up to 182 ms.

read* for tables, compute* for the engine

4.x had getFestivalsForYear, which reads a pre-built table, sitting next to getFestivalsInRange, which runs the engine. The names looked alike and did different things. v5 uses one convention across all four families:

4.xv5What it does
getFestivalsForYear / ForDate / YearRangereadFestivalsForYear / …reads a table
getEclipsesForYear / ForDate / YearRangereadEclipsesForYear / …reads a table
getMoonPhasesForYear / ForDate / YearRangereadMoonPhasesForYear / …reads a table
—readMuhurtaForYear / ForDate / YearRange / readBestMuhurtaDaysnew — reads a table
getFestivalsInRangecomputeFestivalsInRangeruns the engine
getEclipsesInRangecomputeEclipsesInRangeruns the engine
getMoonPhasesInRangecomputeMoonPhasesInRangeruns the engine
findAuspiciousDatescomputeAuspiciousDatesInRangeruns the engine
getEkadashiDatesForYearcomputeEkadashiDatesForYearruns the engine
getSankrantisForYearcomputeSankrantisForYearruns the engine

New single-year entry points, the shape most callers reach for first:

computeFestivalsForYear(2027, location, { timezone: 330 });
computeEclipsesForYear(2027, location, { timezone: 330 });
computeMoonPhasesForYear(2027, { timezone: 330 });
computeAuspiciousDatesForYear(2027, vivahRule, location, { timezone: 330 });

result.timezone is now an object

- result.timezone            // 330
+ result.timezone            // { offsetMinutes: 330, zone: 'Asia/Kolkata' }
+ result.timezone.offsetMinutes

zone is present only when you passed a zone name.

The DST limit: the offset is resolved once per call from a reference date, so a Hindu day that contains a DST transition is computed at one offset throughout. That is correct on almost every day. On the one or two transition days a year, times after the jump are shifted by the size of the jump.

One rule for “not applicable”: always present, null or []

- if ('chandraBalam' in r) …        // 4.x: field absent without janmaRashi
- r.chandraBalam!.house             // and the `!` was mandatory
+ if (r.chandraBalam !== null) …    // v5: always present, null when unasked
+ r.chandraBalam?.house

Only chandraBalam and tarabala changed behaviour. Everything else already followed the rule. toBeUndefined()-style test assertions against them become toBeNull().

Smaller changes

  • suryaNakshatra typed correctly. It is now sun.nakshatra. Its index has always been 0..26, but its 4.x type said RashiInfo (0..11). The runtime value is unchanged. The type is now NakshatraIndexInfo, and TypeScript will point at any misuse.
  • _debug removed. It was declared in the published type and never written, so it was always undefined.
  • Alias fields are documented rather than removed. sun.dinamanaMinutes / sun.dayDurationMinutes (and the night pair) are the same numbers under classical and English names, and the types now say so. calendar.masa is documented as the solar month and calendar.chandramasa as the lunar one.
  • Built tables are smaller. A 10-year festival table is roughly a quarter the size of the 4.x layout, about 97 KB bilingual for one city. It also parses faster and holds less memory on device. Tables you already cached still read, because the reader detects the format.

New in v5

  • EclipseInfo / EclipseSubtype are now exported.
  • festivals[].key — a stable, language-independent festival id. Match on this, never on name.
  • bhadra.locationName — a localized display name beside the machine-readable bhadra.location.
  • MuhurtaScore.factors — structured scoring inputs alongside the English reasons.
  • BirthChart.byPlanet — the nine placements keyed by graha.
  • eclipse.description is now localized under language: 'hi'.
  • sections on getDailyPanchang — ask for a narrower, cheaper call. See Performance.
  • buildMuhurtaTable and the panchang-ts/muhurta subpath. See Muhurta Engine.