About
parkrun Next Milestone Estimate adds expected dates to a parkrunner profile so you can see when they are likely to reach their next milestone club (e.g. parkrunner A4886000).
parkrun rewards persistence over performance: when you reach 10 (for under 18s), 25, 50, 100, 200, 250, 300 and every 100 parkruns up to and including 1,000 Saturday 5k parkruns, whether you walk, jog, run or volunteer, you automatically enter a milestone club. This script estimates those same clubs.
Estimates assume participation at every available event on the chosen day of the week. Special events are excluded from the calculation.
Saturday 5k milestone clubs
The script reads the parkrun total from the profile heading and appends an estimate for the next club on that ladder. Dates are based on weekly Saturdays.
The milestone club of 10 is included only for under 18s (junior age category codes starting with J, including JM15-17 and JW15-17).
Volunteer milestone clubs
Volunteer credits from the Volunteer Summary use the same milestone clubs as Saturday 5k finishes, including 10 for under 18s.
You can choose whether volunteering is counted on Saturdays, Sundays, or both. Those preferences are stored only in your browser and are used to recalculate the volunteer estimate.
2k finisher milestones
When the profile includes a junior parkrun total, an estimate for the next 2k milestone (for example junior parkrun 150) is appended for parkrunners whose most recent age category is under 15: JM10, JW10, JM11-14, or JW11-14.
2k estimates are based on weekly Sundays. Age categories such as JM15-17 and JW15-17 do not receive a 2k estimate.
Imminent dates
If an estimated milestone date falls within the next seven days, that date is highlighted on the page.
Version 1.2.2 Updated
Screenshot
Install
Recommended: userscript. Bookmarklet works where a userscript manager isn’t available.
Userscript (recommended)
Install parkrun Next Milestone Estimate
First time? Userscript basics and installation steps
What is a userscript?
A userscript is a small piece of JavaScript that runs in your browser and enhances specific websites. These scripts work with parkrun event pages, parkrunner profile pages, and results pages, and can be used with any userscript manager including Userscripts, Tampermonkey, Violentmonkey, or any compatible browser extension.
Installation steps
-
Install a userscript manager for your browser:
- Desktop: Userscripts (Safari), Tampermonkey (Chrome, Firefox, Edge, Opera), or Violentmonkey (Orion)
- iOS: Userscripts (Safari) or Violentmonkey (Orion)
- Android: Install Kiwi Browser, then install Tampermonkey or Violentmonkey from the Chrome Web Store.
- Click the Install button above for this script.
- Click “Install” when prompted by your userscript manager.
Bookmarklet
A bookmarklet is a bookmark whose URL is JavaScript. Use it on browsers that support bookmarks but not userscript managers.
Drag this link to your bookmarks bar:
parkrun Next Milestone Estimate bookmarklet
Mobile bookmarklet setup and full code
Mobile bookmarklet
On mobile browsers that let you edit bookmark URLs:
- Copy the JavaScript code below to your clipboard.
- Create a new bookmark for any page.
- Edit the bookmark and replace its URL with the code you copied.
- Navigate to the relevant parkrun page.
- Select the bookmark to run the script.
javascript:function _createForOfIteratorHelper(e,t){var n,r,a,o,l="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(l)return a=!(r=!0),{s:function(){l=l.call(e)},n:function(){var e=l.next();return r=e.done,e},e:function(e){a=!0,n=e},f:function(){try{r||null==l.return||l.return()}finally{if(a)throw n}}};if(Array.isArray(e)||(l=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return l&&(e=l),o=0,{s:t=function(){},n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var n;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}(()=>{var u={10:{restricted_age:"J"},25:{},50:{},100:{},200:{},250:{},300:{},400:{},500:{},600:{},700:{},800:{},900:{},1e3:{}},d={10:{name:"junior parkrun 10"},25:{name:"junior parkrun 25"},50:{name:"junior parkrun 50"},75:{name:"junior parkrun 75"},100:{name:"junior parkrun 100"},150:{name:"junior parkrun 150"},200:{name:"junior parkrun 200"},250:{name:"junior parkrun 250"},300:{name:"junior parkrun 300"}},t=new Set(["JM10","JW10","JM11-14","JW11-14"]);function s(e){return"string"==typeof e&&t.has(e)}function a(e){return"string"==typeof e&&e.startsWith("J")}function c(){var e,t=_createForOfIteratorHelper((0<arguments.length&&void 0!==arguments[0]?arguments[0]:document).querySelectorAll("h3"));try{for(t.s();!(e=t.n()).done;){var n,r,a=e.value,o=(null!=(n=null==(r=a.textContent)?void 0:r.trim())?n:"").match(/([\d,]+)\s+parkruns?\b/i);if(o)return{heading:a,total:parseInt(o[1].replace(/,/g,""),10)}}}catch(e){t.e(e)}finally{t.f()}return null}function g(){var e,t=_createForOfIteratorHelper((0<arguments.length&&void 0!==arguments[0]?arguments[0]:document).querySelectorAll("h3"));try{for(t.s();!(e=t.n()).done;){var n,r,a=e.value,o=(null!=(n=null==(r=a.textContent)?void 0:r.trim())?n:"").match(/([\d,]+)\s+junior\s+parkruns?\s+total/i);if(o)return{heading:a,total:parseInt(o[1].replace(/,/g,""),10)}}}catch(e){t.e(e)}finally{t.f()}return null}function p(){var e,t=_createForOfIteratorHelper((0<arguments.length&&void 0!==arguments[0]?arguments[0]:document).querySelectorAll("p"));try{for(t.s();!(e=t.n()).done;){var n,r=(null!=(n=e.value.textContent)?n:"").match(/Most recent age category was\s+([A-Z]+\d+[-\d]*)/i);if(r)return r[1]}}catch(e){t.e(e)}finally{t.f()}return null}function m(){var e,t=(0<arguments.length&&void 0!==arguments[0]?arguments[0]:document).querySelector("#volunteer-summary");return!t||!(e=(e=t.nextElementSibling)&&"TABLE"===e.tagName?e:null==(t=t.parentElement)?void 0:t.querySelector("table"))||!(t=e.querySelector("tfoot tr"))||(e=t.querySelectorAll("td")).length<2||!(t=e[1].querySelector("strong"))||(e=null!=(t=null==(e=t.textContent)?void 0:e.trim())?t:"",t=parseInt(e.replace(/,/g,""),10),Number.isNaN(t))?null:t}function f(t){var e,n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:u;return null!=(e=Object.keys(r).map(Number).sort(function(e,t){return e-t}).find(function(e){return!(e<=t)&&("J"===(e=r[e]).restricted_age?a(n):e.restricted_age&&n?n.startsWith(e.restricted_age):!e.restricted_age)}))?e:null}function y(e,t,n){e=f(e,t,n);return e?{value:e,definition:n[e]}:null}function h(){var e,t,n=(0<arguments.length&&void 0!==arguments[0]?arguments[0]:document).querySelector("table#results tbody");return!n||!(n=n.querySelector("tr"))||(n=n.querySelectorAll("td")).length<2||!(n=n[1].querySelector("a"))||!(n=null==(n=n.textContent)?void 0:n.trim())||3!==(n=n.split("/")).length?null:(e=parseInt(n[0],10),t=parseInt(n[1],10)-1,n=parseInt(n[2],10),new Date(n,t,e))}function r(){var e,t,n,r,a=0<arguments.length&&void 0!==arguments[0]?arguments[0]:new Date,o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,l=new Date(a),i=l.getDay();return 6===i?(o&&(r=o.getFullYear(),e=o.getMonth(),o=o.getDate(),t=a.getFullYear(),n=a.getMonth(),a=a.getDate(),r===t)&&e===n&&o===a&&l.setDate(l.getDate()+7),l.setHours(0,0,0,0)):(r=(6-i+7)%7,l.setDate(l.getDate()+r),l.setHours(0,0,0,0)),l}function o(){var e,t,n,r,a=0<arguments.length&&void 0!==arguments[0]?arguments[0]:new Date,o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,l=new Date(a),i=l.getDay();return 0===i?(o&&(r=o.getFullYear(),e=o.getMonth(),o=o.getDate(),t=a.getFullYear(),n=a.getMonth(),a=a.getDate(),r===t)&&e===n&&o===a&&l.setDate(l.getDate()+7),l.setHours(0,0,0,0)):(r=(0-i+7)%7,l.setDate(l.getDate()+r),l.setHours(0,0,0,0)),l}function v(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:new Date;return!t||t<=e?null:(t=t-e,e=r(n,3<arguments.length&&void 0!==arguments[3]?arguments[3]:null),(n=new Date(e)).setDate(e.getDate()+7*(t-1)),n)}function D(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:new Date;return!t||t<=e?null:(t=t-e,e=o(n,3<arguments.length&&void 0!==arguments[3]?arguments[3]:null),(n=new Date(e)).setDate(e.getDate()+7*(t-1)),n)}function x(e){return e.toLocaleDateString(void 0,{weekday:"long",year:"numeric",month:"long",day:"numeric"})}function l(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:new Date,t=new Date(t),n=(t.setHours(0,0,0,0),new Date(t)),e=(n.setDate(n.getDate()+7),new Date(e));return e.setHours(0,0,0,0),t<=e&&e<n}function S(e,t){var n,r,a=2<arguments.length&&void 0!==arguments[2]?arguments[2]:new Date;t&&e&&l(t,a)&&(n=x(t),r=[],function e(t){if(t.nodeType===Node.TEXT_NODE)r.push(t);else for(var n=0;n<t.childNodes.length;n++)e(t.childNodes[n])}(e),r.forEach(function(e){var t;e.textContent.includes(n)&&((t=document.createElement("span")).style.backgroundColor="#ffeb3b",t.style.padding="0 2px",t.style.borderRadius="2px",t.textContent=e.textContent,e.parentNode.replaceChild(t,e))}))}var i="parkrun-volunteer-credits",C="parkrun-volunteer-days";function b(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:i;if("undefined"!=typeof localStorage){var t=localStorage.getItem(e);if(t)try{return JSON.parse(t)}catch(e){return{saturday:!0,sunday:!0}}t=localStorage.getItem(C);if(t)try{var n=JSON.parse(t);return localStorage.setItem(e,JSON.stringify(n)),localStorage.removeItem(C),n}catch(e){}}return{saturday:!0,sunday:!0}}function E(e){"undefined"!=typeof localStorage&&localStorage.setItem(1<arguments.length&&void 0!==arguments[1]?arguments[1]:i,JSON.stringify(e))}function A(e,t){var n,r,a,o,l,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:new Date;return t&&!(t<=e)&&(t=t-e,(e=(3<arguments.length&&void 0!==arguments[3]?arguments[3]:null)||b()).saturday||e.sunday)?e.saturday&&e.sunday?(a=(6-(l=new Date(i)).getDay()+7)%7,l.setDate(l.getDate()+a),r=(0-(a=new Date(i)).getDay()+7)%7,a.setDate(a.getDate()+r),r=l<a?l:a,l=l<a,a=Math.floor((t-1)/2),o=(t-1)%2,(n=new Date(r)).setDate(r.getDate()+7*a),1==o&&n.setDate(n.getDate()+(l?1:6)),n.setHours(0,0,0,0),n):e.saturday?(a=(6-(r=new Date(i)).getDay()+7)%7,r.setDate(r.getDate()+a),r.setDate(r.getDate()+7*(t-1)),r.setHours(0,0,0,0),r):e.sunday?(l=(0-(o=new Date(i)).getDay()+7)%7,o.setDate(o.getDate()+l),o.setDate(o.getDate()+7*(t-1)),o.setHours(0,0,0,0),o):null:null}function N(e,t,n){var r,a,o,l=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;e&&"true"!==e.dataset.milestoneEstimateApplied&&(t=document.createTextNode(" (expected to reach ".concat(t," around ").concat(n,")")),(n=Array.from(e.childNodes).filter(function(e){return e.nodeType===Node.TEXT_NODE}).find(function(e){return null==(e=e.textContent)?void 0:e.includes("&")}))?0<=(a=(r=null!=(r=n.textContent)?r:"").indexOf("&"))?(o=r.slice(0,a).trimEnd(),r=r.slice(a).trimStart(),a=document.createTextNode(o),o=document.createTextNode(" ".concat(r)),e.insertBefore(a,n),e.insertBefore(t,n),e.insertBefore(o,n),e.removeChild(n)):e.insertBefore(t,n):e.appendChild(t),e.dataset.milestoneEstimateApplied="true",l)&&S(e,l)}function k(e,t){var n,r,a,o,l,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,u=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;e&&"true"!==e.dataset.volunteerCreditsApplied&&((n=document.createElement("p")).id="volunteer-credits-summary",t="".concat(t," volunteer credits total"),i&&u&&(t+=" (expected to reach ".concat(i," around ").concat(u,")")),n.textContent=t,(i=document.createElement("div")).id="volunteer-credits-preferences",i.style.marginTop="0.5em",i.style.fontSize="0.9em",t=b(),(r=document.createElement("label")).style.marginRight="1em",r.style.cursor="pointer",(a=document.createElement("input")).type="checkbox",a.checked=t.saturday,a.id="volunteer-saturday",a.addEventListener("change",function(){var e=b();e.saturday=a.checked,E(e),window.location.reload()}),r.appendChild(a),r.appendChild(document.createTextNode(" Volunteer on Saturdays")),(o=document.createElement("label")).style.cursor="pointer",(l=document.createElement("input")).type="checkbox",l.checked=t.sunday,l.id="volunteer-sunday",l.addEventListener("change",function(){var e=b();e.sunday=l.checked,E(e),window.location.reload()}),o.appendChild(l),o.appendChild(document.createTextNode(" Volunteer on Sundays")),i.appendChild(r),i.appendChild(o),e.insertAdjacentElement("afterend",n),n.insertAdjacentElement("afterend",i),e.dataset.volunteerCreditsApplied="true",u)&&(t=new Date(u),Number.isNaN(t.getTime())||S(n,t))}function w(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;e&&"true"!==e.dataset.juniorMilestoneEstimateApplied&&(e.appendChild(document.createTextNode(" (expected to reach ".concat(t," around ").concat(n,")"))),e.dataset.juniorMilestoneEstimateApplied="true",r)&&S(e,r)}function I(e){var t,n,r,a,o;e&&"true"!==e.dataset.assumptionsInfoApplied&&((t=document.createElement("div")).id="milestone-assumptions-info",t.style.marginTop="1em",t.style.padding="0.75em",t.style.backgroundColor="#f5f5f5",t.style.border="1px solid #ddd",t.style.borderRadius="4px",t.style.fontSize="0.85em",t.style.color="#555",(n=document.createElement("strong")).textContent="ℹ️ Assumptions behind expected dates:",n.style.display="block",n.style.marginBottom="0.5em",(r=document.createElement("ul")).style.margin="0.5em 0 0 0",r.style.padding="0 0 0 1.25em",r.style.listStyle="none",(a=document.createElement("li")).textContent="You participate at every available parkrun day",a.style.margin="0.25em 0",(o=document.createElement("li")).textContent="Special events are excluded from the calculations",o.style.margin="0.25em 0",r.appendChild(a),r.appendChild(o),t.appendChild(n),t.appendChild(r),((null==(a=e.parentElement)?void 0:a.querySelector("#volunteer-credits-preferences"))||e).insertAdjacentElement("afterend",t),e.dataset.assumptionsInfoApplied="true")}function e(){var e,t,n,r,a,o=0<arguments.length&&void 0!==arguments[0]?arguments[0]:document,l=1<arguments.length&&void 0!==arguments[1]?arguments[1]:new Date,i=c(o);i?(e=p(o),(r=f(i.total,e))?(a=h(o),(a=v(i.total,r,l,a))?(N(i.heading,r,x(a),a),null!==(r=m(o))&&(a=null,(n=f(r,e,u))&&(t=A(r,n,l))&&(a=x(t)),k(i.heading,r,n,a)),(t=g(o))&&s(e)&&null!=(r=y(t.total,e,d))&&null!=(n=r.definition)&&n.name&&(a=D(t.total,r.value,l))&&w(t.heading,r.definition.name,x(a),a),I(i.heading)):console.log("Unable to calculate milestone date")):console.log("No upcoming milestone found")):console.log("Parkrun total heading not found")}"undefined"!=typeof module&&module.exports?module.exports={milestones:u,juniorMilestones:d,is2kEligibleAgeCategory:s,isJuniorAgeCategory:a,findParkrunTotalHeading:c,findJuniorParkrunTotalHeading:g,findAgeCategory:p,findVolunteerCreditsTotal:m,findMostRecentFinishDate:h,getNextMilestone:f,getNextMilestoneDefinition:y,getNextSaturday:r,getNextSunday:o,calculateMilestoneDate:v,calculateJuniorMilestoneDate:D,formatDate:x,isDateInNextWeek:l,highlightDateIfNeeded:S,getVolunteerCreditPreferences:b,setVolunteerCreditPreferences:E,getNextVolunteerMilestoneDate:A,appendMilestoneEstimate:N,appendJuniorMilestoneEstimate:w,appendVolunteerCreditsSummary:k,appendAssumptionsInfo:I,applyMilestoneEstimate:e}:e()})();