nepali-date-utils
v1.0.0
Published
Bikram Sambat (BS) calendar data, BS<->AD conversion, and BS date-string utilities for Nepali dates. Zero dependencies, dual ESM/CJS, fully typed.
Downloads
157
Maintainers
Readme
nepali-date-utils
Bikram Sambat (BS) calendar data, BS↔AD conversion, and BS date-string utilities for Nepali dates.
Zero runtime dependencies · dual ESM/CJS · fully typed · ~8 kB gzipped · tree-shakeable.
Built around a NepaliDate class that is API-compatible with the widely used nepali-date-converter package, so it can be swapped in without touching call sites — but backed by a calendar table that is verified against official BS new-year dates and guarded by tests.
Installation
npm install nepali-date-utils
# or
yarn add nepali-date-utils
# or
pnpm add nepali-date-utilsQuick start
import {
NepaliDate,
adToBs,
bsToAd,
getTodayBs,
isValidDateString,
} from "nepali-date-utils";
// today in BS
const today = new NepaliDate();
today.format("YYYY-MM-DD"); // e.g. "2083-04-13"
getTodayBs(); // same, as a string in one call
// convert
bsToAd(2081, 1, 1); // Date: 2024-04-13 (local midnight)
adToBs(new Date(2024, 3, 13)); // { year: 2081, month: 1, day: 1 }
// validate user input before trusting it
isValidDateString("2081-02-32"); // true (BS months really have 32 days)
isValidDateString("2081-02-33"); // falsePlain JavaScript works identically — there is nothing TypeScript-only in the API:
const { NepaliDate } = require("nepali-date-utils"); // CJS build resolves automatically
new NepaliDate(2081, 0, 15).format("MMMM D, YYYY"); // "Baisakh 15, 2081"Coverage & accuracy
- Range: BS 1970-01-01 through 2099-12-30 (AD 1913-04-13 onwards).
- Anchor: BS 1970-01-01 = AD 1913-04-13, a Sunday. All conversion is pure day-count arithmetic from that anchor, computed in UTC so the device timezone can never shift a date.
- Verification: month lengths are cross-checked against official BS new-year dates for 2000, 2050, 2070, 2075 and 2080–2084; the whole range is weekday-chain consistent. Years beyond ~2085 are projections (Nepal publishes the official calendar only a year or two ahead) kept for UI range purposes — don't trust them for legal dates.
- Every year row is guarded by a test asserting its 12 month lengths sum to its year total. Never hand-edit a single month without running the test suite — an off-by-one shifts every conversion after it.
API reference
NepaliDate
API-compatible replacement for nepali-date-converter's default export. Date-only — time of day is intentionally not carried.
new NepaliDate() // today
new NepaliDate(adDate: Date) // from an AD date (read in local time)
new NepaliDate(other: NepaliDate) // clone
new NepaliDate(2081, 0, 15) // BS year, month0, day — month is 0-INDEXED
NepaliDate.fromAD(date: Date) // alias for new NepaliDate(date)Two compatibility quirks inherited from nepali-date-converter — both are load-bearing, not accidents:
getMonth()is 0-indexed (0 = Baishakh), like JSDate. The string helpers below use 1-indexed months; convert deliberately at the boundary.- JS-Date-style rollover: out-of-range parts normalize instead of throwing —
new NepaliDate(2081, 12, 1)is BS 2082-01-01, day 0 is the last day of the previous month.
| Method | Returns |
| -------------------------------------------- | -------------------------------------------------------------------------------------- |
| getYear() | BS year |
| getMonth() | BS month, 0-indexed |
| getDate() | day of month, 1-indexed |
| getDay() | weekday, 0 = Sunday |
| setYear(y) / setMonth(m0) / setDate(d) | mutates in place (rollover applies) |
| toJsDate() | AD Date at local midnight |
| format(pattern, language?) | formatted string; language is "en" (default) or "np" (Devanagari digits + names) |
| toString() | format("YYYY-MM-DD") |
Format tokens: YYYY YY MMMM MMM MM M DD D dddd ddd dd d.
const d = new NepaliDate(2081, 2, 5);
d.format("dddd, MMMM D"); // "Wednesday, Asar 5"
d.format("YYYY-MM-DD", "np"); // "२०८१-०३-०५"Conversion functions
bsToAd(year: number, month: number, day: number): Date // month 1-INDEXED here
adToBs(date: Date): BsDateParts // { year, month (1-indexed), day }String helpers
The library's canonical format is the zero-padded "YYYY-MM-DD" BS string (BsDateString). All helpers are pure.
| Function | Behaviour |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| isValidDateString(s) | true only for strict "YYYY-MM-DD" naming a real BS day |
| isValidDate(y, m, d) | same check from numeric parts (month 1-indexed) |
| parseDateString(s) | [year, month, day] numbers — does not validate; guard with isValidDateString for user input |
| formatDate(y, m, d) | zero-padded "YYYY-MM-DD" |
| isBetween(date, start?, end?) | inclusive range check; a missing/malformed bound is open-ended; malformed date is never in range |
| getMonthData(y, m) | { totalDays, startDay } for a calendar grid; both undefined outside the table (render nothing, don't throw) |
| getTodayBs() | today as BsDateString (device clock, local time) |
NepaliDate helpers
Pure, immutable functions over NepaliDate instances — arithmetic returns new instances. All throw only at the calendar-table edges (documented per function).
| Function | Behaviour |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| isSameDay(a, b) / isSameMonth(a, b) | calendar equality checks |
| isBefore(a, b) / isAfter(a, b) | strict ordering |
| addDays(date, n) | O(1) day offset, exact across month/year boundaries |
| addMonths(date, n) | month offset with day clamping (Jestha 32 + 1 month → shorter month's last day) |
| startOfWeek(date) / endOfWeek(date) | Sunday/Saturday bracketing the containing week |
| daysBetween(a, b) | absolute day distance, order-independent |
| parseNepaliDate(str) | lenient parse (2081-3-5, 2081/3/5) → NepaliDate or null; never rolls invalid days over |
| adToBsString(date) | AD Date → "YYYY-MM-DD" BS string |
| bsToAdString(bs) | BS string → AD "YYYY-MM-DD" string, null if invalid |
| toNepaliNumerals(v) / toEnglishNumerals(s) | ASCII ↔ Devanagari digit conversion, non-digits pass through |
import {
NepaliDate,
addDays,
daysBetween,
parseNepaliDate,
toNepaliNumerals,
} from "nepali-date-utils";
const due = addDays(new NepaliDate(), 30);
daysBetween(new NepaliDate(), due); // → 30
parseNepaliDate("2081/3/5")?.toString(); // → "2081-03-05"
toNepaliNumerals("2081-01-15"); // → "२०८१-०१-१५"Constants
| Export | What it is |
| ------------------------------- | --------------------------------------------------------------- |
| MIN_BS_YEAR / MAX_BS_YEAR | table bounds — derive UI year ranges from these, never hardcode |
| CALENDAR_DATA | { [bsYear]: number[13] } — 12 month lengths + year total |
| FIRST_DAY_OF_MONTH | derived { [bsYear]: weekday[12] } for calendar grids |
| NEPALI_MONTHS / NEPALI_DAYS | display names, useful for datepicker headers |
| LOCALE | en/np month, weekday, and digit strings for format() |
Types
BsDateParts, BsDateString, and BsMonthData are exported as types for use in your own signatures.
Error handling
Conversions throw (with the supported range in the message) when a date falls outside BS 1970–2099. Two idioms:
// user input → validate first, never try/catch
if (!isValidDateString(input)) return showFieldError();
// programmatic dates near the table edge → catch the range error
try {
const d = new NepaliDate(year, month0, day + offset);
} catch {
// clamp, or surface "date out of supported range"
}getMonthData is the exception: it returns undefined fields instead of throwing, because calendar grids need to render gracefully at the table edge.
FAQ
Why is NepaliDate's month 0-indexed but everything else 1-indexed?
NepaliDate mirrors nepali-date-converter (and JS Date) so it can be swapped in without touching call sites. The string/parts helpers are 1-indexed because that's how humans write dates. The type signatures and docs mark which is which.
Why strings rather than NepaliDate everywhere?
Strings are serializable (forms, APIs, state stores) and cheap to compare. Construct a NepaliDate only when you need arithmetic or formatting.
What happens after BS 2099?
Conversions throw. Extend CALENDAR_DATA as Nepal publishes new years — append rows, run the tests (the integrity test guards every row), done. PRs welcome.
Timezones?
All arithmetic is UTC-internal; bsToAd/toJsDate hand back local midnight so downstream getFullYear()-style reads match what the user sees on a wall calendar.
Does this render a datepicker?
No — this package is calendar logic only, deliberately UI-framework agnostic. getMonthData, FIRST_DAY_OF_MONTH, and NEPALI_MONTHS/NEPALI_DAYS give you everything needed to build a grid in React, Vue, Svelte, or plain DOM.
Performance
Day-offsets per year are precomputed once at module load (~130 years); conversions after that are array lookups plus integer math — suitable for hot paths like rendering calendar grids. No dependencies, ~8 kB gzipped for the full bundle including the calendar table, tree-shakeable (sideEffects: false) so unused helpers drop out.
Contributing
Issues and PRs are welcome at github.com/nativeTiger/nepali-date-utils.
git clone https://github.com/nativeTiger/nepali-date-utils.git
cd nepali-date-utils
npm install
npm run build # tsdown → dual ESM/CJS + .d.ts
npm test # node:test suite
npm run type-check # tsc --noEmitDevelopment requires Node ≥ 22.6 — the test suite runs TypeScript directly via node's type stripping. (The published package targets Node ≥ 18.)
Calendar-data changes must keep the integrity test green — it asserts every year's 12 month lengths sum to its recorded year total. If you're adding a newly published BS year, cite the official source in the PR.
Releasing
./release.sh 1.0.1 # verifies, bumps, commits, tags — does not push
git push origin main v1.0.1
npm publish # prepublishOnly rebuilds; publishConfig sets public accessCheck the tarball with npm pack --dry-run first — only dist/, README.md, and LICENSE should ship.
License
MIT © nativeTiger
