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

cronli5

v0.10.0

Published

Cron Like I'm Five: A Cron to English Utility

Readme

Cron Like I'm Five: Cron Patterns in Plain Language

CI npm version types included minzipped size license

Overview

Cron Like I'm Five (cronli5) generates plain-language, idiomatically rendered descriptions of schedules from cron patterns in several languages.

  • Zero runtime dependencies. Tiny and safe to drop into any project.
  • Runs anywhere. Ships ESM, CommonJS, and a browser global.
  • Typed. Bundled TypeScript definitions, no @types needed.
  • Flexible input. Accepts strings, arrays, or objects.
  • Idiomatic output. Composes lists, ranges, and steps into natural sentences, not comma-joined fragments.
  • Multilingual. Each language is a full renderer shipped as its own module (cronli5/lang/es). You bundle only the languages you import.
  • Input Formats. Accepts classic (five-part) cron patterns, extended (six-part) cron patterns, where the first field is assumed to refer to seconds, and full seven-part (Quartz-style) patterns with a trailing year. See Input Formats for details.

cronli5 is a good library to use if you need to display a natural-language interpretation of a cron pattern in a Node or in a browser environment. If you need to do other things with cron patterns, such as scheduling or computing future run times, consider a library like @breejs/later.

Alternatives: The main alternative for descriptions is cRonstrue. See the head-to-head comparison below for how the two differ and which to pick.

Contents

Installation

Requires Node.js 20 or newer.

Install using npm:

# For a Node project:
npm install --save cronli5

# If you plan to use the cli:
npm install -g cronli5

Browser (script tag) via a CDN:

<script src="https://unpkg.com/cronli5"></script>
<!-- or: https://cdn.jsdelivr.net/npm/cronli5 -->

When included in a script tag, the cronli5 function will be available as a global in the scripts that follow.

Usage

Import as an ES module:

import cronli5 from 'cronli5';

Or with CommonJS require:

const cronli5 = require('cronli5');

A cron pattern can be a string, an array of fields, or an object. All three forms below describe the same schedule:

cronli5('*/5 * * * *');                 // 'every five minutes'
cronli5(['*/5', '*', '*', '*', '*']);   // 'every five minutes'
cronli5({ minute: '*/5' });             // 'every five minutes'

By default cronli5(...) returns the lowercase, embeddable fragment. For the capitalized, standalone sentence, set sentence: true — or call the matching convenience method. cronli5.sentence(...) and cronli5.fragment(...) are thin sugar over that option (each forwards all other options) and read a little better at the call site:

cronli5('0 0 * * *');                     // 'every day at midnight'  (fragment, default)
cronli5.fragment('0 0 * * *');            // 'every day at midnight'  (same as above)
cronli5.sentence('0 0 * * *');            // 'Runs every day at midnight.'
cronli5.sentence('0 0 * * *', {lang: de}); // 'Läuft täglich um Mitternacht.'

(There is intentionally no cronli5.toString(...): it would shadow Function.prototype.toString, which the runtime calls with no arguments for String(cronli5), template literals, and console output — the named methods sidestep that collision.)

TypeScript types are bundled, so usage is fully typed out of the box:

import cronli5, { type Cronli5Options } from 'cronli5';

const options: Cronli5Options = { ampm: false };
const description: string = cronli5('30 13 * * MON-FRI', options);
// 'every Monday through Friday at 13:30'

As a command line tool:

$ cronli5 "*/5 * * * *"
Runs every five minutes.

# Other languages with --lang (de, es, fi, fr, pt, zh):
$ cronli5 --lang de "0 0 * * *"
Läuft täglich um Mitternacht.
$ cronli5 --lang fr "0 0 * * *"
S'exécute tous les jours à minuit.

# --fragment prints the bare, embeddable fragment instead of a sentence:
$ cronli5 --fragment "*/5 * * * *"
every five minutes

Dialects and script variants — including Traditional Chinese (zh-Hant) — are selected through the library's dialect option rather than the CLI; the CLI exposes only --lang and --fragment.

import cronli5 from 'cronli5';
import zh from 'cronli5/lang/zh';

cronli5('0 0 * * *', {lang: zh});                       // '每天凌晨0点' (Simplified, default)
cronli5('0 0 * * *', {lang: zh, dialect: 'zh-Hant'});   // '每天凌晨0點' (Traditional)

Input Formats

cronli5 accepts classic (five-part) cron patterns, extended (six-part) cron patterns, where the first field is assumed to refer to seconds, and full seven-part (Quartz-style) patterns with a trailing year.

It accepts the standard allowed values and the following operators:

  • asterisks (*)
  • commas (,)
  • hyphens (-)
  • slashes (/).

Cron Aliases

@daily and other cron aliases are supported.

Extended Format Support

Ranges in cyclic fields may wrap around (22-2 is an overnight window, and FRI-MON is a long weekend). Quartz-style tokens are also supported in the date and weekday fields: L (last day, or 5L for the last Friday), W (nearest weekday, e.g. 15W), and # (nth weekday, e.g. 1#2 for the second Monday). Quartz's ? ("no specific value") and its day-of-week numbering (1 = Sunday) require the quartz option; see its note for why.

Options

The cronli5 function takes an options object as its 2nd parameter:

| Option | Default | Description | | --- | --- | --- | | ampm | true (English) | Use a 12-hour clock. Set false for 24-hour time. The default is language-specific: English is 12-hour; Spanish and Finnish default to 24-hour (Finnish is 24-hour only). | | dialect | 'us' | The English style. 'us' follows the Chicago Manual of Style: serial commas, through ranges, 9 a.m./5:30 p.m. times, noon/midnight, and January 1 dates. 'gb' follows the Guardian style guide: no serial comma, to ranges, 9am/5.30pm times, midday/midnight, and 1 January dates. 'house' is cronli5's legacy voice (9:30 AM, Monday - Friday). A custom object defines your own style. ('uk' is a deprecated alias for 'gb'.) See docs/dialects.md. | | lang | English | A language module, e.g. import es from 'cronli5/lang/es'. Each language owns its words, conventions, and dialects — see Languages. | | lenient | false | Never throw: invalid input returns the language's fallback description ('an unrecognizable cron pattern') instead. Useful when rendering arbitrary user crontabs. | | quartz | false | Read the pattern with Quartz semantics. Quartz numbers the day-of-week 1 = Sunday … 7 = Saturday (standard cron uses 0/7 = Sunday, 1 = Monday) and requires exactly one of day-of-month or day-of-week to be ? ("no specific value"). With this off, ? is rejected outright rather than silently mis-read — a ? is the unambiguous mark of a Quartz pattern, so '0 0 ? * 2' would otherwise read as Tuesday when its author meant Monday. The Quartz numbering also applies inside the day-of-week operators (6L, 2#2) and the weekday L alias. Day names (MON) and the other fields are unaffected. Composable with seconds/years. | | sentence | false | Return a complete standalone sentence ('Runs every day at midnight.', 'Läuft täglich um Mitternacht.') instead of the embeddable fragment. Each language supplies its own wrapping. Wraps a schedule and @reboot, but not the lenient fallback. The methods cronli5.sentence(...) / cronli5.fragment(...) (see Usage) are sugar for this option set to true / false. | | short | false | Compact output: abbreviated month and weekday names, and hyphenated ranges everywhere through/to would appear (Mon-Fri, Jan-Mar, 1st-5th, 9 a.m.-5:45 p.m.). | | seconds | false | Always treat the first field of strings and arrays as the second field. | | years | false | Treat the last field of a six-field string/array as the year field. Otherwise the first field of a six-field pattern is treated as the second field. Seven-field patterns are unambiguous (seconds first, year last) and need no option. |

When a specific year is given — via a seven-field pattern, an object's year property, or a six-field pattern with years: true — it is folded into a specific calendar date ('on January 1, 2030 at noon') or otherwise trails the description ('every Friday at 1 p.m. in 2030').

cronli5('0 0 12 1 1 * 2030');  // 'on January 1, 2030 at noon'
cronli5({ hour: 9, year: 2030 }); // 'every day at 9 a.m. in 2030'
import cronli5 from 'cronli5';

const weekdaysAt1330 = '30 13 * * MON-FRI';

cronli5(weekdaysAt1330, { ampm: true, short: false });
// 'every Monday through Friday at 1:30 p.m.'

cronli5(weekdaysAt1330, { ampm: false, short: true });
// 'every Mon-Fri at 13:30'

cronli5(weekdaysAt1330, { dialect: 'gb' });
// 'every Monday to Friday at 1.30pm'

Languages

English is the default. Other languages are full renderers over the same language-independent core. A language ships as its own module and is selected per call with the lang option. If you never import it, it never reaches your bundle (each language adds about 3.3 KB gzipped).

import cronli5 from 'cronli5';
import es from 'cronli5/lang/es';
import fi from 'cronli5/lang/fi';

cronli5('30 9 * * MON-FRI');             // 'every Monday through Friday at 9:30 a.m.'
cronli5('30 9 * * MON-FRI', {lang: es}); // 'de lunes a viernes a las 09:30'
cronli5('30 9 * * MON-FRI', {lang: fi}); // 'maanantaista perjantaihin klo 9.30'

Each language carries its own conventions and defaults — Spanish and Finnish default to the 24-hour clock, for instance (Spanish takes {ampm: true} for 12-hour times with day periods). See the per-language docs below.

| Language | Module | Anchors | Doc | | --- | --- | --- | --- | | English | built in (also cronli5/lang/en) | Chicago Manual of Style; Guardian ('gb' dialect) | docs/lang/en.md | | German | cronli5/lang/de | Duden (de-AT/de-CH dialects) | docs/lang/de.md | | Spanish | cronli5/lang/es | RAE / FundéuRAE | docs/lang/es.md | | Finnish | cronli5/lang/fi | Kielitoimiston ohjepankki; SFS 4175 | docs/lang/fi.md | | French | cronli5/lang/fr | Imprimerie nationale / Académie française (fr-FR; fr-CA is a future axis) | docs/lang/fr.md | | Portuguese | cronli5/lang/pt | VOLP / Academia Brasileira de Letras (pt-BR; pt-PT is a future axis) | docs/lang/pt.md | | Ukrainian | cronli5/lang/uk | Ukrainian National Commission on Spelling (2019) | docs/lang/uk.md | | Chinese (Mandarin) | cronli5/lang/zh | Simplified (zh-Hans) default; Traditional (zh-Hant) | docs/lang/zh.md |

Language maturity

Languages ship as experimentalbetastable (model-drafted → model-validated → verified by a fluent human). A new language is usually built by sibling-derivation — deriving it from its nearest validated relative (porting that renderer and translating its reviewed corpus, then TDD to green) — and reaches beta once it passes the objective gates (round-trip, fuzz, OR-scope, the cRonstrue comparison) and review by a blind panel of Claude Sonnet instances. It graduates to stable only on review by a fluent human:

| Language | Status | | --- | --- | | German | beta | | English | stable | | Spanish | beta | | Finnish | beta | | French | beta | | Portuguese | beta | | Ukrainian | beta | | Chinese (Mandarin, Simplified) | beta | | Chinese (Mandarin, Simplified) (zh-Hant) | experimental |

For the review evidence behind each status, see docs/language-status.md.

Each language doc includes a generated side-by-side table against the matching cRonstrue locale. The architecture is described in docs/i18n-design.md.

Output Examples

Output follows the Chicago Manual of Style by default (serial commas, 9 a.m. times, noon/midnight, cardinal month-day dates). See the dialect option for Guardian-style British English. Bare days of the month use suffixed ordinals (on the 1st and 15th).

// Single values and steps
cronli5('*/5 * * * *');     // 'every five minutes'
cronli5('0 9 * * MON');     // 'every Monday at 9 a.m.'

// Lists
cronli5('5,10,15 * * * * *'); // 'at 5, 10, and 15 seconds past the minute'
cronli5('0 9,17 * * *');      // 'every day at 9 a.m. and 5 p.m.'
cronli5('0 0 1,15 * *');      // 'on the 1st and 15th at midnight'
cronli5('0 12 * 6,12 *');     // 'every day in June and December at noon'

// Ranges (wrap-around ranges describe overnight and weekend windows)
cronli5('0-29 * * * *'); // 'every minute from 0 through 29 past the hour'
cronli5('0 9-17 * * *');  // 'every hour from 9 a.m. through 5 p.m.'
cronli5('0 0 1-15 * *');  // 'on the 1st through 15th at midnight'
cronli5('0 22-2 * * *');  // 'every hour from 10 p.m. through 2 a.m.'
cronli5('0 0 * * FRI-MON'); // 'every Friday through Monday at midnight'

// Compound patterns
cronli5('0,30 9 * * *');   // 'every day at 9 a.m. and 9:30 a.m.'
cronli5('*/15 9-17 * * *'); // 'every 15 minutes from 9 a.m. through 5 p.m.'
cronli5('30 9-17 * * *');
// 'at 30 minutes past the hour from 9 a.m. through 5 p.m.'
cronli5('0 12 1 1 *');     // 'on January 1 at noon'
cronli5('0 * 13 * *');     // 'every hour on the 13th'

// Quartz tokens
cronli5('0 0 L * *');      // 'on the last day of the month at midnight'
cronli5('0 0 * * 5L');     // 'on the last Friday of the month at midnight'
cronli5('0 0 * * 1#2');    // 'on the second Monday of the month at midnight'
cronli5('0 0 15W * *');    // 'on the weekday nearest the 15th at midnight'

cronli5 vs. cRonstrue

cRonstrue is the most widely used cron-description library, but it differs from cronli5 in philosophy. cronli5 writes one flowing sentence and does additional validation; its languages are full renderers (seven so far). cRonstrue assembles per-field fragments from translated templates, which is how it covers 39 locales. The same compound pattern — 5,10 30 9 * * MON — in every language:

| Language | cronli5 | cRonstrue 3.14.0 | | --- | --- | --- | | English | at 5 and 10 seconds past the minute, every Monday at 9:30 a.m. | At 5 and 10 seconds past the minute, at 30 minutes past the hour, at 09:00 AM, only on Monday | | German | in den Sekunden 5 und 10, montags um 9:30 Uhr | Bei Sekunde 5 und 10, bei Minute 30, um 09:00, nur jeden Montag | | Spanish | los lunes, en los segundos 5 y 10 de las 09:30 | A los 5 y 10 segundos del minuto, a los 30 minutos de la hora, a las 09:00, sólo el lunes | | Finnish | 5 ja 10 sekunnin kohdalla, maanantaisin klo 9.30 | 5 ja 10 sekunnnin jälkeen, 30 minuuttia yli, klo 09:00, vain maanantai | | French | le lundi, aux secondes 5 et 10 de 9 h 30 | 5 et 10 secondes après la minute, 30 minutes après l'heure, 09:00, uniquement le lundi | | Portuguese | às segundas-feiras, nos segundos 5 e 10 das 09:30 | Aos 5 e 10 segundos do minuto, aos 30 minutos da hora, Às 09:00, somente de segunda-feira | | Ukrainian | о 5-й і 10-й секунді, по понеділках о 9:30 | О 5 та 10 секунді, о 30 хвилині, о 09:00, тільки в понеділок | | Chinese (Simplified) | 每周一,9点30分的第5、10秒 | 在一分钟后的第 5 和 10 秒, 在整点后的第 30 分钟, 在上午 09:00, 仅星期一 | | Chinese (Traditional) | 每週一,9點30分的第5、10秒 | 在一分鐘後的 5 和 10 秒, 在整點後的 30 分, 在 09:00, 僅在 星期一 |

See docs/cronli5-vs-cronstrue.md for more generated side-by-side output tables, and docs/migrating-from-cronstrue.md if you are switching from cRonstrue.

Description Accuracy

Sometimes minimizing verbosity results in ambiguities. For example, "every two minutes" could reasonably refer to two behaviorally distinct cron patterns with minute accuracy: */2 * * * * and 1/2 * * * * (and 120 behaviorally distinct cron patterns with second accuracy). As a tradeoff, this library does not qualify cases that begin on the first second, minute, or hour of the corresponding minute, hour, or day. So */3 * * * * will be "every three minutes", while 2/3 * * * * will be "every three minutes from two minutes past the hour".

Note on Timezones

cronli5 always describes cron patterns with respect to whatever system timezone the cron pattern is being run in. This utility does not, nor does it ever intend to, deal with timezone conversions. That functionality would require some non-trivial dependencies like moment-timezone and moment to even approximate correctness and the output could still be wrong anyways because timezones are problematic. Associate the displayed description with a timezone (e.g. America/Phoenix) when there is the possibility for confusion.

Module Formats and Types

cronli5 is authored as an ES module in src/. It is published with dual builds so it works everywhere:

  • ESM (import cronli5 from 'cronli5') resolves to dist/cronli5.js.
  • CommonJS (const cronli5 = require('cronli5')) resolves to dist/cronli5.cjs.
  • Browser (<script src="cronli5.min.js">) exposes a global cronli5 (English only).

Language subpaths (cronli5/lang/en, cronli5/lang/es, cronli5/lang/fi) ship the same dual ESM + CJS builds under dist/lang/.

TypeScript type definitions ship in cronli5.d.ts (and lang.d.ts for the language subpaths) and are picked up automatically: no @types package required.

Development

The library has no runtime dependencies. Development requires Node.js 20 or newer (the Vitest 4 test runner needs it); the toolchain (ESLint, Vitest, Chai, esbuild) lives in devDependencies.

npm install       # install dev dependencies (also wires the git hooks)
npm test          # run the Vitest suite (runs against src/, no build needed)
npm run coverage  # run tests with Vitest V8 coverage and enforce thresholds
npm run lint      # lint source and tests with ESLint
npm run build     # emit dist/ (ESM + CJS) and the minified browser global
npm run verify    # the full CI gate: lint, types, tests, coverage, docs, build

A pre-push git hook runs npm run verify so a push only lands when the full gate is green. It is wired automatically on npm install (via core.hooksPath → .githooks/); bypass it in an emergency with git push --no-verify.

About

The project name is a reference to the phrase Explain Like I'm Five (ELI5), which is used to ask for a friendly, simplified, and layman-accessible summary of material that may be hard to understand without some background.

cronli5 was partially inspired by prettycron, which itself is based on code from a gist by dunse. Although prettycron was close to meeting my needs, I wasn't fully satisfied with the output. cronli5 tries to render as many cron patterns in as direct and as idiomatic language as possible in every target language. Test cases that describe where it fails to do so and which prescribe an obviously better description would be greatly appreciated. Native speakers of target languages are the best.

License

MIT License
Copyright © 2026 Andrew Brož