Workout log
Exercises logged as sets x reps at a weight on real dates, kept exact in tenths of a kilogram: the history day by day, each exercise's heaviest weight and best estimated one-rep max (Epley), and the volume lifted week by week, Monday to Sunday, drawn as a chart that adds back up to the total.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Log exercises as sets x reps at a weight on real dates. See the history day by day, each exercise's records - the heaviest weight and the best estimated one-rep maximum - and the volume lifted week by week as a chart.
main.eml- the menu, reading workout lines, and the history, records and weekly viewslog.eml- entries: weights, volume, the one-rep estimate, records and weeksdates.eml- calendar dates, from P055: day numbers since 1900-01-01 and weekdays by Zeller's congruence
How each part works:
- Weights are kept in whole tenths of a kilogram, so 102.5 kg is 1025, and every sum is exact. The volume of an entry is sets x reps x weight.
- The one-rep estimate is Epley's: weight x (1 + reps / 30), to the nearest tenth, rounded half up. For a single rep it is the weight itself. Records keep the earliest entry when two are equal.
- A week runs from Monday to Sunday. Each entry falls in exactly one week, from its Monday up to, but not including, the next. So the weekly volumes add back up to the total of all entries, and the weekly view shows both numbers. This is the check the corpus case
histogram-buildermakes of its buckets. Weeks with no workout are shown too, and the bars are scaled so the biggest week gets 30 marks. - The log keeps entries in date order. A workout typed for an earlier date goes in its place; entries on the same day keep the order typed.
What is checked: menu choices 1 to 6; a real date from 1900-01-01 to 2099-12-31, or nothing for the last date used; workout lines with: - an exercise name of up to 20 characters; - sets x reps from 1x1 to 20x100; - a weight from 0.1 to 500 kg with at most one decimal.
At most 200 entries. An empty line stops.
Sessions: sessions/basic.in loads three weeks of sample workouts: squat, bench press and deadlift, a little heavier each time. - The history and the records come first. Squat's heaviest is 120 kg for 1 rep, but its best estimate, 122.5 kg, comes from 105 kg x 5. Bench's 62.5 x 8 makes 79.2 kg, and it wins over the equal entry a week later because it came first. - The weekly volumes are 6577.5, 6520 and 945 kg, which add up to the 13 entries' 14042.5 kg. - A workout for Monday 2026-10-12 is logged. Squat's 112.5 kg x 5 sets a new estimate of 131.3 kg (131.25, rounded up), and a fourth week appears.
sessions/bad-input.in gives: - menu choices 0 and x; - the three views of an empty log; - the date 2026-02-30, then nothing, for the last date used; - the lines "squat", "squat 3x5", 3x, 0x5, 21x5 and 3x101; - weights 0, 500.5, 100.25 and abc; - a name of 25 characters; - a weight of .5 kg, which is kept, so the records show 0.5 kg and an estimate of 0.6 kg.
Built on the verified corpus case histogram-builder (values counted into ranges, every value in exactly one, the counts adding back up to the total).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Workout log ==
Log exercises as sets x reps at a weight; see records and weekly volume.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 0
Pick a number from 1 to 6.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> x
Pick a number from 1 to 6.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 2
The log is empty.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 3
The log is empty.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 4
The log is empty.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 1
date (YYYY-MM-DD, nothing for 2026-10-12)> 2026-02-30
Type a real date from 1900-01-01 to 2099-12-31, like 2026-10-12.
date (YYYY-MM-DD, nothing for 2026-10-12)>
Exercises for 2026-10-12 Mon, one a line, like squat 3x5 100; nothing to stop.
exercise> squat
Type an exercise, sets x reps and a weight, like squat 3x5 100.
exercise> squat 3x5
Type an exercise, sets x reps and a weight, like squat 3x5 100.
exercise> squat 3x 100
Type sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps.
exercise> squat 0x5 100
Type sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps.
exercise> squat 21x5 100
Type sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps.
exercise> squat 3x101 100
Type sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps.
exercise> squat 3x5 0
Type a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5.
exercise> squat 3x5 500.5
Type a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5.
exercise> squat 3x5 100.25
Type a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5.
exercise> squat 3x5 abc
Type a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5.
exercise> a very long exercise name 3x5 100
Type an exercise name of at most 20 characters.
exercise> squat 3x5 .5
squat 3 x 5 x 0.5 kg: volume 7.5 kg
exercise>
1 added, 1 in the log.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 3
squat: heaviest 0.5 kg (5 reps, 2026-10-12); best estimated 1-rep max 0.6 kg (0.5 x 5, 2026-10-12)
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 6
Bye.
What was typed (23 lines)
0
x
2
3
4
1
2026-02-30
squat
squat 3x5
squat 3x 100
squat 0x5 100
squat 21x5 100
squat 3x101 100
squat 3x5 0
squat 3x5 500.5
squat 3x5 100.25
squat 3x5 abc
a very long exercise name 3x5 100
squat 3x5 .5
3
6
basic
interpreter: byte-equal== Workout log ==
Log exercises as sets x reps at a weight; see records and weekly volume.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 5
Three weeks of sample workouts, 2026-09-21 to 2026-10-07.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 2
2026-09-21 Mon
squat 3 x 5 x 100 kg
bench press 3 x 8 x 60 kg
2026-09-23 Wed
deadlift 1 x 5 x 120 kg
2026-09-25 Fri
squat 3 x 5 x 102.5 kg
bench press 3 x 8 x 62.5 kg
2026-09-28 Mon
squat 3 x 5 x 105 kg
bench press 3 x 6 x 65 kg
2026-09-30 Wed
deadlift 1 x 5 x 125 kg
2026-10-02 Fri
squat 5 x 3 x 110 kg
bench press 3 x 8 x 62.5 kg
2026-10-05 Mon
squat 1 x 1 x 120 kg
bench press 2 x 3 x 70 kg
2026-10-07 Wed
deadlift 1 x 3 x 135 kg
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 3
squat: heaviest 120 kg (1 rep, 2026-10-05); best estimated 1-rep max 122.5 kg (105 x 5, 2026-09-28)
bench press: heaviest 70 kg (3 reps, 2026-10-05); best estimated 1-rep max 79.2 kg (62.5 x 8, 2026-09-25)
deadlift: heaviest 135 kg (3 reps, 2026-10-07); best estimated 1-rep max 148.5 kg (135 x 3, 2026-10-07)
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 4
week of 2026-09-21 6577.5 kg ##############################
week of 2026-09-28 6520 kg ##############################
week of 2026-10-05 945 kg ####
Total 14042.5 kg over 3 weeks - the same as all 13 entries added up, 14042.5 kg.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 1
date (YYYY-MM-DD, nothing for 2026-10-07)> 2026-10-12
Exercises for 2026-10-12 Mon, one a line, like squat 3x5 100; nothing to stop.
exercise> squat 3x5 112.5
squat 3 x 5 x 112.5 kg: volume 1687.5 kg
exercise> bench press 5x5 65
bench press 5 x 5 x 65 kg: volume 1625 kg
exercise>
2 added, 15 in the log.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 3
squat: heaviest 120 kg (1 rep, 2026-10-05); best estimated 1-rep max 131.3 kg (112.5 x 5, 2026-10-12)
bench press: heaviest 70 kg (3 reps, 2026-10-05); best estimated 1-rep max 79.2 kg (62.5 x 8, 2026-09-25)
deadlift: heaviest 135 kg (3 reps, 2026-10-07); best estimated 1-rep max 148.5 kg (135 x 3, 2026-10-07)
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 4
week of 2026-09-21 6577.5 kg ##############################
week of 2026-09-28 6520 kg ##############################
week of 2026-10-05 945 kg ####
week of 2026-10-12 3312.5 kg ###############
Total 17355 kg over 4 weeks - the same as all 15 entries added up, 17355 kg.
1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit
choice> 6
Bye.
What was typed (12 lines)
5
2
3
4
1
2026-10-12
squat 3x5 112.5
bench press 5x5 65
3
4
6
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P059 workout log: log exercises as sets x reps at a weight on real dates,
# see the history, each exercise's records - the heaviest weight and the
# best estimated one-rep max - and the volume lifted week by week.
import dates
import log
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def words(s):
[] => out
"" => word
for c in s + " ":
if c == " ":
if word != "":
out + [word] => out
"" => word
else:
word + c => word
return out
def whole(s, high):
# A whole number from 1 to high, or -1.
if s == "" or len(s) > 4:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
if n < 1 or n > high:
return -1
return n
def tenths(s):
# A weight like 100, 102.5 or 0.5 (kilograms, at most one decimal, up to
# 500) in tenths, or -1.
0 => k
while k < len(s) and s[k] != ".":
k + 1 => k
s[0:k] => a
"0" => b
if k < len(s):
s[k + 1:len(s)] => b
if len(b) != 1:
return -1
if a == "":
"0" => a
for c in a + b:
if not (c >= "0" and c <= "9"):
return -1
if len(a) > 3:
return -1
int(a) * 10 + int(b) => t
if t < 1 or t > 5000:
return -1
return t
def parsed_line(line):
# "bench press 3x8 62.5" as [exercise, sets, reps, tenths], or [why].
words(line) => ws
if len(ws) < 3:
return ["an exercise, sets x reps and a weight, like squat 3x5 100"]
ws[len(ws) - 1] => w
ws[len(ws) - 2] => sr
"" => name
for k in [0:len(ws) - 3]:
if name != "":
name + " " => name
name + ws[k] => name
if len(name) > 20:
return ["an exercise name of at most 20 characters"]
0 => x
while x < len(sr) and sr[x] != "x":
x + 1 => x
whole(sr[0:x], 20) => sets
-1 => reps
if x < len(sr):
whole(sr[x + 1:len(sr)], 100) => reps
if sets == -1 or reps == -1:
return ["sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps"]
tenths(w) => t
if t == -1:
return ["a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5"]
return [name, sets, reps, t]
def reps_text(n):
if n == 1:
return "1 rep"
return str(n) + " reps"
def shown_day(n):
dates.date_of(n) => d
return dates.shown(d) + " " + dates.day_name(d)[0:3]
def entry_text(e):
return e[1] + " " + str(e[2]) + " x " + str(e[3]) + " x " + log.kg(e[4]) + " kg"
def log_workout(state):
if len(state[0]) >= 200:
"The log holds 200 entries, the most it keeps." ^0
return
dates.shown(dates.date_of(state[1])) => last
while True:
trim(input("date (YYYY-MM-DD, nothing for " + last + ")> ")) => answer
if answer == "":
state[1] => day
break
dates.parsed(answer) => d
if len(d) > 0:
dates.day_number(d) => day
break
"Type a real date from 1900-01-01 to 2099-12-31, like 2026-10-12." ^0
day => state[1]
("Exercises for " + shown_day(day) + ", one a line, like squat 3x5 100; nothing to stop.") ^0
0 => added
while len(state[0]) < 200:
trim(input("exercise> ")) => line
if line == "":
break
parsed_line(line) => p
if len(p) == 1:
("Type " + p[0] + ".") ^0
else:
[day, p[0], p[1], p[2], p[3]] => e
# keep the log in date order; same-day entries in the order typed
len(state[0]) => k
while k > 0 and state[0][k - 1][0] > day:
k - 1 => k
state[0][0:k] + [e] + state[0][k:len(state[0])] => state[0]
added + 1 => added
(" " + entry_text(e) + ": volume " + log.kg(log.volume(e)) + " kg") ^0
(str(added) + " added, " + str(len(state[0])) + " in the log.") ^0
def history(state):
if len(state[0]) == 0:
"The log is empty." ^0
return
-1 => day
for e in state[0]:
if e[0] != day:
e[0] => day
shown_day(day) ^0
(" " + entry_text(e)) ^0
def exercises(state):
[] => out
for e in state[0]:
False => seen
for x in out:
if x == e[1]:
True => seen
if not seen:
out + [e[1]] => out
return out
def show_records(state):
if len(state[0]) == 0:
"The log is empty." ^0
return
for x in exercises(state):
log.records(state[0], x) => r
r[0] => heavy
r[1] => best
(x + ": heaviest " + log.kg(heavy[0]) + " kg (" + reps_text(heavy[1][3]) + ", " + dates.shown(dates.date_of(heavy[1][0])) + "); best estimated 1-rep max " + log.kg(best[0]) + " kg (" + log.kg(best[1][4]) + " x " + str(best[1][3]) + ", " + dates.shown(dates.date_of(best[1][0])) + ")") ^0
def weekly(state):
if len(state[0]) == 0:
"The log is empty." ^0
return
log.weekly(state[0]) => weeks
log.bars(weeks, 30) => b
0 => total
for k in [0:len(weeks) - 1]:
log.kg(weeks[k][1]) => v
while len(v) < 9:
" " + v => v
total + weeks[k][1] => total
("week of " + dates.shown(dates.date_of(weeks[k][0])) + v + " kg " + "#" * b[k]) ^0
0 => all
for e in state[0]:
all + log.volume(e) => all
("Total " + log.kg(total) + " kg over " + str(len(weeks)) + " weeks - the same as all " + str(len(state[0])) + " entries added up, " + log.kg(all) + " kg.") ^0
def sample(state):
# Three weeks of squat, bench press and deadlift, each a little heavier.
[] => entries
dates.day_number([2026, 9, 21]) => start
[[0, "squat", 3, 5, 1000], [0, "bench press", 3, 8, 600],
[2, "deadlift", 1, 5, 1200], [4, "squat", 3, 5, 1025], [4, "bench press", 3, 8, 625],
[7, "squat", 3, 5, 1050], [7, "bench press", 3, 6, 650], [9, "deadlift", 1, 5, 1250],
[11, "squat", 5, 3, 1100], [11, "bench press", 3, 8, 625],
[14, "squat", 1, 1, 1200], [14, "bench press", 2, 3, 700], [16, "deadlift", 1, 3, 1350]] => plan
for p in plan:
entries + [[start + p[0], p[1], p[2], p[3], p[4]]] => entries
entries => state[0]
start + 16 => state[1]
"Three weeks of sample workouts, 2026-09-21 to 2026-10-07." ^0
"== Workout log ==" ^0
"Log exercises as sets x reps at a weight; see records and weekly volume." ^0
# [entries in date order, the last date used]
[[], dates.day_number([2026, 10, 12])] => state
True => running
while running:
"" ^0
"1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
log_workout(state)
elif choice == "2":
history(state)
elif choice == "3":
show_records(state)
elif choice == "4":
weekly(state)
elif choice == "5":
sample(state)
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import dates
import log
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def words(s):
out = []
word = ""
for c in s + " ":
if c == " ":
if word != "":
out = out + [word]
word = ""
else:
word = word + c
return out
def whole(s, high):
if s == "" or len(s) > 4:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
if n < 1 or n > high:
return -1
return n
def tenths(s):
k = 0
while k < len(s) and s[k] != ".":
k = k + 1
a = s[0:k]
b = "0"
if k < len(s):
b = s[k + 1:len(s)]
if len(b) != 1:
return -1
if a == "":
a = "0"
for c in a + b:
if not (c >= "0" and c <= "9"):
return -1
if len(a) > 3:
return -1
t = int(a) * 10 + int(b)
if t < 1 or t > 5000:
return -1
return t
def parsed_line(line):
ws = words(line)
if len(ws) < 3:
return ["an exercise, sets x reps and a weight, like squat 3x5 100"]
w = ws[len(ws) - 1]
sr = ws[len(ws) - 2]
name = ""
for k in range(0, len(ws) - 3+1):
if name != "":
name = name + " "
name = name + ws[k]
if len(name) > 20:
return ["an exercise name of at most 20 characters"]
x = 0
while x < len(sr) and sr[x] != "x":
x = x + 1
sets = whole(sr[0:x], 20)
reps = -1
if x < len(sr):
reps = whole(sr[x + 1:len(sr)], 100)
if sets == -1 or reps == -1:
return ["sets x reps as two whole numbers like 3x5, at most 20 sets of 100 reps"]
t = tenths(w)
if t == -1:
return ["a weight in kilograms from 0.1 to 500, at most one decimal, like 102.5"]
return [name, sets, reps, t]
def reps_text(n):
if n == 1:
return "1 rep"
return str(n) + " reps"
def shown_day(n):
d = dates.date_of(n)
return dates.shown(d) + " " + dates.day_name(d)[0:3]
def entry_text(e):
return e[1] + " " + str(e[2]) + " x " + str(e[3]) + " x " + log.kg(e[4]) + " kg"
def log_workout(state):
if len(state[0]) >= 200:
print("The log holds 200 entries, the most it keeps.")
return
last = dates.shown(dates.date_of(state[1]))
while True:
answer = trim(input("date (YYYY-MM-DD, nothing for " + last + ")> "))
if answer == "":
day = state[1]
break
d = dates.parsed(answer)
if len(d) > 0:
day = dates.day_number(d)
break
print("Type a real date from 1900-01-01 to 2099-12-31, like 2026-10-12.")
state[1] = day
print("Exercises for " + shown_day(day) + ", one a line, like squat 3x5 100; nothing to stop.")
added = 0
while len(state[0]) < 200:
line = trim(input("exercise> "))
if line == "":
break
p = parsed_line(line)
if len(p) == 1:
print("Type " + p[0] + ".")
else:
e = [day, p[0], p[1], p[2], p[3]]
k = len(state[0])
while k > 0 and state[0][k - 1][0] > day:
k = k - 1
state[0] = state[0][0:k] + [e] + state[0][k:len(state[0])]
added = added + 1
print(" " + entry_text(e) + ": volume " + log.kg(log.volume(e)) + " kg")
print(str(added) + " added, " + str(len(state[0])) + " in the log.")
def history(state):
if len(state[0]) == 0:
print("The log is empty.")
return
day = -1
for e in state[0]:
if e[0] != day:
day = e[0]
print(shown_day(day))
print(" " + entry_text(e))
def exercises(state):
out = []
for e in state[0]:
seen = False
for x in out:
if x == e[1]:
seen = True
if not seen:
out = out + [e[1]]
return out
def show_records(state):
if len(state[0]) == 0:
print("The log is empty.")
return
for x in exercises(state):
r = log.records(state[0], x)
heavy = r[0]
best = r[1]
print(x + ": heaviest " + log.kg(heavy[0]) + " kg (" + reps_text(heavy[1][3]) + ", " + dates.shown(dates.date_of(heavy[1][0])) + "); best estimated 1-rep max " + log.kg(best[0]) + " kg (" + log.kg(best[1][4]) + " x " + str(best[1][3]) + ", " + dates.shown(dates.date_of(best[1][0])) + ")")
def weekly(state):
if len(state[0]) == 0:
print("The log is empty.")
return
weeks = log.weekly(state[0])
b = log.bars(weeks, 30)
total = 0
for k in range(0, len(weeks)):
v = log.kg(weeks[k][1])
while len(v) < 9:
v = " " + v
total = total + weeks[k][1]
print("week of " + dates.shown(dates.date_of(weeks[k][0])) + v + " kg " + "#" * b[k])
all = 0
for e in state[0]:
all = all + log.volume(e)
print("Total " + log.kg(total) + " kg over " + str(len(weeks)) + " weeks - the same as all " + str(len(state[0])) + " entries added up, " + log.kg(all) + " kg.")
def sample(state):
entries = []
start = dates.day_number([2026, 9, 21])
plan = [[0, "squat", 3, 5, 1000], [0, "bench press", 3, 8, 600], [2, "deadlift", 1, 5, 1200], [4, "squat", 3, 5, 1025], [4, "bench press", 3, 8, 625], [7, "squat", 3, 5, 1050], [7, "bench press", 3, 6, 650], [9, "deadlift", 1, 5, 1250], [11, "squat", 5, 3, 1100], [11, "bench press", 3, 8, 625], [14, "squat", 1, 1, 1200], [14, "bench press", 2, 3, 700], [16, "deadlift", 1, 3, 1350]]
for p in plan:
entries = entries + [[start + p[0], p[1], p[2], p[3], p[4]]]
state[0] = entries
state[1] = start + 16
print("Three weeks of sample workouts, 2026-09-21 to 2026-10-07.")
print("== Workout log ==")
print("Log exercises as sets x reps at a weight; see records and weekly volume.")
state = [[], dates.day_number([2026, 10, 12])]
running = True
while running:
print("")
print("1) log a workout 2) history 3) records 4) weekly volume 5) sample 6) quit")
choice = trim(input("choice> "))
if choice == "1":
log_workout(state)
elif choice == "2":
history(state)
elif choice == "3":
show_records(state)
elif choice == "4":
weekly(state)
elif choice == "5":
sample(state)
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
log.eml
eml# P059 workout log - entries, records and weekly volume. An entry is
# [day number, exercise, sets, reps, weight in tenths of a kilogram]:
# weights are kept as whole tenths, so 102.5 kg is 1025 and every sum is
# exact.
import dates
def kg(tenths):
# Tenths of a kilogram as text: 1025 -> "102.5", 1000 -> "100".
str(int(tenths / 10)) => whole
tenths % 10 => part
if part == 0:
return whole
return whole + "." + str(part)
def volume(e):
# Sets x reps x weight, in tenths.
return e[2] * e[3] * e[4]
def one_rep_max(weight, reps):
# Epley's estimate of the most you could lift once: weight x (1 + reps /
# 30), to the nearest tenth (halves up), and the weight itself for a
# single rep. Whole-number arithmetic: tenths x (30 + reps) / 30.
if reps == 1:
return weight
return int((weight * (30 + reps) * 2 + 30) / 60)
def records(entries, exercise):
# [heaviest weight and its entry, best estimated one-rep max and its
# entry]; the earliest entry wins a tie. [] when the exercise has none.
[] => heavy
[] => best
for e in entries:
if e[1] == exercise:
if len(heavy) == 0 or e[4] > heavy[0]:
[e[4], e] => heavy
one_rep_max(e[4], e[3]) => m
if len(best) == 0 or m > best[0]:
[m, e] => best
if len(heavy) == 0:
return []
return [heavy, best]
def week_start(day):
# The Monday on or before a day.
return day - dates.weekday(dates.date_of(day))
def weekly(entries):
# [[monday, volume in tenths], ...] for every week from the first entry's
# to the last's, empty weeks included, oldest first. Each entry falls in
# exactly one week - from its Monday up to, not including, the next - so
# the weekly volumes add back up to the total, the check the corpus case
# histogram-builder makes of its buckets.
if len(entries) == 0:
return []
entries[0][0] => lo
lo => hi
for e in entries:
if e[0] < lo:
e[0] => lo
if e[0] > hi:
e[0] => hi
[] => out
week_start(lo) => w
while w <= hi:
0 => total
for e in entries:
if e[0] >= w and e[0] < w + 7:
total + volume(e) => total
out + [[w, total]] => out
w + 7 => w
return out
def bars(weeks, width):
# A bar length for each week, scaled so the biggest week gets `width`
# marks (rounded half up); a week with any volume gets at least one.
0 => top
for w in weeks:
if w[1] > top:
w[1] => top
[] => out
for w in weeks:
0 => n
if top > 0:
int((w[1] * width * 2 + top) / (2 * top)) => n
if n == 0 and w[1] > 0:
1 => n
out + [n] => out
return out
Python projection (log.py)
import dates
def kg(tenths):
whole = str(int(tenths / 10))
part = tenths % 10
if part == 0:
return whole
return whole + "." + str(part)
def volume(e):
return e[2] * e[3] * e[4]
def one_rep_max(weight, reps):
if reps == 1:
return weight
return int((weight * (30 + reps) * 2 + 30) / 60)
def records(entries, exercise):
heavy = []
best = []
for e in entries:
if e[1] == exercise:
if len(heavy) == 0 or e[4] > heavy[0]:
heavy = [e[4], e]
m = one_rep_max(e[4], e[3])
if len(best) == 0 or m > best[0]:
best = [m, e]
if len(heavy) == 0:
return []
return [heavy, best]
def week_start(day):
return day - dates.weekday(dates.date_of(day))
def weekly(entries):
if len(entries) == 0:
return []
lo = entries[0][0]
hi = lo
for e in entries:
if e[0] < lo:
lo = e[0]
if e[0] > hi:
hi = e[0]
out = []
w = week_start(lo)
while w <= hi:
total = 0
for e in entries:
if e[0] >= w and e[0] < w + 7:
total = total + volume(e)
out = out + [[w, total]]
w = w + 7
return out
def bars(weeks, width):
top = 0
for w in weeks:
if w[1] > top:
top = w[1]
out = []
for w in weeks:
n = 0
if top > 0:
n = int((w[1] * width * 2 + top) / (2 * top))
if n == 0 and w[1] > 0:
n = 1
out = out + [n]
return out
dates.eml
eml# P059 workout log - real calendar dates, from P055 (habit tracker),
# 1900-01-01 to 2099-12-31. A
# date is [year, month, day]; a day number counts days from 1900-01-01
# (day 0), as in the corpus case days-between-dates, so consecutive days
# have consecutive numbers and a week is seven numbers from a Monday.
def is_leap(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def days_in_month(year, month):
if month == 2:
if is_leap(year):
return 29
return 28
if month == 4 or month == 6 or month == 9 or month == 11:
return 30
return 31
def day_number(d):
0 => total
for y in [1900:d[0] - 1]:
if is_leap(y):
total + 366 => total
else:
total + 365 => total
for m in [1:d[1] - 1]:
total + days_in_month(d[0], m) => total
return total + d[2] - 1
def date_of(n):
# The date of day number n.
1900 => y
365 => length
while True:
365 => length
if is_leap(y):
366 => length
if n < length:
break
n - length => n
y + 1 => y
1 => m
while n >= days_in_month(y, m):
n - days_in_month(y, m) => n
m + 1 => m
return [y, m, n + 1]
def weekday(d):
# Zeller's congruence, as in the corpus case day-of-week-zeller: January
# and February count as months 13 and 14 of the year before. Returns 0
# for Monday up to 6 for Sunday (Zeller's 0 is Saturday).
d[0] => y
d[1] => m
if m < 3:
m + 12 => m
y - 1 => y
y % 100 => k
int(y / 100) => j
(d[2] + int(13 * (m + 1) / 5) + k + int(k / 4) + int(j / 4) + 5 * j) % 7 => h
return (h + 5) % 7
def parsed(s):
# "YYYY-MM-DD" as a date, or [] when it is not a real date in range.
if len(s) != 10 or s[4] != "-" or s[7] != "-":
return []
for i in [0, 1, 2, 3, 5, 6, 8, 9]:
if not (s[i] >= "0" and s[i] <= "9"):
return []
int(s[0:4]) => y
int(s[5:7]) => m
int(s[8:10]) => d
if y < 1900 or y > 2099 or m < 1 or m > 12 or d < 1 or d > days_in_month(y, m):
return []
return [y, m, d]
def shown(d):
str(d[1]) => m
if len(m) == 1:
"0" + m => m
str(d[2]) => dd
if len(dd) == 1:
"0" + dd => dd
return str(d[0]) + "-" + m + "-" + dd
def day_name(d):
return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"][weekday(d)]
Python projection (dates.py)
def is_leap(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def days_in_month(year, month):
if month == 2:
if is_leap(year):
return 29
return 28
if month == 4 or month == 6 or month == 9 or month == 11:
return 30
return 31
def day_number(d):
total = 0
for y in range(1900, d[0]):
if is_leap(y):
total = total + 366
else:
total = total + 365
for m in range(1, d[1]):
total = total + days_in_month(d[0], m)
return total + d[2] - 1
def date_of(n):
y = 1900
length = 365
while True:
length = 365
if is_leap(y):
length = 366
if n < length:
break
n = n - length
y = y + 1
m = 1
while n >= days_in_month(y, m):
n = n - days_in_month(y, m)
m = m + 1
return [y, m, n + 1]
def weekday(d):
y = d[0]
m = d[1]
if m < 3:
m = m + 12
y = y - 1
k = y % 100
j = int(y / 100)
h = (d[2] + int(13 * (m + 1) / 5) + k + int(k / 4) + int(j / 4) + 5 * j) % 7
return (h + 5) % 7
def parsed(s):
if len(s) != 10 or s[4] != "-" or s[7] != "-":
return []
for i in [0, 1, 2, 3, 5, 6, 8, 9]:
if not (s[i] >= "0" and s[i] <= "9"):
return []
y = int(s[0:4])
m = int(s[5:7])
d = int(s[8:10])
if y < 1900 or y > 2099 or m < 1 or m > 12 or d < 1 or d > days_in_month(y, m):
return []
return [y, m, d]
def shown(d):
m = str(d[1])
if len(m) == 1:
m = "0" + m
dd = str(d[2])
if len(dd) == 1:
dd = "0" + dd
return str(d[0]) + "-" + m + "-" + dd
def day_name(d):
return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"][weekday(d)]