Skip to main content

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

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

m26-1.1.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

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

Hashes for m26-1.1.0.tar.gz
Algorithm Hash digest
SHA256 c5ae8776e08b3244b449eefe5df27052329c60a1671e4293d711df8bc7a2edde
MD5 f4dc2709d345620cc1e96a4b8d84e158
BLAKE2b-256 89fcbc98c3293d5e5caaaac37e4f796599aecbc28263abf0a80b09de73870422

See more details on using hashes here.

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

Hashes for m26-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c484dffe681f519a8c2905a5d0acbc4077868b123f3808d7a26d542a0843c645
MD5 29259c012f8547bbc249b15ef6050af5
BLAKE2b-256 e51a81d798b89a1b9655c869e5628e212770b86d8ac12b94ecbac390f0bfafdf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page