Détail du package

quartzcron

fedeghe30.4kMIT0.1.0

version: 0.1.0 CHANGELOG

cron string builder, quartz cron string builder, quartz cron, quartz crontab

readme

Coverage Status NPM License CircleCI

GitHub top language Static Badge

track

quartzcron

version: 0.1.0
CHANGELOG

Quartz scheduler offers way more flexibility compared to traditional cron tool.
That additional freedom reflects into less trivial composition for the cron strings, this library aims to

  • help to programmatically create cron expressions
  • validate cron expressions
  • get the n next precise occurrences

A quartz cron expression is characterized by the following structure:

s  i  h dom m dow y  
⎜  ⎜  ⎜  ⎜  ⎜  ⎜  ⎜  
'--+--+--+--+--+--+--> 1st: seconds
   '--+--+--+--+--+--> 2nd: minutes
      '--+--+--+--+--> 3rd: hours
         '--+--+--+--> 4th: days of month ---\
            '--+--+--> 5th: months           | mutually exclusive
               '--+--> 6th: days of week  ---/
                  '--> 7th: years

sample usage

const QuartzCron = require('quartzcron'),
    qct = new QuartzCron();
let exp = qct.out(); // ->  0 0 0 * * ? *
// thus the default is
// at midnight of everyday
// but default values can be changed when calling the constructor
qct.atHour(12)
    .atHourAdd(22)
    .onLastMonthDay()
    .everyNYears(5, 2025);

exp = qct.out(); //-> 0 0 12,22 L * ? 2025/5
/*
alternatively the cron expression is also returned
as the instance _toString_ invokation
*/

const next = qct.next({
    date: new Date('00:00:00 01-01-2024'),
    n: 3
})
/*
[
  2025-01-31T12:00:00.000Z,
  2025-01-31T22:00:00.000Z,
  2025-02-28T12:00:00.000Z
]
*/

API

constructor 👷🏽‍♂️

Constructor can handle:

  • 0 parameters
    default used 0 0 0 * * ? *
      const qc = new Quartzcron();
    
  • 1 valid expression string
    when invalid throws and exception
      const qc = new Quartzcron('0 0 12,22 L * ? 2025/5');
    
  • 1 object corresponding to a valid expression
      const exp = {
          s:0, i: 0, h: 0,
          dom:'*', m:'*', dow: '?', y: '*'
      }
      const qc = new Quartzcron(exp);
    
    throws an exception when the resulting expression is not valid.

get the quartz cron expression 🧊

Invoke out() ƒunction on the quartzcron instance to get the related expression

qct.out(); // -> "0 0 12,22 L * ? 2025/5"

validation API ✅

Validation can be done on the quartzcron instance just invoking the validate ƒunction. Pass the string to be evaluated as parameter.

When nothing is passed it will validate the expression it would get from out (as useful as expect(true).toBe(true)).

qct.validate('0 0 12,22 L * ? 2025/5')
// -> { valid: true, errors:[]}

returning an object shaped like follows:

{ valid: Boolean, errors:[String]}

Alternatively a static method is available:

QuartzCron.validate(yourExp)
// -> { valid: ?, errors:[?]}

composition API 🧱

Almost all 7 fields composing the final cron expression are independent.
The only exception is represented by the "days of month" (4th field) and the "days of week" (6th field) cause they cannot coexsist.
Within the involved months/years:

  • dom sets target days referencing the month
  • dow sets target days referencing the week

One of the two must hold something valid different from ?
and the other one must just contain ?.

week days and month names aliases

For weeekdays one can use seamlessly:

[1,2,3,4,5,6,7]
// OR 
['SUN','MON','TUE','WED',
    'THU', 'FRI', 'SAT']

similarly for months:

[1,2,3,4,5,6,7,8,9,10,11,12]
// OR 
['JAN','FEB','MAR','APR',
 'MAY','JUN','JUL','AUG',
 'SEP','OCT','NOV','DEC']`.

seconds ⏱️

  • everySecond()
    no explanation needed; still one should consider that this command will only update the s to *.
    Which minute/s will actually be part of the target depends on how the instance was constructed.
    If no other command is executed the target will be from 0-th to 59-th second of the first minutes of the first hour of the following day and this is cause the default values are 0 0 0 * * ? *.
    This clearly applies similarly also for almost all other commands.

  • everyNSeconds(x, start = 0)
    every x seconds (starting from start)

  • atSecond(sec, cad = false)
    overrides any previous value set there;
    when cadence is not passed can be called passing multiple comma separated values within [0, 59]

  • atSecondAdd(sec, cad = false)
    additive version of the previous setter.

  • betweenSeconds(from, to, every)
    all seconds from from to to seconds; optionally set the cadence passing an every integer.

  • betweenSecondsAdd(from, to, every)
    additive version of the previous setter.

minutes ⏱️

  • everyMinute()
    no explanation needed

  • everyNMinutes(x, start = 0)
    every x minutes (starting from start)

  • atMinute(min, cad = false)
    overrides any previous value set there;
    when cadence is not passed can be called passing multiple comma separated values within [0, 59]

  • atMinuteAdd(min, cad = false)
    additive version of the previous setter.

  • betweenMinutes(from, to, every)
    all minutes from from to to minutes; optionally set the cadence passing an every integer.

  • betweenMinutesAdd(from, to, every)
    additive version of the previous setter.

hours ⏱️

  • everyHour()
    no explanation needed

  • everyNHours(x, start = 0)
    every x hours (starting from start)

  • atHour(h, cad = false)
    no explanation needed;
    overrides any previous value set there;
    when cadence is not passed can be called passing multiple comma separated values within [0, 23]

  • atHourAdd(h, cad = false)
    adds h to the list of already set hours (0 there by default); as in the previous can pass multiple values comma separated (when cadence is not passed).

  • betweenHours(from, to, every)
    all hours from from to to hours; optionally set the cadence passing an every integer.

  • betweenHoursAdd(from, to, every)
    additive version of the previous setter.

day of month / day of week 📆

  • everyDay()
    no explanation needed

      qct.everyDay()
      // { dom: '*', dow:'?', ...}
    
  • everyNDays(x, y)
    every x [1-31][1-31] days starting from yth day [1-31] of the target months.

      qct.everyNDays('13, 10) 
      // { dom: `10/13`, dow: '?', ...}
    
  • atWeekDay(wd, cad = false)
    overrides any previous value set there; when cadence is not passed even here more than one comma separated value can be passed.

      qtc.atWeekDay(4)
      // { dom: `?`, dow: 4, ...}
      qtc.atWeekDay(4, 3)
      // { dom: `?`, dow: '4/3', ...}
      qtc.atWeekDay('4,5')
      // { dom: `?`, dow: '4,5', ...}
      qtc.atWeekDay('2-5', 2) // or qtc.atWeekDay('2-5/2')
      // { dom: '?`, dow: '2-5/2', ...}
    
  • atWeekDayAdd(wd, cad = false)
    every wd in [1,7] or (...and corresponding to) {SUN,MON,TUE,WED,THU,FRI,SAT}; adds one more weekday in the current (default empty) list.

      qtc.atWeekDayAdd('MON')
      // { dom: `?`, dow: 'MON', ...}
      qtc.atWeekDayAdd('WED', 2)
      // { dom: `?`, dow: 'MON,WED/2', ...}
      qtc.atWeekDayAdd('FRI-SAT')
      // { dom: '?`, dow: 'MON,WED/2,FRI-SAT', ...}
    
  • everyWeekDay()
    shortcut to set Saturnday and Sunday

      qtc.everyWeekDay()
      // { dom: `?`, dow: '2-6', ...}
    
  • everyWeekEnd()
    shortcut to set Saturnday and Sunday

      qtc.everyWeekEnd()
      // { dom: `?`, dow: '7-1', ...}
    
  • atMonthDay(dom, cad = false)
    sets the target day of month, can be:

    • *: all days
    • n: with n in [1,31]
    • n,m,...: comma separated values all in [1,31]
    • n/c: every c starting from n
    • n-m: from n to m (in [1,31])
    • n-m/c: from n to m (in [1,31]) with c cadence for the last two examples there's also an on purpose method named betweenMonthDays
      qtc.atMonthDay('*') //same as qtc.everyDay()
      // { dom: '*', dow: '?', ...} 
      qtc.atMonthDay(10)
      // { dom: '10', dow: '?', ...} 
      qtc.atMonthDay('10,20')
      // { dom: '10,20', dow: '?', ...} 
      qtc.atMonthDay('10-20')
      // { dom: '10-20', dow: '?', ...} 
      qtc.atMonthDay('10-20/2')
      // { dom: '10-20/2', dow: '?', ...} 
      qtc.atMonthDay('10/2')
      // { dom: '10/2', dow: '?', ...}
      
  • atMonthDayAdd(dom, cad = false)
    allows to add one or more days to the existing target

      qtc.atMonthDayAdd('10')
      // { dom: '10', dow: '?', ...} 
      qtc.atMonthDayAdd('13/3')
      // { dom: '10,13/3', dow: '?', ...} 
      qtc.atMonthDayAdd('23')
      // { dom: '10,13/3,23', dow: '?', ...}
    
  • betweenMonthDays(from, to, every)
    set target days from from to to with, if passed > 1, a cadence bigger than 1

      qtc.betweenMonthDays(10, 20) 
      // { dom: '10-20', dow: '?', ...} 
      qtc.betweenMonthDays(10, 20, 2) 
      // { dom: '10-20/2', dow: '?', ...}
    
  • onLastMonthDay
    set the target day to the last day of the target months

      qtc.onLastMonthDay()  // { dom: 'L', dow: '?', ...}
    
  • onFirstMonthWeekDay
    set as target day the first weekday of the month (working day)
    (same as qtc.onClosestWorkingDayToTheNMonthDay(1))

      qtc.onFirstMonthWeekDay() // { dom: '1W', dow: '?', ...}
    
  • onLastMonthWeekDay
    set as target day the last weekday of the month (working day)

      qtc.onLastMonthWeekDay() // { dom: 'LW', dow: '?', ...}
    
  • onLastMonthNWeekDay(x)
    set as target day the last selected week day of the month

      qtc.onLastMonthNWeekDay(2)
      // last monday of the month 
      // { dom: '?', dow: '2L', ...}
    
  • onNDayBeforeTheEndOfTheMonth(n)
    set as target the X-th day before the end of the month

      qtc.onNDayBeforeTheEndOfTheMonth(9)
      // nine days before the end of the month 
      // { dom: 'L-9', dow: '?', ...}
    
  • onClosestWorkingDayToTheNMonthDay(x)
    set as target the nearest weekday (working day) to the x-th day of the month

      qtc.onClosestWorkingDayToTheNMonthDay(15)
      // the closest woring day (mon->fri) to the 15th 
      // { dom: '15W', dow: '?', ...}
    
  • onNWeekDayOfTheMonth(n, wd)
    set as target the n-th week day of the month

      qtc.onNWeekDayOfTheMonth(4, 2)
      // the 4th tuesday 
      // { dom: '?', dow: '2#4', ...}
    

months 📆

  • everyMonth()
    no explanation needed

  • everyNMonths(freq, start)
    every freq months (starting from start)

  • atMonth(m, cad = false)
    overrides any previous value set there;
    when cadence is not passed can be called passing multiple comma separated values within [1, 12] or [JAN -> DEC]

  • atMonthAdd(m, cad = false)
    adds m to the list of already set months; as in the previous can pass multiple values comma separated (when cadence is not passed).

  • betweenMonths(from, to, every)
    all months from from month to to month; optionally set the cadence passing an every integer.

years 📆

  • everyYear()
    no explanation needed

  • everyNYears(freq, start)
    every x years (starting from start)

  • atYear(y, cad = false)
    overrides any previous value set there;
    when cadence is not passed can be called passing multiple comma separated values within [1970, 2099]

  • atYearAdd(y, cad = false)
    adds min to the list of already set minutes; as in the previous can pass multiple values comma separated (when cadence is not passed), within [1970, 2099]

  • betweenYears(from, to, every)
    all years from from year to to year; optionally set the cadence passing an every integer.

more ➕

This library was primarily designed to achieve a quite simple task: compose the expression through methods; then validation, occurrences, ..
The occurrences composition depends 100% on two data: 1) the current expression 2) the fererence date

thus, calculate the occurrences of an already existing expression (for example given back from and endpoint) one could override manually one or more elements in the expression s,i,h,dom,m,dow,y

 inst.elements.s = 1

OR use

  • updateExp(exp)
    updates the current instance expression, handles

    • an expression string
      when invalid throws and exception
    • an object corresponding to an expression
        {
            s:0, i: 0, h: 0,
            dom:'*', m:'*', dow: '?', y: '*'
        }
      
      throws an exception when the resulting expression is not valid

range resolvers 🛸

Range resolvers might be useful, initially only part of an internal utility, now are exposed in a static object.

qct.out()// -> 45/2 1,2,3 15-19 L 1/2 ? *
Quartzcron.solvers.solve_0_59_ranges(qtc.elements.s)
// -> [45,47,49,51,53,55,57,59]
Quartzcron.solvers.solve_0_59_ranges(qtc.elements.i)
// -> [1,2,3]
Quartzcron.solvers.solve_hours_ranges(qtc.elements.h)
// -> [15,16,17,18,19]
Quartzcron.solvers.solve_dom(2025, 2, qtc.elements.dom)
// -> [28]
Quartzcron.solvers.solve_month_ranges(qtc.elements.m)
// [1,3,5,7,9,11]
Quartzcron.solvers.solve_dow(2025, 2, qtc.elements.dow)
// -> []
Quartzcron.solvers.solve_year_ranges(qtc.elements.y)
// -> [2025, ..., 2099] // here supposing we are in 2025

Clearly enough the two about dom and dow are function since they strictly depend on the year and the month.
Solvers come quite in hand when from an expression one needs the actual targets.

Months and week days will be always resolved numerically, thus [1,12] and [1,7] respectively.

Occurrences 🔭

From an instance call the next ƒunction:

const nextOccurrence = qct.next().map(s=>s.toString())
// ["Mon Jan 01 2024 02:00:00 GMT+0100"],

this ƒunction accepts three options:

  • n: the number of occurrences needed (1 is the default)
  • date: a reference date (js Date object) to be used as present date (default is the current date). No dates before the present date will be returned.



🩼 Limitations and plans 💡

⏱️ Timezones 🗺️

If the plan to use that utility on a browser the chances the client and server run on different timezones is quite high.

A workaround would be so set the timezone to UTC on the server and in the UI explicitly inform the user that all dates & times are UTC.

For the moment this library relies 100% on UTC

The plan is to provide 2 static methods to allow to set the timezones for the client and the server.

QuartzCron.useClientLocalTimezone() // auto (e.g -2)
// or useClientUTCTimezone()

QuartzCron.setServerTimezone("America/Los_Angeles"); // +6
// or setServerUTCTimezone()

Descriptions 📜

Having a quick way to get a user readable internationalized description out of an expression is quite useful.
Meanwhile give a try to the awesome cronstrue npm package.