{
  "schemaVersion": 1,
  "slug": "cycling-speed-calculator",
  "category": "sports",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "327c03de153374a896a7bea5e78407b1c6ff7085a49388fcd10d9868d20e1ec2",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/cycling-speed-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "v = 60D ÷ (60H + M); t = 60D ÷ v",
    "vars": [
      [
        "D",
        "Route distance (mi)"
      ],
      [
        "H",
        "Ride time — hours"
      ],
      [
        "M",
        "Ride time — minutes"
      ],
      [
        "v",
        "Average speed (mph)"
      ],
      [
        "t",
        "Time to cover D (min)"
      ]
    ],
    "note": "For target-speed time, use the optional target mph in place of v. Convert speed with km/h = mph × 1.609344. Pace is m:ss per mile; estimated time is h:mm rounded to the nearest minute. Stops are included only when counted in the entered ride time. No calorie estimate or performance-speed category is calculated."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-18",
    "sources": [
      {
        "name": "NIST SP 811 — SI conversion factors (mile per hour)",
        "url": "https://www.nist.gov/pml/special-publication-811/nist-guide-si-appendix-b-conversion-factors/nist-guide-si-appendix-b8"
      }
    ],
    "implementationHash": "327c03de153374a896a7bea5e78407b1c6ff7085a49388fcd10d9868d20e1ec2",
    "basis": "Average mph is route miles divided by elapsed hours: v = 60D / (60H + M). Multiply mph by 1.609344 for km/h. Time per mile is 60/v minutes and is displayed as m:ss, rounded to the nearest second. At a positive target speed, finish time is distance divided by that speed; h:mm is rounded to the nearest minute, so a trip under 30 seconds can display 0:00. An estimate outside the safe numeric range is shown as — while the average-speed result remains available. Enter elapsed time including stops for an overall ride average, or moving time for a moving average; the tool does not detect stops. Speed decimals are rounded; very small nonzero speeds use scientific notation.",
    "limitations": "Enter route distance in miles and elapsed ride time in whole hours and minutes. The average includes any stops counted in that time. Target speed is optional: leave it blank or enter 0 to omit the finish-time estimate. This is a distance-and-time calculation; terrain, wind, power and calories are not modeled."
  },
  "contract": {
    "compute": "const bad=()=>({averageSpeed:null,averageSpeedKph:null,pacePerMile:null,estimatedTime:null,__error:'inputError'});\nif(![distance,hours,minutes,targetSpeed].every(Number.isFinite)||distance<0.1||distance>500||hours<0||hours>48||!Number.isInteger(hours)||minutes<0||minutes>59||!Number.isInteger(minutes)||targetSpeed<0||targetSpeed>60||hours*60+minutes<=0)return bad();\nconst totalMin=hours*60+minutes,mph=distance/(totalMin/60);\nconst fmtTime=min=>{const n=Math.round(min);if(!Number.isSafeInteger(n)||n<0)return '—';const h=Math.floor(n/60),m=n%60;return h+':'+String(m).padStart(2,'0');};\nconst fmtPace=min=>{const n=Math.round(min*60),m=Math.floor(n/60),sec=n%60;return m+':'+String(sec).padStart(2,'0');};\nreturn{averageSpeed:mph,averageSpeedKph:mph*1.609344,pacePerMile:fmtPace(60/mph),estimatedTime:targetSpeed>0?fmtTime(distance/targetSpeed*60):'—'};",
    "inputs": [
      {
        "name": "distance",
        "type": "number",
        "default": 25,
        "min": 0.1,
        "max": 500,
        "step": 0.1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "hours",
        "type": "number",
        "default": 1,
        "min": 0,
        "max": 48,
        "step": 1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "minutes",
        "type": "number",
        "default": 30,
        "min": 0,
        "max": 59,
        "step": 1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "targetSpeed",
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 60,
        "step": 0.5,
        "slider": true,
        "strictRange": true
      }
    ],
    "outputs": [
      {
        "name": "averageSpeed",
        "precision": 2,
        "unit": "mph",
        "notation": "auto"
      },
      {
        "name": "averageSpeedKph",
        "precision": 2,
        "unit": "km/h",
        "notation": "auto"
      },
      {
        "name": "pacePerMile",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "estimatedTime",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
