<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Daily Calcs]]></title><description><![CDATA[Daily Calcs]]></description><link>https://daily-calcs.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a82b401c67bf03f287fe604/b255cba5-7753-40ed-8d07-5020d6feaba5.png</url><title>Daily Calcs</title><link>https://daily-calcs.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 25 Oct 2026 01:51:53 GMT</lastBuildDate><atom:link href="https://daily-calcs.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Convert a Dog’s Birthday Into Human Years in TypeScript]]></title><description><![CDATA[Turn a dog’s birthday into human years in TS: days ÷ 365.25, then apply a size-based curve (15 / 24 / +4–7). Has the function, tests, & edge cases.]]></description><link>https://daily-calcs.hashnode.dev/how-to-convert-a-dog-s-birthday-into-human-years-in-typescript</link><guid isPermaLink="true">https://daily-calcs.hashnode.dev/how-to-convert-a-dog-s-birthday-into-human-years-in-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Daily Calcs]]></dc:creator><pubDate>Mon, 17 Aug 2026 07:31:05 GMT</pubDate><content:encoded><![CDATA[<p><code>dogAge * 7</code> is a one-liner. It is also wrong.</p>
<p>A 1-year-old dog is about <strong>15</strong> in human years, not 7. A 2-year-old is about <strong>24</strong>. After that, size is a parameter: a Chihuahua adds ~4 human years per calendar year, a Great Dane adds ~7.</p>
<p>I needed a birthday-in, human-years-out function for a public calculator. The AVMA has been saying the ×7 rule is inaccurate for years. AAHA publishes a size chart. The job was to turn that chart into code that is deterministic, testable, and honest about leap years.</p>
<p>Live version: <a href="https://dailycalcs.com/pets/dog-age-calculator-by-birth-date/">Dog Age Calculator by Birth Date</a>. Below is the core logic.</p>
<h2>The model</h2>
<p>Two outputs, not one:</p>
<ol>
<li><p><strong>Human-year equivalent</strong> — a piecewise function of decimal dog years and size</p>
</li>
<li><p><strong>Life stage</strong> — Puppy → Junior → Adolescent → Adult → Mature Adult → Senior, with a size-dependent senior threshold</p>
</li>
</ol>
<p>Size is not cosmetic. It only changes the slope <strong>after year 2</strong>, which is the part most “dog year” widgets get wrong.</p>
<pre><code class="language-ts">type DogSize = "small" | "medium" | "large" | "giant";

const SIZE_CONFIG: Record&lt;
  DogSize,
  { agingRate: number; seniorThreshold: number }
&gt; = {
  small:  { agingRate: 4, seniorThreshold: 10 }, // &lt; 20 lb
  medium: { agingRate: 5, seniorThreshold: 9  }, // 20–50 lb
  large:  { agingRate: 6, seniorThreshold: 8  }, // 51–90 lb
  giant:  { agingRate: 7, seniorThreshold: 6  }, // &gt; 90 lb
};
</code></pre>
<p>Rates come from the size chart adapted from <em>Dog Owner’s Home Veterinary Handbook</em> (also on <a href="https://www.aaha.org/wp-content/uploads/globalassets/02-guidelines/canine-life-stage-2019/canine_and_feline_age_chart_poster.pdf">AAHA’s canine/feline age poster</a>). Senior thresholds follow the <a href="https://www.aaha.org/resources/life-stage-canine-2019/canine-life-stage-definitions/">2019 AAHA Canine Life Stage Guidelines</a> — roughly the last 25% of expected lifespan.</p>
<h2>Birthday → decimal years</h2>
<p>Do not subtract years. Count days, then divide by <strong>365.25</strong>.</p>
<pre><code class="language-ts">function daysBetween(a: Date, b: Date): number {
  return Math.round((b.getTime() - a.getTime()) / 86_400_000);
}

function toDogYears(birthDate: Date, today = new Date()): number {
  if (Number.isNaN(birthDate.getTime())) {
    throw new RangeError("Invalid birth date");
  }
  if (birthDate &gt; today) {
    throw new RangeError("Birth date cannot be in the future");
  }
  return daysBetween(birthDate, today) / 365.25;
}
</code></pre>
<p>Why <code>365.25</code> instead of <code>365</code>? A dog born 1 Mar 2020 is four calendar years old on 1 Mar 2024, but that span included a leap day. Integer-year subtraction hides it. Day-count does not.</p>
<p><code>today</code> is injected so the function is testable. Freeze it in unit tests or you get flakes every midnight.</p>
<h2>Human years across four growth segments</h2>
<p>Puppies do not age linearly. Year one is steep, year two is still steep, then the curve flattens and splits by size.</p>
<pre><code class="language-plaintext">0 ────── 0.5y ────── 1y ────── 2y ──────────►
0        10          15        24
         └── ×20 ─┘ └── ×10 ┘ └── ×9 ┘ └── ×rate(size)
</code></pre>
<pre><code class="language-ts">function toHumanYears(dogYears: number, size: DogSize): number {
  if (dogYears &lt; 0.5) {
    return Math.round(dogYears * 20); // 0 → 10
  }
  if (dogYears &lt; 1) {
    return Math.round(10 + (dogYears - 0.5) * 10); // 10 → 15
  }
  if (dogYears &lt; 2) {
    return Math.round(15 + (dogYears - 1) * 9); // 15 → 24
  }
  const { agingRate } = SIZE_CONFIG[size];
  return Math.round(24 + (dogYears - 2) * agingRate);
}
</code></pre>
<p>Worked examples, same birthday, different size:</p>
<table>
<thead>
<tr>
<th>Dog years</th>
<th>Small (+4)</th>
<th>Medium (+5)</th>
<th>Large (+6)</th>
<th>Giant (+7)</th>
</tr>
</thead>
<tbody><tr>
<td>0.5</td>
<td>10</td>
<td>10</td>
<td>10</td>
<td>10</td>
</tr>
<tr>
<td>1</td>
<td>15</td>
<td>15</td>
<td>15</td>
<td>15</td>
</tr>
<tr>
<td>2</td>
<td>24</td>
<td>24</td>
<td>24</td>
<td>24</td>
</tr>
<tr>
<td>5</td>
<td><strong>36</strong></td>
<td><strong>39</strong></td>
<td><strong>42</strong></td>
<td><strong>45</strong></td>
</tr>
<tr>
<td>10</td>
<td><strong>56</strong></td>
<td><strong>64</strong></td>
<td><strong>72</strong></td>
<td><strong>80</strong></td>
</tr>
</tbody></table>
<p>Until age 2, size is unused. After age 2 it is the whole function. That is the bug in every <code>* 7</code> widget: it applies one slope to a domain that needs four.</p>
<p>Boundary note: <code>&lt; 0.5</code> means <strong>exactly</strong> 6 months uses the second branch and lands on 10. Off-by-one on the comparator changes the 6-month result.</p>
<h2>Life stage is a second function</h2>
<p>Human years are a translation for owners. Care planning uses life stage. Senior starts at 6 for giant breeds and 10 for small ones, so the stage function has to take size too.</p>
<pre><code class="language-ts">type LifeStage =
  | "Puppy"
  | "Junior"
  | "Adolescent"
  | "Adult"
  | "Mature Adult"
  | "Senior";

function toLifeStage(dogYears: number, size: DogSize): LifeStage {
  const days = dogYears * 365.25;
  const seniorAt = SIZE_CONFIG[size].seniorThreshold;

  if (days &lt; 182.625) return "Puppy";       // &lt; 6 months
  if (days &lt; 365.25) return "Junior";       // &lt; 1 year
  if (days &lt; 730.5) return "Adolescent";    // &lt; 2 years
  if (days &lt; (seniorAt - 2) * 365.25) return "Adult";
  if (days &lt; seniorAt * 365.25) return "Mature Adult";
  return "Senior";
}
</code></pre>
<p>A 7-year-old Mastiff is already Senior. A 7-year-old Pomeranian is still Mature Adult. Same <code>dogYears</code>, different <code>size</code>.</p>
<p>AAHA prefers clinical life stages over human-year math for medical decisions. Keep both in the API. Do not let the rounded “39 human years” drive vaccine or surgery timing.</p>
<h2>Unknown birthday</h2>
<p>Rescue math is the reverse: adoption date minus estimated age.</p>
<pre><code class="language-ts">function birthDateFromAdoption(
  adoptionDate: Date,
  months: number,
  weeks: number,
): Date {
  if (months &lt; 0 || months &gt; 11) throw new RangeError("Months must be 0–11");
  if (weeks &lt; 0 || weeks &gt; 12) throw new RangeError("Weeks must be 0–12");

  const estimatedDays = months * 30.44 + weeks * 7;
  const birth = new Date(adoptionDate);
  birth.setDate(birth.getDate() - Math.round(estimatedDays));
  return birth;
}
</code></pre>
<p><code>30.44</code> is <code>365.25 / 12</code>. After this, the rest of the pipeline is unchanged: you now have a <code>Date</code> and can run <code>toDogYears</code>.</p>
<h2>What we did not ship</h2>
<p>The 2019 UC San Diego epigenetic-clock paper fits Labrador DNA methylation with something like:</p>
<pre><code class="language-ts">16 * Math.log(dogYears) + 31;
</code></pre>
<p>While based on solid research, it makes an impractical default for a general calculator. It is breed-specific, lab-based, and diverges from the chart vets and owners already know. We implemented the published size chart so a 2-year-old is 24 everywhere, not 42 from <code>ln(2)</code>.</p>
<h2>Tests worth writing</h2>
<p>Inject <code>today</code>. Assert the breakpoints, not a random Tuesday.</p>
<pre><code class="language-ts">const today = new Date("2026-06-08T12:00:00");

expect(toHumanYears(0.5, "medium")).toBe(10);
expect(toHumanYears(1, "medium")).toBe(15);
expect(toHumanYears(2, "medium")).toBe(24);
expect(toHumanYears(5, "medium")).toBe(39);
expect(toHumanYears(5, "giant")).toBe(45);
expect(toHumanYears(10, "small")).toBe(56);
expect(toHumanYears(10, "giant")).toBe(80);

expect(toLifeStage(5, "medium")).toBe("Adult");
expect(toLifeStage(5, "giant")).toBe("Mature Adult");
</code></pre>
<p>Also cover: invalid date, future date, leap-day birthday, and the <code>&lt; 0.5</code> vs <code>&lt;= 0.5</code> boundary.</p>
<h2>The whole thing</h2>
<pre><code class="language-ts">function dogAgeFromBirthday(
  birthDate: Date,
  size: DogSize,
  today = new Date(),
) {
  const dogYears = toDogYears(birthDate, today);
  return {
    dogYears,
    humanYears: toHumanYears(dogYears, size),
    lifeStage: toLifeStage(dogYears, size),
  };
}
</code></pre>
<p>Three pure functions, one size table, no <code>* 7</code>.</p>
<p>If you want the UI wrapped around this - breed → size, milestone dates, adoption fallback - it is running here: <a href="https://dailycalcs.com/pets/dog-age-calculator-by-birth-date/">https://dailycalcs.com/pets/dog-age-calculator-by-birth-date/</a>.</p>
<hr />
<p><em>Human-year equivalents are planning estimates, not a diagnosis. Sources: AAHA 2019 Canine Life Stage Guidelines, AAHA age poster, AVMA senior-pet guidance.</em></p>
]]></content:encoded></item></channel></rss>