CLI tool
BurntSushi/bttf avatar
BurntSushi/bttf

bttf turns datetime arithmetic into a pipeline of small commands

A command line tool for datetime arithmetic, parsing, formatting and more.

764 stars13 forksRustUnlicense

At a glance

What is it?
bttf is Andrew Gallant's Rust command line tool for datetime arithmetic, parsing and formatting, with IANA zone support, daylight saving safe duration maths, RFC 5545 recurrence sequences and a tagging mode for rewriting timestamps in log files.
Who is it for?
Adopt bttf if you script over dates, especially across time zones and daylight saving boundaries, because its arithmetic is safe on those transitions and its output is designed to be piped. Do not adopt it if you want a calendar interface, and do not expect a small binary, since the debug symbols are deliberate and the locale feature pulls in ICU4X.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 81 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A datetime tool written for scripts, not for calendars

bttf is described as a command line tool for datetime arithmetic, parsing, formatting and more, and the way it is put together tells you the audience. It is not a calendar application with a terminal interface. It is a set of small subcommands that print a datetime in a format you asked for, and that compose with each other through pipes. The package description in the manifest is blunter, calling it a command-line utility for doing datetime arithmetic, parsing and formatting, and it lists a non-exhaustive set of features: generating sequences of datetimes using RFC 5545 recurrence rules, performing calendar and absolute duration arithmetic interchangeably in a way that is safe with respect to daylight saving time, IANA time zone database support, the ability to tag arbitrary data in a JSON lines format with datetimes, datetimes written in a variety of flexible formats, and comparing and sorting datetimes. The project was initially named Biff and has been renamed to bttf, and the author keeps a user guide, a GUIDE.md in the tree, plus a COMPARISON.md that puts it next to other datetime command line tools. That comparison document is the honest place to start if you are choosing between tools, and the project says so itself.

Formatting with named formats, and time in another zone

Running bttf with no arguments prints the current time in a locale aware form:

code
$ bttf
Sat, May 10, 2025, 8:02:04 AM EDT

The time fmt subcommand is where the output shape is decided, and it accepts both named formats and strftime style strings:

code
$ bttf time fmt -f rfc3339 now
2025-05-10T08:08:30.101066734-04:00

$ bttf time fmt -f rfc9557 now
2025-05-10T08:08:33.420946447-04:00[America/New_York]

$ bttf time fmt -f '%Y-%m-%d %H:%M:%S %Z' now
2025-05-10 08:08:48 EDT

The rfc9557 output carries the IANA zone name in brackets, which matters when a timestamp is going to be stored or compared later, because it says which zone the offset belongs to rather than only what the offset was. The time in subcommand moves between zones, and the tool also rounds, which is the kind of thing you want before a timestamp lands in a database column:

code
$ bttf time in Asia/Bangkok now | bttf time round -i 15 -s minute
2025-05-10T19:15:00+07:00[Asia/Bangkok]

One diagnostic worth remembering. If the output looks like 2025 M05 10, Mon 08:02:04 rather than a readable date, the documentation says the likely cause is missing locale support, and the fix is to set BTTF_LOCALE in the shell environment.

Arithmetic that treats calendar and clock time differently

The add subcommand does duration arithmetic, and it accepts both compact relative forms and natural language:

code
$ bttf time add -1w now
2025-05-03T10:34:30.819577918-04:00[America/New_York]

$ bttf time add '1 week, 12 hours ago' now
2025-05-02T22:34:44.114109514-04:00[America/New_York]

$ bttf time add 6mo now
2025-11-10T10:34:49.023321635-05:00[America/New_York]

Note the offset change in that last line. A six month addition moves the wall clock time, and the offset in the result differs from the one in the input, which is exactly the behaviour you want and exactly what naive arithmetic gets wrong. The manifest states the guarantee plainly, that calendar and absolute duration arithmetic can be performed interchangeably in a way that is safe with respect to daylight saving time. The span subcommand goes the other way and measures the distance between two instants, then rounds it to the precision you ask for:

code
$ bttf span since 2025-01-20T12:00
2636h 1m 21s 324ms 691µs 216ns

$ bttf span since 2025-01-20T12:00 | bttf span round -l year -s day
3mo 18d

The full precision output goes down to nanoseconds, and the rounding flags pick the unit to report in, so a duration can be printed the way a person would say it.

Sequences from RFC 5545 recurrence rules

The seq subcommand generates a series of datetimes, and it is where the recurrence rule support shows. You can ask for the next five days that fall on Monday, Wednesday or Friday at a given hour:

code
$ bttf time seq day today -c5 -H 9 -w mon,wed,fri | bttf time fmt -f '%c'
Mon, May 12, 2025, 9:00:00 AM EDT
Wed, May 14, 2025, 9:00:00 AM EDT
Fri, May 16, 2025, 9:00:00 AM EDT

Or the last weekday of each of the next twelve months, using the set-position option:

code
$ bttf time seq -c12 monthly -w mon,tue,wed,thu,fri --set-position -1 | bttf time fmt -f '%a, %Y-%m-%d'
Fri, 2025-05-30
Mon, 2025-06-30
Thu, 2025-07-31

You can also name a weekday position directly, which reads like the rule it implements, with 2-tue meaning the second Tuesday:

code
$ bttf time seq monthly -w 2-tue --until $(bttf time end-of year now) | bttf time fmt -f '%a, %F'
Tue, 2025-05-13
Tue, 2025-06-10

Boundaries can be given as a date, as the end of a unit through the end-of subcommand, or as a count, and the sequence is a plain list of timestamps that you pipe into whatever formatting you want. The one piece of original code in the project is the RFC 5545 implementation, and the README notes that there is relatively little datetime logic inside bttf proper, that most of it is provided by Jiff, and that the RFC 5545 implementation may eventually move out into a library.

Tagging and untagging is how the log file example works

The most distinctive feature is the tagging mechanism, and it is what makes the log file example work. The idea is that a datetime is attached to arbitrary data in a JSON lines format, so a stream of unrelated text can carry timestamps through a pipeline and be reassembled afterwards. The example takes a log file, converts the timestamps to the local system zone, reformats them, and puts the original lines back together:

code
$ bttf tag lines /tmp/access.log | bttf time in system | bttf time fmt -f '%c' | head -n3 | bttf untag -s
Wed, Apr 30, 2025, 1:25:14 AM EDT       INFO    http.log.access.log0    handled request

The same idea turns a git repository into a table of file modification times, which is a good demonstration because every stage is a separate tool and the data survives the trip:

code
$ git ls-files \
    | bttf tag exec git log -n1 --format='%aI' \
    | bttf time sort \
    | bttf time fmt -f '%Y-%m-%d %H:%M:%S %z' \
    | bttf untag -f '{tag}\t{data}'
2025-05-05 21:54:09 -0400       src/tz/timezone.rs
2025-05-05 22:06:38 -0400       Cargo.toml

The tag exec variant runs a command and tags its output, which is why the git pipeline needs nothing beyond git and bttf. The untag step then puts the datetime and the original payload back into one line, with a format string that lets you choose the layout, here a tab between the tag and the data. Compared with a tool that only prints dates, this is a different approach to the problem, and it is the one that makes bttf useful inside a script that has to rewrite timestamps in place rather than merely report one.

Locale support is a compile time feature flag

Two practical details in the installation section will save you time. The first is that the binary may be bigger than expected because it contains debug symbols, and the documentation says this is intentional, with the fix being to run strip on the binary to remove them. The second is the feature flag. Locale support is a default feature, and installing without default features gives a faster compile and a smaller binary:

code
cargo install bttf --no-default-features

The locale feature is what pulls in the ICU4X components, with icu_calendar, icu_datetime, icu_locale, icu_time, jiff-icu and writeable listed in the manifest, and the underlying datetime engine is Jiff itself, with the tzdb-bundle-always feature so the zone database is always available. The project also publishes precompiled binaries for Windows, macOS and Linux, and the Linux and Windows binaries are static executables, which matters for people shipping them into containers. The manifest declares edition 2024 with a rust-version of 1.93, so a recent toolchain is required, and the licensing is unusually generous, dual licensed under MIT or the Unlicense, with both COPYING and LICENSE-MIT and an UNLICENSE file in the tree. The version history is short and young, with 0.1.1 in November 2025, and both 0.1.3 and 0.1.4 tagged on 2026-05-28, and the last push on 2026-08-03.

Editorial conclusion

Adopt bttf if you script over dates, especially across time zones and daylight saving boundaries, because its arithmetic is safe on those transitions and its output is designed to be piped. Do not adopt it if you want a calendar interface, and do not expect a small binary, since the debug symbols are deliberate and the locale feature pulls in ICU4X. Before you commit, read the comparison page the project ships, set BTTF_LOCALE if the default output looks like a raw timestamp, and check the version you install, because the tool was renamed from Biff and its numbering is still below 0.2.

Frequently asked questions

How do I install bttf?

Precompiled binaries are published for Windows, macOS and Linux, with the Linux and Windows builds being static executables, or you can install it with cargo install bttf. Installing with cargo install bttf --no-default-features disables locale support, which makes compilation faster and the binary smaller, at the cost of the readable date output.

Why does bttf print a date in a strange format?

Output like 2025 M05 10, Mon 08:02:04 usually means locale support is not configured. Enabling it requires setting BTTF_LOCALE in your shell environment, and the default output is a readable form such as Sat, May 10, 2025, 8:02:04 AM EDT.

How do I round a timestamp to the nearest quarter hour?

Pipe it through the round subcommand with an increment and a unit, for example bttf time in Asia/Bangkok now | bttf time round -i 15 -s minute. The same idea applies to durations, where bttf span round -l year -s day reports a span rounded to the day.

Can bttf rewrite timestamps inside a log file?

Yes. The README example runs bttf tag lines on the log file, then bttf time in system, then bttf time fmt to reformat, and finally bttf untag -s to rebuild the original lines with the new timestamps. The tag exec variant does the same for the output of another command, such as git log.

What licence is bttf released under?

It is dual licensed under MIT or the Unlicense, with both COPYING and LICENSE-MIT files and an UNLICENSE file in the repository root. Most of the datetime logic comes from Jiff and the localisation from ICU4X.

Official sources

  1. BurntSushi/bttf on GitHub
  2. Issues
  3. License: Unlicense
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/burntsushi-bttf.svg)](https://hysenlabs.com/projects/burntsushi-bttf)

Was this useful?