npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@phrontax/calendar

v1.4.0

Published

Dual Gregorian and Bikram Sambat date engine and React date pickers

Readme

@phrontax/calendar

Gregorian and Bikram Sambat dates in one package: a zero-dependency conversion engine plus React pickers that show both calendars at once.

Built for PhronTax, where a Nepali business books in Gregorian dates but files to the Inland Revenue Department in Bikram Sambat.

pnpm add @phrontax/calendar

Engine

No React, no dependencies — usable from a browser, a server or a worker.

import { adToBs, bsToAd, cbmsDate, cbmsFiscalYear, nepaliFiscalYear } from "@phrontax/calendar";

adToBs("2026-08-31");        // { year: 2083, month: 5, day: 15 }
bsToAd({ year: 2083, month: 5, day: 15 }); // { year: 2026, month: 8, day: 31 }

cbmsDate("2026-08-31");      // "2083.05.15"   the IRD's dotted form
cbmsFiscalYear("2026-08-31"); // "2083.084"

nepaliFiscalYear("2026-08-31");
// { startBsYear: 2083, startsOn: "2026-07-17", endsOn: "2027-07-16",
//   label: "2083/84", cbms: "2083.084" }

The Nepali fiscal year runs Shrawan 1 to the last day of Ashadh, so it turns over in mid-July rather than at either new year.

Pickers

import { DatePicker, DateRangePicker } from "@phrontax/calendar/react";
import "@phrontax/calendar/styles.css";

<DatePicker value={issueDate} onChange={setIssueDate} min={period.startsOn} />
<DateRangePicker value={{ from, to }} onChange={setRange} />

value is always a Gregorian ISO YYYY-MM-DD string, whichever calendar the grid is showing. That makes DatePicker a drop-in replacement for <input type="date"> with no change to what a form submits.

Both pickers give you the AD grid with each day's Bikram Sambat number beneath it, an AD/BS toggle, month and year jump menus, typed entry in either calendar (2083.05.15 is accepted), min/max bounds, and the full ARIA keyboard model — arrows, Home/End, PageUp/PageDown, Enter, Escape.

DateRangePicker adds presets, including This fiscal year, which resolves to the Nepali fiscal year rather than the Gregorian one.

Options

Both pickers take these, and pass them to the panel:

| Prop | Does | | --- | --- | | locale | "en", "ne", or a locale of your own (below) | | firstDayOfWeek | 0 Sunday (the default, as Nepal writes it), 1 Monday, … | | disabledDate | (date: PlainDate) => boolean — holidays, weekends, closed periods | | yearRange | which years the dropdown offers, per calendar: { ad: { from, to } } | | showCalendarToggle | hide the AD/BS switch when the form has settled the question | | showToday | hide the Today button | | min / max | inclusive Gregorian ISO bounds |

DateRangePicker also takes presets (replacing the Nepali defaults), showPresets, fromLabel / toLabel, and displayStyle / displayPattern for how the two chosen dates read in the closed control. Whatever they render, the value it reports stays Gregorian ISO.

Languages

"en" and "ne" are built in. Anything else is data — month and weekday names, digits, which day the week starts on, how a date reads, and every word the pickers say:

import { extendLocale } from "@phrontax/calendar";

// Nepali words, Monday weeks, nothing else retyped.
const locale = extendLocale("ne", { firstDayOfWeek: 1 });

// Or disagree with the romanisation, which people do: Ashwin and Ashoj are the
// same month, and so are Baisakh and Baishakh.
const ashoj = extendLocale("en", {
  bsMonths: ["Baishakh", "Jestha", "Ashar", "Shrawan", "Bhadra", "Ashoj",
             "Kartik", "Mangsir", "Poush", "Magh", "Falgun", "Chaitra"],
  patterns: { medium: "MMM D, YYYY" },
  messages: { today: "Aaja" },
});

Anything a locale leaves out falls back to English, so wanting Monday weeks and nothing else is locale={{ firstDayOfWeek: 1 }}. Digits follow the locale's digits rather than its name, so Devanagari numerals with English month names — how a good deal of Nepal writes a date — is a locale you can describe rather than a special case the package has to bless.

Formatting

formatDate takes a style ("iso", "medium", "long") or a pattern, and formatPattern takes one directly. Tokens are YYYY YY MMMM MMM MM M DD D; anything else is copied through, and text in square brackets is passed through untouched.

formatDate("2026-08-31", { calendar: "bs", locale: "ne", style: "long" });
// "१५ भदौ २०८३"
formatPattern({ year: 2083, month: 5, day: 15 }, "[BS] YYYY.MM.DD"); // "BS 2083.05.15"

One calendar, or both

Every cell carries both readings by default — the Gregorian day large, the Bikram Sambat day beneath it — which is what an operator filing to the IRD needs. A business that works in one calendar and reads the other nowhere can turn the second number off, and defaultCalendar decides which one is left:

// Gregorian only: no Bikram Sambat day, no BS badge in the field.
<DatePicker value={date} onChange={setDate}
  showSecondaryDates={false} showCalendarToggle={false} showBsHint={false} />

// Bikram Sambat only: the grid, the heading and the months are all BS.
<DatePicker value={date} onChange={setDate} defaultCalendar="bs"
  showSecondaryDates={false} showCalendarToggle={false} />

The field reads in whichever calendar you ask for, in whichever form:

<DatePicker value={date} onChange={setDate} displayCalendar="bs" />
// 2083.07.16 — the form the IRD's own documents use, and the default for BS

<DatePicker value={date} onChange={setDate} displayCalendar="bs"
  displayPattern="YYYY-MM-DD" />          // 2083-07-16
<DatePicker value={date} onChange={setDate} displayPattern="YYYY/MM/DD" />
                                          // 2026/11/02
<DatePicker value={date} onChange={setDate} displayCalendar="bs"
  displayPattern="D MMMM YYYY" />         // 16 Kartik 2083

The pattern takes YYYY YY MMMM MMM MM M DD D and [literals] in brackets — the same tokens as formatPattern, and the same prop name the range picker has always had. The badge beside the field carries whichever calendar the field is not showing, so one reading is always a glance away.

The stored value never changes shape. It is Gregorian ISO on the wire whatever is on screen, so a Bikram Sambat picker is a reading of the same field and not a different one — nothing downstream has to know which form an operator prefers. Typing follows the panel's calendar, then the field's, then Gregorian, so a bare 2083.05.15 means Bikram Sambat to someone working in it and an ISO date pasted from elsewhere is still accepted. A pattern with month names displays correctly but cannot be typed back; the field restores rather than guesses.

Time, and time zones

A date is a day — the same day everywhere. A moment is not, and reading one means choosing a clock. DateTimePicker keeps those apart: it stores one ISO instant and reads it in the zone you give it.

import { DateTimePicker } from "@phrontax/calendar/react";

<DateTimePicker
  value={countedAt}            // "2026-09-30T08:45:00.000Z"
  onChange={setCountedAt}
  timeZone="Asia/Kathmandu"    // defaults to the viewer's own
/>

Pass the organisation's zone where the time belongs to the business rather than to whoever is looking: a stocktake happened at 14:30 in Kathmandu whether it is read there or in London, and letting the browser decide makes one record say two different things.

The panel carries the month and the clock together: hour and minute columns sit beside the grid, showing the values either side of the chosen one, which is how people read a clock. step thins the minute column — step={15} offers the quarters — and a stored reading that falls between them is still shown, because a column that silently disagreed with the field above it would be worse than a long one. Typing stays the faster path for anyone entering a run of records: the field takes 2026-09-30 14:30 whole.

The engine is usable on its own, and has no dependencies — the zone database is the runtime's:

import { endOfDayInstant, instantToZoned, zonedToInstant } from "@phrontax/calendar";

endOfDayInstant("2026-09-30", "Asia/Kathmandu");  // "2026-09-30T18:14:59.999Z"
endOfDayInstant("2026-09-30", "UTC");             // "2026-09-30T23:59:59.999Z"

That difference is the point. Appending T23:59:59.999Z to a date is end of day in Greenwich; in Kathmandu it is a quarter to six the following morning, and in Honolulu it is the middle of the same afternoon.

Daylight saving is resolved at the instant, not from a table, so a whole-hour zone and a thirty-minute one behave alike. A time the clocks skipped resolves forward past the gap; a time they repeated resolves to its first occurrence — the same rule the Temporal proposal uses.

Theming

Every colour is a CSS custom property; redefine them on any ancestor.

.my-app { --pcal-accent: #185c43; --pcal-radius: 10px; }

The full set: --pcal-bg, --pcal-fg, --pcal-muted, --pcal-line, --pcal-accent, --pcal-accent-fg, --pcal-accent-fg-soft, --pcal-hover, --pcal-today, --pcal-range-bg, --pcal-shadow, --pcal-radius, --pcal-font. Every rule in the stylesheet carries two classes, so a host form styling .some-form select does not tie with it and win on load order.

Supported range

BS 2000–2099 (AD 1943-04-14 to 2043-04-12), in two halves:

| Range | Status | | --- | --- | | BS 2000–2083 | Verified. Four independently published tables agree year by year. | | BS 2084–2099 | Projected. Converts and displays, but not corroborated. |

Bikram Sambat month lengths follow no formula — they are fixed each year by the Nepal Panchanga Nirnayak Samiti from the sun's transit between zodiac signs. Past 2083 the published tables diverge, and every source reaching that far derives it by assuming the calendar repeats on a roughly nineteen-year cycle rather than from an almanac. Two of the four candidate tables are also arithmetically impossible within a few years of 2085 — a BS year cannot have 364, 367 or 368 days — and were discarded.

So projected years convert, but they never reach a tax filing:

adToBs("2030-06-01");            // { year: 2087, ... } — works
isProvisionalBsDate("2030-06-01"); // true
cbmsDate("2030-06-01");          // undefined — refuses
cbmsFiscalYear("2030-06-01");    // undefined — refuses

DatePicker shows a projected date with a caveat rather than a CBMS date. Outside the table entirely, conversion returns undefined and the pickers show the Gregorian date alone. Nothing is ever extrapolated silently.

The table is guarded by tests across all 100 years: every year sums to 365 or 366, every month is 29–32 days, Nepali New Year falls on 13 or 14 April, and every one of ~36,500 days round-trips AD → BS → AD.

Contributing

This repository is a read-only mirror. The package is developed alongside the application it was built for, in a private repository, and this mirror is regenerated from it at each release — so a pull request opened here cannot be merged and would be overwritten by the next release.

Issues are the way in, and they are read: bug reports, a month name your part of Nepal spells differently, a locale you had to fight the API to describe. Attach a patch to the issue if you have one and it will be applied with credit in the changelog.

Building it yourself needs nothing from the private repository — it has no workspace dependencies and its TypeScript configuration stands alone:

npm install && npm test && npm run build

Licence

Apache-2.0 © Phronetic Labs Pvt. Ltd. See LICENSE and NOTICE.