7

I'm following this example to validate date string.

While this below example evaluates to true.

var date = new Date('12/21/2019');
console.log(date instanceof Date && !isNaN(date.valueOf()));

This below example also evaluates to true even though it's a bad date.By bad date I mean, that date does not exist in calendar.

var date = new Date('02/31/2019');
console.log(date instanceof Date && !isNaN(date.valueOf()));

Is there a better way of doing it?

TheFallenOne
  • 1,598
  • 2
  • 23
  • 57
  • Why exactly do you think second example is a bad date? – ethane Mar 14 '19 at 17:41
  • 2
    @ethane I'd imagine "bad" means "non-existent". To be clear though, JavaScript considers this as the third of March. Much like it would consider the "0th" of April as the last day of March. – Tyler Roper Mar 14 '19 at 17:43
  • There's only 28 days in Feb. 29 on leap year. However, it is Nan regardless. – silencedogood Mar 14 '19 at 17:43
  • if i understood well, you want to be sure if date is an instance of date and not null – Aboubacar Ouattara Mar 14 '19 at 17:43
  • 2
    @TylerRoper, bad in bad out. JS is smart enough not to actually give you 31 Feb 2019. It will produce a date in March. – ethane Mar 14 '19 at 17:44
  • @ethane I'm aware, thank you. But you've just acknowledged *"bad in"* - your original comment was asking *"Why do you think the date is bad"*? I was clarifying OP's intention. – Tyler Roper Mar 14 '19 at 17:45
  • 1
    @TylerRoper I do understand what JS considers it as. But for practical use, that does not help. – TheFallenOne Mar 14 '19 at 17:46
  • @TylerRoper, to clarify further. I was not advocating that the second example wasn't bad input. I like how you edited your comment though. – ethane Mar 14 '19 at 17:47
  • Consider using moment.js for this, or the underlying code: https://momentjs.com/docs/#/parsing/is-valid/ –  Mar 14 '19 at 17:48
  • why don't you use moment js? – Ramy M. Mousa Mar 14 '19 at 17:48
  • @ethane My edited comment says the same as the original but with grammatical improvements, so I'm not entirely sure what you're implicating. Nonetheless, we all understand how this works and the issue here. We can move past the conjecture. – Tyler Roper Mar 14 '19 at 17:48
  • @RamyMohamed This link says it is deprecated. https://stackoverflow.com/a/19368570/7793375 – TheFallenOne Mar 14 '19 at 17:50
  • 1
    @TheFallenOne Calling `moment(input)` *without a format string* is deprecated. The answer that Ramy provided below should still be perfectly valid if you were to go the moment.js route :) – Tyler Roper Mar 14 '19 at 17:53
  • you can just check to make sure the month is the same parsed as it is in the input (-1 of course). – dandavis Mar 14 '19 at 18:02

3 Answers3

6

using momentjs

moment("02/30/2019", "MM/DD/YYYY", true).isValid(); // false
moment("03/30/2019", "MM/DD/YYYY", true).isValid(); // true

from current docs of the library here: isValid momentjs

moment("not a real date").isValid(); // false

You can still write your own solution by splitting the string and evaluating for each section. the problem with this approach is that not only February that is not 31 days, there are other months too that are only 30 days. so it will take time before you develop a "not buggy" solution.

Ramy M. Mousa
  • 5,727
  • 3
  • 34
  • 45
  • 1
    The OP hasn't tagged the question with moment.js or mentioned it. There is no need for a large library, validating parsing of a date string is as simple as checking the resulting date values vs the input values. – RobG Mar 15 '19 at 22:20
4

A lot depends on allowed input formats, but this seem to be possible solution for your examples (pure JS, no extra libraries):

const trueDate = '12/21/2019'
const falseDate = '02/31/2019'

const checkDate = (dateStr) => {
  const dateObj = new Date(dateStr)
  const actualDate = dateObj.getDate()
  const actualMonth = dateObj.getMonth() + 1 // months are from 0 to 11 in JS
  const actualFullYear = dateObj.getFullYear()

  const [strMonth, strDate, strFullYear] = dateStr.split('/').map(Number)

  return strMonth === actualMonth && strDate === actualDate && strFullYear === actualFullYear
}

console.log(checkDate(trueDate))  // true
console.log(checkDate(falseDate))  // false
Georgy
  • 2,410
  • 3
  • 21
  • 35
3

It's not recommended to parse dates using new Date('02/31/2019') (which implicitly calls Date.parse) because of inconsistencies between implementations.

It is not recommended to use Date.parse as until ES5, parsing of strings was entirely implementation dependent. There are still many differences in how different hosts parse date strings, therefore date strings should be manually parsed (a library can help if many different formats are to be accommodated).

Source: MDN

Instead you can use a regular expression to extract parts of the date, and then construct the date object using either new Date(year, monthIndex, day) or new Date(Date.UTC(year, monthIndex, day)) depending on which timezone you assume.

Then you can format your newly created date object as a string and check if it's the same as your initial string. If the date wraps over to the next month (e.g. Feb 30 to Mar 1) the strings won't match.

function parseMMDDYYY(str) {
    const match = str.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
    if (!match) {
        throw new Error(`Invalid date format ${str}`);
    }
    const [, m, d, y] = match;
    const date = new Date(Date.UTC(y, m - 1, d));
    if (!date.toISOString().startsWith(`${y}-${m}-${d}`)) {
        throw new Error(`Invalid date ${str}`);
    }
    return date;
}

try {
    console.log(parseMMDDYYY('02/31/2019'));
} catch (e) {
    console.log(e.toString());
}

try {
    console.log(parseMMDDYYY('12/21/2019'));
} catch (e) {
    console.log(e.toString());
}
Alexey Lebedev
  • 11,988
  • 4
  • 39
  • 47