Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js - What is the fastest way to check if a string represents a valid datetime for a big number of elements?

what is the fastest way to check for a valid DateTime? I need to take into account not just that the string cointains year, month, day, hour and minute, but also that the datetime is valid, eg: 2017-02-29 10:00 should be considered not valid because it is 29th in a non leap year.

I have an array of string elements (300k elements) in the format: YYYYMMDDHHmm, and I need to check each line in the fastest way possible.

Using moment.js to check validity of each elements requires around 5s in a regular for loop:

for (let i = 0; i < length; i++) {
    let el = datetimes[i];
    let d = moment.utc(el, "YYYYMMDDHHmm");
    d.isValid();
}

Are there faster alternatives?

like image 569
smellyarmpits Avatar asked Aug 08 '18 10:08

smellyarmpits


1 Answers

Rule out as much as you can before calling any kind of string matcher or regular expression.

moment is going to evaluate that expression EVERY time. (unless is has come creative caching going on)

You can check for falsy values first (undefined, null, 0, '', etc).

if (!el) {
    // not valid
}

You can check the length after checking null and undefined (thanks Luca)

if (el.length !== 12) {
   // not valid
}

You can also precompile a REGEX and use that.

// define this outside of your loop
let rx2 = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})$/

function leapYear(year) {
  return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

function checkValidDate(strDate) {
  if (!strDate || strDate.length !== 12) throw new Error('invalid date')
  let m = rx2.exec(strDate)
  if (!m) throw new Error('invalid date')

  let year = parseInt(m[1])
  if (year < 2000 || year >= 2100) throw new Error('bad year')

  let month = parseInt(m[2])
  if (month > 11) throw new Error('bad month')

  let day = parseInt(m[3])
  // base 0 days and months
  switch (month) {
    case 0, 2, 4, 5, 6, 7, 9, 11:
      if (day > 30) throw new Error('bad day')
      break;
    case 3, 5, 8, 10:
      if (day > 29) throw new Error('bad day')
      break;
    case 1:
      if (day > 28) throw new Error('bad day')
      if (day === 28 && !isLeapYear(year)) throw new Error('bad day')
      break;
  }

  let hour = parseInt(m[4])
  if (hour > 23) throw new Error('bad hour')

  let minute = parseInt([5])
  if (hour > 59) throw new Error('bad minute')
}

try {
  checkValidDate('asdf')
  console.log('valid')
} catch (e) {
  console.error(e)
}

try {
  checkValidDate('200011241230')
  console.log('valid')
} catch (e) {
  console.error(e)
}

try {
  checkValidDate('200001300000')
  console.log('valid')
} catch (e) {
  console.error(e)
}
like image 147
Steven Spungin Avatar answered Sep 28 '22 07:09

Steven Spungin