Skip to content

Repository files navigation

@nbtca/nbtcal

Typed ESM library for three NBTCA calendars: the club events feed, the school calendar, and a student's timetable from the campus system. It parses feeds, expands recurring events, answers date queries, builds heatmaps, and writes ICS. It never handles 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 include zero-count buckets. To skip downloading an unchanged feed, send back the validators from the last response:

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 the authenticated transport, so this package never sees credentials or cookies. Pass weekOneMonday when JWXT omits term dates; inferWeekOneMonday can supply it from the school calendar. Malformed rows produce structured warnings, and unresolved practice rows keep only allowlisted, non-identifying 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.

Development

npm run check

License

MIT

About

Calendar library for the NBTCA ICS feed

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages