<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cornel Gabriel</title>
    <description>The latest articles on Forem by Cornel Gabriel (@cornel_gabriel_d4237164ca).</description>
    <link>https://forem.com/cornel_gabriel_d4237164ca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1938596%2F2e057bea-b62b-4ed4-8936-564ddba7cdf7.png</url>
      <title>Forem: Cornel Gabriel</title>
      <link>https://forem.com/cornel_gabriel_d4237164ca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cornel_gabriel_d4237164ca"/>
    <language>en</language>
    <item>
      <title>I Built 16+ Date &amp; Time Calculators in Vanilla JavaScript — Here’s What I Learned About Time (and DST)</title>
      <dc:creator>Cornel Gabriel</dc:creator>
      <pubDate>Thu, 12 Feb 2026 14:09:17 +0000</pubDate>
      <link>https://forem.com/cornel_gabriel_d4237164ca/i-built-16-date-time-calculators-in-vanilla-javascript-heres-what-i-learned-about-time-and-2pdn</link>
      <guid>https://forem.com/cornel_gabriel_d4237164ca/i-built-16-date-time-calculators-in-vanilla-javascript-heres-what-i-learned-about-time-and-2pdn</guid>
      <description>&lt;p&gt;Time is deceptively simple — until you try to calculate it correctly.&lt;/p&gt;

&lt;p&gt;What started as a small weekend experiment turned into &lt;strong&gt;16+ date and time utilities&lt;/strong&gt; running entirely in the browser. Along the way, I ran into classic problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daylight Saving Time bugs&lt;/li&gt;
&lt;li&gt;Off-by-one errors&lt;/li&gt;
&lt;li&gt;Timezone inconsistencies&lt;/li&gt;
&lt;li&gt;ISO week calculation quirks&lt;/li&gt;
&lt;li&gt;Performance vs library tradeoffs&lt;/li&gt;
&lt;li&gt;Privacy considerations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article walks through the technical decisions behind building reliable date tools using &lt;strong&gt;vanilla JavaScript only&lt;/strong&gt; — and the lessons that might save you from subtle production bugs.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The Real Problem With “Days Between Dates”&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Most developers try something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const diff = (end - start) / (1000 * 60 * 60 * 24);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works… until it doesn’t.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The DST Problem&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If a date range crosses a Daylight Saving Time boundary, you might get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6.958333 days&lt;/li&gt;
&lt;li&gt;7.041666 days&lt;/li&gt;
&lt;li&gt;6 instead of 7&lt;/li&gt;
&lt;li&gt;8 instead of 7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Because **not **every day has 24 hours.&lt;/p&gt;

&lt;p&gt;The solution is not to divide milliseconds blindly.&lt;/p&gt;

&lt;p&gt;Instead, I switched to a &lt;strong&gt;UTC day index approach&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const _utcDayIndex = (d) =&amp;gt; {
  return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 86400000;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const startIdx = _utcDayIndex(date1);
const endIdx = _utcDayIndex(date2);
const diff = endIdx - startIdx;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It ignores time components&lt;/li&gt;
&lt;li&gt;It normalizes everything to UTC midnight&lt;/li&gt;
&lt;li&gt;It makes DST irrelevant&lt;/li&gt;
&lt;li&gt;The difference is always an integer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This single change removed multiple edge-case bugs.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Business Days: Another Trap&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Calculating business days sounds trivial:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Count weekdays between two dates.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But if you loop using local time and increment with &lt;code&gt;setDate(getDate() + 1)&lt;/code&gt;, DST can shift time values and introduce subtle errors.&lt;/p&gt;

&lt;p&gt;Instead, I iterate using UTC day indices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (let dayIdx = startIdx; dayIdx &amp;lt; endIdx; dayIdx++) {
  const dt = new Date(dayIdx * 86400000);
  const dow = dt.getUTCDay();
  if (dow !== 0 &amp;amp;&amp;amp; dow !== 6) count++;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;getUTCDay()&lt;/code&gt; not &lt;code&gt;getDay()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use a numeric day index&lt;/li&gt;
&lt;li&gt;Avoid mutating original dates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guarantees consistency regardless of user timezone.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Age Calculation Is Harder Than It Looks&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
A naive age calculation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const years = now.getFullYear() - birth.getFullYear();&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s wrong for users whose birthday hasn’t happened yet this year.&lt;/p&gt;

&lt;p&gt;A correct implementation must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subtract years&lt;/li&gt;
&lt;li&gt;Adjust months if needed&lt;/li&gt;
&lt;li&gt;Adjust days if needed&lt;/li&gt;
&lt;li&gt;Handle month-length differences&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (days &amp;lt; 0) {
  months--;
  const lastMonth = new Date(referenceYear, referenceMonth, 0);
  days += lastMonth.getDate();
}

if (months &amp;lt; 0) {
  years--;
  months += 12;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;29 Feb birthdays behave correctly&lt;/li&gt;
&lt;li&gt;Month boundaries don’t break&lt;/li&gt;
&lt;li&gt;Output is calendar-accurate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;Why I Didn’t Use date-fns or Moment.js&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
This was intentional.&lt;/p&gt;

&lt;p&gt;Goals:&lt;/p&gt;

&lt;p&gt;⚡ Near-zero bundle weight&lt;/p&gt;

&lt;p&gt;🔒 All calculations client-side&lt;/p&gt;

&lt;p&gt;🚀 Instant load on mobile&lt;/p&gt;

&lt;p&gt;🧠 Full control over edge cases&lt;/p&gt;

&lt;p&gt;Using vanilla JavaScript:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps the tools extremely fast&lt;/li&gt;
&lt;li&gt;Avoids dependency updates&lt;/li&gt;
&lt;li&gt;Forces deeper understanding of time logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small, deterministic operations, native &lt;code&gt;Date&lt;/code&gt;is enough — if you handle it carefully.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Privacy-First Design&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Every calculation runs locally in the browser.&lt;/p&gt;

&lt;p&gt;No:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Birth dates stored&lt;/li&gt;
&lt;li&gt;Tracking of date inputs&lt;/li&gt;
&lt;li&gt;Server processing&lt;/li&gt;
&lt;li&gt;External APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only analytics (optional, consent-based) are handled via Cookiebot-controlled scripts.&lt;/p&gt;

&lt;p&gt;For date tools especially (age, birthdays, payroll hours), keeping data local builds trust.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Architecture Notes&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
The project structure is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static HTML pages&lt;/li&gt;
&lt;li&gt;Vanilla JS utilities&lt;/li&gt;
&lt;li&gt;Shared helper module&lt;/li&gt;
&lt;li&gt;No framework&lt;/li&gt;
&lt;li&gt;No build step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core utilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DST-safe days between&lt;/li&gt;
&lt;li&gt;Business day calculator&lt;/li&gt;
&lt;li&gt;ISO-8601 week number&lt;/li&gt;
&lt;li&gt;Time duration formatter&lt;/li&gt;
&lt;li&gt;Add/subtract date logic&lt;/li&gt;
&lt;li&gt;Age breakdown calculator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this powers the full suite of tools available on the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unexpected Lessons&lt;br&gt;
**&lt;br&gt;
**1. Timezone Bugs Are Invisible Until Production&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything works on your machine… until someone in Australia reports an issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Midnight Is Dangerous&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creating dates at midnight can cause DST rollover problems.&lt;/p&gt;

&lt;p&gt;Using noon when parsing input avoids this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new Date(year, month - 1, day, 12, 0, 0, 0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminates many edge cases.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;3. Simple Tools Are SEO Gold&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
While building the tools, I discovered something interesting:&lt;/p&gt;

&lt;p&gt;Very specific utilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Age in days&lt;/li&gt;
&lt;li&gt;Birth year from age&lt;/li&gt;
&lt;li&gt;How old will I be in 2035&lt;/li&gt;
&lt;li&gt;Chronological age calculator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;target extremely specific search intent.&lt;/p&gt;

&lt;p&gt;Sometimes micro-tools outperform generic ones.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The Result&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
What began as a technical experiment turned into a full collection of browser-based date utilities:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://datetools.live" rel="noopener noreferrer"&gt;https://datetools.live&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The focus remains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;Final Thoughts&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If you’re building anything involving dates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t divide milliseconds blindly&lt;/li&gt;
&lt;li&gt;Normalize to UTC day indices&lt;/li&gt;
&lt;li&gt;Be careful with DST boundaries&lt;/li&gt;
&lt;li&gt;Treat month arithmetic cautiously&lt;/li&gt;
&lt;li&gt;Prefer deterministic logic over convenience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time is simple — until it isn’t.&lt;/p&gt;

&lt;p&gt;And most production bugs around time are subtle, embarrassing, and avoidable.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
