Profpatsch/users/Profpatsch/timetrack
- migrations/001_initial_schema.sql 831 B
- .gitignore 97 B
- CLAUDE.md 1.6 KiB
- NOTES-from-git-blimey.md 6.5 KiB
- archived_view.go 6.6 KiB
- clamp.go 1.4 KiB
- color_scheme.go 2.8 KiB
- db.go 31.8 KiB
- default.nix 998 B
- go-deps.nix 866 B
- go.mod 1.5 KiB
- go.sum 6.1 KiB
- import.go 7.6 KiB
- invoice_db.go 12.4 KiB
- invoice_preview.go 4.1 KiB
- main.go 59.1 KiB
- repl.go 29.2 KiB
- sfttime.7 3.4 KiB
- table.go 6.7 KiB
- time_helpers.go 5.8 KiB
- timestamp_table.go 24.5 KiB
- timetrack.1 9.7 KiB
timetrack(1)
NAME
timetrack - freelancing time tracker with TUI interface
SYNOPSIS
timetrack
[database]
timetrack
migrate
database
timetrack
import-client
database
client-name
json-file
[shortcode]
timetrack
create-invoice
database
client-name
DESCRIPTION
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.
TUI MODE
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.
Keyboard Shortcuts
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
Input Mode Keys
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
IMPORT MODE
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).
Arguments
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.
JSON Format
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
Import Behavior
- Entries that overlap with existing entries are skipped
- Only one open (in-progress) entry is allowed per client
- Invalid entries (missing required fields, parse errors) are skipped
- All successful imports happen atomically within a database transaction
- A detailed summary is printed showing imported, skipped, and failed entries
INVOICING
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 Numbering
Invoice numbers follow the format SHORTCODE-YYYY-NNN, where:
- SHORTCODE is the client's invoice prefix (e.g., "NP" for NewPipe)
- YYYY is the current year
- NNN is a sequential number starting from 001 for each client+year combination
Examples: NP-2025-001, NP-2025-002, ACME-2025-001
Creating Invoices
Invoices can be created in two ways:
- In TUI mode, press i on a client to preview and create an invoice for all uninvoiced entries
- From the command line using create-invoice
When an invoice is created:
- All uninvoiced completed time entries are included
- Each entry is snapshot as an immutable line item
- The original time entries are marked as invoiced (preventing duplicate invoicing)
- Invoice status starts as "draft"
Invoice Immutability
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.
Client Shortcodes
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.
EXAMPLES
Run Database Migrations
$ timetrack migrate ~/kot/work/timetracking.db
Start TUI with Production Database
$ timetrack ~/kot/work/timetracking.db
Import Time Entries
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
Import with Open Entry
To create an in-progress entry:
[
{"date": "2025-10-20", "start": "10:00"}
]
Import Client with Shortcode
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.
Create Invoice
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.
FILES
~/.local/share/timetrack/timetrack.db
Default database path
EXIT STATUS
The timetrack utility exits 0 on success, and >0 if an error occurs.
SEE ALSO
sqlite3(1)
AUTHORS
Profpatsch
CAVEATS
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)
NAME
sfttime - a hexadecimal time system based on fractional days
DESCRIPTION
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.
FORMAT
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/4096of a day, approximately 21 seconds (one vergil). Four digits give a resolution of1/65536of 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
EPOCH
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.
UNITS
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
EXAMPLES
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)
CONVERSION
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)
TIDBITS
The name 'sft' is the clan name of a group of friends from Munich, Bavaria.
SEE ALSO
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/
HISTORY
sfttime
was created in
3BBE.81[sft]
(approximately 2013).