⚡ Node.jsDate / Time
Date Validation in Node.js
Validate date strings in Node.js — parse ISO 8601 and common formats, detect leap years, validate ranges, verify ages, and handle the edge cases that break regex-based date validators.
Also available in Python
Contents
1. Why regex fails for dates
A pattern like /^\d{4}-\d{2}-\d{2}$/ is not date validation — it's format validation. It passes these invalid dates:
2024-02-30 ✗ February never has 30 days 2024-13-01 ✗ Month 13 does not exist 2023-02-29 ✗ 2023 is not a leap year — February has 28 days 1900-02-29 ✗ 1900 is not a leap year (century rule) 2000-02-29 ✓ 2000 IS a leap year (400-year exception)
ℹ️The leap year rule: a year is a leap year if it is divisible by 4, except centuries — unless the century is divisible by 400. So 1900 ≠ leap, 2000 = leap, 2100 ≠ leap. The IsValid API handles all of this automatically.
2. API response structure
Endpoint: GET /v0/date?value=…
{ "valid": true, "date": "2024-02-29", "year": 2024, "month": 2, "day": 29, "dayOfWeek": "Thursday", "isLeapYear": true, "timestamp": 1709164800 }
- Check
valid— real calendar date check, not just format - Use
dateas the canonical ISO 8601 form for storage - Use
timestamp(Unix epoch, seconds) for range comparisons - Use
dayOfWeekfor display or business logic (e.g. weekday bookings only)
3. Accepted date formats
| Format | Example | Notes |
|---|---|---|
| ISO 8601 | 2024-03-15 | Preferred — unambiguous |
| ISO 8601 with time | 2024-03-15T10:30:00Z | Date portion extracted |
| DD/MM/YYYY | 15/03/2024 | European format |
| MM/DD/YYYY | 03/15/2024 | US format |
| DD.MM.YYYY | 15.03.2024 | German/Central European |
| D Month YYYY | 15 March 2024 | Long form |
💡Always store dates in ISO 8601 format (
YYYY-MM-DD) regardless of what the user submits. Use the date field from the response as the canonical value.4. Date range validation
Validate both dates in parallel, then compare timestamps for range logic.
import { createClient } from '@isvalid-dev/sdk' const iv = createClient({ apiKey: process.env.ISVALID_API_KEY! }) async function validateDate(value: string) { const result = await iv.date(value) if (!result.valid) { throw new Error(`Invalid date: ${value}`) } return result } // Date range validation — e.g. booking check-in / check-out async function validateDateRange(checkIn: string, checkOut: string) { const [inResult, outResult] = await Promise.all([ iv.date(checkIn), iv.date(checkOut), ]) if (!inResult.valid) throw new Error('Invalid check-in date') if (!outResult.valid) throw new Error('Invalid check-out date') if (inResult.timestamp >= outResult.timestamp) { throw new Error('Check-out must be after check-in') } const nights = Math.round( (outResult.timestamp - inResult.timestamp) / (1000 * 60 * 60 * 24) ) return { checkIn: inResult.date, checkOut: outResult.date, nights } } // Age verification async function verifyMinimumAge(birthdate: string, minimumAge: number) { const result = await iv.date(birthdate) if (!result.valid) throw new Error('Invalid date of birth') const today = new Date() const birth = new Date(result.date) const age = today.getFullYear() - birth.getFullYear() - (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0) return { valid: age >= minimumAge, age, date: result.date } } // Examples const result = await validateDate('2024-02-29') // leap year — valid console.log(result.isLeapYear) // true const booking = await validateDateRange('2024-06-01', '2024-06-07') console.log(booking.nights) // 6 const age = await verifyMinimumAge('2000-03-15', 18) console.log(age.valid) // true (if today is after 2018-03-15)
5. Age verification
For age gating (18+, 21+), validate the date of birth and calculate age from the response timestamp:
async function verifyMinimumAge(birthdate: string, minimumAge: number) { const result = await iv.date(birthdate) if (!result.valid) throw new Error('Invalid date of birth') // Use UTC to avoid timezone-based off-by-one errors const birth = new Date(result.date + 'T00:00:00Z') const today = new Date() let age = today.getUTCFullYear() - birth.getUTCFullYear() const hasHadBirthday = ( today.getUTCMonth() > birth.getUTCMonth() || (today.getUTCMonth() === birth.getUTCMonth() && today.getUTCDate() >= birth.getUTCDate()) ) if (!hasHadBirthday) age-- return { valid: age >= minimumAge, age, date: result.date } }
⚠️Always calculate age in UTC to avoid timezone-related off-by-one errors on birthday boundaries. A user born on March 15 in UTC+12 has their birthday a full day earlier in UTC-11.
6. Edge cases
Leap year — February 29
// These are the tricky cases the API handles correctly: await iv.date('2024-02-29') // valid — 2024 is a leap year await iv.date('2023-02-29') // invalid — 2023 is not a leap year await iv.date('1900-02-29') // invalid — 1900 is a century, not divisible by 400 await iv.date('2000-02-29') // valid — 2000 is divisible by 400
Month-end boundary
await iv.date('2024-04-31') // invalid — April has 30 days await iv.date('2024-01-31') // valid — January has 31 days await iv.date('2024-06-30') // valid — June has 30 days
Future date restriction
const result = await iv.date(input) if (result.valid && result.timestamp > Date.now() / 1000) { throw new Error('Date cannot be in the future') }
Weekday-only bookings
const result = await iv.date(input) if (result.valid && ['Saturday', 'Sunday'].includes(result.dayOfWeek)) { throw new Error('Bookings are only available on weekdays') }
7. Summary checklist
✓Never use regex alone for date validation
✓Validate calendar correctness (leap year, month-end)
✓Store dates in ISO 8601 (YYYY-MM-DD) format
✓Use timestamp for range comparisons
✓Validate date ranges in parallel with Promise.all
✓Calculate age in UTC to avoid boundary errors
✓Check dayOfWeek for business day restrictions
✓Reject future dates where required