dates-le 0.2.0

Extract every date and timestamp, and the exact instant each one resolves to
Documentation

Useful? A star is how other developers find it — ★ GitHub · letools.dev/tools/dates-le

A date is the one literal that looks correct in review and is wrong in production. 2024-01-15 in a config, 1705276800 in a fixture and Mon, 15 Jan 2024 10:30:45 GMT in a test are the same instant written three ways, and no reviewer holds all three in their head. "What dates are hardcoded in here, and which have already passed" is not a question grep can answer, because grep matches text and the question is about time.

Install

Route Command Worth knowing
cargo cargo install dates-le Any platform, needs Rust 1.88+.
From source git clone https://github.com/nolindnaidoo/dates-lecd dates-le/crate && cargo build --release The same build CI runs.

No runtime, no network, nothing written.

dates-le src/                       # every date, as JSON, one line per file
dates-le --before 2026-01-01 .      # everything already in the past
dates-le --sort --iso config/       # ordered by instant, in readable form
dates-le --values . | sort -u       # just the strings, for piping

Exit codes follow grep: 0 dates found, 1 none found, 2 a malformed question. Finding none is an answer, not an error.

A file that is not text is not read and not reported — it was never a candidate — and is counted in the stderr summary instead. A file that is text and cannot be opened is named on stderr and carried in the report rather than failing the run. --strict turns those back into a failure.

What it reads

Every text file. JSON, YAML, CSV, XML, log and plaintext, JavaScript, TypeScript, HTML, TOML and Markdown are names it knows; anything else — Python, Go, Rust, shell, SQL — is scanned with the nine patterns every format shares. There is no parsing; the format only decides which extra patterns join the nine, so a malformed document still yields its dates.

Notation Example
iso 2024-01-15T10:30:45.123Z
rfc2822 Mon, 15 Jan 2024 10:30:45 GMT
unix 1705314645, 1705314645123, and microseconds and nanoseconds
utc Mon Jan 15 2024 10:30:45 GMT+0000
local 1/15/2024 10:30:45
simple 2024-01-15
week 2024-W03, 2024-W03-1
ordinal 2024-015
basic 20240115, 20240115T103045Z
custom syslog, Apache, datetime=, new Date('March 5, 2024')

XML comments are skipped. Log files add syslog and Apache access lines. JavaScript and TypeScript add date-constructor arguments, which is how a string like March 5, 2024 — recognisable only by where it sits — is found at all.

The instant is the point

Every value carries the epoch-millisecond instant it resolves to, and a value whose instant cannot be resolved is not reported. That gate is what keeps a phone number out of the output.

Resolution matches Date.parse in V8 exactly, including the legacy parser no standard describes: garbage words are legal before the first number and fatal after it, weekday names are read and discarded, months match on three letters, EST is a fixed −5 rather than a zone, and a two-digit year is 1900s from 50. Those rules were established by asking V8 and are pinned in fixtures/date-parse.json, which cargo test replays.

Four shapes V8 refuses are read anyway — week dates, ordinal dates, the basic format, and CEST CET BST JST AEST IST — by normalising each into a string V8 does read. Every one is listed as a deliberate divergence in SPEC.md, and the extension diverges identically.

A date with no timezone resolves against this machine's. Several of the shapes carry no zone, so their instant genuinely differs by machine — exactly as it does for the code being audited. --tz UTC names one instead, and applies to --after and --before too, so where the command was typed cannot change the answer. At a daylight-saving transition the offset in force before the transition wins, for both the hour that does not exist and the hour that happens twice.

Options

--after <date>       keep dates at or after this instant
--before <date>      keep dates strictly before this instant
--sort               order by instant rather than by position
--dedupe             collapse repeated dates to their first occurrence
--iso                add each instant as a UTC ISO 8601 string
--tz <zone>          resolve dates that carry no timezone in this IANA
                     zone, e.g. UTC or America/New_York, instead of
                     this machine's
--format <format>    force a format instead of inferring it from the
                     file name; a name nothing recognises falls back to
                     the shared patterns rather than failing
--year <year>        the year a syslog line is assumed to be in, since
                     the line does not carry one. Defaults to this one,
                     which makes that answer move
--values             print only the dates, one per line, for piping
--strict             exit 2 if any file could not be read, rather than
                     reporting it and carrying on
--stdin              read one document from stdin
--hidden             walk hidden files and directories too
--no-ignore          walk files that .gitignore excludes

--after and --before accept anything this tool can read, so --after 2024-01-15 and --after 'March 5, 2024' both work.

As an MCP server

dates-le mcp

Two tools. extract_dates takes document text and is byte-identical to the one the VS Code extension's server offers — the same corpus runs against both. dates_le_scan reads files and directories.

The other half

This is the CLI and MCP half of dates-le, a VS Code extension. Extraction is shared and held identical by fixtures/; the extension's converting, filtering and validating are interactive and stay in the editor. SPEC.md is the behavioural contract, divergences included.

Documentation

What Where
What this tool is allowed to say — scope, output contract, refusals, non-goals SPEC.md
How the code is written and held together — architecture, invariants, the gates AGENTS.md
The VS Code extension this shares its extraction with README.md
What changed CHANGELOG.md
The tool's page, and the other fifteen letools.dev/tools/dates-le

More from the LE family

Sixteen single-purpose tools for the work in front of every model. Each ships a Rust CLI and an MCP server. One page: letools.dev

Get it out

  • String-LE — Extract every string in a codebase, with its position, so a person can read them
  • Numbers-LE — Extract every hardcoded number in a codebase, so a person can check them
  • Units-LE — Extract every quantity with its unit, normalized, and refuse the ambiguous ones by name
  • Dates-LE — Extract every date and timestamp, and the exact instant each one resolves to
  • IDs-LE — Extract every UUID, ULID, NanoID, ObjectId and Snowflake, and decode the time inside
  • IPs-LE — Extract every IP address, CIDR block and MAC, normalized and classified by scope
  • URLs-LE — Extract every URL in a codebase, with its protocol and exact position
  • Paths-LE — Extract every file path in a codebase, and say whether it still points at anything
  • Colors-LE — Extract every color in a codebase, and say which ones are not in your palette

Check it

  • Regex-LE — Find every regex in a codebase, and report which can be driven into catastrophic backtracking
  • Versions-LE — Find where one dependency is constrained differently across a repository's manifests
  • i18n-LE — Identify the i18n library a project uses, then audit its catalogs by that library's rules
  • Scrape-LE — Check whether a page is scrapeable before the scraper is written, and say when it cannot tell

Guard it

  • Secrets-LE — Find hardcoded credentials in a codebase, and never print one into the report
  • EnvSync-LE — Compare the dotenv files in a tree, and say which keys are missing from which
  • Unicode-LE — Find the Unicode that hides meaning — bidi controls, invisibles, homoglyphs, mixed scripts

Each stands on its own: no shared crate, no published core. Where two of them agree, it is because the same answer was right twice.

Contactnolindnaidoo.com · GitHub · LinkedIn

Also by nolindnaidoo

Rust — pixelcoords and pixelactions are one loop: pixelcoords answers where, pixelactions acts there. Their own tools, their own voice — not part of the LE family.

License

MIT — see LICENSE.