Date Calculator: From Date Object Pitfalls to Workday Optimization
Date Calculator: From Date Object Pitfalls to Workday Optimization#
I recently needed date calculations for a project. Thought new Date() with some arithmetic would do—turned out to be a minefield of edge cases. Here’s what I learned.
JavaScript Date Pitfalls#
Month Starts at Zero#
The classic trap:
const date = new Date(2024, 0, 15) // January 15, 2024
const date2 = new Date(2024, 1, 15) // February 15, 2024, NOT January!
Month parameter is 0-11, not 1-12. setMonth() works the same way:
// Wrong: direct month arithmetic
date.setMonth(date.getMonth() + 3) // If November, becomes February next year
// Right: let Date handle overflow
const result = new Date(date)
result.setMonth(result.getMonth() + 3) // Date auto-carries over
Timezone Offset Issue#
new Date('2024-01-15') differs from new Date('2024-01-15T00:00:00'):
new Date('2024-01-15')
// In UTC+8, this becomes 2024-01-15 08:00:00 (parsed as UTC)
new Date('2024-01-15T00:00:00')
// This gives 2024-01-15 00:00:00 local time
Always append T00:00:00 to force local time:
const date = new Date(dateString + 'T00:00:00')
Date Difference Calculation#
Basic Implementation#
Days between two dates:
function dateDiff(start: string, end: string) {
const startDate = new Date(start + 'T00:00:00')
const endDate = new Date(end + 'T00:00:00')
const diffMs = endDate.getTime() - startDate.getTime()
const days = Math.floor(Math.abs(diffMs) / (1000 * 60 * 60 * 24))
return days
}
For weeks and months, weeks is straightforward (divide by 7), but months are tricky since each month has different days.
Month Estimation#
Months can only be estimated using average 30.44 days:
const months = Math.round((days / 30.44) * 10) / 10 // 1 decimal place
30.44 comes from 365.25 / 12, accounting for leap years. Good enough for most use cases.
Date Addition: Cross-Month and Cross-Year#
Simple Version#
function addDays(date: 2026-02-06T16:33:53+00:00
const result = new Date(date)
result.setDate(result.getDate() + days)
return result
}
The magic: setDate() handles overflow automatically:
const date = new Date(2024, 0, 31) // January 31
date.setDate(32) // Automatically becomes February 1
So result.setDate(result.getDate() + 90) works perfectly—Date handles all cross-month/cross-year logic.
Month Addition#
function addMonths(date: 2026-02-06T16:33:53+00:00
const result = new Date(date)
result.setMonth(result.getMonth() + months)
return result
}
Edge case: January 31 + 1 month becomes March 2 or 3 (February has no 31st).
Some business scenarios need “end-of-month to end-of-month” behavior. Extra handling required:
function addMonthsEndOfMonth(date: 2026-02-06T16:33:53+00:00
const result = new Date(date)
const isEndOfMonth = date.getDate() === getLastDayOfMonth(date)
result.setMonth(result.getMonth() + months)
if (isEndOfMonth) {
result.setDate(getLastDayOfMonth(result))
}
return result
}
function getLastDayOfMonth(date: 2026-02-06T16:33:53+00:00
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()
}
Age Calculation: Precise to the Day#
Age isn’t just currentYear - birthYear:
function calculateAge(birthDate: Date): { years: number, months: number, days: number } {
const today = new Date()
today.setHours(0, 0, 0, 0) // Ignore time
let years = today.getFullYear() - birthDate.getFullYear()
let months = today.getMonth() - birthDate.getMonth()
let days = today.getDate() - birthDate.getDate()
// Borrow: if days negative, borrow 1 month
if (days < 0) {
months--
const prevMonth = new Date(today.getFullYear(), today.getMonth(), 0)
days += prevMonth.getDate()
}
// Borrow: if months negative, borrow 1 year
if (months < 0) {
years--
months += 12
}
return { years, months, days }
}
Example: Today is 2024-03-15, birthday is 1990-08-20:
- Year diff: 34 years
- Month diff: 3 - 8 = -5, after borrowing becomes 7 months
- Day diff: 15 - 20 = -5, after borrowing becomes 29 - 5 = 24 days (February’s days)
Result: 33 years 7 months 24 days.
Workday Calculation: Excluding Weekends#
Linear Scan#
Most straightforward: iterate each day, check if weekend:
function countWorkdays(start: Date, end: Date): number {
let count = 0
const cur = new Date(start)
while (cur <= end) {
const day = cur.getDay() // 0=Sunday, 6=Saturday
if (day !== 0 && day !== 6) {
count++
}
cur.setDate(cur.getDate() + 1)
}
return count
}
Time complexity O(n) where n is the date range. For a few hundred days, performance is fine.
Mathematical Optimization#
For very large ranges (10+ years), use math:
function countWorkdaysOptimized(start: Date, end: Date): number {
const totalDays = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1
const fullWeeks = Math.floor(totalDays / 7)
const remainder = totalDays % 7
// Each full week has 5 workdays
let workdays = fullWeeks * 5
// Handle remaining days
const cur = new Date(start)
for (let i = 0; i < remainder; i++) {
const day = cur.getDay()
if (day !== 0 && day !== 6) workdays++
cur.setDate(cur.getDate() + 1)
}
return workdays
}
This version runs in O(1) + O(7) ≈ O(1), much faster for huge ranges.
But in practice, linear scan handles 1000 days in milliseconds. Unless you have extreme requirements, simple is fine.
Related Tools#
This date calculator is now live: Date Calculator, supporting date difference, date addition/subtraction, age calculation, and workday counting.
Other related tools:
- Timestamp Converter - Unix timestamp to date conversion
- Timezone Converter - Multi-timezone comparison