krishna@
10 min read#backend#web

Two calendars, 748 correct answers, and one product

Nepal's legal calendar has no conversion formula, and its almanac has no single right value — two astronomical models, seventeen ayanamshas, eleven house systems. What that does to how you build.

share
Layered mountain ridges silhouetted beneath a dense field of stars

the number on your certificate is not the number in your database

Ask someone in Nepal for their date of birth and you will usually get a number that no date library on earth can parse.

It will be a Bikram Sambat date — the calendar this country actually runs on. It is on your citizenship certificate, your school marksheet, the deadline printed on a government form, the day your family holds a shraddha. It is the legal calendar, the social calendar, and the one people think in. Meanwhile every database, every API, every date picker and every programming language you might reach for speaks Gregorian.

So the first thing any Nepali software has to do is bridge those two. And the first thing you learn when you try is that you cannot.

Not with a formula, anyway. Bikram Sambat is lunisolar. Month lengths are not fixed — a month that ran 31 days last year may run 32 this year, and which months those are varies year to year in a pattern that is not derivable arithmetically. There is no leap rule to encode. There is no closed-form conversion. You either have the data, computed from astronomical positions, or you are guessing.

That is the gap Joishi was built into. It has grown well past it since, but that is where it starts, and it is why it exists.

what most software does instead

The honest answer, and I say this having done it myself early on, is that most implementations ship a table.

Someone computes or copies the month lengths for a range of years, types them into an array, and ships. It works. It keeps working for as long as the table covers, and then it quietly stops being right — usually a few years out, usually in a way nobody notices immediately, because the failure mode of a wrong calendar is not an exception. It is a date that looks completely normal and is one day off.

I do not think less of anyone who has done this. It is a rational response to a hard problem when the calendar is a small feature of a larger product. But it does mean the correctness of an enormous amount of software is downstream of a spreadsheet somebody made once, and that nobody can tell you where the numbers came from.

We decided fairly early that if the calendar was going to be the product rather than a feature of it, we did not get to do that. Which turned out to be the easy half of the decision.

the harder half: there is no single right answer

Converting dates is the entry fee. What people actually want is panchanga — the five-limbed almanac. Which tithi it is today. Which nakshatra the moon sits in. When Rahu Kaal falls, and therefore when not to begin something that matters. Weddings, business openings, travel, naming ceremonies, the timing of funeral rites: a great many real decisions in this country are planned around these numbers.

Here is the thing that took me longest to understand, and that reframed the entire product for me.

There is no single correct value. Not because the mathematics is uncertain, but because there are several legitimate traditions, and they disagree with each other by design.

Start with the siddhanta — the astronomical model itself. You can compute planetary positions observationally, using modern ephemeris data. Or you can compute them the way the classical Surya Siddhanta prescribes, following a text that has been used for centuries. These are different models. They give different answers. Both are correct, in the sense that each is a faithful execution of the tradition it belongs to, and there are practitioners today who use each.

Then the ayanamsha — the offset between the tropical and sidereal zodiacs, which is the thing that decides where a sign begins. Lahiri is the most widely used in India and Nepal. But there is also Raman, Krishnamurti, Yukteshwar, Aryabhata, Fagan-Bradley, several variants derived from Surya Siddhanta, and a family of "true" ayanamshas fixed to particular stars. Joishi supports seventeen of them. Change the ayanamsha and planets move between signs. Everything downstream changes.

Then the house system — how you divide the sky into twelve houses. Placidus, Koch, Whole Sign, Equal, Sripati, Campanus, Regiomontanus, Porphyry, Alcabitius, Topocentric, Krishnamurti. Eleven, in our case. Different systems put the same planet in different houses.

Then whether you are working sidereal or tropical at all.

Multiply it out. Two astronomical models, seventeen ayanamshas, eleven house systems, two zodiac modes. That is 748 valid configurations, and every one of them is correct to somebody.

Two practitioners can compute the same chart for the same person at the same moment, disagree completely about what it says, and both be entirely right within their own tradition. This is not a bug in the domain. It is the domain.

which makes the engineering problem a different problem

Once you accept that, the question stops being "how do we produce the correct answer" — which is not answerable — and becomes something you can actually build against:

How do we make the choice explicit, and the result reproducible?

That reframing is the single most useful thing I took from this project, and it generalises well beyond calendars. When a domain has no single truth, the software's job is not to pick a winner. It is to make sure that every number it produces can be traced to the assumptions that produced it, and that the same inputs and the same assumptions produce the same output, today and in five years.

In practice that meant a few things:

Every configuration option is a first-class input, never a default buried in a helper. If a calculation depends on the ayanamsha — and almost all of them do — the ayanamsha is a parameter of that calculation, threaded explicitly. It is never read from ambient state, never quietly defaulted somewhere three layers down. This is more tedious to write and much easier to reason about, and it means a result can always be reproduced from its inputs alone.

Every result carries its assumptions. A tithi is not a value. It is a value plus the siddhanta, ayanamsha and location that produced it. Stripping that context and passing the bare number around is how two parts of a system start disagreeing without anyone noticing.

The user's choice persists and travels. A practitioner who works in a particular tradition sets it once. Every screen, every report, every calculation honours it. Nothing silently reverts to whatever the platform's default happens to be, because a practitioner getting a Lahiri number on one screen and a Raman number on another has been given something worse than no answer.

If you want to see the actual surface of this rather than read me describing it, it is all exposed in the app — the siddhanta, the ayanamsha, the house system, the zodiac mode, all selectable, all persisting. Go and change one and watch everything downstream move. That is the most direct explanation of this post that exists.

a configuration matrix is not free

I want to be honest about the cost, because "just support all of them" reads as generous and is actually a large ongoing liability.

Every option you support, you own the failure modes of. House systems are the clearest example: several of the classical ones are defined in terms that degrade at extreme latitudes, and some become undefined entirely near the poles. That is not a flaw in our implementation. It is a property of the geometry those systems are built on, known for centuries. But it becomes our problem the moment a user with an unusual birthplace picks one, and it has to be handled explicitly rather than allowed to produce a plausible-looking number.

The same applies across the matrix. Every combination is a code path that can be wrong, and the number of combinations is not something you test exhaustively by hand. The engineering answer is to make the calculation layer cheap to test — no database, no network, no web framework, nothing to stand up — so that testing hundreds of configurations is a thing you do in seconds rather than a thing you plan a sprint around. Getting that property was one of the better decisions on the project, and it is worth more than any individual calculation in it.

compute in one representation, localise at the very edge

The other decision I would repeat on any project, in any domain.

Everything computes on Julian Day. Every instant, internally, is one canonical number. Bikram Sambat is not a calculation format — it is a display format. So are Devanagari numerals. So is Devanagari script. So is the 12-hour clock. All of it is presentation, applied at the final step before something reaches a human, and none of it is ever allowed to touch a calculation.

This sounds obvious written down and is very easy to violate. The temptation is constant, because the user asked for a BS date and it feels natural to carry a BS date around. Then somewhere deep in the system you need to add a day, or compare two instants, or work out a duration — and now you are doing arithmetic in a calendar with no arithmetic. Every bug of that shape is a nightmare to find, because the value looks right at every point you inspect it.

Holding that line is also the only reason four locales — English, Nepali, Hindi and Sanskrit — were possible at all. Not retrofitted, which I do not think would have worked. Designed in, because if presentation is genuinely separate from computation then adding a fourth rendering of an instant is a display concern rather than a rewrite. Shipping Sanskrit as a supported locale is not a problem most codebases have to solve, and it was tractable only because of a boundary drawn years earlier for unrelated reasons.

where it stands

Joishi is live on the web and on both app stores. There is a public side — the calendar, daily panchanga, festivals and public holidays, date conversion, muhurta lookup, rashifal, live gold, silver and foreign exchange rates. There is a professional side for practitioners working with charts, consultations and client records. There is an admin console behind it that is larger than either.

It has been shipping since 2023 and it is the largest single thing I have built. I led it and own the backend, the calculation engine, the infrastructure and every deployment on it.

The part I am most pleased with is not any individual feature. It is that a number the app shows you can be traced back to the assumptions that produced it, and that those assumptions were yours to choose.

what I would do differently

I would design for configurability earlier. The first version had assumptions baked in that felt safe at the time — one ayanamsha, one model, because that is what the first users wanted. Every one of those had to be pulled back out and turned into a parameter, and pulling an assumption out of a codebase is considerably more work than putting a parameter in on day one. I knew this. I did it anyway, because on day one the second configuration is hypothetical and the deadline is not.

I would have written the reproducibility rule down sooner. It existed as an instinct for a long time before it existed as a stated principle, and in between there were places where a value got separated from its assumptions and caused a bug that took a day to find. A principle you have not articulated cannot be applied by anyone else, and cannot be checked in review.

I would be even more suspicious of plausible numbers. The recurring lesson of this whole domain is that wrong answers here do not announce themselves. A date that is one day off looks like a date. A tithi computed with the wrong ayanamsha looks like a tithi. There is no exception, no stack trace, no red text — just a number that is wrong in a way only a domain expert will catch, in a context where somebody may be planning something that matters to them around it.

That is the part that has stayed with me. People plan weddings and funerals around these timings. Shipping a rounded-off number because the real computation was inconvenient would have been the easy path, and the wrong one.


by Krishna Adhikari · Jul 21, 2026
share
// related.transmissions

Keep reading.