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

@verifyhash/iso-duration

v0.1.0

Published

Zero-dependency ISO 8601 duration parse, format, calendar-correct date arithmetic, and English humanize for Node.js.

Readme

iso-duration

TODO (owner): pick the final npm name/scope before publishing. The npm package name is not finalized by this project. Convention used here: package.json keeps the working slug iso-duration as a placeholder — replace it (and the npm install line below) with the real published name/scope before running npm publish.

A tiny, zero-dependency, zero-network Node.js library for ISO 8601 durations — the P3Y6M4DT12H30M5S strings that show up in JSON Schema (format: duration), OpenAPI, iCalendar (RRULE/DURATION), GitHub Actions timeouts, Kubernetes manifests, and countless "retry after / cache for / repeat every" API fields.

It does four things and nothing else:

  • parse(str) — string → duration object
  • format(obj) — duration object → canonical string (round-trips parse)
  • addToDate / subtractFromDate — calendar-correct Date arithmetic
  • humanize(obj)"3 hours 30 minutes"

No dependencies, no install step, no I/O. Just index.js (CommonJS) and pure functions. Drop it into any project.

Who it's for

JavaScript/Node developers who receive or emit ISO 8601 durations from APIs, schemas, or schedules and want to parse, normalize, do date math on, or display them — without pulling in Luxon/moment/date-fns just for the duration bits.

The duration object

Every function speaks the same plain object. All keys are optional; missing numeric keys are 0, and negative defaults to false:

{ years, months, weeks, days, hours, minutes, seconds, negative }

Usage

const iso = require('iso-duration'); // or require('./index.js')

iso.parse('P3Y6M4DT12H30M5S');
// { years: 3, months: 6, weeks: 0, days: 4,
//   hours: 12, minutes: 30, seconds: 5, negative: false }

iso.parse('-PT1H30M');   // { ..., hours: 1, minutes: 30, negative: true }
iso.parse('P2W');        // { ..., weeks: 2 }
iso.parse('PT0.5H');     // { ..., hours: 0.5 }  (fractional final component)

iso.format({ hours: 3, minutes: 30 });        // 'PT3H30M'
iso.format({});                               // 'PT0S'
iso.format({ weeks: 2 });                     // 'P2W'
iso.format({ hours: 1, negative: true });     // '-PT1H'

iso.humanize({ hours: 3, minutes: 30 });      // '3 hours 30 minutes'
iso.humanize({ hours: 1 });                   // '1 hour'
iso.humanize({});                             // '0 seconds'

const start = new Date('2020-01-31T00:00:00Z');
iso.addToDate(start, { months: 1 });          // 2020-02-29T00:00:00.000Z
start.toISOString();                          // unchanged — input is never mutated
iso.subtractFromDate(start, { days: 10 });    // 2020-01-21T00:00:00.000Z

Two design choices to know about

Libraries genuinely disagree on these two points, so they are spelled out here rather than left to surprise you.

1. Weeks normalization

ISO 8601 treats W (weeks) as mutually exclusive with the other date fields — P2W is legal, P1W3D technically is not. This library is lenient on input (it will parse P1W3D and keep weeks and days separate) but opinionated on output:

  • format() emits P2W only when weeks is the sole non-zero component.
  • In every other case weeks are folded into days at 1 week = 7 days (e.g. format({ weeks: 1, days: 1 })'P8D').

This keeps the canonical string valid ISO and guarantees format round-trips through parse: format(parse(s)) === s for any canonical s, and repeated format(parse(...)) is stable. In date arithmetic, weeks are always treated as 7 elapsed days.

2. Month (and year) overflow clamps

Adding months and years is calendar arithmetic, and calendars have ragged month lengths. When the target month is shorter than the source day-of-month, this library clamps to the last day of the target month — the same rule Luxon, Temporal, and date-fns use:

  • Jan 31 + P1MFeb 29 in a leap year, Feb 28 otherwise.
  • Mar 31 − P1MFeb 29 / Feb 28 (subtraction clamps the same way).

The arithmetic order is: years/months are applied first as whole calendar steps (with clamping), then weeks/days/hours/minutes/seconds are added as a flat elapsed-milliseconds offset. Because the calendar step happens first, results are deterministic and independent of your machine's timezone (all math is done in UTC) and the input Date is never mutated — a new Date is returned.

Honest limits. Because day/time components are applied as elapsed milliseconds, they do not track daylight-saving wall-clock shifts — that is correct for UTC/absolute instants but means "+1 day" is always exactly 24 hours, not "same local time tomorrow." Fractional years or months (rare, e.g. P0.5M) can't be expressed as an exact number of calendar days, so their fractional part is approximated using the mean Gregorian month (30.436875 days); integer years/months — the normal case — are always exact.

Errors

parse() throws a descriptive Error on malformed input, including: an empty string, a missing P, a T separator with no time components ("PT"), a bare "P", out-of-order fields ("P3M6Y"), and time letters outside the T block ("P5S").

Running the tests

One command, no install, no network:

node test/index.test.js

It prints one line per test and exits non-zero if any fail. Coverage includes parse/format round-trips, P2W weeks, PT0S and negatives, fractional seconds, Jan-31 + 1-month month-overflow arithmetic, the no-mutation guarantee, humanize pluralization, and malformed-input throw cases.

License

MIT.

Install

Placeholder name: the iso-duration below is the working slug, not a finalized npm name — see the owner TODO near the top of this README.

npm install iso-duration
const iso = require('iso-duration');

iso.parse('P3Y6M4DT12H30M5S');         // { years:3, months:6, days:4, hours:12, minutes:30, seconds:5, ... }
iso.format({ hours: 3, minutes: 30 }); // 'PT3H30M'
iso.humanize({ hours: 1, minutes: 30 }); // '1 hour 30 minutes'