10 min read

How Bikram Sambat Calendar Math Works in .NET

The Bikram Sambat calendar cannot be converted to Gregorian with a simple formula. Month lengths are 29 to 32 days, determined by solar transits, and vary year by year. This post explains why, how lookup tables solve it, and how the NepDate library handles it in .NET with O(1) performance and zero heap allocations.

Raju Prasai Senior .NET Developer - Jhapa, Nepal

Why Nepali Developers Encounter This Problem Daily

If you write software in Nepal, you live in two calendars simultaneously. The government issues notifications in Bikram Sambat (BS). Salary slips, land documents, court filings, and the national budget all use BS dates. But your database stores DateTime, your APIs exchange ISO 8601, and your .NET runtime knows nothing about BS.

The result is that every Nepali developer eventually writes a date conversion utility. Most do it badly the first time: a hardcoded lookup array, an off-by-one at the year boundary, and a different epoch offset than the one used by the government Patro. Two years later, the audit team reports that salary records for Falgun 2078 have incorrect AD equivalents. The month length was wrong for that year.

Understanding the actual math - not just the API - is the difference between using a library correctly and debugging a conversion mismatch in production.

What is Bikram Sambat?

Bikram Sambat (also written Vikram Samvat) is a lunisolar calendar originating in the Indian subcontinent. Nepal officially adopted it as the national calendar. All government processes, school calendars, and public holidays are defined in BS.

The calendar has 12 months with Nepali names. The rough Gregorian equivalents are:

#BS MonthNepaliApprox. Gregorian
1BaishakhबैशाखMid-April to Mid-May
2JesthaजेठMid-May to Mid-June
3AshadhअसारMid-June to Mid-July
4Shrawanश्रावणMid-July to Mid-August
5Bhadraभाद्रMid-August to Mid-September
6Ashwinआश्विनMid-September to Mid-October
7Kartikकार्तिकMid-October to Mid-November
8MangsirमंसिरMid-November to Mid-December
9PoushपौषMid-December to Mid-January
10MaghमाघMid-January to Mid-February
11Falgunफाल्गुणMid-February to Mid-March
12Chaitraचैत्रMid-March to Mid-April

The BS new year, Baishakh 1, falls somewhere in mid-April in the Gregorian calendar. Its exact date varies slightly each year. This is the first source of conversion complexity.

The Year Offset

BS is currently about 56 to 57 years ahead of the Gregorian calendar. The offset is not constant because the two calendars have different new year dates. From January 1 to around mid-April, the BS year is AD + 56. From mid-April to December 31, it is AD + 57. As of mid-2025, Nepal is in BS year 2082.

The calendar counts from the legendary coronation of King Vikramaditya, approximately 57 BCE. The epoch - BS year 0, day 1 - corresponds to approximately 57 BCE in the proleptic Gregorian calendar, though different sources give slightly different starting points. This matters for software because an incorrect epoch produces wrong results for all dates.

The Month Length Problem

This is the fundamental reason BS conversion is hard. In the Gregorian calendar, month lengths follow a fixed pattern: 31, 28 (or 29 in a leap year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31. A programmer can compute the day count for any Gregorian month with arithmetic.

BS months do not follow a fixed pattern. Each month's length is determined by when the sun transits from one zodiac sign (Rashi) to the next. Because the sun moves at a non-uniform angular velocity along its elliptical orbit, the time between successive transits is not constant. Some transits take about 29 days, others take about 32 days.

The month lengths for a given BS year are published annually by the Nepalese calendar authorities in the official Patro. They cannot be computed from a formula. The same month in different BS years may have a different number of days.

Concrete Example: BS 2082 Month Lengths

Here are the actual month day counts for BS 2082:

MonthDaysMonthDays
Baishakh31Shrawan31
Jestha31Bhadra30
Ashadh32Ashwin30
Kartik29Magh29
Mangsir30Falgun30
Poush30Chaitra30

Ashadh has 32 days. Kartik has 29. These values are not computable from arithmetic - they are empirical. The total for the year is 363 days. Compare that with BS 2083 where some months shift by one day, changing the total.

A library that does not embed a pre-computed table for each supported year will produce wrong results. There is no way around this.

The Lookup Table Approach

Every correct BS/AD conversion library uses a pre-computed data table. The table records, for each BS year in the supported range:

  • The Gregorian date of Baishakh 1 (the first day of that BS year)
  • The day count of each of the 12 months

With this data, the algorithms are straightforward.

AD to BS Conversion

Given a Gregorian date, find which BS year it falls in by comparing it to the Baishakh 1 dates. Then subtract the Baishakh 1 date to get the day-of-year offset. Accumulate month lengths until the offset is consumed - the current month is where it runs out, and the remainder is the day of month.

BS to AD Conversion

Look up the Gregorian date of Baishakh 1 for the target BS year. Sum the day counts of all months before the target month. Add the target day minus one. The result is the number of days to add to Baishakh 1, giving the Gregorian date.

How NepDate Encodes the Data

NepDate uses a compact binary encoding. Each BS year's 12 month lengths are packed into a small integer representation, reducing the table's memory footprint. The lookup itself is a flat array index: given a year within the supported range (BS 1901 to 2199), the index is year - 1901. No tree traversal, no hash lookup, no iteration - a single array access.

This is why NepDate runs at approximately 4.55 nanoseconds per conversion. The bottleneck is not the algorithm but the memory access latency. The conversion is as fast as it can practically be on modern hardware.

Data integrity matters. If a lookup table is wrong for any year in its range, every date in that year converts incorrectly. NepDate validates its table against officially published government Patro data. Using a library whose data provenance is unknown is the same as writing your own buggy lookup array.

Nepal's Fiscal Year

Understanding BS calendar math is incomplete without the fiscal year, because it is the most common reason Nepali developers need BS date arithmetic in business software.

The Government of Nepal defines the fiscal year as running from Shrawan 1 to Ashadh end - that is, from the first day of BS month 4 to the last day of BS month 3 in the following year. Shrawan 1 typically falls around July 16-17 in the Gregorian calendar. The fiscal year is labeled by the BS year in which Shrawan falls: the FY 2082/83 runs from Shrawan 1, 2082 to Ashadh end, 2083.

This matters for:

  • Payroll and HR systems - salary calculations, bonus computations, and leave balances reset on fiscal year boundaries
  • Accounting systems - journal periods, tax filings, and audit trails are organized by BS fiscal year
  • Government reports - any data submission to a government body uses fiscal year framing
  • Budget software - project allocations and expenditure tracking follow Shrawan-to-Ashadh periods

The Fiscal Year Boundary Edge Case

The tricky case is dates in Ashadh (month 3). Ashadh has 31 or 32 days depending on the year. A query for "all transactions in FY 2082/83" must end on Ashadh 32 in BS 2083 if that year has a 32-day Ashadh, not Ashadh 31. Hardcoding 31 as the last day is a silent data truncation bug.

The correct approach is always to ask the library for the actual day count of Ashadh in the target year, not assume it.

Working with NepDate in .NET

NepDate is a zero-dependency .NET library (available on NuGet) that wraps all of this complexity. It is a readonly partial struct, so it allocates nothing on the heap and behaves like a value type throughout.

Installation

dotnet add package NepDate

Basic Conversion

using NepDate;

// Gregorian to Bikram Sambat
var adDate = new DateTime(2025, 10, 17);
var bsDate = new NepaliDate(adDate);
// bsDate → 2082-07-01 (Kartik 1, 2082)

// Bikram Sambat to Gregorian
var bs = new NepaliDate(2082, 3, 15);  // Ashadh 15, 2082
DateTime ad = bs.EnglishDate;
// ad → 2025-06-29

Getting the Days in a Month

Always use the library to get the actual day count. Never hardcode month lengths.

// How many days does Ashadh have in BS 2083?
int days = new NepaliDate(2083, 3, 1).MonthEndDay;
// The only correct way to know - do not assume 31 or 32.

Fiscal Year Range for Database Queries

This is the pattern that matters most for business software. Given today's date, compute the inclusive AD date range for the current fiscal year and use it in a SQL or LINQ query.

using NepDate;

var today = NepaliDate.Now;

// FiscalYearStartAndEndDate() returns (Shrawan 1, last day of Ashadh next year)
var (fyStartBs, fyEndBs) = today.FiscalYearStartAndEndDate();

// Convert to AD for database queries
DateTime adStart = fyStartBs.EnglishDate;
DateTime adEnd   = fyEndBs.EnglishDate.AddDays(1).AddSeconds(-1);

// Use adStart and adEnd in your EF Core or Dapper query

Current Date and Fiscal Year Label

var now     = NepaliDate.Now;
int fyStart = now.FiscalYearStartDate().Year;  // e.g. 2082
string fyLabel = $"FY {fyStart}/{(fyStart + 1) % 100:D2}";
// e.g. "FY 2082/83"

// Display full BS date
Console.WriteLine($"Today: {now.Year}-{now.Month:D2}-{now.Day:D2} BS");

Common Mistakes to Avoid

Hardcoding Month Lengths

Do not write arrays like { 31, 31, 32, 31, 30, 30, 29, 30, 30, 29, 30, 30 } for "the standard year". There is no standard year. Every year's month lengths are different. Any hardcoded array is wrong for some subset of years in your supported range.

Assuming a Fixed BS-to-AD Offset

The offset between BS and AD is 56 for part of the year and 57 for the rest. Calculating adYear = bsYear - 57 is wrong for dates before mid-April. The correct approach is always to convert through a proper lookup, not arithmetic.

Assuming the Fiscal Year Ends on Ashadh 31

Ashadh has 32 days in many years. A fiscal year query that uses new NepaliDate(fy + 1, 3, 31) as the upper bound silently excludes the last day of that year. Use FiscalYearEndDate() or new NepaliDate(year, 3, 1).MonthEndDay to get the correct day count rather than hardcoding 31.

Different Libraries, Different Epochs

If your system receives BS dates from an external source (a government API, a partner system, a scanned document), verify that both systems use the same epoch. A one-day epoch difference means every date in the system is off by one. This is a silent data corruption problem - the code compiles, the tests pass on hardcoded dates, but the live data is consistently wrong.

Conclusion

Bikram Sambat is a solar calendar where month lengths are determined by astronomical observation, not arithmetic. The only correct implementation is a lookup table validated against the official Patro. The conversion algorithms themselves are simple once you have correct data.

For .NET developers working with Nepali dates, NepDate provides a correctly validated table covering BS 1901 to 2199, a readonly struct value type with zero heap allocations, O(1) lookup performance, and fiscal year helpers built in. It is what I built after encountering the same conversion bugs in production software across multiple projects.

The library is on NuGet and the source is on GitHub. Full API documentation is at nepdate.rajuprasai.com.np.