thecodex.expert · The Codex Family of Knowledge
Tier 3 · Upper-Intermediate · Python Project

Habit Tracker

Track daily habits, mark them complete, and calculate streaks. Learn working with dates and computing runs of consecutive days.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a habit tracker: define habits, mark them done on a given day, and compute the current streak — how many consecutive days you have kept it up. It teaches working with dates and the satisfying-but-tricky logic of finding consecutive runs.

Where this shows up: habit and fitness apps, attendance systems, subscription renewals, anything that reasons about dates and consecutive periods. Date logic is famously error-prone, so learning it carefully pays off.

2 How to Think About It

Think about what a streak means, before any code:

The plan — in plain English
1. Each habit stores the set of dates it was completed. → 2. Mark done adds today’s date. → 3. The streak is the count of consecutive days ending today: check today, yesterday, the day before, until you hit a gap. → 4. Persist to a file.

Yes

No

Habit: set of completed dates

Mark done today

Add today's date

Compute streak

Start at today

Done that day?

Count + 1, step back a day

Streak = count

3 The Build — explained part by part

Here is the complete habit tracker. The streak logic uses real dates. Each part is explained below.

Pythonhabits.py
from datetime import date, timedelta

def mark_done(completed_dates, day=None):
    # Record a habit as done on a day (default today).
    day = day or date.today()
    completed_dates.add(day.isoformat())
    return completed_dates

def current_streak(completed_dates, today=None):
    today = today or date.today()
    streak = 0
    day = today
    # Step backwards one day at a time while each is completed.
    while day.isoformat() in completed_dates:
        streak += 1
        day = day - timedelta(days=1)
    return streak

if __name__ == "__main__":
    done = set()
    mark_done(done, date(2026, 6, 22))
    mark_done(done, date(2026, 6, 23))
    mark_done(done, date(2026, 6, 24))
    print("Streak:", current_streak(done, date(2026, 6, 24)))
What each part does — in plain words
from datetime import date, timedeltadate represents a calendar day; timedelta represents a span (like “one day”) we can subtract.

completed_dates — a set of date strings (ISO format like "2026-06-24"). A set gives instant “was this day done?” checks and ignores duplicate marks.

day.isoformat() — turn a date into its text form for storing and comparing.

the while loop in current_streak — the heart of it: start at today, and while that day is in the completed set, count it and step back one day with day - timedelta(days=1). The moment a day is missing, the streak ends.

day = day - timedelta(days=1) — proper date maths: subtracting a timedelta correctly handles month and year boundaries, which manual arithmetic would get wrong.
Common mistakes — and how to avoid them
✗ Doing date maths by hand (adding/subtracting day numbers) — it breaks at month boundaries.
✓ Use timedelta(days=1) so month and year rollovers are handled.
✗ Storing dates as inconsistent strings — comparisons fail.
✓ Always store with .isoformat() for a consistent format.
✗ Using a list instead of a set for dates — slow lookups and duplicate marks.
✓ A set gives instant membership checks and ignores duplicates.

4 Test & Prove Each Part

We test the streak logic with fixed dates, so we can verify the consecutive-day counting exactly.

Three consecutive days give a streak of 3
A gap breaks the streak
No completions gives a streak of 0
Pythontest_habits.py
from datetime import date, timedelta


def current_streak(completed_dates, today):
    streak = 0
    day = today
    while day.isoformat() in completed_dates:
        streak += 1
        day = day - timedelta(days=1)
    return streak


def test_three_day_streak():
    """Three consecutive days give a streak of 3."""
    dates = {"2026-06-22", "2026-06-23", "2026-06-24"}
    assert current_streak(dates, date(2026, 6, 24)) == 3


def test_gap_breaks_streak():
    """A missing day stops the streak."""
    dates = {"2026-06-22", "2026-06-24"}  # 23rd missing
    assert current_streak(dates, date(2026, 6, 24)) == 1


def test_no_completions():
    """No completions means a streak of 0."""
    assert current_streak(set(), date(2026, 6, 24)) == 0

Run with pytest -v. By passing a fixed today, the tests are deterministic — they do not depend on the real date. The gap test proves a missing day correctly breaks the streak.

5 The Interface

markmark_done(habit, day)record a completion
streakcurrent_streak(habit)consecutive days
Returns
Streak: 3

6 Run It & Automate It

Run it locally
python3 habits.py
See a 3-day streak computed. Mark habits done each day to build real streaks.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
Streak: 3
If it breaks — how to fix it
🚨 The streak is always 0.
Your stored date format does not match what you compare against. Use .isoformat() consistently.
🚨 Streak counts non-consecutive days.
Make sure you step back exactly one day each loop and stop at the first gap.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Longest streak. Track the best streak ever, not just current. (Teaches: scanning all dates.)
  2. Multiple habits. Track several habits at once. (Teaches: a dict of habits.)
  3. Weekly view. Show a calendar grid of done days. (Teaches: date formatting.)
What you learned
You learned to work with real dates using date and timedelta, and to compute consecutive runs by stepping backward day by day. Proper date maths avoids the bugs that manual day-counting causes. Related: datetime, Control Flow.