tithi_engine 4.4.0 copy "tithi_engine: ^4.4.0" to clipboard
tithi_engine: ^4.4.0 copied to clipboard

Accurate Vedic tithi, lunar month, and festival date calculator for any city worldwide. Pure Dart, no dependencies, 200-year accuracy (1900–2100).

tithi_engine #

CI pub package License

A pure Dart library for Hindu lunar calendar (tithi/panchang) calculations. Computes accurate tithis, lunar months, and festival dates for any city worldwide.

Features #

  • Tithi calculation — date → tithi number, name, paksha, lunar month
  • Festival dates — muhurta-accurate (nishita, madhyahna, pradosh rules)
  • Month resolution — moment-based adhika/kshaya detection, Purnimant & Amant systems
  • Date finding — tithi → Gregorian date in any year
  • 230 cities — per-city correction tables verified against JPL Swiss Ephemeris (.se1)
  • Sunrise convention — upper-limb "first ray" (default) or centre-of-disc "half disk visible", both Swiss-corrected
  • Pure Dart — no dependencies, works in Flutter, server, CLI, and web (WASM)
  • 200-year accuracy — exhaustively validated 1900–2100 (33.77M city-days, 14/14 compute points 🟢)

Installation #

dependencies:
  tithi_engine: ^4.4.0

Quick Start #

import 'package:tithi_engine/tithi_engine.dart';
import 'package:tithi_engine/data/all.dart'; // city correction tables

// City data is REQUIRED in the constructor — pass a pack's registrar.
// data/all.dart links all cities; a region pack (e.g. data/india.dart → registerIndia)
// links only that region so the tree-shaker drops the rest.
final panchang = Panchang([registerAllCities]);

// Date → Tithi (sunrise tithi of the panchang day; for display/observance)
final info = panchang.tithiOnDate(DateTime.utc(2026, 2, 15), City.ujjain);
print(info.displayName); // "Phalguna Krishna Trayodashi"

// Exact moment → Tithi (birth-time precision). Pass a true UTC instant plus the
// DST-aware offset that was in effect (the app resolves the offset, e.g. via
// package:timezone). The offset is used only to pick the civil day's data.
final cdt = const Duration(hours: -5);
final birthUtc = DateTime.utc(2006, 5, 30, 20, 0).subtract(cdt); // 8 PM CDT → UTC
final birthTithi = panchang.tithiAtInstant(birthUtc, 'Austin', offset: cdt);
print(birthTithi.displayName);

// Festival date
final shivaratri = panchang.dateFor(
  festivals.firstWhere((f) => f.name == 'Maha Shivaratri'), 2026, City.ujjain);
print('Maha Shivaratri 2026: ${shivaratri?.date}');

// Tithi → Date
final date = panchang.getDate(
  LunarMonth.bhadrapada, Paksha.krishna, 8, 2026, City.seattle);
print('Janmashtami 2026 Seattle: $date');

There is no zero-arg Panchang(). The constructor requires a list of data-pack registrars, so you can't accidentally run with no city data. Choosing which packs to import is also what lets the tree-shaker drop unused cities (an India-only consumer links ~30 cities, not 230). Registrars are idempotent, so constructing repeatedly is cheap.

Data packs #

Import Pass to constructor Links
package:tithi_engine/data/all.dart Panchang([registerAllCities]) all 230 cities
package:tithi_engine/data/india.dart Panchang([registerIndia]) India (30 cities)
package:tithi_engine/data/all_center.dart Panchang([registerAllCities, registerAllCitiesCenterDisc], convention: SunriseConvention.centerDisc) centerDisc tables for all 230 cities (opt-in)

Combine packs: Panchang([registerIndia, registerEurope]).

API #

Panchang is the single entry point. Value types (TithiInfo, TithiSegment, LunarMonth, MonthSystem, Paksha, SunriseConvention, FestivalDef, MuhurtaRule, festivals, FestivalDate, City, CityLocation, Location, LocationSource) are exported; the engine internals are not.

FestivalDate carries month (actual LunarMonth of the occurrence) and isAdhika, computed from tithiOnDate at the occurrence date — so recurring festivals show the correct month (not the definition's placeholder).

The time-aware API is UTC-instant based: you pass true UTC instants and, where a civil day matters, the DST-aware offset in effect (the engine does no timezone resolution — resolve the offset yourself, e.g. via package:timezone).

New in 4.4.0: typed value types Tithi (Tithi.shukla(8), Tithi.krishna(11), Tithi.ofNumber(23); .number/.name own the Purnima/Amavasya rule) and City (City(name, {region}), City.isSupported, resolveCityName). New typed findDate/findDates take a Tithi. The legacy getDate/getDates, findNext (primitive form), tithiNames, supportedCities, CityLocation, getLocationForCity, and FestivalDate.festival are deprecated (removed in 5.0, where methods take City/Tithi).

Method Description
Panchang(data, {system, convention}) Construct with city-data registrars (data required); convention selects the sunrise definition (default upperLimb)
panchang.tithiOnDate(date, city) Sunrise tithi of the panchang day (display/observance)
panchang.tithiAtInstant(utcInstant, city, {offset}) Tithi at an exact UTC moment (birth-time)
panchang.tithiSegments(windowStartUtc, windowEndUtc, city, {offset}) Every tithi segment in a UTC window (N transitions → N+1 segments)
panchang.findDate(month, Tithi, year, city) Tithi spec → Gregorian date (typed; preferred)
panchang.findDates(month, Tithi, year, city) Tithi spec → all dates, adhika-aware (typed; preferred)
panchang.getDate(...) / getDates(...) Deprecated (5.0): use findDate/findDates with a Tithi
panchang.dateFor(festival, year, city) Festival → date with muhurta rules
panchang.recurringDates(festival, year, city) Recurring festival → all occurrences in the year
panchang.findNext(month, paksha, tithi, city) Next occurrence from today
panchang.at(location) Bind to a Location (city name or raw lat/lng) → PanchangAt (same methods, no city arg)
panchang.sunrise(date, city) / sunset(date, city) Sunrise/sunset UTC DateTime (Meeus, ~1 min; no per-city correction)
TithiInfo.fromStored(...) Render a saved tithi (with optional Purnimant↔Amant display conversion)

City names #

Every method takes a city name. Resolution is case- and space-insensitive and accepts the qualified "City, Region" form, so all of these resolve to the same city:

getLocationForCity('New York');      // canonical
getLocationForCity('new york');      // case-insensitive
getLocationForCity('New York, NY');  // qualified form (as City.qualifiedName emits)

The canonical identity is the (city, region) pair: the bare name maps to the primary city for that name, and the qualified "City, Region" form selects a specific one when several share a name (e.g. a future 'Vancouver, WA' vs 'Vancouver, BC'). There is no fuzzy/region-stripping match — a wrong region ('Vancouver, WA' when only BC exists) is treated as unknown.

An unsupported city throws ArgumentError — the engine never silently substitutes another location, because a wrong location produces wrong sunrise-based tithis and festival dates. To check without throwing, use resolveCityName(name) (returns null if unsupported) or inspect supportedCities. Need a city added? Open an issue: https://github.com/misrilibrary/tithi-engine-dart/issues.

By coordinates #

Have raw lat/lng (a GPS fix, a map pin) instead of a name? Bind a Location with Panchang.at(...):

final panchang = Panchang([registerAllCities]);

// A point that rounds into a supported city's 0.1° cell reuses that city
// wholesale (Swiss-corrected). offset is optional here.
final here = panchang.at(Location.at(47.61, -122.33));
final info = here.tithiOnDate(DateTime.utc(2026, 2, 15));
here.source; // LocationSource.cityCorrected

// A point outside every city's cell is Meeus-only (~99.97%) and REQUIRES the
// DST-aware UTC offset (used to frame the civil day).
final remote = panchang.at(Location.at(0.0, -140.0, offset: Duration(hours: -9)));
remote.source; // LocationSource.meeusRaw

// Location.city(name) is the named-city form of the same binding.
panchang.at(Location.city('Seattle')).tithiOnDate(DateTime.utc(2026, 2, 15));

PanchangAt exposes the same read methods as Panchang minus the city argument (tithiOnDate, tithiAtInstant, tithiSegments, getDate(s), findNext, dateFor, recurringDates). Cities are stored at ~0.1° (~11 km), so any point within a city's cell is treated as that city.

Recommendation: prefer Location.city(name) (or a coordinate that lands in a supported city's cell) when you can. A named city carries Swiss‑Ephemeris correction tables — guaranteed accuracy. Off‑grid coordinates are Meeus‑only (~99.97% on day‑assignment): excellent, but the rare knife‑edge days a city's correction would fix are not covered. Use raw coordinates only when no nearby supported city exists.

Sunrise convention #

The instant of "sunrise" — which the whole sunrise-tithi/observance model hinges on — has two common definitions. Pick one at construction (default is unchanged):

import 'package:tithi_engine/data/all.dart';
import 'package:tithi_engine/data/all_center.dart';

// Default: upper limb at the horizon ("first ray"), −0.833° (34′ refraction +
// 16′ semidiameter). Omitting `convention` reproduces the original behavior.
final p = Panchang([registerAllCities]);

// Centre of the disc on the horizon ("half disk visible"), −0.5667° (refraction
// only). Sunrise lands ~1–4 min later (sunset earlier), latitude-dependent.
// REQUIRES the centerDisc data pack for Swiss accuracy.
final pc = Panchang([registerAllCities, registerAllCitiesCenterDisc],
    convention: SunriseConvention.centerDisc);

Both conventions are Swiss-corrected when their data pack is registered (0 mismatches over 230 cities × 73,414 days). The convention threads through tithiOnDate, tithiAtInstant, tithiSegments, month boundaries, festival muhurtas, and sunrise/sunset. If you register centerDisc but want the default behavior, just construct without the convention argument — the two table sets are independent and the upper-limb path is untouched.

Accuracy #

Source of truth: JPL Swiss Ephemeris (.se1), exhaustively verified 1900–2100, all 230 cities, both conventions (upper limb / centre of disc), both month systems.

Metric Value
Day-tithi vs .se1 (tithiOnDate) 0 mismatches (33.77M city-days: 230 cities × 73,414 days × 2 conventions)
Transition instants vs .se1 0 >30 s (all 74,582 transitions, global correction applied)
Sunrise/sunset vs .se1 0 >60 s, max 15.7 s (33.77M, both conventions)
Lunar month + adhika vs .se1 0 mismatches (all 2,486 new moons, both systems)
getDates (tithi→date, reverse + forward) 0 mismatches (16.9M, kshaya/vriddhi-aware)
tithiAtInstant (birth-time) 0 whole-day-shift days; 0.0145% near-transition residual ≤30 s
Festival dates vs Drik Panchang 22/22 (2025–2026)
Test coverage 586 tests

All 14 compute points are 🟢 at the minute-level accuracy bar. The only residual is sub-minute (≤30 s near-transition slivers, provably below display resolution). Full benchmark: ACCURACY_BENCHMARK.md.

Cross-Platform #

This is the Dart implementation of tithi-engine (Java). Both compute identical tithi/panchang results, validated against the Swiss Ephemeris.

The two packages version independently — each version string is a semver compatibility contract for that ecosystem. What stays locked in step is the astronomy engine revision (the correctness-critical part) and the feature parity tracked below.

  • Engine revision: r3 — VSOP87 Sun + Meeus Moon in Terrestrial Time (Espenak–Meeus ΔT), nutation cancelled in the Moon–Sun elongation, global tithi-transition correction (16,266 delta-encoded entries vs JPL .se1), iterative sunrise/sunset (3-iter refinement, latitude-robust). Per-city correction tables regenerated against .se1. Dart 4.3.0+ ⟷ Java (pending). Verified: 0 mismatches over 230 cities × 73,414 days × 2 conventions (33.77M city-days).
Capability Dart (tithi_engine) Java (tithi-engine)
Astronomy engine rev r2 (VSOP87/TT) 2.1.0+ 1.1.0+
230 cities, Swiss-verified tables 2.1.0+ 1.1.0+
City display-name disambiguation (region / qualifiedName / displayName) 2.2.0+ 2.0.0+
Time-aware API (tithiOnDate / tithiAtInstant / tithiSegments) 3.0.0+ 2.0.0+
recurringDates / findNext / TithiInfo.fromStored 2.0.0+ 2.0.0+
Strict city resolution (resolveCityName, fail-fast on unknown, "City, Region" form) 4.0.0+ 3.0.0+
Coordinate input (Location / Panchang.at, 0.1° cell reuse) 4.0.0+ 3.0.0+
Sunrise / sunset (sunrise / sunset, Meeus) 4.1.0+ 3.1.0+
Sunrise-convention toggle (SunriseConvention, centerDisc Swiss tables) 4.2.0+ (pending)
Engine rev r3: global transition correction + iterative sunrise (JPL .se1 parity) 4.3.0+ (pending)
FestivalDate.month actual month (not def placeholder) for recurring festivals 4.3.1+ (pending)
Tithi + City value types, typed findDate/findDates, getTithiName/resolveCityName exports (legacy surface deprecated) 4.4.0+ (pending)

API generation: Dart 3.x and Java 2.x are the same (UTC-instant) API generation. Dart 4.0.0 ⟷ Java 3.0.0 add strict city resolution (unknown cities now throw — a behavior break) and coordinate input (Location / Panchang.at); Dart 4.1.0 ⟷ Java 3.1.0 add sunrise/sunset. Dart 4.2.0 adds the sunrise-convention toggle (upper-limb default / centerDisc half-disk, Swiss-corrected). Dart 4.3.0 upgrades to engine revision r3: JPL .se1 parity via global transition correction + iterative sunrise (a data+precision improvement — the public API is unchanged from 4.2.0). Dart 4.3.1 adds FestivalDate.month (actual month of occurrence for recurring festivals). Java counterparts for 4.2+ are pending. The version numbers differ only because each follows its own ecosystem's semver.

License #

Licensed under the Apache License 2.0.

1
likes
160
points
1.59k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Accurate Vedic tithi, lunar month, and festival date calculator for any city worldwide. Pure Dart, no dependencies, 200-year accuracy (1900–2100).

Repository (GitHub)
View/report issues
Contributing

Topics

#astronomy #calendar #hindu-calendar #tithi

License

Apache-2.0 (license)

More

Packages that depend on tithi_engine