How to validate a date string using date-fns

538 Views Asked by At

I'm trying to validate a user input for a datetime using date-fns:

import { isDate, isValid } from 'date-fns';

console.log('DATE:', stringVal);
console.log('IS_DATE:', isDate(new Date(stringVal)));
console.log('IS_VALID:', isValid(new Date(stringVal)));

This produces:

DATE: 2023-02-31 03:03:03
IS_DATE: true
IS_VALID: true

This seems to be incorrect because February 31, 2023 is not a valid date. What is the correct way of validating an ISO date string?

2

There are 2 best solutions below

1
On

Looks like I can use the parseISO function:

import { isDate, isValid, parseISO } from 'date-fns';

if (!isDate(parseISO(stringVal)) || !isValid(parseISO(stringVal))) {
  throw new Error('Invalid date value');
}
4
On

There are a couple of things going on here.

The date has already been corrected by the time you call isValid

You are seeing if the return value of new Date('2023-02-31 03:03:03') is a valid date. At that point, the problem in the date string has already been corrected.

console.log('DATE:', new Date(stringVal)); // Fri Mar 03 2023 03:03:03 
console.log('IS_DATE:', isDate(new Date(stringVal))); // yes, Mar 03 is a date
console.log('IS_VALID:', isValid(new Date(stringVal))); // yes, Mar 03 is valid

The Date constructor is very optimistic - implementations are allowed to parse unexpected formats however they like:

Date.parse() and the Date() constructor both accept strings in the date time string format as input. Furthermore, implementations are allowed to support other date formats when the input fails to match this format.

You'd need to use a different parse function.

Your string is not in ISO date format.

Your string has spaces. Usually, there's a T indicator to separate the time and date parts. It should be 2023-02-31T03:03:03 (although in some cases you're allowed to omit the T, you can't have a space there). Ideally, it should include timezone information too.


A fix

I recommend DateTime.fromISO from luxon, as it is strict about the format, and gives you clear reasoning for why the date didn't parse:

const maybeDate = DateTime.fromISO(`2023-02-31T03:03:03`);
if(maybeDate.invalid) {
   console.log(maybeDate.invalid.reason); // unit out of range
   console.log(maybeDate.invalid.explanation) // you specified 31 (of type number) as a day, which is invalid
}

Note that if you use it with your original string, you get

the input "2023-02-31 03:03:03" can't be parsed as ISO 8601

because the original string isn't in ISO format. You would get this error with valid dates written in non-ISO format too:

DateTime.fromISO(`2023-02-28 03:03:03`)
// the input "2023-02-28 03:03:03" can't be parsed as ISO 8601