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

@nbtca/nbtcal

v0.4.4

Published

Data-only calendar library for public feeds and authenticated personal timetables.

Readme

@nbtca/nbtcal

Typed ESM library for NBTCA calendar feeds and personal academic timetables. It handles parsing, recurrence expansion, date queries, heatmaps, and ICS generation without owning credentials, sessions, files, or UI.

Install

npm install @nbtca/nbtcal

Usage

import { loadCalendar } from '@nbtca/nbtcal';

const calendar = await loadCalendar();
const start = new Date('2026-09-01T00:00:00Z');
const end = new Date('2026-10-01T00:00:00Z');

const upcoming = calendar.upcoming({ days: 30 });
const nextFive = calendar.next(5);
const range = calendar.inRange(start, end);
const dailyCounts = calendar.heatmap({ start, end, bucket: 'day' });

Recurring events are expanded within each query window. Heatmaps are dense and include zero-count day or week buckets. Lower-level feed, parser, query, and ICS functions are available from the package root.

To skip downloading an unchanged feed, keep the validators from the last response and send them back:

import { fetchFeedConditional } from '@nbtca/nbtcal';

const result = await fetchFeedConditional(undefined, { validators: saved.validators });
const text = result.status === 'modified' ? result.text : saved.text;
saved = { text, validators: result.validators };

School calendar

The school calendar is a separate feed. Pass its events to the academic helpers to get the current term, week, or break:

import {
  SCHOOL_FEED_URL,
  loadCalendar,
  currentAcademicWindow,
  inferWeekOneMonday,
} from '@nbtca/nbtcal';

const school = await loadCalendar({ url: SCHOOL_FEED_URL });
const now = new Date();
const span = 400 * 86400000;
const events = school.inRange(new Date(now.getTime() - span), new Date(now.getTime() + span));
const window = currentAcademicWindow(events, now); // inTerm, onBreak, or null
const weekOne = inferWeekOneMonday(events, now); // 'YYYY-MM-DD' or null

Personal timetable

The @nbtca/nbtcal/timetable subpath accepts an authenticated transport for the campus JWXT protocol:

import {
  createNbtTimetableClient,
  createTimetableSchedule,
  findAcademicTerm,
  timetableToIcs,
} from '@nbtca/nbtcal/timetable';

const client = createNbtTimetableClient(authenticatedTransport, {
  baseUrl: 'https://jwxt-443.webvpn.nbt.edu.cn',
});

const terms = await client.listTerms();
const current = findAcademicTerm(terms);
if (!current) throw new Error('No current academic term');
const timetable = await client.fetchTerm(current);
const schedule = createTimetableSchedule(timetable, { weekOneMonday: '2026-09-07' });
const week = schedule.weekAt(new Date());
const today = schedule.meetingsOnDay(week, schedule.weekdayAt(new Date()));
const nextClass = schedule.next();

const ics = timetableToIcs(timetable, {
  // Confirm this date for the selected term.
  weekOneMonday: '2026-09-07',
});

findAcademicTerm accepts an opaque year:code selector or the year-1, year-2, and year-3 semester aliases.

The host injects an authenticated transport; this package never receives credentials or a cookie jar. If JWXT omits authoritative calendar dates, weekOneMonday is required. Malformed rows produce structured warnings, and unresolved practice rows retain only allowlisted, identity-free fields.

Schedules run on campus time (Asia/Shanghai) whatever the host zone. campusIsoDate(date), campusDateTime('2026-09-07', '08:00') and campusWeekday(date) convert between instants and campus dates.

Quality checks

npm run check

License

MIT