Profpatsch/users/Profpatsch/timetrack

timetrack(1)

timetrack - freelancing time tracker with TUI interface

timetrack [database]
timetrack migrate database
timetrack import-client database client-name json-file [shortcode]
timetrack create-invoice database client-name

timetrack is a terminal-based time tracking application designed for freelancers managing multiple clients. It provides a TUI (Terminal User Interface) for interactive time tracking and a CLI import command for bulk data import.

When invoked without arguments, timetrack starts in TUI mode using the default database at ~/.local/share/timetrack/timetrack.db. An optional database path can be specified to use a different SQLite database.

The migrate command applies any pending database migrations to the specified database and displays the results. This is useful for upgrading database schemas after software updates or inspecting which migrations have been applied. All migrations run atomically within transactions.

The import-client command imports time entries from a JSON file into the specified database, either creating a new client or appending to an existing one. An optional shortcode can be provided when creating a new client for invoice numbering.

The create-invoice command creates a new invoice for all uninvoiced time entries for the specified client. Invoices are immutable once created and use a unique numbering scheme based on the client's shortcode.

The TUI displays time entries in a tabular format with columns for Date, Start, End, Duration, and Comment. Multiple clients are shown as tabs at the top.

Milestone markers can be inserted into the entry list to track project phases. Milestones display as a flag (🏁) in the Date column and show cumulative hours in the Start column, representing the total time worked from that milestone until the next milestone (or end of list). This helps visualize time spent across different project stages.

The TUI automatically reloads data when the terminal gains focus, allowing you to see changes made by external processes or other instances of timetrack.

q or Ctrl-c

Quit the application

t

Toggle time tracking for the current client. If a completed entry ended less than 15 minutes ago, it will be restarted (extending the previous entry). Otherwise, a new entry is started. If an entry is running, it will be stopped.

Ctrl-t

Force start a new entry (bypass the 15-minute restart logic)

Tab

Switch to the next client

Shift-Tab

Switch to the previous client

up or k

Move selection up

down or j

Move selection down

PgUp

Move selection up one page

PgDown

Move selection down one page

home or g

Jump to first entry

end or G

Jump to last entry

e

Edit the comment for the selected entry

m

Create a new milestone at the selected entry's timestamp, or edit the selected milestone. Milestones are markers that show project phases and display cumulative hours worked between milestones.

Ctrl-d

Delete the selected entry or milestone (cannot delete invoiced entries)

Ctrl-Left

For entries: adjust end time backward by 15 minutes. For milestones: move milestone up to the previous item's timestamp (cannot adjust running or invoiced entries).

Ctrl-Right

For entries: adjust end time forward by 15 minutes. For milestones: move milestone down to the next item's timestamp (cannot adjust running or invoiced entries).

Ctrl-Shift-Left

Adjust start time backward by 15 minutes (cannot adjust invoiced entries)

Ctrl-Shift-Right

Adjust start time forward by 15 minutes (cannot adjust invoiced entries)

i

Preview and create an invoice for all uninvoiced entries of the current client

Ctrl-n

Create a new client (prompts for name and shortcode)

Ctrl-a

Archive the current client

Ctrl-u

Toggle archived clients view

When creating a client, editing a comment, or creating/editing a milestone:

Enter

Confirm input and save

Escape or Ctrl-c

Cancel input and discard changes

Left / Right

Move cursor

Home or Ctrl-a

Move cursor to start

End or Ctrl-e

Move cursor to end

Ctrl-Left / Ctrl-Right

Jump to previous/next word boundary

Backspace

Delete character before cursor

Ctrl-Backspace or Ctrl-w

Delete word before cursor

The import-client command reads time entries from a JSON file and imports them into the specified database. Entries are checked for overlaps with existing entries, and the import is performed atomically (all or nothing).

database

Path to the SQLite database file (e.g., ~/kot/work/timetracking.db)

client-name

Name of the client. If the client doesn't exist, it will be created with a default target of 40 hours.

json-file

Path to the JSON file containing time entries

shortcode (required for new clients)

Invoice shortcode for new clients (e.g., "NP" for NewPipe). This shortcode is used to generate unique invoice numbers in the format SHORTCODE-YYYY-NNN. Required when creating a new client; ignored if the client already exists.

The JSON file must contain an array of time entry objects with the following fields:

date (required)

Date in YYYY-MM-DD format (e.g., "2025-10-18")

start (required)

Start time in HH:MM format (e.g., "10:00")

end (optional)

End time in HH:MM format. If omitted, creates an open (in-progress) entry. If the end time is earlier than the start time, it's assumed to cross midnight.

timezone (optional)

Timezone name (e.g., "UTC", "Europe/Berlin", "America/New_York"). Defaults to local time if not specified.

comment (optional)

Description or comment for the time entry

timetrack provides built-in invoicing functionality to create immutable snapshots of completed time entries. Each client must have a unique shortcode that is used to generate invoice numbers.

Invoice numbers follow the format SHORTCODE-YYYY-NNN, where:

Examples: NP-2025-001, NP-2025-002, ACME-2025-001

Invoices can be created in two ways:

  1. In TUI mode, press i on a client to preview and create an invoice for all uninvoiced entries
  2. From the command line using create-invoice

When an invoice is created:

Once created, invoice line items are immutable snapshots that preserve the exact state of time entries at invoice creation time. This ensures legal compliance and prevents accidental modification of billed work. The original time entries remain in the database for reference and can be navigated from invoice line items via their source_entry_id.

Every client must have a shortcode before invoices can be created. Shortcodes are assigned when creating a client (either via Ctrl-n in TUI mode or via the shortcode parameter in import-client ). Shortcodes must be unique across all clients and are typically 2-4 uppercase letters.

$ timetrack migrate ~/kot/work/timetracking.db

$ timetrack ~/kot/work/timetracking.db

Create a JSON file (entries.json) with the following content:

[
  {"date": "2025-10-18", "start": "10:00", "end": "18:00",
   "timezone": "UTC", "comment": "Feature development"},
  {"date": "2025-10-17", "start": "09:00", "end": "17:00",
   "comment": "Bug fixes"}
]

Then import:

$ timetrack import-client ~/kot/work/timetracking.db ClientName entries.json

To create an in-progress entry:

[
  {"date": "2025-10-20", "start": "10:00"}
]

When importing a new client, specify the shortcode for invoice numbering:

$ timetrack import-client ~/kot/work/timetracking.db NewPipe entries.json NP

This creates a client with shortcode "NP" that will generate invoices like NP-2025-001, NP-2025-002, etc.

To create an invoice for all uninvoiced entries:

$ timetrack create-invoice ~/kot/work/timetracking.db NewPipe

This will create an invoice (e.g., NP-2025-001) containing all completed, uninvoiced time entries for the client.

~/.local/share/timetrack/timetrack.db

Default database path

The timetrack utility exits 0 on success, and >0 if an error occurs.

sqlite3(1)

Profpatsch

The import functionality uses the database path from users/Profpatsch/timetrack/CLAUDE.md which specifies ~/kot/work/timetracking.db as the production database. Always verify the database path before importing to production.

sfttime(7)

sfttime - a hexadecimal time system based on fractional days

sfttime is an alternative representation of UTC time. For example, 2026-04-23 at 13:37 UTC (the start of sft day 5056) is written:

5056.000[sft]

Time is expressed as a hexadecimal count of days since the sfttime epoch, with a fractional component for sub-day precision. It is very regular, and timestamps are concise: a full timestamp with second-level precision fits in 12 characters. It is also a good fit for shell prompts and status lines, where only the sub-day part is needed — that requires only five characters, for example the start of the sfttime day (13:37 UTC) is .0000, and noon UTC is .EEC0.

A full sfttime timestamp looks like:

DDDD.FFF[sft]

Where:

DDDD

Hexadecimal count of days since the sfttime epoch. Grows by 1 each day.

.FFF

Fractional part of the current day in hexadecimal. Three digits give a resolution of 1/4096 of a day, approximately 21 seconds (one vergil). Four digits give a resolution of 1/65536 of a day, approximately 1.3 seconds (one tick).

[sft]

Optional suffix indicating the value is in sfttime. May be omitted in contexts where it is unambiguous.

The sub-day compact form omits the date entirely and is written as:

.F3C9

This is useful for timestamps where the date is known from context, such as log entries or status lines within a running session. Adding a fifth digit gives ~82ms precision (roughly 1/10 of a second), and a sixth digit gives millisecond-level precision (~5ms):

.F3C9A4

The sfttime epoch is 1970-01-01 13:37:00 UTC, which is 49020 seconds after the Unix epoch (1970-01-01 00:00:00 UTC). The choice of 13:37 is intentional.

Each power of 16 in the fractional part has a name:

**Unit**           **Duration**    **Example**   **Name**  
`[sft]0`           1 day           `1.0000[sft]` day  
`[sft]-1`          1.5 hours       `.1000[sft]`  major (one university lecture)  
`[sft]-2`          ~5.6 minutes    `.0100[sft]`  schinken  
`[sft]-3`          ~21 seconds     `.0010[sft]`  vergil  
`[sft]-4`          ~1.3 seconds    `.0001[sft]`  tick

Converting the current time:

$ sfttime
5055.F3C[sft]

A compact sub-day timestamp as used in status lines:

These timestamps represent the same moment expressed at different precisions:

**Timestamp**     **Precision**  
`5055[sft]`       day (24h)  
`5055.F[sft]`     major (1.5h)  
`5055.F3[sft]`    schinken (~5.6min)  
`5055.F3C[sft]`   vergil (~21s)  
`5055.F3C9[sft]`  tick (~1.3s)

To convert a sfttime timestamp to Unix time:

unix = hex_to_decimal(sfttime) * 86400 + 49020

To convert Unix time to sfttime:

sfttime = decimal_to_hex((unix - 49020) / 86400)

The name 'sft' is the clan name of a group of friends from Munich, Bavaria.

date(1)

Coordinated Universal Time (UTC)

https://en.wikipedia.org/wiki/Coordinated_Universal_Time

dot-time — a universal convention for conveying time

https://dotti.me/

sfttime was created in 3BBE.81[sft] (approximately 2013).