DEV Community

Cornel Gabriel
Cornel Gabriel

Posted on

I Built 16+ Date & Time Calculators in Vanilla JavaScript — Here’s What I Learned About Time (and DST)

Time is deceptively simple — until you try to calculate it correctly.

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

  • Daylight Saving Time bugs
  • Off-by-one errors
  • Timezone inconsistencies
  • ISO week calculation quirks
  • Performance vs library tradeoffs
  • Privacy considerations

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

*The Real Problem With “Days Between Dates”
*

Most developers try something like:

const diff = (end - start) / (1000 * 60 * 60 * 24);

Enter fullscreen mode Exit fullscreen mode

It works… until it doesn’t.

*The DST Problem
*

If a date range crosses a Daylight Saving Time boundary, you might get:

  • 6.958333 days
  • 7.041666 days
  • 6 instead of 7
  • 8 instead of 7

Why? Because **not **every day has 24 hours.

The solution is not to divide milliseconds blindly.

Instead, I switched to a UTC day index approach:

const _utcDayIndex = (d) => {
  return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 86400000;
};
Enter fullscreen mode Exit fullscreen mode

Then:

const startIdx = _utcDayIndex(date1);
const endIdx = _utcDayIndex(date2);
const diff = endIdx - startIdx;
Enter fullscreen mode Exit fullscreen mode

This works because:

  • It ignores time components
  • It normalizes everything to UTC midnight
  • It makes DST irrelevant
  • The difference is always an integer

This single change removed multiple edge-case bugs.

*Business Days: Another Trap
*

Calculating business days sounds trivial:

“Count weekdays between two dates.”

But if you loop using local time and increment with setDate(getDate() + 1), DST can shift time values and introduce subtle errors.

Instead, I iterate using UTC day indices:

for (let dayIdx = startIdx; dayIdx < endIdx; dayIdx++) {
  const dt = new Date(dayIdx * 86400000);
  const dow = dt.getUTCDay();
  if (dow !== 0 && dow !== 6) count++;
}

Enter fullscreen mode Exit fullscreen mode

Key decisions:

  • Use getUTCDay() not getDay()
  • Use a numeric day index
  • Avoid mutating original dates

This guarantees consistency regardless of user timezone.

*Age Calculation Is Harder Than It Looks
*

A naive age calculation:

const years = now.getFullYear() - birth.getFullYear();

That’s wrong for users whose birthday hasn’t happened yet this year.

A correct implementation must:

  1. Subtract years
  2. Adjust months if needed
  3. Adjust days if needed
  4. Handle month-length differences

Example logic:

if (days < 0) {
  months--;
  const lastMonth = new Date(referenceYear, referenceMonth, 0);
  days += lastMonth.getDate();
}

if (months < 0) {
  years--;
  months += 12;
}
Enter fullscreen mode Exit fullscreen mode

This ensures:

  • 29 Feb birthdays behave correctly
  • Month boundaries don’t break
  • Output is calendar-accurate

*Why I Didn’t Use date-fns or Moment.js
*

This was intentional.

Goals:

⚡ Near-zero bundle weight

🔒 All calculations client-side

🚀 Instant load on mobile

🧠 Full control over edge cases

Using vanilla JavaScript:

  • Keeps the tools extremely fast
  • Avoids dependency updates
  • Forces deeper understanding of time logic

For small, deterministic operations, native Dateis enough — if you handle it carefully.

*Privacy-First Design
*

Every calculation runs locally in the browser.

No:

  • Birth dates stored
  • Tracking of date inputs
  • Server processing
  • External APIs

The only analytics (optional, consent-based) are handled via Cookiebot-controlled scripts.

For date tools especially (age, birthdays, payroll hours), keeping data local builds trust.

*Architecture Notes
*

The project structure is intentionally simple:

  • Static HTML pages
  • Vanilla JS utilities
  • Shared helper module
  • No framework
  • No build step

Core utilities include:

  • DST-safe days between
  • Business day calculator
  • ISO-8601 week number
  • Time duration formatter
  • Add/subtract date logic
  • Age breakdown calculator

All of this powers the full suite of tools available on the project.

Unexpected Lessons
**
**1. Timezone Bugs Are Invisible Until Production

Everything works on your machine… until someone in Australia reports an issue.

2. Midnight Is Dangerous

Creating dates at midnight can cause DST rollover problems.

Using noon when parsing input avoids this:

new Date(year, month - 1, day, 12, 0, 0, 0);
Enter fullscreen mode Exit fullscreen mode

This eliminates many edge cases.

*3. Simple Tools Are SEO Gold
*

While building the tools, I discovered something interesting:

Very specific utilities:

  • Age in days
  • Birth year from age
  • How old will I be in 2035
  • Chronological age calculator

target extremely specific search intent.

Sometimes micro-tools outperform generic ones.

*The Result
*

What began as a technical experiment turned into a full collection of browser-based date utilities:

👉 https://datetools.live

The focus remains:

  • Accuracy
  • Performance
  • Privacy
  • Zero dependencies

*Final Thoughts
*

If you’re building anything involving dates:

  • Don’t divide milliseconds blindly
  • Normalize to UTC day indices
  • Be careful with DST boundaries
  • Treat month arithmetic cautiously
  • Prefer deterministic logic over convenience

Time is simple — until it isn’t.

And most production bugs around time are subtle, embarrassing, and avoidable.

Top comments (0)