{"_id":"dtrexp","_rev":"3-75e64b570c6422a8942fd7f93e83d969","name":"dtrexp","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"dtrexp","version":"1.0.0","keywords":["dtrexp","date","time","datetime","range","recurrence","expression","schedule","interval","coverage","cron","rrule","typescript","esm"],"author":{"name":"Onur Yıldırım","email":"onur@cutepilot.com"},"license":"MIT","_id":"dtrexp@1.0.0","maintainers":[{"name":"onury","email":"onur@cutepilot.com"}],"homepage":"https://github.com/DTRExp/dtrexp-js#readme","bugs":{"url":"https://github.com/DTRExp/dtrexp-js/issues"},"dist":{"shasum":"34be8dd5014f43379564676fb9247d9d0c190a90","tarball":"https://registry.npmjs.org/dtrexp/-/dtrexp-1.0.0.tgz","fileCount":107,"integrity":"sha512-I+rpfns7k1IwU9Im1UtZH9d2mUE87lj+mXYNxht5CAl+i9mFaFjPmu9PYkWQhoRAh44TlD992GooqEkEbtP0Cg==","signatures":[{"sig":"MEUCIQDz5PmIf+xZSb08cCSO78+MWsmInCmQ2U8bnH48SOD7tgIgc1639gADs8tXj1Vkf1SYNZQJJD+QCLj0XzJ/NgRM9wc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":235736},"main":"./lib/index.js","type":"module","types":"./lib/index.d.ts","module":"./lib/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./lib/index.d.ts","import":"./lib/index.js"},"./package.json":"./package.json"},"gitHead":"08ddddb3091a651e8c67610f5cf754d0b15d7ce6","scripts":{"lint":"biome check src test","test":"vitest run","build":"tsc -p tsconfig.build.json","cover":"vitest run --coverage","format":"biome check --write src test","pretest":"npm run lint && npm run typecheck","mutation":"stryker run","typecheck":"tsc --noEmit -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"onury","email":"onur@cutepilot.com"},"repository":{"url":"git+https://github.com/DTRExp/dtrexp-js.git","type":"git"},"_npmVersion":"11.16.0","description":"Reference TypeScript implementation of DTRExp — compact date-time range & recurrence expressions, evaluated by coverage.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"4.1.10","typescript":"6.0.3","@types/node":"26.1.0","tsconfig-oy":"2.0.1","@biomejs/biome":"2.5.2","biome-config-oy":"1.1.0","@stryker-mutator/core":"9.6.1","@vitest/coverage-istanbul":"4.1.10","@stryker-mutator/vitest-runner":"9.6.1"},"_npmOperationalInternal":{"tmp":"tmp/dtrexp_1.0.0_1784122683462_0.843104443253009","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"dtrexp","version":"1.0.1","keywords":["dtrexp","date","time","datetime","range","recurrence","expression","schedule","interval","coverage","cron","rrule","typescript","esm"],"author":{"name":"Onur Yıldırım","email":"onur@cutepilot.com"},"license":"MIT","_id":"dtrexp@1.0.1","maintainers":[{"name":"onury","email":"onur@cutepilot.com"}],"homepage":"https://dtrexp.org","bugs":{"url":"https://github.com/DTRExp/dtrexp-js/issues"},"dist":{"shasum":"b4afdbae22edd2391a2ce5865ad241b7cda87b42","tarball":"https://registry.npmjs.org/dtrexp/-/dtrexp-1.0.1.tgz","fileCount":107,"integrity":"sha512-xWByJgkeUBLQuZfzCWqlVIb6gwc0yOgh1577cmaE/tMSkp9Sx5UbijPSY/dYuI3sbQnUz4lMgXkUZl6yyGxC3Q==","signatures":[{"sig":"MEQCIB8z7x938byzuQLpOLjE/XECIErlRLWfmbYaIsJh9awwAiBPw1HNfroNrU3tttTHWAkPDgSknRU66zzGuqWtLBIPfw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":235825},"main":"./lib/index.js","type":"module","types":"./lib/index.d.ts","module":"./lib/index.js","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./lib/index.d.ts","import":"./lib/index.js"},"./package.json":"./package.json"},"gitHead":"0a15664caac99f6c6a3d124fd48e013a61bd1da1","scripts":{"lint":"biome check src test","test":"vitest run","build":"tsc -p tsconfig.build.json","cover":"vitest run --coverage","format":"biome check --write src test","pretest":"npm run lint && npm run typecheck","mutation":"stryker run","typecheck":"tsc --noEmit -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"onury","email":"onur@cutepilot.com"},"repository":{"url":"git+https://github.com/DTRExp/dtrexp-js.git","type":"git"},"_npmVersion":"11.16.0","description":"Reference TypeScript implementation of DTRExp — compact date-time range & recurrence expressions, evaluated by coverage.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"4.1.10","typescript":"6.0.3","@types/node":"26.1.0","tsconfig-oy":"2.0.1","@biomejs/biome":"2.5.2","biome-config-oy":"1.1.0","@stryker-mutator/core":"9.6.1","@vitest/coverage-istanbul":"4.1.10","@stryker-mutator/vitest-runner":"9.6.1"},"_npmOperationalInternal":{"tmp":"tmp/dtrexp_1.0.1_1784174030555_0.7696607009361154","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"dtrexp","version":"1.1.0","description":"Reference TypeScript implementation of DTRExp — compact date-time range & recurrence expressions, evaluated by coverage.","type":"module","main":"./lib/index.js","module":"./lib/index.js","types":"./lib/index.d.ts","exports":{".":{"types":"./lib/index.d.ts","import":"./lib/index.js"},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=20.0.0"},"scripts":{"lint":"biome check src test","format":"biome check --write src test","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json","pretest":"npm run lint && npm run typecheck","test":"vitest run","cover":"vitest run --coverage","mutation":"stryker run","prepublishOnly":"npm run build"},"repository":{"type":"git","url":"git+https://github.com/DTRExp/dtrexp-js.git"},"homepage":"https://dtrexp.org","bugs":{"url":"https://github.com/DTRExp/dtrexp-js/issues"},"keywords":["dtrexp","date","time","datetime","range","recurrence","expression","schedule","interval","coverage","cron","rrule","typescript","esm"],"author":{"name":"Onur Yıldırım","email":"onur@cutepilot.com"},"license":"MIT","devDependencies":{"@biomejs/biome":"2.5.2","@stryker-mutator/core":"9.6.1","@stryker-mutator/vitest-runner":"9.6.1","@types/node":"26.1.0","@vitest/coverage-istanbul":"4.1.10","biome-config-oy":"1.1.0","tsconfig-oy":"2.0.1","typescript":"6.0.3","vitest":"4.1.10"},"gitHead":"f4e437384f87d88429003f28c9c6f5e22651e84b","_id":"dtrexp@1.1.0","_nodeVersion":"24.18.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-9VgsOtnySgZu+Zg3qPR2zxuVLUl9IQMHUR40f2sIXbPiphonxE5CmdZhhC5sNXz/Rc1cvlY7QGSvmSn8MeEVRw==","shasum":"69367a416a4b3271396a83c7685ffdc48989b44d","tarball":"https://registry.npmjs.org/dtrexp/-/dtrexp-1.1.0.tgz","fileCount":107,"unpackedSize":255236,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID+V6vsKk1sr1GNF2gVF0w5i0gIt+MF2JBopD1Mo8XtUAiAMsSuxP7OfsMdM9RKwOFZWtV37XWhJlTXYq6O+LG/Dcw=="}]},"_npmUser":{"name":"onury","email":"onur@cutepilot.com"},"directories":{},"maintainers":[{"name":"onury","email":"onur@cutepilot.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dtrexp_1.1.0_1789253248771_0.9715993212562728"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-15T13:38:03.380Z","modified":"2026-09-12T22:47:29.077Z","1.0.0":"2026-07-15T13:38:03.610Z","1.0.1":"2026-07-16T03:53:50.699Z","1.1.0":"2026-09-12T22:47:28.903Z"},"bugs":{"url":"https://github.com/DTRExp/dtrexp-js/issues"},"author":{"name":"Onur Yıldırım","email":"onur@cutepilot.com"},"license":"MIT","homepage":"https://dtrexp.org","keywords":["dtrexp","date","time","datetime","range","recurrence","expression","schedule","interval","coverage","cron","rrule","typescript","esm"],"repository":{"type":"git","url":"git+https://github.com/DTRExp/dtrexp-js.git"},"description":"Reference TypeScript implementation of DTRExp — compact date-time range & recurrence expressions, evaluated by coverage.","maintainers":[{"name":"onury","email":"onur@cutepilot.com"}],"readme":"<p align=\"center\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/DTRExp/dtrexp-js/main/.github/logo-dark.svg\"><img src=\"https://raw.githubusercontent.com/DTRExp/dtrexp-js/main/.github/logo.svg\" width=\"200\" alt=\"dtrexp-js\" /></picture></p>\n\n# dtrexp-js\n\n<p align=\"center\">\n  <a href=\"https://github.com/DTRExp/dtrexp-js/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/DTRExp/dtrexp-js/ci.yml?branch=main&style=flat\" alt=\"build\" /></a>\n  <img src=\"https://img.shields.io/badge/coverage-100%25-2BB150?style=flat\" alt=\"coverage 100%\" />\n  <img src=\"https://img.shields.io/badge/mutation-100%25-2BB150?style=flat\" alt=\"mutation 100%\" />\n  <a href=\"https://www.npmjs.com/package/dtrexp\"><img src=\"https://img.shields.io/npm/v/dtrexp?style=flat&label=&logo=npm&color=C6234B\" alt=\"npm\" /></a>\n  <img src=\"https://img.shields.io/badge/deps-zero-2BB150?style=flat\" alt=\"zero dependencies\" />\n  <a href=\"https://gist.github.com/onury/d3f3d765d7db2e8b2d050d14315f2ac7\"><img src=\"https://img.shields.io/badge/ESM-F7DF1E?style=flat\" alt=\"ESM\" /></a>\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.shields.io/badge/TS-3260C7?style=flat\" alt=\"TS\" /></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-blue?style=flat\" alt=\"MIT license\" /></a>\n</p>\n\n> This module is **ESM** 🔆. Please [**read this**](https://gist.github.com/onury/d3f3d765d7db2e8b2d050d14315f2ac7).\n\nReference TypeScript implementation of **[DTRExp](https://github.com/DTRExp/dtrexp)** (read: \"**DTR Expression**\") — a compact string expression for date-time ranges and recurrence, evaluated by **coverage** rather than enumeration.\n\n```\nT0900:1800 E1:5          Mon–Fri, 09:00–18:00\nE7#-1 M4                 last Sunday of April, every year\n20200106/10D             every 10 days from 2020-01-06 (cron can't say this)\nD13 E5                   every Friday the 13th (cron can't say this either)\nD-7:* Y*                 last 7 days of every year\nM!7                      every month except July\n```\n\nA 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.\n\n## Install\n\n```sh\nnpm i dtrexp\n```\n\nRequires Node.js ≥ 20. Zero runtime dependencies.\n\n## Quick Start\n\n```ts\nimport { parse } from 'dtrexp';\n\nconst dtr = parse('T0900:1800 E1:5');\n\n// coverage — O(#components), built for per-request hot paths\ndtr.covers(new Date(), { tz: 'Europe/Berlin' });\n// → true (weekday, 09:00–18:00 Berlin local time)\n\n// enumeration on demand — a finite window is always a finite list\ndtr.intersect('2026-07-06T00:00:00Z', '2026-07-13T00:00:00Z');\n// → 5 intervals, one per business day\n\n// \"when does it next apply?\"\ndtr.next('2026-07-11T10:00:00Z');\n// → { start: 2026-07-13T09:00:00Z, end: 2026-07-13T18:00:00Z }\n\n// \"does it apply now, and until when?\"\ndtr.covering('2026-07-13T10:00:00Z');\n// → { start: 2026-07-13T09:00:00Z, end: 2026-07-13T18:00:00Z }\n\nparse('E7#-1 M4').describe();\n// → 'the last Sunday in April'\n\nparse('D25 M12').toRRule();\n// → 'RRULE:FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25'\n```\n\n> [!IMPORTANT]\n> 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.\n\n## API\n\n### Functions\n\n| Function | Description |\n| --- | --- |\n| `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`. |\n| `validate(expression)` | Non-throwing variant. Returns `{ valid, errors, warnings }`; warnings include the spec's *unsatisfiability lint* (`D30 M2` parses but can never match). |\n\n### `DTRExp` Instance\n\n| Member | Description |\n| --- | --- |\n| `covers(instant, opts?)` | Whether the expression covers the instant. O(#components) integer tests after one field extraction. |\n| `intersect(start, end, opts?)` | Covered intervals clipped to `[start, end)`: a finite, sorted, merged list of half-open `{ start: Date, end: Date }` intervals. |\n| `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: the coverage is exhausted, or it is continuous from `after` on (`E1:7` covers every instant, so nothing ever *starts*); neither means \"never applies\", `covers(after)` says whether it applies now. |\n| `covering(instant, opts?)` | The **maximal** covered interval containing `instant`, or `null` when it is not covered (`covers(t)` holds iff `covering(t)` is not `null`). The \"applies now, until …\" half of a display; `next()` is the \"next applies at …\" half. Coverage that reaches the edge of the year 1–9999 domain starts or ends there. |\n| `describe(locale?)` | Human-readable English rendering (`'E7#-1 M4'` → *\"the last Sunday in April\"*). v1 supports `'en'`; the parameter is reserved. |\n| `toRRule()` | RFC 5545 RRULE (+ `DTSTART` line when anchored) for the losslessly-mappable subset, else `null`. Constrained cadences emit RFC 7529 `SKIP=BACKWARD`. |\n| `toString()` | Canonical normalized form (redundant components dropped, canonical order, wraps re-fused). |\n| `warnings` | The spec [§9.1](https://github.com/DTRExp/dtrexp/blob/main/spec.md#91-the-existence-rule) warnings of the parsed expression: same content as `validate().warnings`, so parsing directly doesn't lose them. |\n| `source` | The original expression, verbatim. |\n\n### Inputs & Options\n\n- **Instants** (`DateInput`): `Date`, epoch milliseconds, ISO 8601 string, or any Temporal-like object exposing `epochMilliseconds` (no Temporal dependency).\n- **`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](https://github.com/DTRExp/dtrexp/blob/main/spec.md#93-dst-and-local-time): spring-forward gap times cover nothing; repeated fall-back times are covered on both passes. `intersect()` and `next()` return exactly the instants `covers()` accepts, transition days included: a clock time inside a gap yields no interval, a repeated one yields two.\n\n## Expression Syntax (spec draft 2.9)\n\nThe full grammar and semantics live in the **[specification](https://github.com/DTRExp/dtrexp/blob/main/spec.md)**; the essentials:\n\n| Component | Example | Meaning |\n| --- | --- | --- |\n| Selectors | `M3:7`, `E1:5`, `D1,15`, `W53`, `Q2`, `Y2018` | inclusive values/ranges/lists per calendar unit |\n| Negative index | `D-1`, `D-7:*` | from the end of the actual parent (last day, leap-safe) |\n| Exclusion | `M!5,7:9` | domain minus the set |\n| Ordinal | `E7#2`, `E7#-1` | nth / nth-from-last weekday in scope |\n| Time of day | `T0900:1200,1300:1800`, `T2200:0600` | half-open clock ranges; midnight wrap stays within the day |\n| Stride | `H0/4`, `M1/5/2`, `Y2020:2040/3` | calendar-locked recurrence: `/interval[/duration]` |\n| Cadence | `20200106/10D/3D`, `20180301/14M` | anchor-based recurrence that drifts across the calendar |\n| Bounds | `20150101:*`, `*:20291231`, `20180120` | absolute window / single day |\n| Union | `E5#1 \\| E5#3` | either expression |\n\nComponents in one expression **intersect**; `T0900:1800 E1:5 M!8` reads naturally as *\"9–18, on weekdays, except in August.\"*\n\n## Quality\n\n- **Conformance-first:** the test suite is driven by the shared [`vectors.json`](https://github.com/DTRExp/dtrexp/blob/main/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](https://github.com/DTRExp/dtrexp/blob/main/VECTORS.md) for how the suite works.\n- **100% coverage** on all four metrics (lines, statements, functions, branches), enforced as hard thresholds in CI.\n- **100% mutation score** ([Stryker](https://stryker-mutator.io/), `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.\n- **CI matrix** on Node 20 / 22 / 24 / 26, gate ladder `typecheck → lint → build → cover` plus a dedicated mutation job.\n- 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.\n\n## Related Projects\n\n- [**dtrexp** (spec)](https://github.com/DTRExp/dtrexp): the DTRExp specification (grammar, semantics, conformance vectors) this package implements.\n- [**dtrexp-py**](https://github.com/DTRExp/dtrexp-py) · [**dtrexp-go**](https://github.com/DTRExp/dtrexp-go) · [**dtrexp-swift**](https://github.com/DTRExp/dtrexp-swift) · [**dtrexp-rs**](https://github.com/DTRExp/dtrexp-rs) · [**dtrexp-java**](https://github.com/DTRExp/dtrexp-java): the ports; same core interface.\n- [**dtrexp-wasm**](https://github.com/DTRExp/dtrexp-wasm): the Rust core compiled to WebAssembly for JS hosts.\n\n## License\n\n© 2026, Onur Yıldırım. [**MIT**](LICENSE) License.\n","readmeFilename":"README.md"}