Tiempo#

Synopsys#

t -h|--help

t help SUBCOMMAND

t SUBCOMMAND -h|--help

t SUBCOMMAND [OPTIONS]

Description#

Tiempo is a command-line time tracker. Register the start and end times of your activities and get results by day, week, month or custom periods of time in different formats, including custom ones.

Entries are optionally organized in timesheets which you can use to represent projects, clients etc. Most commands (notably the ones that display entries) work on the current or active timesheet.

The entries are stored in a local Sqlite3 database whose location you can learn by looking at your config file (whose location you can learn running t c). See Files, paths and the environment for more info on setting your config file.

tiempo stores entries as UTC and then converts to the local timezone for displaying and querying. This means that even if you change timezones your recorded times will be correct, although their displayed results might vary if grouping by day for example.

This tool is compatible with a previous one with the same purpose called timetrap. The most notable difference being that timetrap stores times as local timezone times, which makes it messy if you switch timezones. Another difference is that in timetrap you used the internal ruby API to add custom display formats and in tiempo you’ll use standard input/output and command-line arguments in any language of your choice.

Subcommands#

Bellow you can see the full list of subcommands. All of them can be abbreviated to their first letter, so for example t backend can also be typed t b.

Command

Description

t archive

Archive entries to a hidden timesheet.

t backend

Open an sqlite shell to the database

t configure

Configure tiempo in-place or get path to config file.

t display

Display all entries in the current sheet.

t edit

Edit an entry.

t in

Start an entry in the current timesheet.

t kill

Delete an entry or an entire timesheet.

t list

List existing sheets.

t month

Display entries starting this month.

t now

Show all running entries.

t out

End the active entry in the current timesheet.

t resume

Restart the timer for an entry.

t sheet

Switch to a new or existing timesheet.

t today

Display entries that started today.

t week

Display entries starting last monday or later.

t yesterday

Display entries that started yesterday.

Tutorial#

Life Cycle#

Most of the time you’ll be using these commands:

  1. t in starts time tracking for an activity.

  2. t out ends time tracking for the current activity.

  3. t resume resumes time tracking for a previous activity.

  4. t edit allows to quickly edit an entry’s details.

  5. t archive archives entries without deleting them from the database.

  6. t kill permanently deletes entries.

You can see the state of your activities using:

  • t list shows time tracking summary for all timesheets.

  • t now shows running entries for all timesheets.

  • t display shows more detailed information about activities in the current timesheet. Variants of this subcommand include t today, t yesterday, t week and t month.

To begin, let’s start a task with:

$ t in 'task 1'
Checked into sheet "default".

At any time see what’s happening with:

$ t display
Timesheet: default
    Day                Start      End        Duration Notes
    Sun Oct 30, 2022   11:24:18 -             0:00:03 first task
                                              0:00:03
----------------------------------------------------------------
    Total                                     0:00:03

After some time of hard work you can stop tracking with:

$ t out
Checked out of sheet "default".

If later (say, after lunch) you decide to continue with this activity you may do so with:

$ t resume
Resuming "task1"
Checked into sheet "default".

Which by default resumes the last task of the timesheet.

After some time (days, weeks, months…) of activities being recorded and queried you might want to archive some of them. For that use:

$ t archive
A total of 2 entries accounting for 9m 38s will be archived.
Proceed? [y/N] y

If you do list or display, no activities are going to be active. If you want to see archived activities, you can do it with:

$ t display full
Timesheet: _default
    Day                Start      End        Duration Notes
    Mon Oct 24, 2022   18:57:03 - 19:01:25    0:04:21 task1
                       19:06:49 - 19:12:05    0:05:16 task1
                                              0:09:38
-----------------------------------------------------------
    Total                                     0:09:38

Using sheets#

Usually you need to track activities for different projects or clients. That’s what timesheets are for.

To switch (and create if necessary) to a new timesheet use:

$ t sheet 'My Amazing Project'
Switching to sheet 'My Amazing Project'

Then, start a new activity in the new timesheet with:

$ t in 'An Amazing Activity'
Checked into sheet "My Amazing Project".

Just like before you can use t display to see your progress. To see a summary of all of your timesheets use:

$ t list
  Timesheet            Running     Today   Total Time

* My Amazing Project   0:00:00   0:00:00      0:00:00
- default              0:00:00   0:00:05      0:00:05
-----------------------------------------------------
                       0:00:00   0:00:05      0:00:05

You can switch timesheets with:

$ t sheet default
Switching to sheet 'default'

If you switch very often between two timesheets pass - to switch to the previously active timesheet.

Next steps#

See the list of subcommands and their options bellow to really take advantage of tiempo. Learn about the different settings (see Settings) and how to use the different formatters (see Default formatters) that are shipped by default or learn how to make your own (see Custom formatters).

Happy time tracking!

Subcommands#

t archive#

t a|archive [OPTIONS] [SHEET]

To archive means to move entries from the current or specified timesheet to a different “hidden” sheet so that when you query the current sheet you don’t see them but they are kept in the database. Archived entries are stored in a sheet with the same name of the original with an underscore as prefix. Use t list to see archived sheets.

By default this command will ask for you confirmation, then archive ALL entries in the current timesheet. Pass --fake to only display the entries using the text formatter (useful if you want to make sure that the archived time/entries are correct).

Using --start and --end you can further limit the period of time that will be archived (See Specifying times section of the docs on the accepted formats). You can use --grep to only archive certain entries that match the given regular expression.

If what you need is to archive a specific elapsed time, for example only 15 hours out of however many the timesheet has use --time with the amount of hours you want to archive.

Options

-f, --fake

Don’t actually archive the entries, just display them

-e, --end <TIME>

Include entries that start on this date or earlier

-g, --grep <REGEXP>

Include entries where the note matches this regexp.

-s, --start <TIME>

Include entries that start on this date or later

-t, --time <HOURS>

Time in hours to archive. Archived time will be equal or less than this.

SHEET

Archive entries from this timesheet instead of the current one.

t backend#

t b|backend

Open a sqlite3 shell into the database. Sqlite3 must be installed and in the PATH variable for this command to work.

t configure#

t c|configure [OPTIONS]

If called without arguments t c will print the absoulte path to the config file. If you pass arguments they’ll be used to edit your config file in place.

Warning

This will erase any comments you might have added to your toml or yaml file. It is better to edit the config file manually with your preferred text editor:

vim $(t c)

Options

--auto-checkout

Checkout of current running entry when starting a new one. Not implemented.

--no-auto-checkout

Starting a new entry fails if one is running (default).

--no-require-note

Entries can be created without a note.

--require-note

Prompt for a note if one isn’t provided when checking in (default).

--append-notes-delimiter <DELIMITER>

delimiter used when appending notes via t edit –append. Default: ‘ ‘ (space).

--database-file <PATH>

Set the absoulte path to the sqlite database. A new database will be created at this location if one does not exist.

--default-formatter <FORMATTER>

The format to use when display commands are invoked without a --format option. Defaults to 'text'. See Settings section of the docs for per-command default formatter.

--formatter-search-paths <PATH>[,<PATH>...]

comma separated paths to search for user defined fomatters. See Custom formatters section for more info on this.

--interactive-entries <N>

How many unique previous notes to show when selecting interactively. An example of an interactive command is t resume -i.

--note-editor <EDITOR>

Specify a custom command used to edit an entry’s note. Default: $EDITOR

--round-in-seconds <SECONDS>

The duration of time to use for rounding with the -r flag. Default: 900 (15 m). Not implemented.

--week-start <DAY>

The day of the week to use as the start of the week for t week. Default: monday [possible values: monday, tuesday, wednesday, thursday, friday, saturday, sunday]

t display#

t d|display [OPTIONS] [SHEET|all|full]

Display all entries in the current timesheet using the specified formatter (by default this is text). For more information on formatters see Default formatters. For information on custom formatters see Custom formatters.

It is worh noting that BOTH the --start and --end arguments act on the start time of the entry.

Options

-v, --ids

Print database ids (for use with edit).

-e, --end <TIME>

Include entries that start on this date or earlier.

-f, --format <FORMAT>

The output format. Valid built-in formats are ‘chart’, ‘text’, ‘ical’, ‘csv’, ‘json’, ‘ids’, ‘activities’ and ‘csvactivities’. See Default formatters for more info. See Custom formatters to learn how to make your own.

-g, --grep <REGEXP>

Only display entries whose notes match this regular expression.

-s, --start <TIME>

Include entries that start on this date or later.

--sort <COLUMN>

Sort entries by this in the activities or csvactivities formatters. Options are text, time and last_started. Add a - (minus sign) before the column name to make it a descending order. Default is -last_started.

<SHEET>

The sheet to display. Pass ‘all’ to see entries from all sheets or ‘full’ to see hidden entries.

New in 1.7

Pass partial formatter names. For example pass t as argument to commands that accept –format and it will be understood as text formatter. It uses a prefix match.

t edit#

t e|edit [OPTIONS] [NOTE]

If run without arguments this command will launch a text editor with the text of the current timesheet’s last entry’s note. Upon saving and exiting the new text will be set. If a string is given as command-line argument it will be used as the new note and no editor will be launched. If used with --append the new text will be appended to the previous one using whatever string is set as append_notes_delimiter in the settings file.

The last entry is defined as the one that was started last, not necessarily the one with the highest ID in the current timesheet.

You can also leave the note untouched and edit other parts of the entry like its start and end times using --start and --end respectively, see Specifying times. Or you can move an entry that was created in the wrong timesheet by using --move.

Options

-z, --append

Append the new text to the current note instead of replacing it.

-e, --end <TIME>

Set this as the end time.

--id <ID>

Edit entry with this ID instead of the last one in the current sheet.

-m, --move <SHEET>

Move entry to another sheet.

-s, --start <TIME>

Set this as the start time.

<NOTE>

Set this as the entry’s note. It will replace the previous one unless --append is given.

t help#

t help SUBCOMMAND

Get help on the specified subcommand. It is equivalent to passing -h to any subcommand.

t in#

t i|in [--at TIME] [NOTE]

Start an entry in the current timesheet. If used without arguments and require_note is set to true in the settings then it will launch a text editor for you to write a note. Pass --at to set a specific time of start instead of now, like in case you forgot to start an activity five minutes ago:

t i --at '5 min ago' 'writing the codez'

t kill#

t k|kill (--id ID|--last|SHEET)

Use this to completely delete entries. Pass a timesheet name to delete all entries from it or use --id to delete an entry by its ID. IDs from entries can be shown passing -v to ‘display’ subcommand and variants. To delete only the last entry from the sheet pass --last as single argument.

This command will ask for confirmation before touching the database.

If you’d prefer to keep record of entries but have them out of the way use t archive.

Options

-l, --last

Delete the last entry of the current sheet

--id <ID>

Delete entry with this ID instead of sheet

<SHEET>

Delete an entire sheet by its name

t list#

t l|list [-a|--all] [-f|--flat]

Lists existing timesheets. Pass --all to also list hidden (archived) ones. The output includes 4 columns: the name of the timesheet, the elapsed time of the running entry, the accumulated time for the day and the total time (sum of all entries per timesheet).

  Timesheet   Running     Today   Total Time

  default     0:00:00   0:00:00      2:39:38
- foo         0:00:00   0:30:01      0:30:01
* pink        0:00:00   0:05:04      0:05:04
--------------------------------------------
              0:00:00   0:35:06      3:14:44

An asterisk (*) at the left will indicate the current timesheet and a dash (-) indicates the previously used one. This is relevant for t sheet where you can use - as the timesheet name to just switch to the previous one.

New in 1.5.3

Pass --flat to only display timesheet names one per line. Useful for passing the list to other commands (it is used for the different completion scripts).

$ t l --flat
default
foo
pink

t month#

t m|month [OPTIONS] [SHEET|all|full]

Like t display but only display entries that started this month in the current timezone. Pass --month MONTH to display entries of different months or the special strings this (default), current or last. It accepts whole month names or just the first three letters as in jun or sep.

Just like t-display this accepts --grep, --ids, and --format.

t now#

t n|now

Show only timesheets with running entries. Since every timesheet can only have one running entry at a time this command displays its note.

  Timesheet   Running Activity

* default     0:00:01 reading
- pink        0:00:21 gardening

Just like t list it displays an asterisk for the current timesheet and a dash for the previous one.

t out#

t o|out [--at TIME]

End the active entry in the current timesheet, if any. End time is set as the current time unless --at is passed, case in which the specified time is set as end time for the entry. See Specifying times to learn the accepted formats.

t resume#

t r|resume [OPTIONS]

If called without arguments this command will start a new entry with the same note as the last started entry in the current sheet. It is an easy way of continuing with a task that was suspended.

To restart an entry with one of the last 10 unique notes pass --interactive which will show an UI like this:

Latest entries of sheet 'dafault':

  # Note                                                Total time  Last started

  5 try to find a pair for this sock                      11:35:35    1 week ago
  4 pensar en la inmortalidad del cangrejo                 1:31:09    1 week ago
  3 cook fried rice                                        0:50:03    4 days ago
  2 investigate quantum mechanics                         10:59:17  11 hours ago
  1 turn lead into gold                                    1:24:14  10 hours ago

Enter a number to select, write q or send EOF to cancel, or write a note wrapped
in '' or \"\" to start a new entry.
>>

The number of unique entries shown is configurable with the interactive_entries setting. To proceed pass any of the shown numbers of the left column and press intro or enter q or Ctrl+D to exit. It is also possible (from tiempo 1.9.0 onwards) to start a new entry by writing its note wrapped in double or single quotes in the prompt.

To restart an entry whose ID you know pass --id ID.

Options

-i, --interactive

Select one of the latest unique entries interactively

--id ID

Resume entry by its ID

t sheet#

t s|sheet [SHEET]

Switch to a timesheet with name SHEET, creating it if it doesn’t exist. If used without arguments this command is an alias to t list so you can see what timesheets you have.

tiempo remembers the last sheet you used (unless it doesn’t exist anymore), so you can switch quickly between the current and the previous one passing - (a dash) as the sheet name.

t today#

t t|today [OPTIONS] [SHEET|all|full]

Like t display but only display entries that started today in the current timezone. Pass --end to further filter entries that started earlier than the given time.

Just like t-display this accepts --grep, --ids, and --format.

t week#

t w|week [OPTIONS] [SHEET|all|full]

Like t display but only display entries that started this week in the computer’s timezone. The first day of the week by default is monday but this is configurable. See Settings and t configure.

Just like t-display this accepts --grep, --ids, and --format.

t yesterday#

t y|yesterday [OPTIONS] [SHEET|all|full]

Like t display but only display entries that started yesterday in the computer’s current timezone.

Just like t-display this accepts --grep, --ids, and --format.

Settings#

Tiempo settings are kept in a single TOML file. You can learn its location by running t c. Here is the full list with descriptions.

append_notes_delimiter#

Type: string Default: " "

String used to concatenate text when --append is passed to t edit.

auto_checkout#

Type: bool Default: false

Not implemented. Kept for compatibility with timetrap. Tracking issue https://gitlab.com/categulario/tiempo-rs/-/issues/11

auto_sheet#

Type: string Default: "dotfiles"

Not implemented. Kept for compatibility with timetrap. Tracking issue: https://gitlab.com/categulario/tiempo-rs/-/issues/9

auto_sheet_search_paths#

Type: [string] Default Depends on OS, irrelevant anyways

Not implemented. Kept for compatibility with timetrap. Tracking issue: https://gitlab.com/categulario/tiempo-rs/-/issues/9

commands#

tiempo allows having per-command configuration. For now it limits to per-command default formatters introduced in 1.4.0. To set a default formatter other than the default text use something like this in your settings file:

In toml

[commands.week]
default_formatter = "chart"

In yaml

commands:
  week:
    default_formatter: chart

To set a default formatter for every command use default_formatter.

database_file#

Type: string Default: Depends on OS

Absolute path to the database file.

default_command#

Type: string|null Default: null

Not implemented. Kept for compatibility with timetrap. Implementation planned. Tracking issue: https://gitlab.com/categulario/tiempo-rs/-/issues/10

default_formatter#

Type: string Default: "text"

Formatter used for t display subcommand and variants when no --format argument is passed. Included formatters are explained in the Default formatters section.

To set a default formatter for only one command see the commands config above.

formatter_search_paths#

Type: [string] Default: Depends on OS, but not empty

Array of paths to search for custom formatters. See Custom formatters to learn how to write them. Custom formatters must be executable files.

formatters#

The chart formatter introduced in 1.4.0 has a few settings. It is also possible to store settings for your custom formatters in this variable. Take a look at the following example:

[formatters.earnings]
currency = "USD"
hourly_rate = 400

[formatters.chart]
daily_goal_hours = 4
weekly_goal_hours = 20
character_equals_minutes = 30
formatters:
  earnings:
    currency: MXN
    hourly_rate: 280
  chart:
    daily_goal_hours: 4
    weekly_goal_hours: 20
    character_equals_minutes: 30

Check Custom formatters to learn how to read this settings in your formatters.

Available settings for the chart formatter are:

  • daily_goal_hours (integer, default: 0) Number of hours you set as your daily goal. Will display a uniform gray bar at the set hours that will turn green as you make progress.

  • weekly_goal_hours (integer, default: 0) Number of hours you expect to work in a week. Controls wether the weekly hour count displays in green color.

  • character_equals_minutes (integer, default: 30). Controls how many minutes each character of the chart represents. Set lower for wider charts. Set to 0 to make tiempo crash.

interactive_entries#

Type: integer Default 10

Number of unique entries to show when interactive commands like t resume are invoked.

note_editor#

Type: string|null Default null

Specify an editor to use when starting an entry without a note and when require_note is true. By default tiempo respects the $EDITOR variable so you can leave this setting as null most of the time.

require_note#

Type: bool Default true

Wether or not entries require a note to be recorded. If true then running t in without arguments will launch the text editor configured in note_editor

round_in_seconds#

Type: integer Default: 900 (15 min)

Not implemented, kept for compatibility with timetrap. Tracking issue: https://gitlab.com/categulario/tiempo-rs/-/issues/6

week_start#

Type: string Default "monday"

Day of the week that should be considered as the start for t week. Options are "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday".

Files, paths and the environment#

Tiempo uses the same directory to store the database and configuration. It’s location depends on the operative system, but it’s likely one of these options:

On Linux

tiempo respects XDG variables. So most likely the config directory is $HOME/.config/tiempo.

On windows

{FOLDERID_RoamingAppData}

On mac

$HOME/Library/Application Support

To be 100% sure run t c.

You can set the config file to a different location by setting the TIEMPO_CONFIG envornment variable to an absolute path that includes the filename like this:

TIEMPO_CONFIG=/path/to/my_config.toml

Inside the config directory you’ll find two files: config.toml and database.sqlite3 which are your settings file and database respectively.

Tiempo also recognises traditional timetrap config, environment and directories, which in all platforms should be: $HOME/.timetrap.yml for the config file, $HOME/.timetrap.db for the database and TIMETRAP_CONFIG_FILE to set the database to a different location.

From within the config file you can set the paths to the database and custom formatters as explained in the Settings.

Specifying times#

Some arguments accept a time as value, like t in’s --at or t d --start. These are the accepted formats:

Something similar to ISO format will be parsed as a time in the computer’s timezone.

  • 2021-01-13 a date

  • 2019-05-03 11:13 a date with portions of a time

ISO format with offset or UTC will be parsed as a time in the specified timezone. Use Z for UTC and an offset for everything else

  • 2021-01-13Z

  • 2005-10-14 19:20:35+05:00

something that looks like an hour will be parsed as a time in the current day in the computer’s timezone. Add Z or an offset to specify the timezone.

  • 11:30

  • 23:50:45 (with seconds)

some human times, like time ago or yesterday times:

  • an hour ago

  • a minute ago

  • 50 min ago

  • 1h30m ago

  • two hours thirty minutes ago

  • yesterday at 15:20 (from 1.8.0)

  • now (from 1.9.0)

Default formatters#

Tiempo comes with a handful of formatters that display your entries in different ways. Here’s the full list.

chart#

(New in 1.4.0) Displays a nice chart of the weekly progress:

  Date  Day Chart      Hours

Sep  5  Mon #######    3.8
     6  Tue ####       2.3
     7  Wed #######    3.9
     8  Thu            0.0
     9  Fri #########  4.5
    10  Sat #########  4.7
    11  Sun            0.0

       Week 19.3/20.0

Timesheet: default

This formatter comes with a few settings. See formatters for more.

text#

Displays nicely the entries grouped by day. This is the default formatter. If multiple sheets are displayed at once a column with the sheet name is added. Pass -v to also reveal each entry’s id:

Timesheet: foo
    Day                Start      End        Duration Notes
    Tue Sep 20, 2022   09:09:58 - 09:40:13    0:30:15 write tiempo's docs
                       09:41:01 - 10:11:20    0:30:18 work on pizarra.categulario.xyz
                                              1:00:34
-------------------------------------------------------------------------------------
    Total                                     1:00:34

ical#

This formatter’s output can be redirected to a file and then uploaded to a calendar app:

BEGIN:VCALENDAR
CALSCALE:GREGORIAN
METHOD:PUBLISH
PRODID:tiempo-rs
VERSION:2.0
BEGIN:VEVENT
DESCRIPTION:write tiempo's docs
DTEND:20220920T134013Z
DTSTAMP:20220920T130958Z
DTSTART:20220920T130958Z
SEQUENCE:0
SUMMARY:write tiempo's docs
UID:tiempo-10@abraham-lenovo-t470s
END:VEVENT
BEGIN:VEVENT
DESCRIPTION:work on pizarra.categulario.xyz
DTEND:20220920T141120Z
DTSTAMP:20220920T134101Z
DTSTART:20220920T134101Z
SEQUENCE:0
SUMMARY:work on pizarra.categulario.xyz
UID:tiempo-11@abraham-lenovo-t470s
END:VEVENT
END:VCALENDAR

csv#

Dump the entries as CSV to stdout. Useful for passing entries to a different tool. Pass -v to add an id column.

start,end,note,sheet
2022-09-20T13:09:58.805280Z,2022-09-20T13:40:13.837994Z,write tiempo's docs,foo
2022-09-20T13:41:01.478854Z,2022-09-20T14:11:20.461322Z,work on pizarra.categulario.xyz,foo

json#

Dump the entries in JSON format:

[{"id":10,"note":"write tiempo's docs","start":"2022-09-20T13:09:58.805280283Z","end":"2022-09-20T13:40:13.837994932Z","sheet":"foo"},{"id":11,"note":"work on pizarra.categulario.xyz","start":"2022-09-20T13:41:01.478854071Z","end":"2022-09-20T14:11:20.461322041Z","sheet":"foo"}]

ids#

Dump only the ids in a single line:

2196 2197 2198 2199 2200 2201

activities#

Show a list of activities grouped by note with the accumulated time and how long ago it was last started.

Note                                                   Time  Last started

elaboración de reporte                              0:48:21   2 hours ago
construcción de la interfaz de grafos              23:08:59    5 days ago
contribuir a nested_inline                          0:37:53   3 weeks ago

Pass --sort COLUMN to sort the table by one of time, note and last_started. Prepend a - to sort in descending order.

csvactivities#

New in 1.10

Like the activities formatter, except in csv output so it can be imported to a spreadsheet or used some other way. The last_started time is in ISO format.

Same as with activities you can sort by time, note and last_started.

note,time,last_started
a note,048:21,2022-12-13T16:59:28.667153Z

Custom formatters#

You can implement your own formatters for all subcommands that display entries (like t display, t week etc.). It is as easy as creating an executable file written in any programming language (interpreted or compiled) and placing it in a path listed in the config value for formatter_search_paths.

This executable will be given as standard input a csv stream with each row representing a time entry with the same structure as the csv formatter output. It will also be given a command line argument representing user settings for this formatter stored in the config file and formatted as JSON.

Example#

Let’s create a simple earnings formatter. It is as easy as creating the following python script under the name earnings (no extension) inside one of the paths listed in your formatter_search_paths setting and marking it as executable (chmod +x earnings):

#!/usr/bin/env python3
import sys, json, csv
from datetime import datetime, timezone, timedelta
from math import ceil

# Settings for this formatter are passed as the first argument
config = json.loads(sys.argv[1])

reader = csv.DictReader(
    sys.stdin,  # entries are received from stdin
    fieldnames=['id', 'start', 'end', 'note', 'sheet'],
)

total = timedelta(seconds=0)

for line in reader:
    # times are formatted as ISO format
    start = datetime.strptime(line['start'], '%Y-%m-%dT%H:%M:%S.%fZ')

    # entries that are still running don't have a value in the 'end' column
    if not line['end']:
        end = datetime.utcnow()
    else:
        end = datetime.strptime(line['end'], '%Y-%m-%dT%H:%M:%S.%fZ')

    total += end - start

hours = total.total_seconds() / 3600
# use the settings
earnings = hours * config['hourly_rate']
currency = config['currency']

print(f'You have earned: ${earnings:.2f} {currency}')

You’ll also need to add these settings to your config file:

[formatters.earnings]
hourly_rate = 300
currency = "USD"

Now if you run t display -f earnings you will get something like:

You have earned: 2400 USD