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

dtrexp

v1.0.1

Published

Reference TypeScript implementation of DTRExp — compact date-time range & recurrence expressions, evaluated by coverage.

Readme

dtrexp-js

This module is ESM 🔆. Please read this.

Reference TypeScript implementation of DTRExp (read: "DTR Expression") — a compact string expression for date-time ranges and recurrence, evaluated by coverage rather than enumeration.

T0900:1800 E1:5          Mon–Fri, 09:00–18:00
E7#-1 M4                 last Sunday of April, every year
20200106/10D             every 10 days from 2020-01-06 (cron can't say this)
D13 E5                   every Friday the 13th (cron can't say this either)
D-7:* Y*                 last 7 days of every year
M!7                      every month except July

A DTRExp denotes a possibly infinite set of time intervals. You don't expand it into dates; you ask it questions: does it cover this instant? What does it cover between these two dates? When does it next apply? That makes it the right shape for storing "when does this apply?" as data; permission windows, price rules, maintenance schedules, availability — in a database column, an ACL grant, or a config value.

Install

npm i dtrexp

Requires Node.js ≥ 20. Zero runtime dependencies.

Quick Start

import { parse } from 'dtrexp';

const dtr = parse('T0900:1800 E1:5');

// coverage — O(#components), built for per-request hot paths
dtr.covers(new Date(), { tz: 'Europe/Berlin' });
// → true (weekday, 09:00–18:00 Berlin local time)

// enumeration on demand — a finite window is always a finite list
dtr.intersect('2026-07-06T00:00:00Z', '2026-07-13T00:00:00Z');
// → 5 intervals, one per business day

// "when does it next apply?"
dtr.next('2026-07-11T10:00:00Z');
// → { start: 2026-07-13T09:00:00Z, end: 2026-07-13T18:00:00Z }

parse('E7#-1 M4').describe();
// → 'the last Sunday in April'

parse('D25 M12').toRRule();
// → 'RRULE:FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25'

[!IMPORTANT] Parse once (at write/config time), evaluate many; DTRExp instances are immutable, and covers() performs a single calendar-field extraction followed by integer comparisons. No occurrence iteration, ever.

API

Functions

| Function | Description | | --- | --- | | parse(expression) | Parses a DTRExp string into an immutable DTRExp. Throws DTRExpSyntaxError with a stable code and character position on invalid input. The only way to construct a DTRExp. | | validate(expression) | Non-throwing variant. Returns { valid, errors, warnings }; warnings include the spec's unsatisfiability lint (D30 M2 parses but can never match). |

DTRExp Instance

| Member | Description | | --- | --- | | covers(instant, opts?) | Whether the expression covers the instant. O(#components) integer tests after one field extraction. | | intersect(start, end, opts?) | Covered intervals clipped to [start, end): a finite, sorted, merged list of half-open { start: Date, end: Date } intervals. | | next(after, opts?) | The first maximal covered interval starting strictly after after (coverage containing after is skipped). null when nothing starts before the year-9999 horizon. | | describe(locale?) | Human-readable English rendering ('E7#-1 M4'"the last Sunday in April"). v1 supports 'en'; the parameter is reserved. | | toRRule() | RFC 5545 RRULE (+ DTSTART line when anchored) for the losslessly-mappable subset, else null. Constrained cadences emit RFC 7529 SKIP=BACKWARD. | | toString() | Canonical normalized form (redundant components dropped, canonical order, wraps re-fused). | | warnings | The spec §9.1 warnings of the parsed expression: same content as validate().warnings, so parsing directly doesn't lose them. | | source | The original expression, verbatim. |

Inputs & Options

  • Instants (DateInput): Date, epoch milliseconds, ISO 8601 string, or any Temporal-like object exposing epochMilliseconds (no Temporal dependency).
  • opts.tz: IANA time zone for evaluation, default 'UTC'. The zone is always an evaluation parameter, never part of the expression; T0900:1800 means local business hours wherever you evaluate it. DST is handled per spec §9.3: spring-forward gap times cover nothing; repeated fall-back times are covered on both passes.

Expression Syntax (spec draft 2.8)

The full grammar and semantics live in the specification; the essentials:

| Component | Example | Meaning | | --- | --- | --- | | Selectors | M3:7, E1:5, D1,15, W53, Q2, Y2018 | inclusive values/ranges/lists per calendar unit | | Negative index | D-1, D-7:* | from the end of the actual parent (last day, leap-safe) | | Exclusion | M!5,7:9 | domain minus the set | | Ordinal | E7#2, E7#-1 | nth / nth-from-last weekday in scope | | Time of day | T0900:1200,1300:1800, T2200:0600 | half-open clock ranges; midnight wrap stays within the day | | Stride | H0/4, M1/5/2, Y2020:2040/3 | calendar-locked recurrence: /interval[/duration] | | Cadence | 20200106/10D/3D, 20180301/14M | anchor-based recurrence that drifts across the calendar | | Bounds | 20150101:*, *:20291231, 20180120 | absolute window / single day | | Union | E5#1 \| E5#3 | either expression |

Components in one expression intersect; T0900:1800 E1:5 M!8 reads naturally as "9–18, on weekdays, except in August."

Quality

  • Conformance-first: the test suite is driven by the shared vectors.json from the spec repo: every coverage, rejection, warning and quiet vector, including the calendar traps (Feb 29 in 2000/2024/2100, W53 existence, DST gap/overlap in Europe/Berlin, constrain arithmetic on month-end anchors). See VECTORS.md for how the suite works.
  • 100% coverage on all four metrics (lines, statements, functions, branches), enforced as hard thresholds in CI.
  • 100% mutation score (Stryker, break: 100): inclusivity mutants (< vs <=) are exactly the class of bug a date-range library must not ship, and line coverage alone can't catch them.
  • CI matrix on Node 20 / 22 / 24 / 26, gate ladder typecheck → lint → build → cover plus a dedicated mutation job.
  • Pure integer calendar math (Hinnant civil-date algorithms, ISO week arithmetic). The only platform dependency is Intl for IANA zone offsets, with a fast path for UTC.

Related Projects

License

© 2026, Onur Yıldırım. MIT License.