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

@peterseibel/bells

v0.1.10

Published

A framework-agnostic JavaScript library for querying school bell schedules. Built on the [Temporal API](https://tc39.es/proposal-temporal/).

Readme

bells

A framework-agnostic JavaScript library for querying school bell schedules. Built on the Temporal API.

Installation

npm install bells @js-temporal/polyfill

@js-temporal/polyfill is a peer dependency. In Node 22+ you can use the native Temporal global and omit the polyfill.

Calendar data format

Calendar data is an array of year objects, one per academic year:

[
  {
    "year": "2025-2026",
    "timezone": "America/Los_Angeles",
    "firstDay": "2025-08-13",
    "firstDayTeachers": "2025-08-11",
    "lastDay": "2026-06-04",
    "schedules": {
      "default": {
        "NORMAL": [
          { "name": "Period 0",   "start": "7:26",  "end": "8:24",  "tags": ["optional", "zero"] },
          { "name": "Period 1",   "start": "8:30",  "end": "9:28" },
          { "name": "Period 2",   "start": "9:34",  "end": "10:37" },
          { "name": "Period 3",   "start": "10:43", "end": "11:41" },
          { "name": "Lunch",      "start": "11:41", "end": "12:21" },
          { "name": "Period 4",   "start": "12:27", "end": "1:25" },
          { "name": "Period 5",   "start": "1:31",  "end": "2:29" },
          { "name": "Period 6",   "start": "2:35",  "end": "3:33" },
          { "name": "Period 7",   "start": "3:39",  "end": "4:37",  "tags": ["optional", "seventh"] },
          { "name": "Period Ext", "start": "3:39",  "end": "5:09",  "tags": ["optional", "ext"] }
        ],
        "LATE_START": [
          { "name": "Staff meeting", "start": "8:03", "end": "9:33", "teachers": true },
          { "name": "Period 1",      "start": "10:00", "end": "10:43" }
        ]
      },
      "2025-08-13": [
        { "name": "Orientation", "start": "8:30", "end": "15:00" }
      ]
    },
    "holidays": ["2025-09-01", "2025-11-27"],
    "teacherWorkDays": ["2025-11-27"],
    "breakNames": {
      "2025-11-24": "Thanksgiving Break"
    }
  }
]

Fields

  • year — academic year label (e.g. "2025-2026")
  • timezone — IANA timezone identifier (e.g. "America/Los_Angeles")
  • firstDay — first student day ("YYYY-MM-DD")
  • firstDayTeachers — first teacher day, if different (optional)
  • lastDay — last day of school ("YYYY-MM-DD")
  • schedules.default.NORMAL — default daily schedule
  • schedules.default.LATE_START — schedule used on Mondays
  • schedules["YYYY-MM-DD"] — schedule override for a specific date
  • holidays — array of holiday date strings
  • teacherWorkDays — holiday dates that teachers still work
  • breakNames — map of date string to break name (used in "Break!" interval labels)

Time strings

Times may omit the leading 24-hour prefix for PM hours. "1:25" is resolved to 13:25 by context (each time must be after the previous one in the sequence). Times with hour ≥ 13 are unambiguous.

Tags

Periods may have a tags array:

  • A period with no "optional" tag is always included.
  • A period tagged "optional" is included only if one of its other tags appears in the caller's includeTags config for that day.
  • A period tagged ["optional"] alone is never included (always trimmed from day boundaries).

The teachers boolean field marks teacher-only periods that are excluded for students.

API

BellSchedule

import { BellSchedule } from 'bells';
import calendarData from './my-calendars.json' with { type: 'json' };

const bells = new BellSchedule(calendarData, {
  role: 'student',        // 'student' | 'teacher'  (default: 'student')
  includeTags: {
    1: ['seventh'],       // Monday: include Period 7
    2: ['zero', 'seventh'],
    3: ['seventh'],
    4: ['seventh'],
    5: ['seventh'],
  },
  // Or use a flat array for the same tags every weekday:
  // includeTags: ['seventh']
});

// What's happening right now?
const interval = bells.currentInterval();
console.log(interval.name);       // e.g. "Period 3"
console.log(interval.type);       // 'period' | 'passing' | 'before-school' | 'after-school' | 'break'
console.log(interval.left());     // Temporal.Duration until end of interval

// Other queries (all accept optional Temporal.Instant):
bells.periodAt()                  // Period | null (null if passing/break)
bells.isSchoolDay()               // boolean (accepts optional Temporal.PlainDate)
bells.currentDayBounds()          // { start, end } | null
bells.nextSchoolDayStart()        // Temporal.Instant
bells.previousSchoolDayEnd()      // Temporal.Instant
bells.schoolTimeLeft()            // Temporal.Duration
bells.schoolTimeDone()            // Temporal.Duration
bells.totalSchoolTime()           // Temporal.Duration
bells.schoolDaysLeft()            // number
bells.calendarDaysLeft()          // number
bells.nextYearStart()             // Temporal.Instant (throws if not loaded)
bells.schoolTimeBetween(a, b)     // Temporal.Duration
bells.summerBounds()              // { start, end } | null

Calendars

For loading per-year JSON files from a directory or URL:

import { Calendars } from 'bells';

const calendars = new Calendars('./calendars/');
// or: new Calendars('https://example.com/calendars/');

// Load a specific year:
const bells = await calendars.forYear('2025-2026', options);

// Load whatever is appropriate for right now (handles summer automatically):
const bells = await calendars.current(options);

Files must be named {year}.json (e.g. 2025-2026.json). In Node.js, paths are read with fs.readFile. Under a URL base, fetch() is used.

Validation

import { validateCalendarData } from 'bells/validate';

const { valid, errors } = validateCalendarData(data);
if (!valid) {
  console.error(errors);
}

CLI:

npx bells-validate calendars.json

Checks include required fields, valid timezone, date range consistency, unambiguous time strings, start < end for every period, and no overlapping non-optional periods.