Time Tracking for Contractors and Freelancers Guide
The most effective time tracking methods for contractors and freelancers are plain-text log files with a parsing script for simplicity, ActivityWatch for automatic activity detection, Git Time Metric for development-integrated tracking, and CLI tools like timetrap for quick session timers. Combine multiple methods for maximum accuracy, and use a reconciliation script weekly to catch unbilled hours before invoicing.
The Business Case for Precise Time Tracking
Every hour you fail to track is an hour you cannot bill. For contractors and freelancers, time equals revenue directly. Unlike salaried employees where hours blend together, your time has explicit monetary value. Clients expect detailed invoices with breakdown by task or project. Accurate time records protect both you and your client from disputes about billed hours.
Beyond invoicing, time tracking reveals patterns in your work. You discover which tasks consume more time than estimated, identify your peak productivity hours, and make data-driven decisions about pricing and project scoping.
Manual Time Tracking Methods
For developers who prefer simplicity, plain text files work surprisingly well. Create a timestamped log using a simple format:
# At start of work session
echo "$(date '+%Y-%m-%d %H:%M') - START - Project Alpha" >> time.log
# At end of session
echo "$(date '+%Y-%m-%d %H:%M') - END - Project Alpha" >> time.log
This produces a simple log file you can parse later. A Python script can calculate total hours:
import re
from datetime import datetime
def parse_time_log(filename):
with open(filename) as f:
lines = f.readlines()
sessions = []
for i in range(0, len(lines) - 1, 2):
start = datetime.strptime(lines[i].split(' - ')[0], '%Y-%m-%d %H:%M')
end = datetime.strptime(lines[i+1].split(' - ')[0], '%Y-%m-%d %H:%M')
duration = (end - start).total_seconds() / 3600
sessions.append(duration)
return sum(sessions)
print(f"Total hours: {parse_time_log('time.log'):.2f}")
This approach requires discipline but gives you complete control over your data. No subscriptions, no cloud services, no vendor lock-in.
Automated Time Tracking with Activity Detection
For developers who forget to start timers, automatic activity tracking fills the gap. Tools like ActivityWatch (open source) run in the background and log window titles, active applications, and idle time.
Install ActivityWatch on macOS via Homebrew:
brew install activitywatch
Configure it to track specific categories:
# .config/activitywatch/aw-watcher-window.toml
[aw-watcher-window]
name = "aw-watcher-window"
poll_time = 5
exclude_title = ["ActivityWatch", "Spotify", "Discord"]
The watcher captures window titles which you can map to projects. Create a mapping file:
{
"project_mapping": {
"VS Code": "client-alpha",
"Terminal": "internal-tools",
"Slack": "communication",
"Figma": "design-work"
}
}
This generates detailed reports showing exactly where your time goes. Export data weekly to generate client-ready invoices.
Git-Based Time Tracking for Developers
Since developers already use Git constantly, tracking time through commit messages integrates naturally into existing workflows. Tools like Git Time Metric (GTM) attach time spent to each file automatically.
Install GTM:
# macOS
brew install git-time-metric
# Initialize in your repo
git time-metric init
Enable tracking:
git time-metric enable
The tool monitors file access and editing, then calculates time per file. Generate reports:
git time-metric report --format=json
Output shows time spent per file:
{
"files": {
"src/main.rs": {
"time": 3600,
"commits": 12
},
"src/utils.rs": {
"time": 1800,
"commits": 5
}
},
"total_time": 5400
}
Combine this with client or project-based repositories to separate billing categories cleanly.
Command-Line Timers for Quick Sessions
When you need a quick timer without full automation, command-line tools provide instant feedback. The timer command (available via Homebrew) offers simple countdown and stopwatch functionality:
# Install
brew install timer
# Start a 25-minute pomodoro
timer -m 25
# Count up from zero
timer
For more sophisticated CLI tracking, consider timetrap:
gem install timetrap
# Start tracking
timetrap start "Client Project"
# Switch to different task
timetrap switch "Code Review"
# Display timesheet
timetrap display
This Ruby gem stores data in SQLite locally, giving you a searchable database of all tracked time.
Combining Methods for Maximum Accuracy
The most reliable approach combines multiple tracking methods. Use automatic activity tracking as a background safety net, manual timers for billable client work, and Git-based tracking for development tasks. Periodically compare outputs to catch discrepancies.
Create a simple reconciliation script:
import json
from datetime import datetime, timedelta
def reconcile_time_logs(automatic_log, timetrap_export, git_metrics):
"""Compare time from multiple sources and flag discrepancies."""
discrepancies = []
# Check if automatic tracking shows significantly more time
auto_total = sum(s['hours'] for s in automatic_log)
manual_total = sum(s['hours'] for s in timetrap_export)
if auto_total > manual_total * 1.2:
discrepancies.append({
'type': 'untracked_time',
'auto_hours': auto_total,
'manual_hours': manual_total,
'message': '20%+ untracked time detected'
})
return discrepancies
# Example usage
with open('auto.json') as f:
auto_data = json.load(f)
with open('timetrap.json') as f:
timetrap_data = json.load(f)
with open('git.json') as f:
git_data = json.load(f)
issues = reconcile_time_logs(auto_data, timetrap_data, git_data)
for issue in issues:
print(f"Warning: {issue['message']}")
Run this weekly to ensure your billing stays accurate.
Data Export and Invoice Generation
Once tracking is in place, generate invoices from your data. Most tools support CSV or JSON export. Create a simple invoice generator:
from datetime import datetime
def generate_invoice(time_entries, hourly_rate, client_name):
total_hours = sum(entry['hours'] for entry in time_entries)
total_amount = total_hours * hourly_rate
invoice = f"""INVOICE
Client: {client_name}
Date: {datetime.now().strftime('%Y-%m-%d')}
Rate: ${hourly_rate}/hour
Time Entries:
"""
for entry in time_entries:
invoice += f" {entry['date']}: {entry['hours']:.2f}h - {entry['description']}\n"
invoice += f"""
Total Hours: {total_hours:.2f}
Total Due: ${total_amount:.2f}
"""
return invoice
# Usage
entries = [
{'date': '2026-03-10', 'hours': 4.5, 'description': 'API integration'},
{'date': '2026-03-11', 'hours': 3.0, 'description': 'Bug fixes'},
]
print(generate_invoice(entries, 150, "Acme Corp"))
This outputs a clean invoice ready to send to clients.
Choosing Your Tracking Approach
Start with the simplest method that fits your workflow. If you already use Git for development, add GTM and get time tracking with minimal behavior change. If you work across many applications, automatic tracking with ActivityWatch provides comprehensive coverage. For pure simplicity, a CLI timer or even a text file works perfectly.
The best time tracking system is the one you actually use consistently. Experiment with different approaches until you find the rhythm that works for your specific situation.
Related Reading
Built by theluckystrike — More at zovo.one