The full story
How it works. No secrets.
This page shows what the site does, how we tested it, and where it can be wrong. All the code you see here is the real code that runs.
The big idea
A tithi comes from one number: the gap between the moon and the sun, as seen from Earth. Cut that gap into steps of 12°, and each step is one tithi.
tithi = floor((moon − sun) ÷ 12°) + 1
Here is the trick that makes this site work. A tithi starts at the same moment everywhere on Earth. Only the date depends on where you are, because each place has its own sunrise.
So we work out every tithi once, ahead of time, and keep the answers in one small file. Your device reads that file and works out sunrise for your place.
- NASA JPL dataExact sun and moon positions
- Python scriptFinds every tithi change, once
- A 367 KB table92,839 moments, 1899 to 2150
- Your deviceRust engine, run as WebAssembly
Find the sun and the moon
Positions come from DE440, made by NASA's Jet Propulsion Laboratory. It is the kind of data used to fly spacecraft. We read it with Skyfield, a free Python library. We use the moon and sun as they appear from the centre of the Earth.
def longitudes(self, t):
"""Apparent geocentric longitudes (degrees, true ecliptic and equinox of date)."""
e = self.earth.at(t)
_, sun, _ = e.observe(self.sun).apparent().frame_latlon(ecliptic_frame)
_, moon, _ = e.observe(self.moon).apparent().frame_latlon(ecliptic_frame)
return sun.degrees, moon.degrees
def tithi_index(self, t):
"""0..29: 0 is Shukla Pratipada, 14 Purnima, 29 Amavasya."""
sun, moon = self.longitudes(t)
return (((moon - sun) % 360.0) // 12.0).astype(int)Find every tithi change
The script steps through 250 years, 12 hours at a time. The shortest tithi is about 20 hours, so it can not miss one. When the tithi changes between two steps, it closes in on the exact second.
def index(t):
return sky.tithi_index(t)
index.step_days = 0.5 # the shortest tithi is ~20 h, so 12 h steps cannot skip one
# de440s covers 1849-12-26 to 2150-01-21; start early enough that 1900 has a
# preceding new moon (needed for its lunar month).
edges = [ts.utc(1899, 11, 1)] + [ts.utc(y, 1, 1) for y in range(1910, 2150, 10)] + [ts.utc(2150, 1, 15)]
tts, values = [], []
for a, b in zip(edges, edges[1:]):
t, v = find_discrete(a, b, index)
tts.append(t.tt)
values.append(v)That gives 92,839 moments. The script stops if the tithis ever come out of order, and they never do. Each one lasts between 20 and 27 hours, just as it should. Each moment is saved as the seconds since the one before, so the whole table is only 367 KB.
Look up your moment
Your birth date, time and place become one exact moment. The engine finds where that moment falls in the table. Halving the list again and again takes about 17 steps.
/// The tithi in effect at `unix`.
pub fn tithi_at(&self, unix: i64) -> Option<TithiSpan> {
let i = self.times.partition_point(|&t| t <= unix);
(i > 0 && i < self.times.len()).then(|| self.span(i - 1))
}Work out sunrise
A panchang day starts at sunrise, so we need sunrise for your place. We use a standard formula from Jean Meeus's book Astronomical Algorithms. Sunrise here means the moment the top edge of the sun appears, with the bending of light by the air counted in. That is what Drik Panchang appears to use too.
pub fn sunrise_near(lat_deg: f64, lon_deg: f64, guess: f64) -> Option<f64> {
let (sin_lat, cos_lat) = lat_deg.to_radians().sin_cos();
let sin_h0 = SUNRISE_ALTITUDE_DEG.to_radians().sin();
let mut t = guess;
for _ in 0..10 {
let (ra, dec, gast) = sun_equatorial(t / 86400.0 + 2440587.5);
let cos_h0 = (sin_h0 - sin_lat * dec.sin()) / (cos_lat * dec.cos());
if !(-1.0..=1.0).contains(&cos_h0) {
return None;
}
let rising_hour_angle = -cos_h0.acos().to_degrees();
let hour_angle = gast + lon_deg - ra.to_degrees();
// The Sun's hour angle advances about 360 degrees a day.
let step = wrap180(rising_hour_angle - hour_angle) / 360.0 * 86400.0;
t += step;
if step.abs() < 0.1 {
break;
}
}
Some(t)
}Give each tithi a date
A tithi gets the date whose sunrise falls inside it. Usually that is one date. Two cases need care:
Kshaya: no sunrise inside
The tithi is so short that it starts and ends between two sunrises. We list it on the day it falls in.Vriddhi: two sunrises inside
The tithi is so long that it covers two sunrises. We list the first day and show the second.
/// The civil date an occurrence of a tithi is observed on: the day whose
/// sunrise falls inside it.
pub fn assign_date(table: &Table, place: &Place, span: TithiSpan) -> Result<Occurrence, Error> {
let first = place.local(span.start).date_naive();
let mut inside = Vec::with_capacity(2);
for offset in 0..3 {
let day = first + Days::new(offset);
let sr = sunrise(table, place, day)?;
if sr >= span.end {
break;
}
if sr >= span.start {
inside.push(day);
}
}
let (date, kind) = match inside.as_slice() {
[day] => (*day, DayKind::Normal),
[day, second, ..] => (*day, DayKind::Vriddhi(*second)),
[] => {
let day = if sunrise(table, place, first)? <= span.start { first } else { first - Days::new(1) };
(day, DayKind::Kshaya)
}
};
Ok(Occurrence { span, date, kind })
}Name the moon month
A moon month runs from one new moon to the next. Its name comes from the zodiac sign the sun is in at the start, using the Lahiri ayanamsa, India's official standard. If the sun is still in the same sign at the next new moon, that month is an extra one, called adhika.
/// The amanta month containing `unix`: named after the sidereal sign the Sun
/// is in at the new moon that starts it, and adhika when the Sun is still in
/// that sign at the next new moon (no sankranti in between).
pub fn masa_at(&self, unix: i64) -> Option<Masa> {
let i = self.times.partition_point(|&t| t <= unix).checked_sub(1)?;
let k = i.checked_sub(self.first_new_moon)? / 30;
let this = *self.new_moon_rashi.get(k)?;
let next = *self.new_moon_rashi.get(k + 1)?;
Some(Masa { index: (this as usize + 1) % 12, adhika: this == next })
}How accurate is it?
We did not only check our work against the data we used to make it. We also checked it against a completely separate astronomy system, and against published calendars.
| What we checked | Against | Result |
|---|---|---|
| Tithi at 3,019 moments, half of them within 10 minutes of a change | JPL DE440, read directly | All match |
| 5,856 sunrises in 8 cities, 1900 to 2149 | JPL DE440, read directly | Worst gap: 2.7 seconds |
| All 92,839 tithi changes | Swiss Ephemeris, a separate astronomy system | Half are within 1.7 seconds. Up to 2026, the worst is 6.5 seconds. The worst of all, near 2150, is 31.7 seconds |
| Tithi at sunrise in New Delhi, every day for 250 years (91,311 days) | Swiss Ephemeris | 8 days differ. All 8 are within 18 seconds of a tithi change |
| All 3,095 moon month names | Swiss Ephemeris, with Lahiri ayanamsa | No differences |
| Sunrise and the end of Purnima on 26 October 2026, New Delhi | Drik Panchang, one day checked by hand | Sunrise 06:28:47 against 06:29. Purnima ends 09:41:49 against 09:41 |
| The extra months of 2012, 2015, 2018, 2020, 2023 and 2026 | Published calendars | All match |
| Ugadi dates, 2023 to 2026 | Published dates | All match, including 2026, when the tithi covers no sunrise |
The Swiss Ephemeris checks used its built-in Moshier model. The test suite and the check scripts are part of the project, so anyone with the code can run them again.
Why not a simple formula?
We tried one. A short formula for the moon puts a tithi change about 7 minutes off, and sometimes 36 minutes off. That is enough to name the wrong tithi on about 2 days a year. A good library is far better: usually about 4 seconds off, and under 2 minutes at worst. Exact data is better still, so we use that.
The middle bar is worth a look. Choosing the wrong meaning of "sunrise" changes more days than a good library's small errors. Rules matter as much as maths.
Where it can be wrong
No tool is perfect. Here is what to watch for.
Very close calls
If a tithi changes within seconds of sunrise, any two good systems can disagree. This is rare: 8 days in 250 years in our test.The far future
The Earth's spin slowly changes in ways nobody can predict exactly. We include the expected drift, about 27 seconds by 2100, but times that far ahead can be off by a minute or more.Old birth times
Mumbai kept its own Bombay Time until 1955, and Kolkata kept Calcutta Time until 1948. Our time data treats all of India as one zone, so an old time from those cities can be up to 39 minutes off. Check how the time was written down.Other traditions
Older panchangs use different maths, and many festivals use their own rules, such as the tithi at noon or at night. Your family panchang can be a day apart, and it is not wrong.Near the poles
In some places the sun does not rise for days at a time. There is no sunrise to use, so the site tells you and stops.
Choices we made
A panchang involves choices, not only maths. Here are ours, so you can see them.
Your tithi
The tithi at your exact moment of birth, even if you were born before sunrise. We also show the tithi that names the panchang day.Your moon birthday
The same tithi in the same moon month each year. If that month has an extra copy that year, we use the regular one.Month names
We count months from new moon to new moon (amanta), and also show the name used in much of North India (purnimanta).
How the site runs
There is no server doing the work. The engine is written in Rust and compiled to WebAssembly, so it runs inside your browser. Your birth details are never sent anywhere. When you share a result, the details sit after the # in the link, and browsers do not send that part to servers. The site then removes them from the address as soon as the link opens.
We use Google Analytics and the Meta Pixel to count visits and measure our ads. They see which pages are opened, but we keep your birth details out of their reach. You can decline them on the finder page or with Cookie settings in the footer.
The site is built with Next.js and hosted on Vercel as plain files. The moon is drawn live with WebGL, and its light comes from the real angle between the sun and the moon.
Built using Claude Opus 5.5 by Vishwajeet Sharma.
Find my moon birthdayOpen for everyone
All the code is on GitHub. Star it, fork it, or help make it better.