Skip to main content

Time tracking library with billing calculations

Project description

Stundu

codecov

Note: This project was previously published as billable on the Snap Store and GitLab. The name was too generic and heavily overused across the web, making the project hard to find. The old billable snap is deprecated — please install stundu instead.

A command-line time tracking application with built-in billing calculations. Track work entries with timestamps and tags, calculate durations, compute billing based on configurable hourly rates with support for afterhours and holiday/weekend modifiers, generate professional invoices, and import data from other time trackers.

Table of Contents

Installation

There are three ways to get Stundu depending on your use case:

Method Best for
Snap End users on Linux who want the CLI
pip Developers who want to use the core library
From source Contributing or hacking on the project

Snap

The easiest way to install the CLI on Linux:

sudo snap install stundu

Or visit snapcraft.io/stundu.

pip

Install the core library (no CLI dependencies):

pip install stundu

Install with the CLI included:

pip install stundu[cli]

Install with PDF invoice support:

pip install stundu[cli,pdf]

The core library exposes the billing engine, data models, storage backend, and importers/exporters. The stundu command requires the [cli] extra.

From Source

Prerequisites

  • Python 3.11 or higher

Setup

  1. Clone the repository and navigate to the project directory:
cd stundu
  1. Create a virtual environment:
python3 -m venv venv
  1. Activate the virtual environment:
# Linux/macOS
source venv/bin/activate

# Windows
venv\Scripts\activate
  1. Install dependencies:
pip install -r requirements.txt

Quick Start

python stundu.py start "Acme Corp" "Development"
Started tracking: Acme Corp, Development
Started at: 09:00
python stundu.py status
Currently tracking: Acme Corp, Development
Started at: 09:00
Elapsed: 2h 15m
python stundu.py stop
Stopped tracking: Acme Corp, Development
Duration: 3h 0m
python stundu.py log
@    Start    End      Duration    Tags                    Status
--   -------  -------  ----------  ----------------------  --------
@1   09:00    12:00    3h 0m       Acme Corp, Development
python stundu.py configure rate "Acme Corp" 50 EUR
python stundu.py report
Report for 'Acme Corp' (Jan 1, 2026 to Jan 31, 2026)
────────────────────────────────────────────────────────────
╭──────────────┬───────┬───────┬──────────┬─────────────┬────────────╮
│ Date         │ Start │ End   │ Duration │ Tags        │ Cost       │
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
│ Jan 15, 2026 │ 09:00 │ 12:00 │ 3h 0m    │ Development │ 150.00 EUR │
│              │ 14:00 │ 18:00 │ 4h 0m    │ Development │ 200.00 EUR │
│              │ 18:00 │ 20:00 │ 2h 0m    │ Development │ 150.00 EUR │
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
│ Jan 18, 2026 │ 10:00 │ 14:00 │ 4h 0m    │ Development │ 400.00 EUR │
╰──────────────┴───────┴───────┴──────────┴─────────────┴────────────╯

────────────────────────────────────────────────────────────
Total time: 13h 0m

Total:
  900.00 EUR

See docs/examples.md for more detailed examples.

Configuration

First Run

On first run, Stundu displays a welcome message and prompts you to run the setup wizard:

Welcome to Stundu!
This appears to be your first run.
Run 'stundu.py setup' to configure.

Run stundu.py setup to create a configuration file interactively.

Config File

The default config file location is ~/.config/stundu/config.json:

{
  "locale": "lv",
  "style": "rounded_grid",
  "data_file": "/path/to/my/timesheet.csv",
  "formats": {
    "date": "short",
    "time": "short",
    "datetime": "medium"
  }
}

Run configure without arguments to see which config file is in use and what values are active.

Table Style

The style key controls how tables are rendered in report, log, and rates. Set it with:

python stundu.py configure style rounded_grid

Available styles (run configure style --help for the full list):

Tier Styles Divider
ASCII plain, simple, grid, github, psql, pipe, presto, pretty -
Light simple_grid, rounded_grid, outline, simple_outline, rounded_outline
Heavy heavy_grid, double_grid, fancy_grid, heavy_outline, double_outline, fancy_outline

The default is simple. The Quick Start section shows an example report rendered with rounded_grid.

Date/Time Format Options

The formats section controls how dates and times are displayed. You can use:

Preset styles (locale-aware):

  • short - Compact format (e.g., "1/15/26" in English, "15.01.26" in German)
  • medium - Default format (e.g., "Jan 15, 2026")
  • long - Full month name (e.g., "January 15, 2026")
  • full - Complete format (e.g., "Thursday, January 15, 2026")

Custom patterns (using Unicode CLDR format):

{
  "formats": {
    "date": "dd.MM.yyyy",
    "time": "HH:mm",
    "datetime": "yyyy-MM-dd HH:mm"
  }
}

Environment Variables

Variable Description
STUNDU_CONFIG Custom config file path
STUNDU_DATA Custom data file path

CLI Options

Option Description
--lang, -L Override language (e.g., --lang=de)
--data, -d Override data file path for this command

Precedence

Data file location (highest to lowest priority):

  1. --data CLI option
  2. STUNDU_DATA environment variable
  3. data_file in config file
  4. Default: stundu_data.csv in current directory

Config file location:

  1. STUNDU_CONFIG environment variable
  2. Default: ~/.config/stundu/config.json

Expression-Based Rates

Beyond fixed hourly rates, Billable supports MathJSON expressions as the hourly_rate. This lets you implement billing models where the effective rate depends on how much time has already been billed — retainers, volume discounts, loyalty pricing, and more. Entries that cross a rate-change threshold mid-session are automatically split at the exact minute the threshold is reached.

Expression-based rates are set by editing the data file directly (they are an advanced feature):

SET_HOURLY_RATE "Client" <expression> <currency>

First Hour Free, Then 50 EUR/h (Monthly Retainer)

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],1],0],50] EUR

The expression sums all logged hours for "Acme Corp" in the current calendar month. While the total is under 1 hour the rate is 0; after that it's 50 EUR/h. The counter resets automatically at the start of each month.

Volume Discount — Full Rate for First 20 h, Lower Rate After

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],20],80],60] EUR

First 20 hours each month: 80 EUR/h. Everything beyond: 60 EUR/h.

Loyalty Rate — Cheaper After 100 Lifetime Hours

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][:-1][hours]"],100],75],50] EUR

[:-1] sums all entries before the current one. The rate starts at 75 EUR/h and drops to 50 EUR/h once the client has accumulated 100 hours of lifetime work.

See docs/concepts.md for the full reference, including the entry reference syntax, available fields, and how expression-based rates compose with afterhours and holiday modifiers.

Localization

The CLI supports multiple languages with full localization of messages and date/time formats. The language is detected automatically from your system locale, or you can override it with --lang:

python stundu.py --lang=de status
python stundu.py --lang=es log

Date and time formatting automatically adapts to your locale using Babel:

Locale Date Example Time
en Jan 15, 2026 2:30 PM
de 15.01.2026 14:30
fr 15 janv. 2026 14:30
lv 2026. gada 15. janv. 14:30

Available Translations

Code Language
en English
de German
es Spanish
et Estonian
fr French
it Italian
lt Lithuanian
lv Latvian
pl Polish
pt Portuguese
uk Ukrainian

Commands

Time Tracking

Command Description
start <tags...> Start tracking time with one or more tags
stop Stop the currently active entry
status Show the currently active entry
continue Resume tracking with the same tags as the last entry
cancel Discard the currently active entry without saving

Viewing Entries

Command Description
log Show all time entries in a table
report Show billing report (defaults to current month)
rates Show configured rates for all tags

Report options:

  • --tag, -t - Filter by tag
  • --month, -m - Current month (default when no filter is given)
  • --last-month, -l - Previous month
  • --from - Start date filter (YYYY-MM-DD)
  • --to - End date filter (YYYY-MM-DD)

When no date filter is specified, the report shows the current month. If there are no entries in the current month, it falls back to all entries from the last day that has data.

Configuration

All configuration commands are grouped under the configure command:

Command Description
configure Show active config sources and available subcommands
configure rate <tag> <amount> <currency> Set hourly rate for a tag
configure workday <hours> [tag] Set workday hours (e.g., 9:00-18:00)
configure afterhours <tag> <multiplier> Set afterhours rate multiplier
configure holiday <tag> <multiplier> Set holiday/weekend rate multiplier
configure week-start <0-6> Set week start day for calendar expressions (0=Mon, 6=Sun)
configure style <name> Set table display style

Personal information (for invoices):

Command Description
configure my-name <name> Set your name
configure my-address <address> Set your address
configure my-email <email> Set your email
configure my-vat <vat> Set your VAT number
configure my-bank <bank> Set your bank name
configure my-bank-account <account> Set your bank account number
configure my-bic <bic> Set your bank BIC/SWIFT code
configure my-note <note> Set a note to appear on invoices

Client information (for invoices):

Command Description
configure client-address <tag> <address> Set client address
configure client-vat <tag> <vat> Set client VAT number
configure client-note <tag> <note> Set client-specific note

Entry Management

Command Description
modify <@N> [options] Modify an existing entry
delete <@N or tag> Delete entries by reference or tag

Invoicing

Command Description
invoice <tag> [options] Generate invoice for a tag

Invoice options:

  • --output, -o - Output file path (default: invoice_<tag>_<date>.html)
  • --format, -f - Output format: html or pdf (default: html)
  • --from - Start date filter (YYYY-MM-DD)
  • --to - End date filter (YYYY-MM-DD)

Importing Data

Command Description
import <source> <file> [options] Import time entries from external tools

Import options:

  • --dry-run - Preview import without saving
  • --yes, -y - Skip confirmation prompt

Other

Command Description
setup Run the setup wizard to configure stundu

Supported Importers

Stundu can import data from other time tracking tools:

Source Description
timewarrior Timewarrior data files

Data Storage

All data is stored in a single CSV file (stundu_data.csv) containing:

  • Time entries with start/end timestamps and tags
  • Rate configuration commands (SET_HOURLY_RATE, SET_WORKDAY, SET_WEEK_START, etc.)
  • Personal and client information (SET_MY_*, SET_CLIENT_*)

This makes it easy to backup, version control, or transfer your time tracking data.

Documentation

  • Examples - Detailed usage examples
  • Concepts - Tags, rate types, MathJSON expressions

Contributing

See CONTRIBUTING.md for development setup, testing, and contribution guidelines.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stundu-0.3.0.tar.gz (404.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stundu-0.3.0-py3-none-any.whl (80.6 kB view details)

Uploaded Python 3

File details

Details for the file stundu-0.3.0.tar.gz.

File metadata

  • Download URL: stundu-0.3.0.tar.gz
  • Upload date:
  • Size: 404.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for stundu-0.3.0.tar.gz
Algorithm Hash digest
SHA256 354f5e9d09d5403675951f1c5907903cecc828167367183492e211a361a68ca3
MD5 35f827d38164bf9a852b3d27b8a907a3
BLAKE2b-256 1860de1d0f062835d4a95d89bbd6f74e71eff8f47118f26c7803143e13aec7e6

See more details on using hashes here.

File details

Details for the file stundu-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: stundu-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 80.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for stundu-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7f51a65bf0f933c8f9f1711fb911b44b276918280c2049238b56be296da9b901
MD5 38166be0232891ea59b473cff5e858c1
BLAKE2b-256 277cde636f5d802b5ee814929435bba25fdc06e6ec001e958816faf9d69966e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page