⚡ 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

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
}
  1. Check valid — real calendar date check, not just format
  2. Use date as the canonical ISO 8601 form for storage
  3. Use timestamp (Unix epoch, seconds) for range comparisons
  4. Use dayOfWeek for display or business logic (e.g. weekday bookings only)

3. Accepted date formats

FormatExampleNotes
ISO 86012024-03-15Preferred — unambiguous
ISO 8601 with time2024-03-15T10:30:00ZDate portion extracted
DD/MM/YYYY15/03/2024European format
MM/DD/YYYY03/15/2024US format
DD.MM.YYYY15.03.2024German/Central European
D Month YYYY15 March 2024Long 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

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →