rusty-ts 0.2.1

Prefix each line of stdin with a timestamp — a Rust port of moreutils `ts` with strict-compat mode, IANA timezone control, and a reusable byte-typed library API.
Documentation

rusty-ts

Prefix every line of stdin with a timestamp. Rust port of moreutils ts(1).

crates.io docs.rs CI MSRV license: MIT OR Apache-2.0

Default mode adds the niceties moreutils doesn't have: -u/--utc, --tz=<IANA>, RUSTY_TS_FORMAT env-var default, & shell completions. Strict mode reverts every observable surface to byte-equal moreutils ts for drop-in migration. Prebuilt binaries for Linux, macOS, & Windows ship on every release.

Part of the Rusty portfolio.

Install

cargo install rusty-ts
# or, with prebuilt binaries:
cargo binstall rusty-ts
# or, download directly from GitHub Releases:
# https://github.com/jsh562/rusty-ts/releases

To also install a ts binary alias (argv[0] auto-detect routes into Strict mode):

cargo install rusty-ts --features ts-alias

Usage

# Prefix every log line with a timestamp (moreutils-default format `%b %d %H:%M:%S`)
some-command | rusty-ts

# Custom strftime format for sortable timestamps
some-command | rusty-ts '%Y-%m-%d %H:%M:%S'

# Show elapsed time between lines (debug slow pipelines)
some-command | rusty-ts -i

# Show elapsed time since program start using the monotonic clock
some-command | rusty-ts -s -m

# Force UTC instead of local time (consistent across hosts)
some-command | rusty-ts -u

# Use a specific IANA timezone
some-command | rusty-ts --tz=America/New_York

# Convert already-stamped log lines to relative form
cat logfile | rusty-ts -r

# Implicit default format via env var (Default mode only)
RUSTY_TS_FORMAT='[%H:%M:%S]' some-command | rusty-ts

# Strict moreutils-compat mode (drop-in moreutils ts replacement)
some-command | rusty-ts --strict
RUSTY_TS_STRICT=1 some-command | rusty-ts
some-command | ts                          # via ts-alias feature or argv[0] symlink

# Shell completions
rusty-ts completions bash                   # > ~/.bash_completion.d/rusty-ts
rusty-ts completions zsh                    # > ~/.zfunc/_rusty-ts
rusty-ts completions fish                   # > ~/.config/fish/completions/rusty-ts.fish
rusty-ts completions powershell

Library API

The crate exposes a byte-typed streaming surface. Non-UTF-8 input bytes round-trip unchanged. Use it inside a long-running daemon when you'd rather not shell out to a binary.

use rusty_ts::{TimestamperBuilder, Format, CompatibilityMode, TimezoneSource};
use std::io::{BufReader, Cursor};

let mut ts = TimestamperBuilder::new()
    .format(Format::Strftime("%Y-%m-%d %H:%M:%S".into()))
    .compat(CompatibilityMode::Default)
    .timezone(TimezoneSource::Utc)
    .build()?;

let input = BufReader::new(Cursor::new("hello\nworld\n"));
for line in ts.prefix_lines(input) {
    let bytes = line?;
    print!("{}", String::from_utf8_lossy(&bytes));
}
# Ok::<(), Box<dyn std::error::Error>>(())

For library-only consumers without CLI deps see the Cargo Features section.

Cargo Features

default enables full, which (for this single-capability port) resolves to the cli umbrella. ts-classic reproduces v0.1.x bare-port behavior matching upstream moreutils ts 1:1. To strip the CLI surface use default-features = false or --no-default-features & then add what you want.

rusty-ts is a single-capability port: its one documented job is "prefix each line of stdin with a timestamp". No optional feature leaves are carved beyond the required umbrellas; see docs/feature-layout.md for why.

Feature matrix

Feature Description Umbrella(s)
cli All CLI-only dependencies (clap, clap_complete, anyhow) and the binary entry point. Library consumers strip via default-features = false. full, ts-classic, ts-minimal, ts-alias
ts-alias Installs an additional ts binary alongside rusty-ts. Both share source; argv[0] auto-detect routes ts invocations into Strict mode. (standalone, implies cli)
bench Pulls criterion and enables benches/throughput.rs. Dev-tooling only; outside the convention's leaf surface. Name preserved verbatim from v0.1.x. (standalone)

Preset bundles

Bundle Composition Use case
ts-classic cli Drop-in upstream moreutils ts replacement. Strict mode is invoked via --strict, RUSTY_TS_STRICT, or ts-alias argv[0] auto-detect. No extra feature flag is required.
ts-minimal cli Explicit minimal-CLI alias for users who prefer the <port>-minimal naming convention seen across other portfolio ports (figlet-minimal, pwgen-minimal). Identical composition to ts-classic.

Keep-list workaround (Cargo features are union-only)

Cargo features cannot subtract from default. To get "everything except a specific feature," disable defaults & enumerate the features you want:

cargo install rusty-ts --no-default-features --features "cli"
# → bare CLI with no ts-alias binary, no bench tooling.
#   Equivalent to ts-classic / ts-minimal.

cargo install rusty-ts --no-default-features --features "cli ts-alias"
# → CLI + the ts alias binary.

For the common cases the named preset bundles are usually sufficient.

Library-only consumers

[dependencies]
rusty-ts = { version = "0.2", default-features = false }

This strips clap, clap_complete, & anyhow. The resulting build pulls only chrono, chrono-tz, regex, & thiserror. The CI test-no-default job runs cargo tree --no-default-features on every PR & fails the build if any CLI-only dep leaks back in.

Convention authority

This layout follows the portfolio-wide Cargo Features Convention. The "why" lives in ADR-0006; the "what" lives in project-instructions.md §Cargo Feature Surface. Every Rusty port from v0.2 onward exposes the same umbrella set (default / full / cli / <port>-classic), per-port leaves named in kebab-case, & 2 to 4 preset bundles.

Compatibility

rusty-ts has two modes:

  • Default mode. clap-styled flag parser. UTF-8 input. -u/--utc, --tz=<IANA>, RUSTY_TS_FORMAT env-var default, & the completions subcommand are all available. -r recognizes ISO-8601, RFC-3339, & Unix epoch (integer + fractional).
  • Strict mode (activated by --strict, RUSTY_TS_STRICT=1, or invoking the binary as ts). Byte-equal stdout, stderr, exit codes, & --help/--version layouts against moreutils ts at the pinned upstream commit recorded in fixtures/README.md. -u, --tz, & completions MUST be rejected. RUSTY_TS_FORMAT MUST be ignored. -r expands to the full moreutils recognized-timestamp set.

Byte-level fidelity is verified by snapshot tests against captured moreutils ts output under a pinned environment (TZ=UTC & LC_ALL=C.UTF-8), so results reproduce across hosts.

Documented intentional divergences

  1. -r recognized-timestamp set is a Default-mode subset. ISO-8601, RFC-3339, & Unix epoch only. --strict expands to the full moreutils set.
  2. -u / --utc. Default-mode addition; rejected in Strict.
  3. --tz=<IANA>. Default-mode addition; rejected in Strict.
  4. RUSTY_TS_FORMAT env var. Honored in Default; ignored in Strict.
  5. completions subcommand. Default-mode addition; rejected in Strict.

See docs/COMPATIBILITY.md for the full per-flag matrix & exit-code table.

What's not shipped

  • The full moreutils -r recognized-timestamp set in Default mode. Strict mode covers it; Default mode keeps the surface small (ISO-8601, RFC-3339, Unix epoch). Niche extended formats live behind --strict.
  • Source-code derivation from moreutils. This is a clean-room reimplementation. The moreutils ts Perl script is GPL'd & untouched. Snapshot tests compare runtime output bytes only, which are facts, not creative expression. Same posture as uutils/coreutils.

If you want the original moreutils ts, install it via your platform package manager (apt install moreutils, brew install moreutils). It coexists fine with this port.

MSRV

Rust 1.85 (edition 2024). Re-verified against the portfolio's stable-minus-two policy at each release.

License

Dual-licensed under MIT or Apache-2.0 at your option.