Skip to main content

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
  • 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.

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)

Source distribution for m26 1.1.0
File Size Uploaded
m26-1.1.0.tar.gz 12.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for m26 1.1.0
File Interpreter ABI Platform
m26-1.1.0-py3-none-any.whl Python 3 none any Details

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}

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

1 release file

0.2.0

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page