A small always-on-top desktop widget for punching in and out, recording breaks and lunches, and reviewing your hours and overtime.
No installation, no dependencies beyond Python 3 — just tkinter and SQLite, both of which ship with Python.
powershell -ExecutionPolicy Bypass -File .\Install.ps1That creates a TimeTracker shortcut on your Desktop and asks whether you
want it to start with Windows. To skip the prompt use -Startup or
-NoStartup. To remove the shortcuts later (your hours are kept):
powershell -ExecutionPolicy Bypass -File .\Install.ps1 -UninstallYou can also just double-click TimeTracker.vbs, which launches it without a
console window.
The widget is a small pill that floats above other windows. Drag it anywhere — it remembers where you put it.
| Action | What happens |
|---|---|
| Click the widget | Opens the action menu for whatever state you're in |
| Right-click | Dashboard, reset position, quit |
| Drag | Moves the widget (position is saved) |
The action menu only ever offers moves that make sense right now:
| Current state | Menu offers |
|---|---|
| Clocked out | Punch In |
| Working | Start Break · Start Lunch · Punch Out |
| On break | End Break |
| At lunch | End Lunch |
So the loop is: punch in → work → break → back → lunch → back → punch out. Break and lunch time is excluded from your worked hours; the widget shows a live counter while you're on one.
The pill itself shows a coloured status dot, the live figure that matters right
now (hours worked today, or how long you've been on break), and a subtitle with
the day's context. If you've passed your standard day it appends OT 0:45.
The pill floats directly on the desktop, so a dark widget on a dark wallpaper can be hard to spot. Open the dashboard and click the small ◑ icon next to the "Hours" heading — that's the whole control, tucked out of the way rather than cluttering the widget.
| Theme | Good for |
|---|---|
| Midnight (default) | Most wallpapers |
| Paper | Dark or black wallpapers — light pill, dark text |
| High contrast | Busy or photographic wallpapers — black fill, white outline |
| Ocean / Sunset / Violet | Tinted alternatives when you just want a different look |
Each row in the picker is a live miniature of the real pill, and clicking one applies it immediately — so you can see it against your actual desktop before deciding. The choice is remembered.
Themes change the widget only; the dashboard stays dark, since it's a normal window that never sits on top of your wallpaper.
Click the widget → View hours & timesheet (or right-click → dashboard).
Summary cards across the top: total worked, regular, overtime, holiday & leave credit, total paid, days worked, average per day, and total break + lunch time for the selected range.
Daily table: date, weekday, day type, first punch in, last punch out, break,
lunch, worked, regular, overtime and credited hours. Overtime days are
highlighted in amber; a day still in progress shows in green as running;
marked days are tinted by type.
Footer shows expected hours for the range and whether you're ahead or behind.
The dashboard opens on the current pay period. Pay periods are semi-monthly:
- 1st – 15th, which stays on screen as the default until the 20th
- 16th – end of month, which stays on screen until the 5th of the next month
So on the 18th of a month you still see the 1–15 period; on the 3rd you still see the previous month's 16–EOM period.
Use ◀ / ▶ to step through periods, or the Range dropdown for
current/previous pay period, this or last month, this week, last 7 days, today,
or all time. For anything else type dates into From / To
(YYYY-MM-DD or DD-MM-YYYY) and hit Apply.
Anything worked beyond your standard day counts as overtime, computed per day. The standard day defaults to 8 hours and is editable via the hrs/day box in the dashboard toolbar — changing it recalculates history immediately, since overtime is derived rather than stored.
Right-click any day in the dashboard → Mark … as → pick a type. Select several days first (click, then shift-click) to mark a whole stretch at once — useful for a week of leave. There's also a Mark days button in the toolbar, and Mark with a note… if you want to record why.
| Day type | Credits by default |
|---|---|
| Public holiday | 8 h |
| Paid leave | 8 h |
| Sick leave | 8 h |
| Week off | 0 h |
The credit follows your standard day, so setting 7.5 hrs/day makes a holiday
credit 7.5 h. You can override the credit on any individual mark — set it to
4 for a half-day of leave, or 0 for an unpaid holiday.
Credited hours are deliberately kept separate from hours worked:
- Total worked only ever means hours you actually worked. A holiday never inflates it.
- Holiday & leave holds the credited hours.
- Total paid = worked + credited. This is the number for a payslip.
- Expected counts a day if you worked it or it credits hours, so a holiday doesn't show up as a shortfall. Week off credits nothing and is excluded entirely.
If you work on a holiday, normal overtime rules apply — overtime only beyond
your 8-hour standard day, exactly as on a regular day. The holiday credit still
applies on top, so a 9-hour holiday shift shows 9 h worked (1 h overtime) plus
8 h credited. If you'd rather not be credited for a holiday you worked, open the
mark and set its credit to 0.
To remove a mark: right-click → Clear mark, or open the dialog and hit Remove mark.
Export CSV writes the visible range with a totals row, in decimal hours, ready to paste into a timesheet or spreadsheet. It includes the day type, its note, credited hours and a per-day paid total, alongside the worked figures.
Forgot to punch out? Started lunch late? Double-click any day in the dashboard to open its raw punch list, where you can add, retime or delete individual punches. Totals recalculate immediately.
If the app finds you were still clocked in from a previous day — say the machine was shut down mid-shift — it asks on startup whether to punch you out at a specific time or keep the clock running.
C:\Users\<you>\Documents\TimeTracker\timetracker.db
Set the TIMETRACKER_DIR environment variable to keep it somewhere else.
It is a plain SQLite file holding an append-only log of punches plus a small table of day marks. Every number the app shows — daily totals, breaks, overtime, period summaries — is derived from that log at display time. Nothing is precomputed, so corrections and changes to your standard day apply retroactively and correctly, and a crash can never leave a half-written total.
Note on Microsoft Store Python. If Python came from the Microsoft Store it runs sandboxed and silently redirects
%LOCALAPPDATA%writes into a package cache that Windows wipes when Python is reset or uninstalled. TimeTracker therefore stores your timesheet in Documents instead, and automatically migrates any timesheet it finds in the old sandboxed location on first run.
Back it up by copying that one .db file.
- Overnight shifts are split at midnight, so a 22:00 → 03:00 shift puts 2h on one day and 3h on the next.
- Impossible punches are ignored, not crashed on. A duplicate punch-in or an "end lunch" with no lunch started is skipped when totals are computed, so a messy log still produces sane numbers.
- State survives restarts. The current state is replayed from the log on every launch, so closing the widget mid-shift loses nothing.
- One instance only. A second copy refuses to start rather than have two widgets writing to the same log.
| File | Purpose |
|---|---|
timetracker.py |
Entry point, the widget, action popup, session recovery |
tt_core.py |
Storage, state machine, time math, pay-period rules |
tt_dashboard.py |
Dashboard, day editor, CSV export |
tt_ui.py |
Palette and shared widget helpers |
make_icon.py |
Generates icon.ico (no image libraries needed) |
Install.ps1 |
Desktop / startup shortcuts |
TimeTracker.vbs |
Console-free launcher |
test_core.py |
Time math and pay-period checks |
test_gui.py |
Builds every window against a throwaway database |
Run the checks with:
python test_core.py && python test_gui.py