DEV Community

Cover image for Meet bsday.js — The Ultra-Fast, Zero-Dependency Bikram Sambat (BS) Calendar Engine for JavaScript & TypeScript 🇳🇵
Shuraj Shampang
Shuraj Shampang

Posted on Originally published at bsdayjs.vercel.app

Meet bsday.js — The Ultra-Fast, Zero-Dependency Bikram Sambat (BS) Calendar Engine for JavaScript & TypeScript 🇳🇵

If you've ever built a web or mobile application in Nepal, you know the struggle.

Handling Bikram Sambat (BS) dates in JavaScript has historically been painful:

  • Legacy libraries from 2014 with outdated Date prototype modifications.
  • No Day.js / date-fns syntax parity, forcing you to learn awkward, proprietary helper functions.
  • Bloated bundles loaded with megabytes of un-treeshakable JSON.
  • Missing TypeScript types or broken CJS/ESM compatibility in modern bundlers (Next.js App Router, Vite, Turbopack).
  • No headless UI support, forcing you to use un-themeable, rigid datepicker widgets from the jQuery era.
  • Zero built-in support for Nepali Fiscal Years (आर्थिक वर्ष), Banking Workdays (T+2 settlements), KYC age verification, or Devanagari numerals (०-९).

Today, that changes. 🎉

Introducing bsday.js — an ultra-fast, zero-dependency, Day.js-compatible dual calendar (Bikram Sambat ↔ Gregorian) SDK and 111-year astronomical Panchang engine for modern JavaScript and TypeScript.

🌟 What is bsday.js?

bsday.js is a modern, modular ecosystem built from the ground up for modern web development:

bsday.js Ecosystem
├── 📦 @bsday.js/core       # ~12KB gzipped, zero-dep dual calendar engine with Day.js API parity
├── 🕉️ @bsday.js/dataset    # 111-Year (1990–2100 BS / 40,543 days) astronomical Panchang & festivals
├── ⚛️ @bsday.js/react      # Headless React hooks (useBSDatePicker, useBSRangePicker, WAI-ARIA)
├── 🟢 @bsday.js/vue        # Vue 3 Composition API composables with v-model support
├── 🅰️ @bsday.js/angular    # Headless Angular Signals & Reactive Form ControlValueAccessor
└── 🟧 @bsday.js/svelte     # Svelte 4/5 reactive stores and runes primitives
Enter fullscreen mode Exit fullscreen mode

🌐 Interactive Playground: https://bsdayjs.vercel.app

📖 Full Documentation: https://bsdayjs.vercel.app/docs

🐙 GitHub Repository: https://github.com/shurajcodx/bsday.js

🚀 Quick start in 60 Seconds

Install the core package:

npm install @bsday.js/core
# or
pnpm add @bsday.js/core
# or
yarn add @bsday.js/core
Enter fullscreen mode Exit fullscreen mode

1. Familiar Day.js syntax parity

If you know Day.js or Moment.js, you already know bsday.js:

import bsday from '@bsday.js/core';

// 1. Create a Bikram Sambat Date
const today = bsday(); // Current BS date
const customDate = bsday.bs(2081, 5, 15); // Bhadra 15, 2081

// 2. Chaining & Date Arithmetic
const nextMonth = customDate.add(1, 'month').subtract(2, 'day');
console.log(nextMonth.format('YYYY/MM/DD')); // "2081/06/13"

// 3. Bidirectional BS ↔ AD Conversion
const adDate = customDate.toAD(); // Native JavaScript Date: 2024-08-31
const bsFromAD = bsday(new Date('2024-08-31')); // Converted back to BS

// 4. Start/End of Unit
console.log(customDate.startOf('month').format('YYYY/MM/DD')); // "2081/05/01"
console.log(customDate.endOf('month').format('YYYY/MM/DD'));   // "2081/05/31"

// 5. Query & Comparisons
console.log(customDate.isBefore(nextMonth)); // true
console.log(customDate.isSame(bsday.bs(2081, 5, 15), 'day')); // true
Enter fullscreen mode Exit fullscreen mode

🇳🇵 6 Killer features you'll love

1. Direct devanagari numeral string parsing (०-९)

Users in Nepal often paste or type dates in Devanagari script. bsday.js handles both ASCII and Devanagari numerals natively:

import bsday, { normalizeNepaliDigits, toDevanagariDigits } from '@bsday.js/core';

// Parse Devanagari string directly!
const date = bsday.bs('२०८१/०५/१५');
console.log(date.format('YYYY-MM-DD')); // "2081-05-15"

// Localize to Nepali with Devanagari output
console.log(date.locale('ne').format('YYYY MMMM DD, dddd'));
// Output: "२०८१ भाद्र १५, शनिबार"

// Standalone conversion helpers
console.log(toDevanagariDigits('2081/05/15')); // "२०८१/०५/१५"
console.log(normalizeNepaliDigits('२०८१/०५/१५')); // "2081/05/15"
Enter fullscreen mode Exit fullscreen mode

2. Built-in nepali banking & business day engine 🏦

In Nepal, the workweek runs from Sunday to Friday, with Saturday as the national weekend. Public holidays during Dashain, Tihar, or Shivaratri alter settlement deadlines (T+2 in NEPSE / clearing house SLA).

bsday.js has a first-class business day calculation engine:

import bsday from '@bsday.js/core';

const friday = bsday.bs(2081, 5, 14); // Friday

// Check business day status
console.log(friday.isBusinessDay()); // true
console.log(friday.isSaturday);      // false

// Add business days (automatically skips Saturdays and holidays!)
const nextWorkday = friday.addBusinessDays(1);
console.log(nextWorkday.format('YYYY/MM/DD')); // Sunday: 2081/05/16 (skips Saturday!)

// Calculate workdays between two dates
const start = bsday.bs(2081, 5, 1);
const end = bsday.bs(2081, 5, 15);
console.log(start.businessDaysBetween(end)); // Exact count excluding Saturdays
Enter fullscreen mode Exit fullscreen mode

3. Nepali fiscal year (आर्थिक वर्ष) Engine 📊

Nepali fiscal years start on Shrawan 1 and end on Ashadh End. Calculating tax quarters and fiscal representations (FY 2081/82 or आ.व. २०८१/८२) is now a one-liner:

import bsday from '@bsday.js/core';

const taxDate = bsday.bs(2081, 5, 15); // Bhadra 2081

console.log(taxDate.fiscalYear('short'));     // "2081/82"
console.log(taxDate.fiscalYear('extended'));  // "FY 2081/82"
console.log(taxDate.locale('ne').fiscalYear('extended')); // "आ.व. २०८१/८२"

// Fiscal Quarters (Q1: Shrawan–Ashwin, Q2: Kartik–Poush, etc.)
console.log(taxDate.fiscalQuarter()); // 1

// Start and End of Fiscal Year
const fyStart = taxDate.startOf('fiscalYear'); // 2081/04/01 (Shrawan 1)
const fyEnd = taxDate.endOf('fiscalYear');     // 2082/03/31 (Ashadh 31)
Enter fullscreen mode Exit fullscreen mode

4. KYC & chronological age verification 🆔

Building banking KYC or onboarding verification? Calculate precise chronological age in years, months, and days:

import bsday from '@bsday.js/core';

const dob = bsday.bs(2062, 10, 5); // Magh 5, 2062

// Check legal age threshold
console.log(dob.isAdult(18)); // true

// Exact breakdown
const ageInfo = dob.age();
console.log(ageInfo); // { years: 19, months: 7, days: 10 }
console.log(dob.formatAge()); // "19 years, 7 months, 10 days"
Enter fullscreen mode Exit fullscreen mode

5. 111-Year astronomical vedic panchang dataset 🕉️

Need Tithi, Nakshatra, Yoga, Karana, Sunrise/Sunset, or Dashain/Tihar festival dates?

Install the dataset package:

npm install @bsday.js/dataset
Enter fullscreen mode Exit fullscreen mode
import { bsday, BSDay } from '@bsday.js/core';
import { dataset } from '@bsday.js/dataset/all';

// Hydrate 111-year dataset (1990–2100 BS / 40,543 verified days)
BSDay.setDataset(dataset);

const dashami = bsday.bs(2081, 6, 26).locale('ne');

console.log(dashami.tithi);     // "दशमी" (Dashami)
console.log(dashami.festivals); // ["विजया दशमी"]
console.log(dashami.isHoliday);  // true

// Astronomical details
const astro = dashami.data('en');
console.log(astro?.nakshatra);   // "Shravana"
console.log(astro?.sunrise);     // "06:04 AM"
Enter fullscreen mode Exit fullscreen mode

6. Headless react hook (@bsday.js/react) ⚛️

Build stunning, custom Tailwind CSS datepickers without fighting opinionated styles. Full WAI-ARIA accessibility and keyboard navigation included!

npm install @bsday.js/react @bsday.js/core
Enter fullscreen mode Exit fullscreen mode
import React from 'react';
import { useBSDatePicker } from '@bsday.js/react';

export function NepaliDatePicker() {
  const {
    isOpen,
    setIsOpen,
    viewYear,
    viewMonth,
    selectedDate,
    calendarMatrix,
    selectDate,
    nextMonth,
    prevMonth,
  } = useBSDatePicker({
    defaultValue: '2081/05/15',
    closeOnSelect: true,
  });

  return (
    <div className="relative inline-block">
      {/* Trigger Input */}
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="px-4 py-2 bg-white dark:bg-slate-900 border rounded-xl shadow-sm text-sm font-medium"
      >
        📅 {selectedDate ? selectedDate.format('YYYY/MM/DD') : 'Select BS Date'}
      </button>

      {/* Calendar Dropdown */}
      {isOpen && (
        <div className="absolute top-12 left-0 z-50 p-4 bg-white dark:bg-slate-950 border rounded-2xl shadow-xl w-72">
          {/* Header */}
          <div className="flex items-center justify-between mb-3">
            <button onClick={prevMonth} className="p-1 hover:bg-slate-100 rounded-lg"></button>
            <span className="font-semibold text-sm">
              {viewYear} Month {viewMonth}
            </span>
            <button onClick={nextMonth} className="p-1 hover:bg-slate-100 rounded-lg"></button>
          </div>

          {/* 6x7 42-Cell Grid */}
          <div className="grid grid-cols-7 gap-1 text-center text-xs">
            {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map((d) => (
              <div key={d} className="font-semibold text-slate-400 py-1">{d}</div>
            ))}
            {calendarMatrix.map((cell, idx) => (
              <button
                key={idx}
                disabled={!cell.isCurrentMonth}
                onClick={() => selectDate(cell.date)}
                className={`h-8 w-8 rounded-lg flex items-center justify-center transition-all ${
                  !cell.isCurrentMonth
                    ? 'text-slate-300 opacity-40'
                    : cell.isSelected
                    ? 'bg-indigo-600 text-white font-bold'
                    : cell.isToday
                    ? 'border border-indigo-500 text-indigo-600'
                    : 'hover:bg-slate-100 text-slate-700 dark:text-slate-200'
                }`}
              >
                {cell.day}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

⚡ Framework support matrix

Framework Package Features
Vanilla / Node.js @bsday.js/core Zero dependencies, ~12KB gzipped, 100% TypeScript
React / Next.js @bsday.js/react useBSDatePicker, useBSRangePicker, useBSCalendarGrid
Vue 3 / Nuxt @bsday.js/vue Composition API composables with v-model binding
Angular @bsday.js/angular Signals-based services & Reactive Form Directives
Svelte @bsday.js/svelte Svelte 4/5 reactive stores & runes calendar primitives

📊 Performance & bundle size

Feature / Metric @bsday.js/core Legacy Libraries
Bundle Size (Gzipped) ~12 KB 45 KB – 120 KB
Runtime Dependencies 0 (Zero) 2–6 dependencies
Day.js API Parity ✅ 100% Parity ❌ Proprietary APIs
Devanagari Parsing ✅ Native ❌ Manual regex
Banking / Workday Engine ✅ Built-in ❌ Not available
Fiscal Year (आ.व.) Support ✅ Built-in ❌ Not available
Headless UI Primitives ✅ React/Vue/Angular/Svelte ❌ jQuery / Vanilla DOM only
TypeScript Support ✅ 100% Strict ⚠️ Partial or .d.ts missing

🛠️ Backend & schema validation (zod example)

Easily validate Bikram Sambat date inputs in your backend APIs or frontend forms:

import { z } from 'zod';
import { isValidBSDate } from '@bsday.js/core';

export const UserRegistrationSchema = z.object({
  fullName: z.string().min(2),
  dateOfBirthBS: z.string().refine(
    (val) => isValidBSDate(val),
    { message: 'Invalid Bikram Sambat date format (expected YYYY/MM/DD)' }
  ),
});
Enter fullscreen mode Exit fullscreen mode

🤝 Try it out & get involved!

bsday.js is 100% open-source under the MIT License.

If you find this project useful for your apps in Nepal, please consider starring the repository on GitHub ⭐ and sharing it with your fellow developers!

Let me know in the comments below: what features or framework recipes would you like to see next? 💬🚀

Top comments (0)