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

everwhen

v0.3.0

Published

Parse natural-language recurrence rules (English + French) and compute their occurrences.

Readme

everwhen

Parse natural-language recurrence rules (English and French) and compute their occurrences. Small, dependency-light, strict TypeScript (ESM).

import { nextOccurrence, validate } from "everwhen";

nextOccurrence("every weekday at 09:00", { now: "2026-03-12T12:00:00" });
// -> Date for 2026-03-13 09:00 (Europe/Paris)

nextOccurrence("tous les mois le dernier vendredi à 18h00", { now: "2026-03-12T12:00:00" });
// -> Date for 2026-03-27 18:00

validate("every day at 10:00 until 2026-03-13 except 2026-03-13"); // throws InvalidRuleError

Install

npm install everwhen

As a git dependency:

npm install github:coality/everwhen

Runtime dependency: luxon (timezone math). Node 18+.

API

nextOccurrence(text, options?) => Date

Returns the next occurrence strictly after now.

  • options.now — reference instant: a Date (absolute) or an ISO string (interpreted as wall-clock in the schedule's zone). Defaults to the current time.
  • options.tz — default IANA zone when the rule doesn't specify one (default Europe/Paris). A rule can override it inline: … in America/New_York (EN) or … (America/New_York) (FR).

Throws InvalidRuleError for empty / unsupported / structurally-invalid rules, and NoOccurrenceError when a well-formed rule has no future occurrence (e.g. its window has passed).

occurrences(text, options?) => Date[]

Returns up to options.count upcoming occurrences (default 10), each strictly after the previous. Stops early when the rule is exhausted (window ended or repetition limit reached). Same now / tz options as nextOccurrence.

validate(text, options?) => true

Returns true when the rule is well-formed and has an occurrence within a ~1-year horizon; throws InvalidRuleError otherwise.

Parsing (no engine)

  • parseSchedule(text, defaultTz?) / parseRule(text) — auto-detect the language (EN/FR) and parse to the IR, falling back to the other grammar on failure.
  • parseScheduleEn / parseRuleEn, parseScheduleFr / parseRuleFr — force a grammar.
  • detectLanguage(text) => "en" | "fr".

All IR types (IRSchedule, IRRule, …) and the error classes are exported.

Supported grammar

The same rules work in English and French; the formatters and parser accept either.

| Kind | English | French | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Daily | every day at 10:00 | tous les jours à 10h00 | | Multiple times | every day at 09:00 and 18:00 | tous les jours à 09h00 et 18h00 | | Weekday set | every monday and thursday at 18:00 | tous les lundis et jeudis à 18h00 | | Business days | every weekday at 09:00 | tous les jours ouvrés à 09h00 | | Interval | every 2 days at 10:00, every 3 weeks on monday at 08:30 | tous les 2 jours à 10h00, toutes les 3 semaines le lundi à 08h30 | | Long interval | every 2 months on the 1st …, every quarter …, every 2 years on 03-14 … | tous les 2 mois le 1er …, tous les trimestres …, un mois sur deux … | | Sub-day step | every day every 2 hours between 09:00 and 17:00 | tous les jours, toutes les 2 heures entre 09h00 et 17h00 | | Hourly window | every hour between 18:00 and 23:00, … from 09:00 to 17:00 | toutes les heures entre 18h00 et 23h00, … de 09h00 à 17h00 | | Minutely/hourly | every 15 minutes, every 6 hours | toutes les 15 minutes, toutes les 6 heures | | Weekend | every weekend at 10:00 | tous les week-ends à 10h00 | | Weekday range | every monday to friday at 09:00 | du lundi au vendredi à 09h00 | | Day of month | every month on the 15th at 08:00, … on the last day … | tous les mois le 15 à 08h00, … le dernier jour … | | Business day | every month on the last/first weekday at 18:00 | le dernier/premier jour ouvré du mois à 18h00 | | Nth weekday | every month on the first monday at 09:00, … on the first and third monday … | tous les mois le premier lundi à 09h00, … le premier et troisième lundi … | | Biweekly | every other tuesday …, every fortnight … | un mardi sur deux …, tous les quinze jours … | | Yearly | every year on 03-14 at 10:00, every year on the last sunday of october at 23:00 | tous les ans le 03-14 à 10h00, tous les ans le dernier dimanche d'octobre à 23h00 | | Time words | at noon, at midnight | à midi, à minuit | | One-shot | 2026-03-13 at 02:00 | le 2026-03-13 à 02h00 | | Date window | … between 2026-02-01 and 2026-03-31, … until 2026-12-31, … starting 2026-04-01 | … entre le … et le …, … jusqu'au …, … à partir du 2026-04-01 | | Repetition cap | … 5 times (pairs well with starting …) | … 5 fois | | Exceptions | … except thursday, … except 2026-03-13, … except weekends, … except in august, … except the 15th, … except between … and … | … sauf le jeudi, … sauf le 2026-03-13, … sauf le week-end, … sauf en août, … sauf le 15 du mois, … sauf entre le … et le … | | Except nth wday | … except the last tuesday of the month | … sauf le dernier mardi du mois | | Weekend shift | … if weekend then next monday / … next business day | … si week-end alors lundi suivant / … prochain jour ouvré | | Composed | rule A, and rule B (earliest wins) | règle A, et règle B |

Times accept 09:00, 9, 3PM, noon, midnight (EN) and 09h00, 9h, 18h30, midi, minuit (FR). Public-holiday exclusions (except public holidays / sauf les jours fériés) are recognized but rejected with a clear error, since there is no holiday data source.

Behaviour notes

  • All recurrence math is done on naive wall-clock values and the result is materialized into an absolute Date in the schedule's zone. Comparisons (e.g. "earliest of composed rules") are wall-clock, matching the reference implementation.
  • Zero/negative intervals and steps, and empty/unparseable rules, are rejected up front rather than looping.

Development

npm install
npm test          # vitest
npm run build     # tsup -> dist/ (ESM + .d.ts)
npm run lint      # eslint + prettier

Parity

everwhen is a faithful TypeScript port of the recpyx Python library. Its full engine + robustness test matrix (English, French, timezones, calendar edge cases) is ported to vitest and passes identically — that parity is the definition of "done".

License

MIT