cron-parser icon indicating copy to clipboard operation
cron-parser copied to clipboard

Add method for comparing date against cron pattern

Open andeersg opened this issue 5 years ago • 5 comments

I was trying to find a library that allowed my to check the current time against a cron pattern to see if it matches. So far this library is the one closest to what I want, but I had some problems with milliseconds and rounding errors when I tried to convert it to seconds.

The closest I got was to create this function:

function timeMatches(expression, date) {
  var interval = parser.parseExpression(expression);
  var data = interval._fields;
  
  if (!data.second.includes(date.getSeconds())) {
    return false;
  }
  if (!data.minute.includes(date.getMinutes())) {
    return false;
  }
  if (!data.hour.includes(date.getHours())) {
    return false;
  }
  if (!data.dayOfMonth.includes(date.getDate())) {
    return false;
  }
  if (!data.month.includes(date.getMonth() + 1)) {
    return false;
  }
  if (!data.dayOfWeek.includes(date.getDay())) {
    return false;
  }
  return true;
}

It allows me to do to:

if (timeMatches('* 20 * * * *', new Date()) {
  // This is true when minute is 20.
}

Could it be possible to add a method like this to the library? (Not saying my checks are the best, but something to validate current date against pattern).

andeersg avatar Mar 26 '19 20:03 andeersg

@andeersg there was some (similar) discussion regards implementing validation interface for such cases in #145.

The idea itself is great, but I'm not completely sure how reasonable it's to add this into core (maybe separate cron-data-validation library or something like that). I must digest this a little bit.

harrisiirak avatar Mar 29 '19 18:03 harrisiirak

Yes I'm not the one to say if this is a feature that belongs here or not. But of all the libraries I found this was the one I thought was the most fitting one, since all the others focus on running functions at cron not parsing the cron string.

But for now I can use my function, so take your time to consider it, and let me know if you need input or something.

andeersg avatar Mar 29 '19 23:03 andeersg

A couple of thoughts:

  1. As mentioned in #145, exposing _fields would make this cleaner that accessing _fields directly. Maybe just as a fields() function?
  2. A simple test() or match() function would serve this purpose--pass in an object that contains one or more props that match the array names in _fields (only those passed in are tested, which gives the caller the ability to set the level of granularity they are interested in); each can be a single number or an array of numbers; the function returns true if all of the props' values match what's in _fields. This serves the OP's use-case, and I think it's also generic enough to be a reasonable addition to the library. As the OP mentions, there is no library (that I could find, either) that has the functionality of being able to parse a cron string and compare it to a specific moment in time.

PS - For 2, an alternative might be to pass in a date and a "resolution" prop (a string value of second|minute|hour|day|month|weekday). The concept is the same, it's just a different way to specifying the "test" date and the level of granularity.

Eric24 avatar Oct 28 '19 22:10 Eric24

Here's what I ended up doing. Since you already have a dependency on moment-timezone, it's a pretty lightweight function:

function match(expression, date, scope = 'second') {
  scope = ['second', 'minute', 'hour', 'day', 'month', 'weekday'].indexOf(scope.toLowerCase());
  try {
    let data = cron.parseExpression(expression)._fields;

    if (scope <= 0 && !data.second.includes(date.second())) return false;
    if (scope <= 1 && !data.minute.includes(date.minute())) return false;
    if (scope <= 2 && !data.hour.includes(date.hour())) return false;
    if (scope <= 3 && !data.dayOfMonth.includes(date.date())) return false;
    if (scope <= 4 && !data.month.includes(date.month() + 1)) return false;
    if (scope <= 5 && !data.dayOfWeek.includes(date.day())) return false;

    return true;
  } catch (e) {
    return false;
  }
}

Eric24 avatar Feb 23 '20 18:02 Eric24

This is what I do

export function isTimeMatches(cronExpression: string, date: Date): boolean {
    const currentDate = date.valueOf()
    const parsed = cronParser.parseExpression(cronExpression, {
        // this is bug of cron-parser, if date is exact match current cronExpression,
        // current date will be missing from parsed, neither prev() nor next() would have current date
        // thus we need to push currentDate back a little
        currentDate: new Date(date).setSeconds(-1),
        tz: 'UTC',
    })
    return parsed.next().toDate().valueOf() === currentDate
}

chengB12 avatar Oct 27 '22 23:10 chengB12