Lập trình · 21/10/2026

JavaScript Date Validation: A Date Object Does Not Prove Valid Input

February 30 does not exist, yet constructing a Date can produce a day in March. Date normalizes components; obtaining an object does not prove the original input was a valid calendar date. Date-only fields need explicit format and semantic contracts.

Kiểm tra ngày trong JavaScript: tạo được Date chưa có nghĩa đầu vào hợp lệ

February 30 does not exist, yet constructing a Date can produce a day in March. Date normalizes components; obtaining an object does not prove the original input was a valid calendar date. Date-only fields need explicit format and semantic contracts.

1. Out-of-range components can normalize

const date = new Date(Date.UTC(2026, 1, 30));
console.log(date.toISOString()); // 2026-03-02T00:00:00.000Z
console.log(Number.isFinite(date.getTime())); // true

Date.UTC uses zero-based months, making 1 February. Day 30 rolls into March. A finite timestamp establishes a representable instant rather than validity of the original component tuple.

2. Invalid Date remains truthy

const date = new Date('not-a-date');
console.log(Boolean(date)); // true
console.log(Number.isNaN(date.getTime())); // true
try {
  date.toISOString();
} catch (error) {
  console.log(error instanceof RangeError); // true
}

An if (date) check misses this failure. Validate the timestamp before serialization instead of discovering the problem during output or logging. Avoid relying on acceptance of arbitrary date formats.

3. Validate syntax and compare the parsed calendar date

This helper requires exactly ten characters in YYYY-MM-DD, with a four-digit ISO year and no trimming. A temporary UTC instant validates the calendar date, while the result remains the original date string. Business limits such as future-only deadlines are separate.

function validateDateOnly(text) {
  if (typeof text !== 'string' || text.length !== 10 ||
      !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(text)) {
    throw new TypeError('Expected YYYY-MM-DD');
  }
  const expected = text + 'T00:00:00.000Z';
  const date = new Date(expected);
  if (!Number.isFinite(date.getTime()) || date.toISOString() !== expected) {
    throw new RangeError('Invalid calendar date');
  }
  return text;
}
console.log(validateDateOnly('2024-02-29')); // 2024-02-29
try {
  validateDateOnly('2026-02-30');
} catch (error) {
  console.log(error instanceof RangeError); // true
}

ISO comparison detects normalization to another date. The finite check catches Invalid Date before serialization. Empty text, missing padding and whitespace are rejected by this narrow contract.

4. A calendar date is not automatically an instant

const instant = new Date('2026-10-20T00:00:00.000Z');
function dayIn(timeZone) {
  return new Intl.DateTimeFormat('en-GB', {
    timeZone, day: '2-digit'
  }).formatToParts(instant).find(part => part.type === 'day').value;
}
console.log(dayIn('UTC')); // 20
console.log(dayIn('America/Los_Angeles')); // 19

This fixed instant illustrates different calendar days across zones. Keep a date-only contract for birthdays or date fields rather than silently assigning UTC midnight and formatting it in the viewer's zone.

Appointments that require an instant need an explicit time and timezone policy. The helper's temporary UTC value validates the date; it does not define an appointment's timezone.

5. Keep boundary tests

  • Leap days and February 29 in non-leap years.
  • Day 31 in a thirty-day month.
  • Months 00 and 13; days 00 and 32.
  • Empty text, spaces, newlines and missing padding.
  • Date-only values preserved through the API.
  • Explicit zones when formatting instants.

All eight bilingual code blocks ran with Node.js, alongside leap-year and invalid-input checks. Both example timezones are explicit; browser forms and real APIs were not tested. References: MDN Date, toISOString and Intl.DateTimeFormat.

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.