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.
Metadata
Release files for m26 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| m26-1.1.0.tar.gz | 12.9 kB | Without provenance | Details for m26-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| m26-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for m26-1.1.0-py3-none-any.whl |
Total release size: 22.2 kB
Release files / m26-1.1.0.tar.gz
| Download URL | m26-1.1.0.tar.gz |
|---|---|
| Size | 12.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5ae8776e08b3244b449eefe5df27052329c60a1671e4293d711df8bc7a2edde
|
|
BLAKE2b-256 checksum How to use checksums |
89fcbc98c3293d5e5caaaac37e4f796599aecbc28263abf0a80b09de73870422
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / m26-1.1.0-py3-none-any.whl
| Download URL | m26-1.1.0-py3-none-any.whl |
|---|---|
| Size | 9.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c484dffe681f519a8c2905a5d0acbc4077868b123f3808d7a26d542a0843c645
|
|
BLAKE2b-256 checksum How to use checksums |
e51a81d798b89a1b9655c869e5628e212770b86d8ac12b94ecbac390f0bfafdf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for m26 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.