@nepali-utils/core
v0.1.0
Published
A framework-agnostic TypeScript library for Bikram Sambat (Nepali) date operations, number conversion, and calendar utilities.
Maintainers
Readme
@nepali-utils/core
Framework-agnostic TypeScript library for Bikram Sambat (Nepali) date operations, number conversion, calendar utilities, location data, phone validation, and currency formatting.
npm install @nepali-utils/corepnpm add @nepali-utils/coreyarn add @nepali-utils/coreFeatures
- BS ↔ AD date conversion — Accurate conversion between Bikram Sambat and Gregorian dates (BS 2000–2100)
- Date formatting — Format BS dates with custom patterns in English or Nepali (Devanagari)
- Nepali numerals — Convert between Arabic and Nepali digits
- Calendar grid generation — Generate ready-to-render calendar grids with today/selected/disabled state
- Date comparison — Equality, before/after checks for BS dates
- Calendar data — Days per month/year, year validation, range queries
- Location data — Complete dataset for all 7 provinces, 77 districts, municipalities, and wards of Nepal
- Phone validation — Validate Nepali mobile numbers with carrier detection (NTC, Ncell, etc.)
- Currency formatting — Format amounts in Nepali rupees, convert numbers to Nepali words
- Zero runtime dependencies — Fully tree-shakeable, works in any JS/TS project
Quick start
import { getTodayBs, formatBsDate, bsToAd, adToBs } from "@nepali-utils/core"
// Today's date in BS
const today = getTodayBs()
// → { year: 2081, month: 5, day: 15 }
// Format with custom pattern
formatBsDate(today, "en", { pattern: "YYYY MMMM DD" })
// → "2081 Bhadra 15"
formatBsDate(today, "ne", { pattern: "YYYY MMMM DD" })
// → "२०८१ भदौ १५"
// Convert BS → AD
const adDate = bsToAd(2081, 5, 15)
// → Date object
// Convert AD → BS
const bsDate = adToBs(new Date())
// → { year: 2081, month: 5, day: 15 }Modules
Dates
Types
| Type | Description |
|---|---|
| DateBS | { year: number, month: number, day: number } — 1-indexed month |
| MonthInfo | { value, nameEn, nameNe } |
| WeekdayInfo | { value, shortEn, shortNe, longEn, longNe } |
| BsLang | "en" \| "ne" |
| BsFormatOptions | { pattern?: "YYYY-MM-DD" \| "YYYY MMMM DD" \| "DD MMMM YYYY" \| "MMMM DD, YYYY" } |
| CalendarDay | { day, date, isToday, isSelected, isDisabled, isOutside } |
| MonthNavigation | { year, month } |
Constants
import { MONTHS, WEEKDAYS } from "@nepali-utils/core"
MONTHS[0]
// → { value: 1, nameEn: "Baisakh", nameNe: "बैशाख" }
WEEKDAYS[0]
// → { value: 0, shortEn: "Sun", shortNe: "आइत", longEn: "Sunday", longNe: "आइतबार" }Conversion
import { bsToAd, adToBs, getTodayBs, getBsDayOfWeek } from "@nepali-utils/core"
bsToAd(2081, 5, 15) // → Date
adToBs(new Date()) // → DateBS
getTodayBs() // → DateBS
getBsDayOfWeek(2081, 5, 15) // → 0 (Sun) – 6 (Sat)Formatting
import { formatBsDate, toNepaliNumber, parseNepaliNumber, getPrevMonth, getNextMonth } from "@nepali-utils/core"
formatBsDate(date, "ne", { pattern: "YYYY MMMM DD" })
// → "२०८१ भदौ १५"
toNepaliNumber(2081) // → "२०८१"
parseNepaliNumber("२०८१") // → 2081
getPrevMonth(2081, 1) // → { year: 2080, month: 12 }
getNextMonth(2081, 12) // → { year: 2082, month: 1 }Calendar data
import { getDaysInBsMonth, getDaysInBsYear, isValidBsYear, getBsYearRange, validateBsDate } from "@nepali-utils/core"
getDaysInBsMonth(2081, 5) // → 32
getDaysInBsYear(2081) // → 365
isValidBsYear(2081) // → true
getBsYearRange() // → { min: 2000, max: 2100 }
validateBsDate(2081, 5, 15) // → throws if invalidCalendar grid
import { generateCalendarGrid } from "@nepali-utils/core"
const weeks = generateCalendarGrid(2081, 5, selectedDate, todayDate, disabledFn)
// → CalendarDay[][] — 6 weeks × 7 days, ready for renderingEach CalendarDay object:
day: number | null— day number (null for padding cells)date: DateBS | null— the BS date objectisToday: boolean— matches todayisSelected: boolean— matches selected dateisDisabled: boolean— disabled by callbackisOutside: boolean— padding cell outside the current month
Comparison
import { isSameBsDate, isBeforeBsDate, isAfterBsDate } from "@nepali-utils/core"
isSameBsDate(a, b) // → true if same year, month, day
isBeforeBsDate(a, b) // → true if a < b
isAfterBsDate(a, b) // → true if a > bLocation
Complete dataset for Nepal's administrative divisions with lookups by ID, name, and parent.
import {
provinces, getProvinceById, getProvinceByNameEn,
districts, getDistrictById, getDistrictsByProvince,
municipalities, getMunicipalityById, getMunicipalitiesByDistrict, getMunicipalitiesByProvince,
wards, getWardsByMunicipality, getWardsByDistrict, getWardsByProvince,
} from "@nepali-utils/core"Types
| Type | Fields |
|---|---|
| ProvinceInfo | id, name, nameEn |
| DistrictInfo | id, name, provinceId, provinceName, nameEn, provinceNameEn |
| MunicipalityInfo | id, name, districtId, districtName, provinceId, provinceName, nameEn, districtNameEn, provinceNameEn |
| WardInfo | id, number, municipalityId, municipalityName, districtId, districtName, provinceId, provinceName, municipalityNameEn, districtNameEn, provinceNameEn |
All include Nepali (name) and English (nameEn) names.
Examples
// All provinces (7)
provinces
// → [{ id: "...", name: "कोशी प्रदेश", nameEn: "Koshi Province" }, ...]
// Province by ID
getProvinceById("31648741-3bc6-4b03-ad68-7950a5ffec17")
// → { id: "...", name: "बागमती प्रदेश", nameEn: "Bagmati Province" }
// Province by English name
getProvinceByNameEn("Bagmati Province")
// All districts (77)
districts
// Districts in a province
getDistrictsByProvince("1a2d297a-b4d4-4062-9801-62813c324753")
// Municipalities in a district
getMunicipalitiesByDistrict("6accfa6f-c4ab-4b95-b0bf-aaf740375f62")
// All wards in a municipality
getWardsByMunicipality("097e65c5-27b3-4bd8-b4ae-3bc46704e633")Validation
Nepali phone number validation with carrier detection and formatting.
import { isValidNepaliMobile, getPhoneType, formatNepaliPhone, type PhoneType } from "@nepali-utils/core"isValidNepaliMobile(phone: string): boolean
Validates a 10-digit Nepali mobile number. Supports numbers with or without +977 / 977 prefix.
isValidNepaliMobile("9841234567") // → true
isValidNepaliMobile("+9779841234567") // → true
isValidNepaliMobile("9779841234567") // → true
isValidNepaliMobile("01-1234567") // → false (landline)
isValidNepaliMobile("12345") // → falsegetPhoneType(phone: string): PhoneType
Returns "mobile", "landline", or "unknown".
- Mobile prefixes: 980–982, 984–986, 961–963, 974–978, 988
- Landline area codes: 01 (Kathmandu), 011 (Lalitpur), 014 (Bhaktapur), 021 (Biratnagar), 025 (Jhapa), 026 (Dhankuta), 027 (Bhojpur), 029 (Rajbiraj), 031 (Janakpur), 033 (Mithila), 041 (Pokhara), 061 (Butwal), 064 (Nepalgunj), 071 (Mahendranagar), 081 (Hetauda), 083 (Chitwan), 091 (Dhangadhi), 096 (Dipayal)
getPhoneType("9841234567") // → "mobile"
getPhoneType("01-1234567") // → "landline"formatNepaliPhone(phone: string): string
Formats a phone number with dashes.
formatNepaliPhone("9841234567") // → "984-123-4567"
formatNepaliPhone("01-1234567") // → "011234567"Currency
Nepali currency formatting and number-to-words conversion.
import { formatNepaliCurrency, toNepaliWords, type NepaliCurrencyOptions } from "@nepali-utils/core"formatNepaliCurrency(amount: number, options?: NepaliCurrencyOptions): string
| Option | Type | Default | Description |
|---|---|---|---|
| lang | "en" \| "ne" | "ne" | Output language for numerals |
| symbol | boolean | true | Show रु / Rs. prefix |
| decimal | boolean | true | Show decimal places |
formatNepaliCurrency(12345.67)
// → "रु १२,३४५.६७"
formatNepaliCurrency(1000, { lang: "en", symbol: true, decimal: false })
// → "Rs. 1,000"
formatNepaliCurrency(-500, { symbol: false })
// → "-५००.००"toNepaliWords(num: number): string
Converts a number to its Nepali word representation (uses लाख, करोड, etc.).
toNepaliWords(0) // → "शून्य"
toNepaliWords(100) // → "एक सय"
toNepaliWords(12345) // → "बाह्र हजार तीन सय पैंतालीस"
toNepaliWords(100000) // → "एक लाख"
toNepaliWords(-50) // → "ऋण पचास"Supports numbers up to करोड (10 million) with Nepali place value system (सय, हजार, लाख, करोड).
Calendar data
The library includes encoded calendar data for BS years 2000–2100. Each year is encoded as a 12-character string where each character represents the extra days beyond 28 for each month (Baisakh through Chaitra). This allows accurate date calculations without external data sources.
- BS 2000 corresponds to AD 1943-04-14
- Range: BS 2000–2100 (AD 1943–2043)
- Month length: 29–32 days (28 + hex digit 1–4)
API reference
Constants
MONTHS
const MONTHS: MonthInfo[]Array of 12 Bikram Sambat months. Each entry has:
value: number— 1-indexed month number (1 = Baisakh)nameEn: string— English namenameNe: string— Nepali name (Devanagari)
MONTHS[0] // { value: 1, nameEn: "Baisakh", nameNe: "बैशाख" }WEEKDAYS
const WEEKDAYS: WeekdayInfo[]Array of 7 weekdays (Sunday–Saturday). Each entry has:
value: number— 0 = Sunday, 6 = SaturdayshortEn: string— 3-letter English abbreviationshortNe: string— Nepali abbreviationlongEn: string— Full English namelongNe: string— Full Nepali name
WEEKDAYS[0] // { value: 0, shortEn: "Sun", shortNe: "आइत", longEn: "Sunday", longNe: "आइतबार" }Conversion functions
bsToAd
function bsToAd(year: number, month: number, day: number): DateConverts a Bikram Sambat date to a JavaScript Date (AD/Gregorian).
Throws Error if:
monthis outside 1–12dayis outside the valid range for the given month/year- The year is outside the supported range (2000–2100)
bsToAd(2081, 5, 15) // → Date (AD 2024-08-30)
bsToAd(2000, 1, 1) // → Date (AD 1943-04-14) — reference dateadToBs
function adToBs(date: Date): DateBSConverts a JavaScript Date (AD) to a Bikram Sambat date.
Throws Error if the date is before BS 2000-01-01 (AD 1943-04-14).
adToBs(new Date()) // → { year: 2081, month: 5, day: 15 }
adToBs(new Date(1943, 3, 14)) // → { year: 2000, month: 1, day: 1 }getTodayBs
function getTodayBs(): DateBSReturns today's date in the Bikram Sambat calendar system. Equivalent to adToBs(new Date()).
getTodayBs() // → { year: 2081, month: 5, day: 15 }getBsDayOfWeek
function getBsDayOfWeek(year: number, month: number, day: number): numberReturns the day of the week for a given BS date.
- Return value:
0(Sunday) through6(Saturday)
Throws if the date is invalid.
getBsDayOfWeek(2081, 5, 15) // → 5 (Friday)Formatting functions
formatBsDate
function formatBsDate(
date: DateBS,
lang?: BsLang, // default: "en"
options?: BsFormatOptions
): stringFormats a BS date to a string.
| Option | Type | Default | Description |
|---|---|---|---|
| pattern | BsFormatPattern | "YYYY-MM-DD" | Output format |
Supported patterns:
| Pattern | en example | ne example |
|---|---|---|
| YYYY-MM-DD | 2081-05-15 | २०८१-०५-१५ |
| YYYY MMMM DD | 2081 Bhadra 15 | २०८१ भदौ १५ |
| DD MMMM YYYY | 15 Bhadra 2081 | १५ भदौ २०८१ |
| MMMM DD, YYYY | Bhadra 15, 2081 | भदौ १५, २०८१ |
formatBsDate({ year: 2081, month: 5, day: 15 }, "en", { pattern: "YYYY MMMM DD" })
// → "2081 Bhadra 15"toNepaliNumber
function toNepaliNumber(num: number): stringConverts an Arabic numeral to Nepali Devanagari digits. Handles negative numbers (prepends -).
toNepaliNumber(2081) // → "२०८१"
toNepaliNumber(0) // → "०"
toNepaliNumber(-5) // → "-५"
toNepaliNumber(1234567890) // → "१२३४५६७८९०"parseNepaliNumber
function parseNepaliNumber(str: string): numberParses a Nepali numeral string back to an Arabic number. Non-Nepali digits pass through unchanged.
parseNepaliNumber("२०८१") // → 2081
parseNepaliNumber("१२३") // → 123
parseNepaliNumber("abc") // → NaN
parseNepaliNumber("२०८१-०५-१५") // → 20810515getPrevMonth
function getPrevMonth(year: number, month: number): { year: number; month: number }Returns the previous month. Wraps from Baisakh (month 1) to Chaitra (month 12) of the previous year.
getPrevMonth(2081, 1) // → { year: 2080, month: 12 }
getPrevMonth(2081, 5) // → { year: 2081, month: 4 }getNextMonth
function getNextMonth(year: number, month: number): { year: number; month: number }Returns the next month. Wraps from Chaitra (month 12) to Baisakh (month 1) of the next year.
getNextMonth(2081, 12) // → { year: 2082, month: 1 }
getNextMonth(2081, 5) // → { year: 2081, month: 6 }Calendar data functions
getDaysInBsMonth
function getDaysInBsMonth(year: number, month: number): numberReturns the number of days in a given BS month (range: 29–32).
Throws Error if:
monthis outside 1–12- Year is outside the supported range
getDaysInBsMonth(2081, 5) // → 32 (Bhadra has 32 days in BS 2081)
getDaysInBsMonth(2081, 1) // → 31 (Baisakh has 31 days)getDaysInBsYear
function getDaysInBsYear(year: number): numberReturns the total number of days in a given BS year (range: 354–366).
Throws Error if the year is outside the supported range.
getDaysInBsYear(2081) // → 365isValidBsYear
function isValidBsYear(year: number): booleanChecks if a BS year falls within the supported calendar data range (2000–2100).
isValidBsYear(2081) // → true
isValidBsYear(1999) // → false
isValidBsYear(2101) // → falsegetBsYearRange
function getBsYearRange(): { min: number; max: number }Returns the minimum and maximum supported BS years.
getBsYearRange() // → { min: 2000, max: 2100 }validateBsDate
function validateBsDate(year: number, month: number, day: number): voidValidates a BS date. Throws a descriptive error if invalid, returns undefined if valid.
Throws Error if:
monthis outside 1–12dayis outside the valid range for the given month and year
validateBsDate(2081, 5, 15) // → undefined (valid)
validateBsDate(2081, 13, 1) // → throws "Invalid BS month: 13"
validateBsDate(2081, 5, 33) // → throws "Invalid BS day: 33 for month 5/2081"generateCalendarGrid
function generateCalendarGrid(
year: number,
month: number,
selectedDate?: DateBS | null,
todayDate?: DateBS | null,
disabledFn?: (date: DateBS) => boolean
): CalendarDay[][]Generates a 2D array (weeks × days) of CalendarDay objects ready for rendering in a calendar UI.
Each CalendarDay:
| Field | Type | Description |
|---|---|---|
| day | number \| null | Day of month (null for padding cells) |
| date | DateBS \| null | The BS date object (null for padding) |
| isToday | boolean | Matches the current/today date |
| isSelected | boolean | Matches the selectedDate parameter |
| isDisabled | boolean | Returns true from disabledFn |
| isOutside | boolean | Padding cell before day 1 or after last day |
- The grid always has 6 rows × 7 columns (42 cells)
- Empty cells before the first day and after the last day are padding (
day: null,isOutside: true) todayDatedefaults togetTodayBs()if not provided
const weeks = generateCalendarGrid(2081, 5)
// → CalendarDay[][]
// weeks[0] = week 1 (possibly with leading nulls)
// weeks[0][0].day = null (padding for days before Baisakh 1)
// weeks[1][3].day = 10 (day 10 of Bhadra 2081)Comparison functions
isSameBsDate
function isSameBsDate(a: DateBS, b: DateBS): booleanReturns true if both dates have the same year, month, and day.
isSameBsDate(
{ year: 2081, month: 5, day: 15 },
{ year: 2081, month: 5, day: 15 }
) // → true
isSameBsDate(
{ year: 2081, month: 5, day: 15 },
{ year: 2081, month: 5, day: 16 }
) // → falseisBeforeBsDate
function isBeforeBsDate(a: DateBS, b: DateBS): booleanReturns true if date a is chronologically before date b. Compares year first, then month, then day.
isBeforeBsDate(
{ year: 2080, month: 12, day: 30 },
{ year: 2081, month: 1, day: 1 }
) // → trueisAfterBsDate
function isAfterBsDate(a: DateBS, b: DateBS): booleanReturns true if date a is chronologically after date b.
isAfterBsDate(
{ year: 2081, month: 5, day: 15 },
{ year: 2081, month: 5, day: 14 }
) // → trueLocation
Data arrays
| Export | Type | Description |
|---|---|---|
| provinces | ProvinceInfo[] | All 7 provinces (Koshi, Madhesh, Bagmati, Gandaki, Lumbini, Karnali, Sudurpashchim) |
| districts | DistrictInfo[] | All 77 districts |
| municipalities | MunicipalityInfo[] | All municipalities (includes both nagarpalika and gaupalika) |
| wards | WardInfo[] | All wards across all municipalities |
getProvinceById
function getProvinceById(id: string): ProvinceInfo | undefinedLooks up a province by its UUID. Returns undefined if not found.
getProvinceByNameEn
function getProvinceByNameEn(nameEn: string): ProvinceInfo | undefinedLooks up a province by its English name (e.g. "Bagmati Province"). Case-sensitive.
getDistrictById
function getDistrictById(id: string): DistrictInfo | undefinedLooks up a district by its UUID.
getDistrictsByProvince
function getDistrictsByProvince(provinceId: string): DistrictInfo[]Returns all districts in a given province. Returns empty array if no match.
getMunicipalityById
function getMunicipalityById(id: string): MunicipalityInfo | undefinedLooks up a municipality by its UUID.
getMunicipalitiesByDistrict
function getMunicipalitiesByDistrict(districtId: string): MunicipalityInfo[]Returns all municipalities in a given district.
getMunicipalitiesByProvince
function getMunicipalitiesByProvince(provinceId: string): MunicipalityInfo[]Returns all municipalities in a given province.
getWardsByMunicipality
function getWardsByMunicipality(municipalityId: string): WardInfo[]Returns all wards in a given municipality.
getWardsByDistrict
function getWardsByDistrict(districtId: string): WardInfo[]Returns all wards in a given district.
getWardsByProvince
function getWardsByProvince(provinceId: string): WardInfo[]Returns all wards in a given province.
const koshiProvince = getProvinceByNameEn("Koshi Province")
const koshiDistricts = getDistrictsByProvince(koshiProvince.id)
const district = koshiDistricts[0]
const municipalities = getMunicipalitiesByDistrict(district.id)
const wards = getWardsByMunicipality(municipalities[0].id)Validation
isValidNepaliMobile
function isValidNepaliMobile(phone: string): booleanValidates whether a string is a valid Nepali mobile number.
Validation rules:
- Strips spaces, dashes, and parentheses
- Strips optional
+977or977country code prefix - Must be exactly 10 digits after stripping country code
- Must start with
9 - The first 3 digits (prefix) must be a valid mobile operator prefix
Supported prefixes: 980, 981, 982, 984, 985, 986, 961, 962, 963, 974, 975, 976, 977, 978, 979, 988
isValidNepaliMobile("9841234567") // → true
isValidNepaliMobile("+9779841234567") // → true
isValidNepaliMobile("9779841234567") // → true
isValidNepaliMobile("984-123-4567") // → true (strips dashes)
isValidNepaliMobile("01-1234567") // → false (landline)
isValidNepaliMobile("12345") // → false
isValidNepaliMobile("") // → false
isValidNepaliMobile("984123456") // → false (9 digits)
isValidNepaliMobile("98412345678") // → false (11 digits)getPhoneType
function getPhoneType(phone: string): PhoneTypeReturns "mobile", "landline", or "unknown" based on the number format.
| Return value | Criteria |
|---|---|
| "mobile" | Starts with 9, exactly 10 digits, valid mobile prefix |
| "landline" | Matches a known area code prefix (01, 011, 014, 021, 025, 026, 027, 029, 031, 033, 041, 061, 064, 071, 081, 083, 091, 096) |
| "unknown" | Does not match mobile or landline patterns |
Note: Landline detection checks the 2-digit area code first, then the 3-digit area code.
getPhoneType("9841234567") // → "mobile"
getPhoneType("01-1234567") // → "landline"
getPhoneType("021-123456") // → "landline" (Biratnagar)
getPhoneType("99999999") // → "unknown"formatNepaliPhone
function formatNepaliPhone(phone: string): stringFormats a phone number with standard dashes.
- Mobile:
XXX-XXX-XXXXformat - Landline/non-mobile: returns the national number as-is (after stripping country code and punctuation)
formatNepaliPhone("9841234567") // → "984-123-4567"
formatNepaliPhone("+9779841234567") // → "984-123-4567"
formatNepaliPhone("01-1234567") // → "011234567"Currency
formatNepaliCurrency
function formatNepaliCurrency(
amount: number,
options?: NepaliCurrencyOptions
): string| Option | Type | Default | Description |
|---|---|---|---|
| lang | "en" \| "ne" | "ne" | Output numerals: Nepali (Devanagari) or English |
| symbol | boolean | true | Prepend currency symbol: रु (ne) or Rs. (en) |
| decimal | boolean | true | Include .XX decimal places |
Behavior:
- Uses Indian number grouping (
toLocaleString("en-IN")) for integer formatting - Negative amounts get a
-prefix after the symbol - Decimals are rounded to 2 places
formatNepaliCurrency(12345.67)
// → "रु १२,३४५.६७"
formatNepaliCurrency(12345.67, { lang: "en" })
// → "Rs. 12,345.67"
formatNepaliCurrency(1000, { symbol: false })
// → "१,०००.००"
formatNepaliCurrency(1000, { decimal: false })
// → "रु १,०००"
formatNepaliCurrency(-500)
// → "रु -५००.००"
formatNepaliCurrency(0)
// → "रु ०.००"toNepaliWords
function toNepaliWords(num: number): stringConverts a number to its Nepali word representation using the traditional place value system.
| Place | Value | Nepali | |---|---|---| | सय | 100 | Hundred | | हजार | 1,000 | Thousand | | लाख | 100,000 | Lakh | | करोड | 10,000,000 | Crore |
Supported range: 0 to 99,99,99,999 (under 10 करोड), and negative numbers.
toNepaliWords(0) // → "शून्य"
toNepaliWords(5) // → "पाँच"
toNepaliWords(10) // → "दश"
toNepaliWords(21) // → "एक्काइस"
toNepaliWords(100) // → "एक सय"
toNepaliWords(115) // → "एक सय पन्ध्र"
toNepaliWords(1000) // → "एक हजार"
toNepaliWords(1001) // → "एक हजार एक"
toNepaliWords(12345) // → "बाह्र हजार तीन सय पैंतालीस"
toNepaliWords(100000) // → "एक लाख"
toNepaliWords(10000000) // → "एक करोड"
toNepaliWords(-50) // → "ऋण पचास"
toNepaliWords(-1) // → "ऋण एक"License
MIT
