The Next Problem After Conversion
Most Nepali .NET developers reach for a date library when they first need to display a date in BS. They convert, it works, and they move on. The harder problem arrives later: building a report for the current fiscal year, computing a 30-day deadline, or checking whether a submitted date falls within an allowed range.
BS date arithmetic has the same constraint as BS conversion: month lengths vary by year and cannot be derived from a formula. "Add 3 months to Ashadh 15" requires knowing the actual day count of Shrawan, Bhadra, and Ashwin for that year. There is no arithmetic shortcut.
NepDate handles this through three arithmetic methods on NepaliDate and the NepaliDateRange struct, which provides factory methods for the most common interval types.
dotnet add package NepDate
AddDays
AddDays delegates to the Gregorian calendar: the date is converted to a DateTime, the specified number of days is added, and the result is converted back. It is always correct across all month and year boundaries with no special cases.
using NepDate;
var today = NepaliDate.Today; // 2083/02/19 on 2026-06-02
var probationEnd = today.AddDays(30);
// probationEnd → 2083/03/18 (AD: 2026-07-02)
Negative values move backward. Fractional values are accepted but since NepaliDate has no time component, only the integer portion matters.
AddMonths and AddYears
AddMonths and AddYears operate in month and year space and still respect variable month lengths. Both methods take an awayFromMonthEnd parameter that controls what happens when the target month is shorter than the current day number.
When false (the default), the day is clamped to the last valid day of the target month. When true, the overflow days carry forward into the following month.
// Ashadh 2082 has 32 days. Shrawan 2082 has 31 days.
var lastOfAshadh = new NepaliDate(2082, 3, 32);
// Default (awayFromMonthEnd: false): clamp to last valid day
var clamped = lastOfAshadh.AddMonths(1);
// clamped → 2082/04/31
// awayFromMonthEnd: true - carry the 1 extra day into Bhadra
var overflow = lastOfAshadh.AddMonths(1, awayFromMonthEnd: true);
// overflow → 2082/05/01
The clamped default is correct for most HR and payroll use cases. An employee hired on Ashadh 32 whose review falls in a 30-day month should land on the last day of that month.
var hireDate = new NepaliDate(2082, 3, 32);
var reviewDate = hireDate.AddMonths(6); // awayFromMonthEnd: false (default)
// Poush 2082 has 30 days - day 32 is clamped
// reviewDate → 2082/09/30
AddYears follows the same semantics:
var today = new NepaliDate(2083, 2, 19);
var nextYear = today.AddYears(1);
// nextYear → 2084/02/19 (AD: 2027-06-02)
Subtraction and Comparison
The - operator between two NepaliDate values returns a TimeSpan:
var y2083Start = new NepaliDate(2083, 1, 1);
var y2082Start = new NepaliDate(2082, 1, 1);
TimeSpan span = y2083Start - y2082Start;
// span.Days → 365
Comparison operators work as expected. NepaliDate implements IComparable<NepaliDate>, so LINQ OrderBy, sorted sets, and min/max work without a custom comparer:
var d1 = new NepaliDate(2083, 2, 19);
var d2 = new NepaliDate(2083, 2, 20);
bool earlier = d1 < d2; // true
bool same = d1 == new NepaliDate(2083, 2, 19); // true
bool after = d2 > d1; // true
Working with NepaliDateRange
NepaliDateRange is an inclusive [start, end] interval with factory methods that construct the most common ranges correctly without any manual calculation.
// Jestha 2083 as a month range (31 days)
var jestha = NepaliDateRange.ForMonth(2083, 2);
// jestha.Start → 2083/02/01
// jestha.End → 2083/02/31
// jestha.Length → 31
// BS 2082 calendar year
var calYear = NepaliDateRange.ForCalendarYear(2082);
// calYear.Start → 2082/01/01 (AD: 2025-04-14)
// calYear.End → 2082/12/30 (AD: 2026-04-13)
// calYear.Length → 365
// FY 2082/83: Shrawan 1, 2082 through last day of Ashadh 2083
var fy2082 = NepaliDateRange.ForFiscalYear(2082);
// fy2082.Start → 2082/04/01 (AD: 2025-07-17)
// fy2082.End → 2083/03/32 (AD: 2026-07-16)
// fy2082.Length → 365
ForFiscalYear calls MonthEndDate() internally to get the actual last day. A query that hardcodes 31 silently drops one day of data.
The Current* variants derive the range from today's date:
// As of Jestha 19, 2083 - month 2 is before Shrawan (month 4),
// so the current fiscal year is still FY 2082/83
var currentFY = NepaliDateRange.CurrentFiscalYear();
// Start → 2082/04/01, End → 2083/03/32, Length → 365
var currentMonth = NepaliDateRange.CurrentMonth();
// Start → 2083/02/01, End → 2083/02/31
var currentYear = NepaliDateRange.CurrentCalendarYear();
// Start → 2083/01/01, End → 2083/12/30
You can also construct a range manually:
var custom = new NepaliDateRange(
new NepaliDate(2082, 6, 1),
new NepaliDate(2082, 9, 30));
NepaliDateRange implements IEnumerable<NepaliDate> so you can enumerate every date in the range for calendar grids or daily summaries.
Contains and Overlaps
Contains checks whether a specific date falls within the range, inclusive on both ends:
var fy = NepaliDateRange.ForFiscalYear(2082);
// Chaitra 15, 2082 - month 12, calendar year 2082, but Q3 of FY 2082/83
bool a = fy.Contains(new NepaliDate(2082, 12, 15)); // true
// Jestha 15, 2083 - calendar year 2083, but still in FY 2082/83
bool b = fy.Contains(new NepaliDate(2083, 2, 15)); // true
FY 2082/83 runs through Ashadh end 2083. Dates in Baishakh, Jestha, and Ashadh of calendar year 2083 belong to FY 2082/83, not FY 2083/84.
For database queries, convert range bounds to Gregorian and use an exclusive upper bound:
var fy = NepaliDateRange.ForFiscalYear(2082);
DateTime adStart = fy.Start.EnglishDate; // 2025-07-17 (inclusive)
DateTime adEnd = fy.End.EnglishDate.AddDays(1); // 2026-07-17 (exclusive upper bound)
var rows = dbContext.Transactions
.Where(t => t.Date >= adStart && t.Date < adEnd)
.ToList();
Parsing Mixed Input with SmartDateParser
SmartDateParser accepts over 100 spellings of Nepali month names - English transliterations, Devanagari names, common abbreviations - as well as Devanagari digit strings and numeric formats with any common separator.
using NepDate;
NepaliDate d1 = SmartDateParser.Parse("15 Jestha 2083"); // 2083/02/15
NepaliDate d2 = SmartDateParser.Parse("२०८३/०२/१९"); // 2083/02/19 - Devanagari digits
NepaliDate d3 = SmartDateParser.Parse("15 Jeth 2083"); // 2083/02/15 - alternate spelling
NepaliDate d4 = SmartDateParser.Parse("15 जेठ 2083"); // 2083/02/15 - Devanagari month name
For controlled input where the format is known, use NepaliDate.Parse or TryParse at system boundaries:
// Strict - throws on invalid input
NepaliDate parsed = NepaliDate.Parse("2083-02-19");
// Safe - returns false instead of throwing
if (NepaliDate.TryParse(userInput, out NepaliDate result))
{
// result is a valid NepaliDate
}
Conclusion
The full date arithmetic surface in NepDate: AddDays always delegates to Gregorian space for correctness; AddMonths and AddYears operate in BS month space with a configurable overflow policy; the - operator returns a TimeSpan; NepaliDateRange factory methods produce correct interval bounds including variable Ashadh end days; and SmartDateParser handles the full range of real-world BS date string formats.
- NuGet: nuget.org/packages/NepDate
- GitHub: github.com/RajuPrasai/NepDate
- API docs: nepdate.rajuprasai.com.np
- Previous post: How Bikram Sambat Calendar Math Works in .NET