PatchHub

← patchhub/pomodoro

Focus session log with today's total

Published ✓ Safe by @lena 0 users1 vote 2 files changed (+87 −0)

The timer now saves each finished focus session to a spreadsheet-style file in your home folder. It shows how much you've focused today when it starts and again when you stop.

What it does

- Each time a focus session runs to completion, append one row to a CSV log file. Breaks and sessions stopped early with Ctrl+C are not logged. - Default log location is `pomodoro_log.csv` in the user's home folder. Add a `--log FILE` option (short form `-l`) to use a different file. - The CSV has a header row `finished_at,minutes`. If the file is missing or empty, write the header first. Each row has the finish time as local `YYYY-MM-DD HH:MM:SS` and the session length in minutes (e.g. `25`). - When the timer starts, after the intro line and before "Press Ctrl+C to stop.", print `Today so far: <N> focus session(s), <H> hour(s) <M> minute(s) in total.` Example: `Today so far: 3 focus sessions, 1 hour 15 minutes in total.` - Totals count only rows whose finish time starts with today's date. Rows that are malformed or have a non-numeric minutes value are skipped. A missing or unreadable file counts as 0 sessions, 0 minutes. - Formatting: use "session" when the count is 1, otherwise "sessions". Use "hour"/"hours" and "minute"/"minutes" correctly. Omit the hours part when it is 0. Omit the minutes part when it is 0 and hours is nonzero. Show "0 minutes" when the total is 0. Round total minutes to the nearest whole minute. - When the timer stops, after "Focus sessions completed: N. Nice work!", print `Today's total: <same format>.` - If the log cannot be written, print a short note like `(Couldn't save this session to the log file <path>: <reason>)` and keep the timer running. - Update the README with a "Your focus log" section and a `--log FILE` row in the options table.

Safety check ✓ Safe

The change only reads and writes a local CSV file, either in the home folder or at a path the user chooses. It adds no network access, no new dependencies, and nothing unrelated to the request. Only Python standard library modules are used.

Conversation that created this patch
@lena
Keep a log of finished focus sessions and show today's total when the timer starts.
AI
I've added a focus log to the timer. Each focus session you finish is now saved, and when you start the timer it shows how much you've already focused today, for example: "Today so far: 3 focus sessions, 1 hour 15 minutes in total." It also shows the updated total when you stop. I ran some quick test sessions and the log and daily totals worked. Things to know: - **Where it's saved:** a file called `pomodoro_log.csv` in your home folder. It has one row per session, with the time it finished and its length in minutes. You can open it in Excel, Numbers or Google Sheets. - **What counts:** only sessions that run all the way to the end. If you stop one early with Ctrl+C, it isn't counted. - **If the file can't be saved:** the timer shows a short note and keeps running. - **Saving it elsewhere:** there's a new `--log` option to save the log in a different file, for example a separate one for work. - **Starting fresh:** delete the log file. I also added a section about the log to the README.
Code changes (for the curious, 2 files) · raw
README.md +17 −0
@@ -28,6 +28,22 @@ It's a single Python file with nothing extra to install.
The timer keeps going (focus, break, focus, break...) until you press
**Ctrl+C**. When you stop, it tells you how many focus sessions you finished.
+## Your focus log
+
+Every focus session you finish is saved to a log file called
+`pomodoro_log.csv` in your home folder. When you start the timer it shows
+how much you've already focused today, for example:
+
+```
+Today so far: 3 focus sessions, 1 hour 15 minutes in total.
+```
+
+and it shows the updated total again when you stop. Sessions you stop
+early with Ctrl+C aren't counted. The log is a plain spreadsheet file
+(one row per session, with the time it finished and how many minutes it
+was), so you can open it in Excel, Numbers or Google Sheets. Delete the file
+if you want to start fresh.
+
## Options
You can change the times if you want:
@@ -38,6 +54,7 @@ You can change the times if you want:
| `--break MINUTES` | Length of each break (default 5) | `--break 10` |
| `--rounds N` | Stop after N focus sessions (default: keep going) | `--rounds 4` |
| `--quiet` | Don't beep | `--quiet` |
+| `--log FILE` | Save the focus log somewhere else (default: `pomodoro_log.csv` in your home folder) | `--log work.csv` |
For example, four rounds of 50 minutes work with 10 minute breaks:
pomodoro.py +70 −0
@@ -5,15 +5,24 @@ Alternates between focus sessions (25 minutes by default) and breaks
(5 minutes by default), showing a live countdown with a progress bar and
ringing the terminal bell when each session ends.
+Every finished focus session is written to a small log file (a CSV file in
+your home folder by default), and today's total is shown when the timer
+starts.
+
Uses only the Python standard library.
"""
import argparse
+import csv
+import datetime
+import os
import shutil
import sys
import time
BELL = "\a"
+DEFAULT_LOG_FILE = os.path.join(os.path.expanduser("~"), "pomodoro_log.csv")
+LOG_HEADER = ["finished_at", "minutes"]
def pick_bar_chars():
@@ -72,6 +81,59 @@ def run_session(label, minutes):
sys.stdout.flush()
+def log_session(path, minutes):
+ """Add one finished focus session to the log file.
+
+ Returns True if it was saved. Problems writing the file are reported but
+ never stop the timer.
+ """
+ finished_at = datetime.datetime.now().replace(microsecond=0).isoformat(sep=" ")
+ try:
+ is_new = not os.path.exists(path) or os.path.getsize(path) == 0
+ with open(path, "a", newline="", encoding="utf-8") as f:
+ writer = csv.writer(f)
+ if is_new:
+ writer.writerow(LOG_HEADER)
+ writer.writerow([finished_at, f"{minutes:g}"])
+ return True
+ except OSError as e:
+ print(f"(Couldn't save this session to the log file {path}: {e.strerror or e})")
+ return False
+
+
+def today_totals(path, today=None):
+ """Return (number of sessions, total minutes) logged for today."""
+ today = (today or datetime.date.today()).isoformat()
+ count = 0
+ minutes = 0.0
+ try:
+ with open(path, newline="", encoding="utf-8") as f:
+ for row in csv.reader(f):
+ if len(row) < 2 or not row[0].startswith(today):
+ continue
+ try:
+ value = float(row[1])
+ except ValueError:
+ continue
+ count += 1
+ minutes += value
+ except (OSError, UnicodeDecodeError):
+ pass
+ return count, minutes
+
+
+def describe_total(count, minutes):
+ sessions = "session" if count == 1 else "sessions"
+ total = int(round(minutes))
+ hours, mins = divmod(total, 60)
+ parts = []
+ if hours:
+ parts.append(f"{hours} hour" + ("" if hours == 1 else "s"))
+ if mins or not hours:
+ parts.append(f"{mins} minute" + ("" if mins == 1 else "s"))
+ return f"{count} focus {sessions}, {' '.join(parts)} in total"
+
+
def ring(times):
for i in range(times):
sys.stdout.write(BELL)
@@ -122,6 +184,11 @@ def parse_args(argv=None):
"-q", "--quiet", action="store_true",
help="don't ring the terminal bell",
)
+ parser.add_argument(
+ "-l", "--log", default=DEFAULT_LOG_FILE, metavar="FILE",
+ help="file where finished focus sessions are recorded "
+ "(default: %(default)s)",
+ )
return parser.parse_args(argv)
@@ -135,6 +202,7 @@ def main(argv=None):
focus_text = f"{args.focus:g} {plural(args.focus)}"
break_text = f"{args.break_minutes:g} {plural(args.break_minutes)}"
print(f"Pomodoro timer: {focus_text} focus, {break_text} break.")
+ print(f"Today so far: {describe_total(*today_totals(args.log))}.")
print("Press Ctrl+C to stop.\n")
completed = 0
@@ -145,6 +213,7 @@ def main(argv=None):
print(f"Round {round_number}{suffix}")
run_session("Focus", args.focus)
completed += 1
+ log_session(args.log, args.focus)
ring(ring_times)
is_last = args.rounds and round_number >= args.rounds
@@ -161,6 +230,7 @@ def main(argv=None):
print("\n\nStopped.")
print(f"Focus sessions completed: {completed}. Nice work!")
+ print(f"Today's total: {describe_total(*today_totals(args.log))}.")
return 0