Skip to content
Documentation · all sections

Upgrading from 4.x

panchang-ts · v5.0.0 · MIT

Every breaking change in v5: real Date instants, the grouped result, 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. The rest is renames — mechanical, and listed in full below.

Published Dates are real instants — the flagship change

- 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"

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

v5 applies the fix to 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 sutak window. Each gains a *Local companion 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.

Cost: rendering the strings adds ~0.04 ms per daily panchang — invisible on a cold call and ~20% of a fully cached warm one. The release still warms to 0.17 ms, against published 4.3.1's 6.20 ms.

The result object is grouped

DailyPanchangResult had ~50 flat top-level fields mixing five categories. v5 sorts them into seven groups — see the group table. This is a large break, and it lands in the same release as the Date change on purpose: migrating both at once is one pass over your read sites, not 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 — same value; the new eclipse.magnitude is the diameter fraction catalogues publish, negative for a penumbral lunar eclipse

Unmoved: date, location, timezone, ayanamsa, specialYogas, anandadiYoga, festivals, eclipse, chandraBalam, tarabala.

For getInstantPanchang: tithi / nakshatra / yoga / karana / varaangas.*; siderealSunsun.siderealLongitude; siderealMoon moon.siderealLongitude; suryaNakshatra sun.nakshatra; chandraRashimoon.rashi; chandramasa / samvatcalendar.*; panchaka / gandaMulainauspicious.*.

Lahiri ayanamsa corrected by +38″

The library's Lahiri constant sat 38 arcseconds behind DrikPanchang's. The replacement was solved from Drik's own published values across 1950–2050, which agree on it to within 0.01″ — a century-wide baseline, so the precession polynomial is pinned too. 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 vs Drik dropped from 131 s to 60 s. If you have snapshot tests or cached charts from 4.x, expect them to need re-pinning.

ΔT now uses 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 it reads about 5.9 s high. v5 takes ΔT from the leap-second chain wherever ΔT has actually been measured, and resumes Espenak–Meeus beyond it 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 located sunrise by searching forward from date − 12 h; for an evening instant it 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, discard them.

precision removed

precision: 'standard' | 'high' and the Precision type no longer exist. Element transitions are now solved by secant iteration, which converges to the root, so there is nothing left for the option to select. Removing it from your options object is the whole migration; leaving it in is a type error, not a silent no-op.

Sunrise, moonrise and moonset are single-valued per location-day

Solar and lunar rise/set events resolve through canonical per-location-day caches, so an event has one timestamp no matter which caller asks. Sunrise values shift by ≤108 ms vs 4.x; published moon.rise / moon.set by ≤182 ms. This removes an inconsistency: 4.x returned a different sunrise depending on which instant the caller happened to search from.

read* for tables, compute* for the engine

4.x had getFestivalsForYear (reads a pre-built table) beside getFestivalsInRange (runs the engine) — near-identical names, completely different semantics. v5 settles 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, stated explicitly: the offset is resolved once per call from a reference date, so a Hindu day containing a DST transition is computed at a single offset throughout. Correct for almost every day; on the one or two transition days a year, times after the jump are shifted by its size.

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. 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 written nowhere — always undefined.
  • Alias fields documented rather than removed. sun.dinamanaMinutes / sun.dayDurationMinutes (and the night pair) are the same numbers under classical and English names; the types now say so. calendar.masa is documented as the solar month and calendar.chandramasa the lunar one.
  • Built tables are dictionary-encoded. A 10-year festival table drops from 315 KB to 89.9 KB; the win is parse time and resident memory on Hermes. Tables you already cached still read — the reader detects the format.

Additive, but worth knowing

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