calculations for sports like running, cycling, and swimming
Project description
m26
m26 - calculations for sports like running, cycling, and swimming
Urls
Features
- Create Distances in either miles, kilometers, or yards.
- Translates Distances to the other units of measure.
- Specify ElapsedTime either in 'hh:mm:ss' strings, or int second values.
- Calculates Speed from a given Distance and ElapsedTime - per mile, per kilometer, and per yard.
- Calculates pace_per_mile and seconds_per_mile for a given Speed.
- Projects one Speed to another Distance with either a simple or algorithmic formula.
- RunWalkCalculator calculates pace and mph from given time intervals and paces.
- Calculates the Age of person, and age_graded times.
- Prior to version 1.1.0, the "Fox and Haskell formula" was used.
- This is based on 220 - age = max heart rate.
- Since version 1.1.0 the "Tanaka formula" is the default
- This new formula is based on 208 - (0.7 * age) = max heart rate.
- See https://pubmed.ncbi.nlm.nih.gov/11153730/
- Prior to version 1.1.0, the "Fox and Haskell formula" was used.
- Calculates five standard heart-rate training zones based on Age.
Quick start
Installation
$ pip install m26
Use
Sample Program
See sample-program.py in the GitHub repo.
import json
import m26
# Sample program for the m26 library, version 1.1.0
# Chris Joakim, 2026
if __name__ == "__main__":
d1: m26.Distance = m26.Distance(26.2)
print(d1)
print("d1, as_miles: {0}".format(d1.as_miles()))
print("d1, as_kilometers: {0}".format(d1.as_kilometers()))
print("d1, as_yards: {0}".format(d1.as_yards()))
print("")
d2: m26.Distance = m26.Distance(50.0, m26.Constants.uom_kilometers())
print(d2)
print("")
d3: m26.Distance = m26.Distance(7040, m26.Constants.uom_yards())
print(d3)
print("")
d4: m26.Distance = m26.Distance(10.0, m26.Constants.uom_kilometers())
print(d4)
print("")
d4.add(d1)
print(d4)
print("")
d4.subtract(d3)
print(d4)
print("")
t1: m26.ElapsedTime = m26.ElapsedTime("3:47:30")
print(t1)
print("t1 hours: {0}".format(t1.hours()))
print("")
t2: m26.ElapsedTime = m26.ElapsedTime(3662)
print(t2)
print("t2 hours: {0}".format(t2.hours()))
print("")
s: m26.Speed = m26.Speed(d1, t1)
print(s)
print("s, mph: {0}".format(s.mph()))
print("s, kph: {0}".format(s.kph()))
print("s, yph: {0}".format(s.yph()))
print("s, spm: {0}".format(s.seconds_per_mile()))
print("s, ppm: {0}".format(s.pace_per_mile()))
hhmmss_simple = s.projected_time(d2, "simple")
hhmmss_riegel = s.projected_time(d2, "riegel")
print("projected_time, simple: {0}".format(hhmmss_simple))
print("projected_time, riegel: {0}".format(hhmmss_riegel))
print("")
result = m26.RunWalkCalculator.calculate("2:30", "9:16", "0:45", "17:00", 31.0)
print(json.dumps(result, sort_keys=True, indent=2))
print("")
a = m26.AgeCalculator.calculate("1960-10-01", "2015-10-18")
print(a)
print("")
a2: m26.Age = m26.Age(58.1)
graded_tanaka: m26.Speed = s.age_graded(a, a2)
print(f"graded_tanaka: {graded_tanaka}")
graded_fox_haskell: m26.Speed = s.age_graded(a, a2, "fox_haskell")
print(f"graded_fox_haskell: {graded_fox_haskell}")
zones_tanaka: list[dict] = a2.training_zones()
print(json.dumps(zones_tanaka, sort_keys=True, indent=2))
zones_fox_haskell: list[dict] = a2.training_zones(formula="fox_haskell")
print(json.dumps(zones_fox_haskell, sort_keys=True, indent=2))
version: str = m26.VERSION
print(f"m26 version: {version}")
print("")
Sample Program Output
uv run sample-program.py
<Distance value:26.2 uom:m>
d1, as_miles: 26.2
d1, as_kilometers: 42.1648128
d1, as_yards: 46112.0
<Distance value:50.0 uom:k>
<Distance value:7040.0 uom:y>
<Distance value:10.0 uom:k>
<Distance value:52.1648128 uom:k>
<Distance value:45.7274368 uom:k>
<ElapsedTime hh:3.0 mm:47.0 ss:30.0 secs:13650.0>
t1 hours: 3.7916666666666665
<ElapsedTime hh:1 mm:1 ss:2.0 secs:3662.0>
t2 hours: 1.0172222222222222
<Speed dist:<Distance value:26.2 uom:m> etime:<ElapsedTime hh:3.0 mm:47.0 ss:30.0 secs:13650.0>>
s, mph: 6.90989010989011
s, kph: 11.120390189010989
s, yph: 12161.406593406595
s, spm: 520.9923664122138
s, ppm: 8:40.99
projected_time, simple: 04:29:46
projected_time, riegel: 04:32:32
{
"avg_mph": 5.4292343387471,
"avg_ppm": "11:03.07",
"miles": 31.0,
"proj_miles": 31.0,
"proj_time": "05:42:35",
"run_hhmmss": "2:30",
"run_ppm": "9:16",
"walk_hhmmss": "0:45",
"walk_ppm": "17:00"
}
<Age value:55.044490075290895>
graded_tanaka: <Speed dist:<Distance value:26.2 uom:m> etime:<ElapsedTime hh:3 mm:50 ss:24.477961696025886 secs:13824.477961696026>>
graded_fox_haskell: <Speed dist:<Distance value:26.2 uom:m> etime:<ElapsedTime hh:3 mm:51 ss:47.61402391772026 secs:13907.61402391772>>
[
{
"age": 58.1,
"max": 167.32999999999998,
"pct_max": 0.55,
"pulse": 92.0315,
"zone": 1
},
{
"age": 58.1,
"max": 167.32999999999998,
"pct_max": 0.65,
"pulse": 108.7645,
"zone": 2
},
{
"age": 58.1,
"max": 167.32999999999998,
"pct_max": 0.75,
"pulse": 125.49749999999999,
"zone": 3
},
{
"age": 58.1,
"max": 167.32999999999998,
"pct_max": 0.85,
"pulse": 142.23049999999998,
"zone": 4
},
{
"age": 58.1,
"max": 167.32999999999998,
"pct_max": 0.95,
"pulse": 158.96349999999998,
"zone": 5
}
]
[
{
"age": 58.1,
"max": 161.9,
"pct_max": 0.55,
"pulse": 89.04500000000002,
"zone": 1
},
{
"age": 58.1,
"max": 161.9,
"pct_max": 0.65,
"pulse": 105.23500000000001,
"zone": 2
},
{
"age": 58.1,
"max": 161.9,
"pct_max": 0.75,
"pulse": 121.42500000000001,
"zone": 3
},
{
"age": 58.1,
"max": 161.9,
"pct_max": 0.85,
"pulse": 137.615,
"zone": 4
},
{
"age": 58.1,
"max": 161.9,
"pct_max": 0.95,
"pulse": 153.805,
"zone": 5
}
]
m26 version: 1.1.0
Changelog
Current version: 1.1.0
- 2026/07/03, version 1.1.0, Added default Tanaka formula for age-grading, modified training zones, added typing hints, 100% test coverage
- 2026/05/29, version 1.0.0, Transition to from pip to uv, pyproject.toml, and python 3.13
- 2024/09/24, version 0.3.2, python 3.11 support
- 2024/09/23, version 0.3.1, Class AgeCalculator modified to use datetime delta for mac/win consistency
- 2024/09/21, version 0.3.0, Removed arrow dependency, upgraded to python 3.12
- 2020/02/19, version 0.2.1, Jinja2 upgrade
- 2017/09/27, version 0.2.0, Converted to the pytest testing framework; coverage at 100%.
- 2015/10/31, version 0.1.0, Initial Production release.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
m26-1.1.0.tar.gz
(12.9 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
m26-1.1.0-py3-none-any.whl
(9.3 kB
view details)
File details
Details for the file m26-1.1.0.tar.gz.
File metadata
- Download URL: m26-1.1.0.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5ae8776e08b3244b449eefe5df27052329c60a1671e4293d711df8bc7a2edde
|
|
| MD5 |
f4dc2709d345620cc1e96a4b8d84e158
|
|
| BLAKE2b-256 |
89fcbc98c3293d5e5caaaac37e4f796599aecbc28263abf0a80b09de73870422
|
File details
Details for the file m26-1.1.0-py3-none-any.whl.
File metadata
- Download URL: m26-1.1.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c484dffe681f519a8c2905a5d0acbc4077868b123f3808d7a26d542a0843c645
|
|
| MD5 |
29259c012f8547bbc249b15ef6050af5
|
|
| BLAKE2b-256 |
e51a81d798b89a1b9655c869e5628e212770b86d8ac12b94ecbac390f0bfafdf
|