🛰️ time, reimagined

BSH — the BrightShell

A zsh-compatible shell with BrightDate woven natively into its builtins, parameters, prompt escapes, and file timestamps. One sortable, timezone-free scalar — everywhere.

# date — current BrightDate (universal, no timezone)
[9628.195697] % date
9628.195697
# subtract two timestamps — no parsing, no math libs
[9628.195697] % echo $(( 9700.5 - 9622.84 ))
77.66 # days, sortable as a float
# live read-only param — updates on every access
[9628.195697] % echo $BRIGHTEPOCH
9628.195698
$ Build from source → View on GitHub

Why a BrightDate-native shell?

Shell scripts that touch time pay a hidden tax: parsing date output, juggling $TZ, sub-shelling out to gdate on macOS, fighting epoch formats that differ between stat on Linux vs BSD, and re-implementing duration math by hand. BSH replaces all of that with a single decimal scalar — a timezone-free Float64 count of SI days since J2000.0 (the same epoch used by every space agency and observatory).

BSH is the living proof: every time-related surface in the shell — prompt, ls -l, stat, history -d, sched, times — emits the same float. No translation. No format mismatch. Duration arithmetic in $(( )) just works.

The BSH chsh challenge: switch your login shell for a week. Your prompt shows the BrightDate. Your files are stamped in BrightDate. Your history is indexed in BrightDate. Duration arithmetic happens in your head. Once you stop translating, it's hard to go back — that's the point.

🕰️ One scalar, everywhere

BrightDate is a Float64 count of SI days since the astronomical epoch J2000.0, computed on a TAI substrate. Trivially sortable, diffable, and storable — b − a = elapsed days, no exceptions.

Problem BrightDate fix
Timezone soup Single universal value, no zones
Leap-second stutters TAI substrate — monotonic, no jumps
Duration arithmetic b - a in shell $(( … ))
Sorting timestamps Float compare; works in sort -n, awk, jq
2038 / Y10K ±287,000 years at sub-µs in current era
date portability Identical output on macOS, Linux, BSD, WSL

🐚 BSH in one line

BSH = zsh 5.10 + a statically-linked Rust core (brightdate-rust) wired into the shell's C internals through a single FFI entry point:

double bsh_unix_to_brightdate(double unix_secs);
double bsh_brightdate_to_unix(double bd);

From those two anchors, every time-related surface — builtins, parameters, prompt escapes, history, file timestamps — speaks BrightDate. Nothing parses strings. Nothing re-derives the epoch. The number that comes out of $BRIGHTEPOCH is the same number that stat prints for mtime, the same number in your prompt, and the same number you can subtract in an arithmetic expansion.

BSH stays zsh-compatible: every traditional builtin, option, and module still works. BrightDate is layered on top — see how BSH differs from zsh.

How BSH differs from zsh.

BSH is a fork of zsh 5.10, not a new shell language. Your zsh scripts, completion functions, options, and module layout largely carry over unchanged — including setopt, zmodload, compinit, and the full zsh grammar. BSH adds a BrightDate-native time layer on top and a handful of BSH-only modules; it does not remove upstream behaviour.

Rule of thumb: if a feature does not touch time, it behaves like zsh. When a command prints a timestamp, measures a duration, or schedules an event, BSH prefers BrightDate decimals and millidays over calendar strings and raw Unix seconds.

Side-by-side: time-related surfaces

Surface zsh BSH
Time scalar Unix epoch ($EPOCHSECONDS, $EPOCHREALTIME) BrightDate — SI days since J2000.0 ($BRIGHTEPOCH)
date External command; locale and OS dependent Shell builtin (bdate); BrightDate by default, with ISO/JD/GPS/strftime conversions
time / btime Reserved word; TIMEFMT reports seconds (%E, %U, %S) Same keyword plus btime builtin; default TIMEFMT uses millidays (%dE, %dU, %dS). GNU time -f support with BrightDate extensions (%B, %b, %N, %n, %Wt, %Ws)
Prompt clock %T, %*… strftime escapes %P — current BrightDate (6 d.p.)
history -d Calendar date/time strings BrightDate command timestamps
fc -l -D Duration in seconds Duration in millidays
ls -l Locale-dependent mtime column BrightDate decimal (zmodload bsh/files); flags -u/-c select atime/ctime
stat Unix seconds or strftime via -F BrightDate defaults; new btime birth field; -r / -r -s for Unix side-by-side
sched HH:MM:SS offsets only Also BrightDate floats: relative +0.020833 or absolute 9628.5
times mm:ss user/system lines CPU time in millidays
uptime / cal / watch External, calendar/clock oriented Builtins reporting millidays and BrightDate clocks (buptime, bcal, bwatch)
Glob .m / .a / .c Integer day/hour/minute counts Fractional decimal-day values (centiday precision); new .b birth-time qualifier
Credential delivery BrightLink (bsh-inject, link-geo) — no zsh equivalent

BrightDate utilities: bdate, btime, and friends

The five classic Unix time commands are reimplemented in Rust as brightdate-rust (currently v0.5.7). Inside BSH they register as shell builtins and shadow date, time, uptime, cal, and watch. Outside BSH the same binaries install as bdate, btime, buptime, bcal, and bwatch — prefixed so they do not fight the system originals on PATH.

Same code, two names: bsh -c date and bdate call the same Rust entry point. Differences vs zsh are therefore identical whether you use the builtin or the standalone CLI — only the invocation name and whether a subprocess is forked change.
Tool BSH builtin Replaces What changes vs the traditional tool
bdate date date(1) Default output is a BrightDate scalar, not a locale string. Accepts BrightDate, ISO 8601, JD:, MJD:, or Unix ms input. Emits ISO/RFC/Julian/GPS, strftime patterns, TAI, breakdown, and day diffs via flags.
btime time / btime /usr/bin/time Default report is a multi-line BrightDate timing summary (days, millidays, start/end BD, CPU %). Optional colored stderr. Full GNU time 1.10 -f/-p/-v modes plus BrightDate % extensions including %Wt/%Ws.
buptime uptime uptime(1) Uptime shown in millidays; boot time as a BrightDate decimal alongside the familiar load-average line.
bcal cal cal(1) Standard month/year grid plus a BrightDate annotation for each day (noon UTC). Supports -3, -y, color, and configurable precision.
bwatch watch watch(1) Re-runs a command on an interval; header interval shown in millidays. Accepts -n seconds and -c iteration count.
📅

bdate / date

Parse almost anything, emit BrightDate by default. Calendar formats honor --local; astronomical formats include Julian, modified Julian, and GPS week.

$ bdate
9628.195697

$ bdate -f iso
2000-01-01T12:00:00Z

$ bdate -d 9628.5
0.304303
🖥️

buptime / uptime

Familiar load-average line, but uptime in millidays and boot time as a BrightDate decimal.

$ buptime
up 24 days, 14:59
(24624 md) — boot 9603.57128
🗓️

bcal / cal

Standard month/year grid with a BrightDate annotation per day (noon UTC). Supports -3, -y, and color.

$ bcal -3
 May 2026
  …
  1: 9628.50
👁️

bwatch / watch

Re-runs a command on an interval; header interval shown in millidays. Accepts -n seconds and -c iteration count.

$ bwatch -n 2 date
Every 0.023 md: …
🔗

Two names, one binary

bsh -c date and bdate call the same Rust entry point. Differences vs zsh are identical whether you use the builtin or the standalone CLI — only the invocation name and whether a subprocess is forked change.

⏱️

btime / time

Three output modes: (1) BrightDate report (default, colorized on TTY via --color=auto), (2) GNU -f format strings, (3) POSIX -p or verbose -v. As a BSH builtin, bad flags return an error code instead of killing the shell.

$ btime sleep 1
real 0.000011574 days (1.000000 s)
0.011574 millidays
user 0.000463 s (0.000005 millidays)
sys 0.000926 s (0.000011 millidays)
cpu 0.2%
start 9628.195697523
end 9628.195709097
$ btime -f '%Wt %C' -- true
9628.195712 true

btime format specifiers (GNU + BrightDate)

When -f, -p, or -v is selected, btime follows GNU time 1.10 for standard letters (%E, %U, %S, %P, %M, …) and adds BrightDate extensions (see FORMAT-SPEC.md):

Specifier Meaning
%dE / %dU / %dS Elapsed / user / system time in millidays (%.6f md) — same shape as BSH default TIMEFMT
%B Elapsed wall time in BrightDate days (9 d.p.)
%b Elapsed in millidays (6 d.p., no suffix — machine form of %dE)
%N / %n Start / end BrightDate wall time (9 d.p.)
%Wt / %Ws End / start BrightDate (9 d.p.) — same format as bfind -printf '%Wt'
%W GNU swap count (unchanged; use %Wt for BrightDate)

Color flags: --color=auto|always|never|ansi|truecolor, --no-color, --color-scheme=default|bright, plus BTIME_COLOR, BTIME_COLOR_SCHEME, NO_COLOR. GNU modes disable color automatically. On a TTY, only the default BrightDate report is colorized — not -f output; labels are cyan/green/yellow/blue by field — same scanning style as bfind and bright-iputils. Full cross-tool rules: FORMAT-SPEC.md.

Install standalone tools: brew install digital-defiance/tap/bdate btime buptime bcal bwatch or cargo install bdate btime buptime bcal bwatch. Inside BSH they are already present as builtins — no separate install required.

What you keep from zsh

BSH preserves the zsh feature set you already rely on: extended globbing, parameter expansion, job control, the line editor (ZLE), completion system, hash directories, multibyte/UTF-8 support, and the module ecosystem. Third-party frameworks such as Oh My Bsh target BSH directly but inherit zsh conventions.

Environment and startup files follow the zsh layout (/etc/bsh, ~/.bshrc, etc.) with BSH naming. Parameters like $ZSH_VERSION are mirrored by $BSH_VERSION and $BSH_PATCHLEVEL.

Implementation difference

BrightDate conversion lives in a statically-linked Rust library (brightdate-rust) exposed through two C FFI entry points. Shell builtins (date, time, …) call into that library instead of fork/execing external tools or reimplementing calendar math in C.

double bsh_unix_to_brightdate(double unix_secs);
double bsh_brightdate_to_unix(double bd);

The same crate powers standalone CLI tools (bdate, btime, …) for use outside the shell, and the BrightDate ecosystem on npm and crates.io.

BrightDate format alignment with bfind

The bright-findutils toolchain (bfind, blocate, …) prints file timestamps with -printf '%Wt' — modification time as a BrightDate decimal with 9 places, matching the C library in brightdate-rust. BSH’s btime builtin uses the same %Wt / %Ws letters in GNU -f format strings, but for command timing:

Specifier bfind -printf btime -f
%Wt File mtime (BrightDate) Command end time (BrightDate)
%Wa / %Wc / %WB atime / ctime / birth time — (not applicable to timing)
%Ws Command start time (BrightDate)
$ bfind . -name '*.log' -printf '%Wt %p\n'
9628.195697 ./app.log

$ btime -f '%Wt %C' -- sleep 0.1
9628.195712 sleep

$ btime -f 'start=%Ws end=%Wt elapsed=%B' -- ./deploy.sh
start=9628.195700 end=9628.195850 elapsed=0.000150

Because both tools emit the same scalar, log lines from bfind, timing output from btime, and your shell prompt (%P) can be compared, sorted, and subtracted without conversion — paste a %Wt from a build log into sched or compare it against $BRIGHTEPOCH in arithmetic expansion.

Default TIMEFMT (the time keyword)

When you prefix a command with the time reserved word, zsh formats resource usage using $TIMEFMT. BSH changes the default template to millidays:

# zsh default (seconds)
TIMEFMT='%J  %U user %S system %P cpu %*E total'

# BSH default (millidays)
TIMEFMT='%J  %dU user  %dS sys  %P cpu  %dE real'
$ time sleep 1
0.011574 md user  0.004630 md sys  0.2% cpu  0.011574 md real

# Override back to zsh-style seconds anytime:
TIMEFMT='%J  %U user %S system %P cpu %E real'

The standalone btime builtin (also invoked as time for timing commands) prints a richer BrightDate report by default — elapsed days, millidays, microdays, nanodays, start/end BrightDate, and CPU percentage — and accepts GNU -f/-p/-v when you need traditional /usr/bin/time output instead.

Builtins that speak BrightDate.

BSH bakes BrightDate directly into the classic Unix date/time commands. date, time, uptime, cal, and watch are shell builtins backed by the same Rust crates as the standalone bdate/btime/… CLI tools — no fork, no external process, no $TZ dance. The traditional binaries are shadowed transparently, so existing scripts switch over for free. btime colorizes its default report on TTYs for easy scanning. See full tool-by-tool differences.

built-in by default
datebdate-f bright/iso/julian/gps/all, --strftime, --local, --precision, --breakdown, --tai, --diff; input as BrightDate, ISO, JD:, MJD:, or Unix ms
timebtime — BrightDate elapsed report (colored on TTY) or GNU -f/-p/-v/-o; %Wt/%Ws/%B/%N format extensions. Use btime --help — not time --help (reserved word)
uptimebuptime — load averages plus uptime in millidays and boot BrightDate
calbcal — month/year grid with per-day BrightDate annotations; -3, -y, --no-color, --precision
watchbwatch — periodic re-run; interval header in millidays; -n seconds, -c count
# default BrightDate report — color on TTY (--color=auto)
[9628.19] % btime sleep 1
real 0.000011574 days (1.000000 s)
0.011574 millidays
11.574 microdays
user 0.000463 s
sys 0.000926 s
cpu 0.2%
start 9628.195697523
end 9628.195709097
# bfind-compatible -f format
[9628.19] % btime -f '%Wt %C' -- true
9628.195712 true
[9628.19] % uptime
24624 md # ≈ 24 days, 14:59
[9628.19] % watch -n 2 'ls | wc -l'
Every 0.023 md: ls | wc -l

Parameters, prompts, history, files.

$BRIGHTEPOCH

From bsh/datetime. Read-only Float64 parameter holding the current BrightDate, updated on every access — the analogue of $EPOCHREALTIME. Perfect for elapsed-time arithmetic in scripts.

start=$BRIGHTEPOCH
do_work
echo "elapsed: $(( BRIGHTEPOCH - start )) md"
💬

Prompt escape %P

In any PROMPT, RPROMPT, or PS4, %P expands to the current BrightDate (6 d.p.). %B is already bold, so BrightDate claims P for "Present."

PROMPT='[%P] %# '
# renders as:
# [9622.841738] %
📜

history -d / fc -D

With EXTENDED_HISTORY set, history -d (or -i, -f, -E) prints command-execution timestamps as BrightDate values instead of clock strings. fc -l -D reports command durations in millidays instead of seconds — so log analysis just becomes sort -n.

📁

ls -l, stat

From bsh/files and bsh/stat. bsh/stat is statically linked and autoloaded — no zmodload required; bsh/files ships as a dynamic module (zmodload bsh/files). ls -l replaces the timestamp column with a BrightDate decimal, following normal ls flags (-u for atime, -c for ctime, etc.). The stat builtin emits all time fields — atime, mtime, ctime, and the new btime (birth time, from st_birthtimespec on macOS/BSDs) — as BrightDate decimals by default. Use -r for raw Unix seconds, -r -s for both side-by-side, or -F fmt for strftime.

-rw-r--r--   1 jessica  staff       55082  9628.198451  README.md

$ stat README.md
  device  16777232
  inode   117800042
  mode    -rw-r--r--
  nlink   1
  uid     jessica
  gid     staff
  rdev    0
  size    55082
  atime   9628.199840
  mtime   9628.198451
  ctime   9628.198451
  blksize 4096
  blocks  112
  link
  btime   9628.198451

$ stat +mtime README.md
9628.198451

$ stat -r -s README.md
  mode    33188 (-rw-r--r--)
  uid     501 (jessica)
  gid     20 (staff)
  atime   1778604397 (9628.199840)
  mtime   1778604277 (9628.198451)
  ctime   1778604277 (9628.198451)
  btime   1778604277 (9628.198451)
🗓️

sched

Schedule commands at future BrightDate times. Accepts traditional HH:MM:SS offsets, BrightDate float offsets (+0.020833 ≈ 30 min), and absolute BrightDate values. List pending events with bare sched.

sched +0.020833 echo hello   # ~30 min from now
sched 9628.5 echo midnight  # absolute BD

$ sched
  1 9627.930118 echo hello
  2 9628.500000 echo midnight
⏱️

times builtin

Reports user and system CPU time (for the shell and its children) in millidays rather than the traditional m:ss format — uniform units across every time surface in BSH.

🦀

Rust core, C shell

The conversion engine is the brightdate Rust crate, statically linked into the bsh binary via a tiny FFI surface. No runtime dependencies. No subprocess overhead. Same TAI/J2000 substrate as the Rust and npm libraries.

Module index: bsh/brightdate bsh/datetime bsh/files bsh/stat bsh/brightlink

Glob time qualifiers in BrightDate units.

BSH extends zsh's glob qualifiers so .m, .a, .c, and the new .b (birth time) accept fractional decimal-day values. Filter files with centiday precision (0.01 d = 864 s ≈ 14 min) directly in the shell — and with .b, use a timestamp that touch can never fake.

Start with two log files, old.log backdated 2 hours:

$ setopt extendedglob nullglob
$ touch old.log new.log
$ perl -e 'utime time()-7200, time()-7200, "old.log"'

$ echo *.log(.m-1)    # within 1 day — both match, too broad
new.log old.log

$ echo *.log(.m-0.05) # within 0.05 d (~72 min) — precision cuts through
new.log

$ echo *.log(.m-0.01) # within centiday (~14 min) — only the fresh file
new.log

$ touch old.log       # reset mtime — but birthtime is immutable
$ echo *.log(.m-0.01) # mtime fooled: both match now
new.log old.log

$ echo *.log(.b-0.01) # birthtime never lies — old.log was born 2 h ago
new.log

All unit suffixes accept fractional values: d (days, default), h (hours), m (minutes), s (seconds), w (weeks), M (months). Combine with sort keys — *.log(.b-1On) gives today's logs oldest-first; *(ob) sorts everything by creation time.

Install BSH.

Homebrew logo

Mac Homebrew tap

The CLI tools and BSH itself are available via the Digital Defiance tap:

brew tap digital-defiance/tap
brew install bdate btime buptime bcal bwatch bsh

Ubuntu & Debian logo

Ubuntu & Debian — PPA

sudo add-apt-repository ppa:digital-defiance/ppa
sudo apt update
sudo apt install bsh-shell
chsh -s /usr/bin/bsh

🛠️

Build from source

BSH builds with the standard autoconf workflow inherited from zsh, plus a Rust toolchain for the brightdate-rust static library.

1

Prerequisites

A C compiler (clang or gcc), make, autoconf, and a recent Rust toolchain (rustup, stable channel). macOS, Linux, and *BSD are all supported.

2

Clone with submodules

git clone --recursive https://github.com/Digital-Defiance/bsh.git
  cd bsh
3

Configure & build

./configure
  make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
  make check          # optional: run the test suite
4

Install & switch shell

sudo make install
  echo "$(command -v bsh)" | sudo tee -a /etc/shells
  chsh -s "$(command -v bsh)"
5

Try the BrightDate surface

bsh -c 'date'                                  # BrightDate, baked in
  bsh -c 'echo $BRIGHTEPOCH'
  bsh -c 'PROMPT="[%P] %# "; print -P "$PROMPT"'

Looking for a configuration framework? See Oh My Bsh — 300+ plugins and 150+ themes layered on top of BSH.

The BrightDate ecosystem.

📦

npm — @brightchain/brightdate

TypeScript/JavaScript library. Same J2000.0 / TAI semantics. npm · source

🦀

crates.io — brightdate

Rust crate powering BSH internally. Also ships standalone CLI tools (bdate, btime, buptime, bcal, bwatch) — the b- prefixed versions for use outside BSH, where the native date etc. still rule. crates.io · source

Homebrew logo

Homebrew tap

The CLI tools and BSH itself are available via the Digital Defiance tap:

brew tap digital-defiance/tap
brew install bdate btime buptime bcal bwatch bsh
Ubuntu CoF logo

Ubuntu & Debian — PPA

BSH is available on Ubuntu and Debian via a Personal Package Archive:

sudo add-apt-repository ppa:digital-defiance/ppa
  sudo apt update
  sudo apt install bsh-shell
  chsh -s /usr/bin/bsh