node-schedule 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Master (Unreleased)
2
+
3
+ # 2.0.0 - 01 February, 2021
4
+
5
+ ### Breaking changes:
6
+
7
+ - Drop support for Node < 6
8
+ - Remove support for job objects. See `UPGRADING.md` for more details. #557
9
+
10
+ ### New features:
11
+
12
+ - Introduce compatibility with browsers and Electron #553
13
+
14
+ ### Bug fixes:
15
+
16
+ - Avoid leaking memory on one-off jobs #581
17
+ - Address anonymous job id collision problem to ensure that node-schedule could run with the unlimited uptime #582
18
+
19
+ ### Internal changes:
20
+
21
+ - Bump cron-parser from 2.18.0 to 3.1.0 #562
22
+ - Bump sinon from 1.17.7 to 9.2.4 #566
23
+ - Switch to nyc from istanbul #574
24
+ - Convert tests from nodeunit to Tape #575
25
+ - Replace var with let/const #577
26
+ - Execute airtap tests on a headless Chromium #578
27
+ - Make structure more modular #579
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  [![NPM version](http://img.shields.io/npm/v/node-schedule.svg)](https://www.npmjs.com/package/node-schedule)
4
4
  [![Downloads](https://img.shields.io/npm/dm/node-schedule.svg)](https://www.npmjs.com/package/node-schedule)
5
- [![Build Status](https://travis-ci.org/node-schedule/node-schedule.svg?branch=master)](https://travis-ci.org/node-schedule/node-schedule)
5
+ [![Build Status](https://github.com/node-schedule/node-schedule/workflows/ci/badge.svg)](https://github.com/node-schedule/node-schedule/actions)
6
+ [![Coverage Status](https://coveralls.io/repos/node-schedule/node-schedule/badge.svg?branch=master)](https://coveralls.io/r/node-schedule/node-schedule?branch=master)
6
7
  [![Join the chat at https://gitter.im/node-schedule/node-schedule](https://img.shields.io/badge/gitter-chat-green.svg)](https://gitter.im/node-schedule/node-schedule?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
8
 
8
9
  [![NPM](https://nodei.co/npm/node-schedule.png?downloads=true)](https://nodei.co/npm/node-schedule/)
@@ -12,45 +13,56 @@ It allows you to schedule jobs (arbitrary functions) for execution at
12
13
  specific dates, with optional recurrence rules. It only uses a single timer
13
14
  at any given time (rather than reevaluating upcoming jobs every second/minute).
14
15
 
15
- ## Usage
16
-
17
- ### Installation
18
-
19
- You can install using [npm](https://www.npmjs.com/package/node-schedule).
20
-
21
- ```
22
- npm install node-schedule
23
- ```
16
+ Node 6+ is supported.
24
17
 
25
- ### Overview
18
+ ## Overview
26
19
 
27
20
  Node Schedule is for time-based scheduling, not interval-based scheduling.
21
+
28
22
  While you can easily bend it to your will, if you only want to do something like
29
- "run this function every 5 minutes", you'll find `setInterval` much easier to use,
30
- and far more appropriate. But if you want to, say, "run this function at the :20
23
+ "run this function every 5 minutes", [toad-scheduler](https://github.com/kibertoad/toad-scheduler) would be a better choice. But if you want to, say, "run this function at the :20
31
24
  and :50 of every hour on the third Tuesday of every month," you'll find that
32
25
  Node Schedule suits your needs better. Additionally, Node Schedule has Windows
33
- support unlike true cron since the node runtime is now fully supported.
26
+ support, unlike true `cron`, since the node runtime is now fully supported.
34
27
 
35
28
  Note that Node Schedule is designed for in-process scheduling, i.e. scheduled jobs
36
29
  will only fire as long as your script is running, and the schedule will disappear
37
30
  when execution completes. If you need to schedule jobs that will persist even when
38
31
  your script *isn't* running, consider using actual [cron].
39
32
 
33
+ In case you need durable jobs that persist across restarts and lock system compatible with multi-node deployments,
34
+ try [agenda](https://github.com/agenda/agenda) or [bree](https://github.com/breejs/bree).
35
+
36
+ ## Usage
37
+
38
+ ### Installation
39
+
40
+ You can install using [npm](https://www.npmjs.com/package/node-schedule).
41
+
42
+ ```
43
+ npm install node-schedule
44
+ ```
45
+
46
+
40
47
  ### Jobs and Scheduling
41
48
 
42
49
  Every scheduled job in Node Schedule is represented by a `Job` object. You can
43
50
  create jobs manually, then execute the `schedule()` method to apply a schedule,
44
51
  or use the convenience function `scheduleJob()` as demonstrated below.
45
52
 
46
- `Job` objects are `EventEmitter`'s, and emit a `run` event after each execution.
47
- They also emit a `scheduled` event each time they're scheduled to run, and a
48
- `canceled` event when an invocation is canceled before it's executed (both events
49
- receive a JavaScript date object as a parameter). Note that jobs are scheduled the
50
- first time immediately, so if you create a job using the `scheduleJob()`
51
- convenience method, you'll miss the first `scheduled` event, but you can query the
52
- invocation manually (see below). Also note that `canceled` is the single-L American
53
- spelling.
53
+ `Job` objects are `EventEmitter`s, and emit the following events:
54
+ * A `run` event after each execution.
55
+ * A `scheduled` event each time they're scheduled to run.
56
+ * A `canceled` event when an invocation is canceled before it's executed.
57
+ Note that `canceled` is the single-L American spelling.
58
+ * An `error` event when a job invocation triggered by a schedule throws or returns
59
+ a rejected `Promise`.
60
+
61
+ (Both the `scheduled` and `canceled` events receive a JavaScript date object as
62
+ a parameter).
63
+ Note that jobs are scheduled the first time immediately, so if you create a job
64
+ using the `scheduleJob()` convenience method, you'll miss the first `scheduled`
65
+ event, but you can query the invocation manually (see below).
54
66
 
55
67
  ### Cron-style Scheduling
56
68
 
@@ -70,9 +82,9 @@ The cron format consists of:
70
82
  Examples with the cron format:
71
83
 
72
84
  ```js
73
- var schedule = require('node-schedule');
85
+ const schedule = require('node-schedule');
74
86
 
75
- var j = schedule.scheduleJob('42 * * * *', function(){
87
+ const job = schedule.scheduleJob('42 * * * *', function(){
76
88
  console.log('The answer to life, the universe, and everything!');
77
89
  });
78
90
  ```
@@ -82,7 +94,7 @@ Execute a cron job when the minute is 42 (e.g. 19:42, 20:42, etc.).
82
94
  And:
83
95
 
84
96
  ```js
85
- var j = schedule.scheduleJob('0 17 ? * 0,4-6', function(){
97
+ const job = schedule.scheduleJob('0 17 ? * 0,4-6', function(){
86
98
  console.log('Today is recognized by Rebecca Black!');
87
99
  });
88
100
  ```
@@ -91,16 +103,16 @@ Execute a cron job every 5 Minutes = */5 * * * *
91
103
 
92
104
  You can also get when it is scheduled to run for every invocation of the job:
93
105
  ```js
94
- var j = schedule.scheduleJob('0 1 * * *', function(fireDate){
106
+ const job = schedule.scheduleJob('0 1 * * *', function(fireDate){
95
107
  console.log('This job was supposed to run at ' + fireDate + ', but actually ran at ' + new Date());
96
108
  });
97
109
  ```
98
110
  This is useful when you need to check if there is a delay of the job invocation when the system is busy, or save a record of all invocations of a job for audit purpose.
99
111
  #### Unsupported Cron Features
100
112
 
101
- Currently, `W` (nearest weekday), `L` (last day of month/week), and `#` (nth weekday
102
- of the month) are not supported. Most other features supported by popular cron
103
- implementations should work just fine.
113
+ Currently, `W` (nearest weekday) and `L` (last day of month/week) are not supported.
114
+ Most other features supported by popular cron implementations should work just fine,
115
+ including `#` (nth weekday of the month).
104
116
 
105
117
  [cron-parser] is used to parse crontab instructions.
106
118
 
@@ -110,10 +122,10 @@ Say you very specifically want a function to execute at 5:30am on December 21, 2
110
122
  Remember - in JavaScript - 0 - January, 11 - December.
111
123
 
112
124
  ```js
113
- var schedule = require('node-schedule');
114
- var date = new Date(2012, 11, 21, 5, 30, 0);
125
+ const schedule = require('node-schedule');
126
+ const date = new Date(2012, 11, 21, 5, 30, 0);
115
127
 
116
- var j = schedule.scheduleJob(date, function(){
128
+ const job = schedule.scheduleJob(date, function(){
117
129
  console.log('The world is going to end today.');
118
130
  });
119
131
  ```
@@ -121,10 +133,10 @@ var j = schedule.scheduleJob(date, function(){
121
133
  To use current data in the future you can use binding:
122
134
 
123
135
  ```js
124
- var schedule = require('node-schedule');
125
- var date = new Date(2012, 11, 21, 5, 30, 0);
126
- var x = 'Tada!';
127
- var j = schedule.scheduleJob(date, function(y){
136
+ const schedule = require('node-schedule');
137
+ const date = new Date(2012, 11, 21, 5, 30, 0);
138
+ const x = 'Tada!';
139
+ const job = schedule.scheduleJob(date, function(y){
128
140
  console.log(y);
129
141
  }.bind(null,x));
130
142
  x = 'Changing Data';
@@ -138,12 +150,12 @@ You can build recurrence rules to specify when a job should recur. For instance,
138
150
  consider this rule, which executes the function every hour at 42 minutes after the hour:
139
151
 
140
152
  ```js
141
- var schedule = require('node-schedule');
153
+ const schedule = require('node-schedule');
142
154
 
143
- var rule = new schedule.RecurrenceRule();
155
+ const rule = new schedule.RecurrenceRule();
144
156
  rule.minute = 42;
145
157
 
146
- var j = schedule.scheduleJob(rule, function(){
158
+ const job = schedule.scheduleJob(rule, function(){
147
159
  console.log('The answer to life, the universe, and everything!');
148
160
  });
149
161
  ```
@@ -153,25 +165,41 @@ object to specify a range of start and end values, with an optional step paramet
153
165
  For instance, this will print a message on Thursday, Friday, Saturday, and Sunday at 5pm:
154
166
 
155
167
  ```js
156
- var rule = new schedule.RecurrenceRule();
168
+ const rule = new schedule.RecurrenceRule();
157
169
  rule.dayOfWeek = [0, new schedule.Range(4, 6)];
158
170
  rule.hour = 17;
159
171
  rule.minute = 0;
160
172
 
161
- var j = schedule.scheduleJob(rule, function(){
173
+ const job = schedule.scheduleJob(rule, function(){
162
174
  console.log('Today is recognized by Rebecca Black!');
163
175
  });
164
176
  ```
165
177
 
178
+ Timezones are also supported. Here is an example of executing at the start of every day in the UTC timezone.
179
+
180
+ ```js
181
+ const rule = new schedule.RecurrenceRule();
182
+ rule.hour = 0;
183
+ rule.tz = 'Etc/UTC';
184
+
185
+ const job = schedule.scheduleJob(rule, function(){
186
+ console.log('A new day has begun in the UTC timezone!');
187
+ });
188
+ ```
189
+
190
+ A list of acceptable tz (timezone) values can be found at <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
191
+
166
192
  #### RecurrenceRule properties
167
193
 
168
- - `second`
169
- - `minute`
170
- - `hour`
171
- - `date`
172
- - `month`
194
+ - `second (0-59)`
195
+ - `minute (0-59)`
196
+ - `hour (0-23)`
197
+ - `date (1-31)`
198
+ - `month (0-11)`
173
199
  - `year`
174
- - `dayOfWeek`
200
+ - `dayOfWeek (0-6) Starting with Sunday`
201
+ - `tz`
202
+
175
203
 
176
204
  > **Note**: It's worth noting that the default value of a component of a recurrence rule is
177
205
  > `null` (except for second, which is 0 for familiarity with cron). *If we did not
@@ -184,7 +212,7 @@ To make things a little easier, an object literal syntax is also supported, like
184
212
  in this example which will log a message every Sunday at 2:30pm:
185
213
 
186
214
  ```js
187
- var j = schedule.scheduleJob({hour: 14, minute: 30, dayOfWeek: 0}, function(){
215
+ const job = schedule.scheduleJob({hour: 14, minute: 30, dayOfWeek: 0}, function(){
188
216
  console.log('Time for tea!');
189
217
  });
190
218
  ```
@@ -195,20 +223,20 @@ It will run after 5 seconds and stop after 10 seconds in this example.
195
223
  The ruledat supports the above.
196
224
 
197
225
  ```js
198
- let startTime = new Date(Date.now() + 5000);
199
- let endTime = new Date(startTime.getTime() + 5000);
200
- var j = schedule.scheduleJob({ start: startTime, end: endTime, rule: '*/1 * * * * *' }, function(){
226
+ const startTime = new Date(Date.now() + 5000);
227
+ const endTime = new Date(startTime.getTime() + 5000);
228
+ const job = schedule.scheduleJob({ start: startTime, end: endTime, rule: '*/1 * * * * *' }, function(){
201
229
  console.log('Time for tea!');
202
230
  });
203
231
  ```
204
232
 
205
233
  ### Handle Jobs and Job Invocations
206
234
 
207
- There are some function to get informations for a Job and to handle the Job and
235
+ There are some function to get information for a Job and to handle the Job and
208
236
  Invocations.
209
237
 
210
238
 
211
- #### job.cancel(reshedule)
239
+ #### job.cancel(reschedule)
212
240
  You can invalidate any job with the `cancel()` method:
213
241
 
214
242
  ```js
@@ -217,7 +245,7 @@ j.cancel();
217
245
  All planned invocations will be canceled. When you set the parameter ***reschedule***
218
246
  to true then the Job is newly scheduled afterwards.
219
247
 
220
- #### job.cancelNext(reshedule)
248
+ #### job.cancelNext(reschedule)
221
249
  This method invalidates the next planned invocation or the job.
222
250
  When you set the parameter ***reschedule*** to true then the Job is newly scheduled
223
251
  afterwards.
@@ -231,8 +259,9 @@ This method returns a Date object for the planned next Invocation for this Job.
231
259
 
232
260
  ## Contributing
233
261
 
234
- This module was originally developed by [Matt Patenaude], and is now maintained by
235
- [Tejas Manohar] and [other wonderful contributors].
262
+ This module was originally developed by [Matt Patenaude] who eventually passed over maintainer's mantle over to [Tejas Manohar].
263
+
264
+ Currently it is being maintained by [Igor Savin] and [our amazing community].
236
265
 
237
266
  We'd love to get your contributions. Individuals making significant and valuable
238
267
  contributions are given commit-access to the project to contribute as they see fit.
@@ -250,7 +279,8 @@ Licensed under the **[MIT License] [license]**.
250
279
  [Contributing]: https://github.com/node-schedule/node-schedule/blob/master/CONTRIBUTING.md
251
280
  [Matt Patenaude]: https://github.com/mattpat
252
281
  [Tejas Manohar]: http://tejas.io
282
+ [Igor Savin]: https://twitter.com/kibertoad
253
283
  [license]: https://github.com/node-schedule/node-schedule/blob/master/LICENSE
254
284
  [Tejas Manohar]: https://github.com/tejasmanohar
255
- [other wonderful contributors]: https://github.com/node-schedule/node-schedule/graphs/contributors
285
+ [our amazing community]: https://github.com/node-schedule/node-schedule/graphs/contributors
256
286
  [cron-parser]: https://github.com/harrisiirak/cron-parser
package/UPGRADING.md ADDED
@@ -0,0 +1,20 @@
1
+ ## Upgrading to new node-schedule versions
2
+
3
+ ### Upgrading to version 2.0.0+
4
+
5
+ * Node.js versions older than 6 are no longer supported, please update your environment before upgrading.
6
+ * In order to prevent memory leaks, one-off jobs (targeted to be executed at an exact date) cannot be rescheduled by name, as reference to them is no longer stored indefinitely. If you want to keep rescheduling them, make sure to store reference to the initial job.
7
+ * The `scheduleJob()` method no longer supports passing an object with the job method. If you were using an object, pass the job method directly.
8
+
9
+ E.g. code that previously looked like this:
10
+ ```javascript
11
+ const obj = {
12
+ execute() {}
13
+ };
14
+ Scheduler.scheduleJob(obj);
15
+ ```
16
+ should be changed to something like this:
17
+ ```javascript
18
+ function execute() {}
19
+ Scheduler.scheduleJob(execute);
20
+ ```
package/index.js ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { cancelJob, rescheduleJob, scheduledJobs, scheduleJob} = require('./lib/schedule')
4
+ const { Invocation, RecurrenceRule, Range} = require('./lib/Invocation')
5
+ const { Job } = require('./lib/Job')
6
+
7
+ module.exports = {
8
+ Job,
9
+ Invocation,
10
+ Range,
11
+ RecurrenceRule,
12
+ cancelJob,
13
+ rescheduleJob,
14
+ scheduledJobs,
15
+ scheduleJob
16
+ }
@@ -0,0 +1,339 @@
1
+ 'use strict';
2
+
3
+ const lt = require('long-timeout')
4
+ const CronDate = require('cron-parser/lib/date')
5
+ const sorted = require('sorted-array-functions')
6
+
7
+ const invocations = [];
8
+ let currentInvocation = null;
9
+
10
+ /* DoesntRecur rule */
11
+ const DoesntRecur = new RecurrenceRule();
12
+ DoesntRecur.recurs = false;
13
+
14
+ /* Invocation object */
15
+ function Invocation(job, fireDate, recurrenceRule, endDate) {
16
+ this.job = job;
17
+ this.fireDate = fireDate;
18
+ this.endDate = endDate;
19
+ this.recurrenceRule = recurrenceRule || DoesntRecur;
20
+
21
+ this.timerID = null;
22
+ }
23
+
24
+ function sorter(a, b) {
25
+ return (a.fireDate.getTime() - b.fireDate.getTime());
26
+ }
27
+
28
+ /* Range object */
29
+ function Range(start, end, step) {
30
+ this.start = start || 0;
31
+ this.end = end || 60;
32
+ this.step = step || 1;
33
+ }
34
+
35
+ Range.prototype.contains = function(val) {
36
+ if (this.step === null || this.step === 1) {
37
+ return (val >= this.start && val <= this.end);
38
+ } else {
39
+ for (let i = this.start; i < this.end; i += this.step) {
40
+ if (i === val) {
41
+ return true;
42
+ }
43
+ }
44
+
45
+ return false;
46
+ }
47
+ };
48
+
49
+ /* RecurrenceRule object */
50
+ /*
51
+ Interpreting each property:
52
+ null - any value is valid
53
+ number - fixed value
54
+ Range - value must fall in range
55
+ array - value must validate against any item in list
56
+
57
+ NOTE: Cron months are 1-based, but RecurrenceRule months are 0-based.
58
+ */
59
+ function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
60
+ this.recurs = true;
61
+
62
+ this.year = (year == null) ? null : year;
63
+ this.month = (month == null) ? null : month;
64
+ this.date = (date == null) ? null : date;
65
+ this.dayOfWeek = (dayOfWeek == null) ? null : dayOfWeek;
66
+ this.hour = (hour == null) ? null : hour;
67
+ this.minute = (minute == null) ? null : minute;
68
+ this.second = (second == null) ? 0 : second;
69
+ }
70
+
71
+ RecurrenceRule.prototype.isValid = function() {
72
+ function isValidType(num) {
73
+ if (Array.isArray(num) || (num instanceof Array)) {
74
+ return num.every(function(e) {
75
+ return isValidType(e);
76
+ });
77
+ }
78
+ return !(Number.isNaN(Number(num)) && !(num instanceof Range));
79
+ }
80
+ if (this.month !== null && (this.month < 0 || this.month > 11 || !isValidType(this.month))) {
81
+ return false;
82
+ }
83
+ if (this.dayOfWeek !== null && (this.dayOfWeek < 0 || this.dayOfWeek > 6 || !isValidType(this.dayOfWeek))) {
84
+ return false;
85
+ }
86
+ if (this.hour !== null && (this.hour < 0 || this.hour > 23 || !isValidType(this.hour))) {
87
+ return false;
88
+ }
89
+ if (this.minute !== null && (this.minute < 0 || this.minute > 59 || !isValidType(this.minute))) {
90
+ return false;
91
+ }
92
+ if (this.second !== null && (this.second < 0 || this.second > 59 || !isValidType(this.second))) {
93
+ return false;
94
+ }
95
+ if (this.date !== null) {
96
+ if(!isValidType(this.date)) {
97
+ return false;
98
+ }
99
+ switch (this.month) {
100
+ case 3:
101
+ case 5:
102
+ case 8:
103
+ case 10:
104
+ if (this.date < 1 || this. date > 30) {
105
+ return false;
106
+ }
107
+ break;
108
+ case 1:
109
+ if (this.date < 1 || this. date > 29) {
110
+ return false;
111
+ }
112
+ break;
113
+ default:
114
+ if (this.date < 1 || this. date > 31) {
115
+ return false;
116
+ }
117
+ }
118
+ }
119
+ return true;
120
+ };
121
+
122
+ RecurrenceRule.prototype.nextInvocationDate = function(base) {
123
+ const next = this._nextInvocationDate(base);
124
+ return next ? next.toDate() : null;
125
+ };
126
+
127
+ RecurrenceRule.prototype._nextInvocationDate = function(base) {
128
+ base = ((base instanceof CronDate) || (base instanceof Date)) ? base : (new Date());
129
+ if (!this.recurs) {
130
+ return null;
131
+ }
132
+
133
+ if(!this.isValid()) {
134
+ return null;
135
+ }
136
+
137
+ const now = new CronDate(Date.now(), this.tz);
138
+ let fullYear = now.getFullYear();
139
+ if ((this.year !== null) &&
140
+ (typeof this.year == 'number') &&
141
+ (this.year < fullYear)) {
142
+ return null;
143
+ }
144
+
145
+ let next = new CronDate(base.getTime(), this.tz);
146
+ next.addSecond();
147
+
148
+ while (true) {
149
+ if (this.year !== null) {
150
+ fullYear = next.getFullYear();
151
+ if ((typeof this.year == 'number') && (this.year < fullYear)) {
152
+ next = null;
153
+ break;
154
+ }
155
+
156
+ if (!recurMatch(fullYear, this.year)) {
157
+ next.addYear();
158
+ next.setMonth(0);
159
+ next.setDate(1);
160
+ next.setHours(0);
161
+ next.setMinutes(0);
162
+ next.setSeconds(0);
163
+ continue;
164
+ }
165
+ }
166
+ if (this.month != null && !recurMatch(next.getMonth(), this.month)) {
167
+ next.addMonth();
168
+ continue;
169
+ }
170
+ if (this.date != null && !recurMatch(next.getDate(), this.date)) {
171
+ next.addDay();
172
+ continue;
173
+ }
174
+ if (this.dayOfWeek != null && !recurMatch(next.getDay(), this.dayOfWeek)) {
175
+ next.addDay();
176
+ continue;
177
+ }
178
+ if (this.hour != null && !recurMatch(next.getHours(), this.hour)) {
179
+ next.addHour();
180
+ continue;
181
+ }
182
+ if (this.minute != null && !recurMatch(next.getMinutes(), this.minute)) {
183
+ next.addMinute();
184
+ continue;
185
+ }
186
+ if (this.second != null && !recurMatch(next.getSeconds(), this.second)) {
187
+ next.addSecond();
188
+ continue;
189
+ }
190
+
191
+ break;
192
+ }
193
+
194
+ return next;
195
+ };
196
+
197
+ function recurMatch(val, matcher) {
198
+ if (matcher == null) {
199
+ return true;
200
+ }
201
+
202
+ if (typeof matcher === 'number') {
203
+ return (val === matcher);
204
+ } else if(typeof matcher === 'string') {
205
+ return (val === Number(matcher));
206
+ } else if (matcher instanceof Range) {
207
+ return matcher.contains(val);
208
+ } else if (Array.isArray(matcher) || (matcher instanceof Array)) {
209
+ for (let i = 0; i < matcher.length; i++) {
210
+ if (recurMatch(val, matcher[i])) {
211
+ return true;
212
+ }
213
+ }
214
+ }
215
+
216
+ return false;
217
+ }
218
+
219
+ /* Date-based scheduler */
220
+ function runOnDate(date, job) {
221
+ const now = Date.now();
222
+ const then = date.getTime();
223
+
224
+ return lt.setTimeout(function() {
225
+ if (then > Date.now())
226
+ runOnDate(date, job);
227
+ else
228
+ job();
229
+ }, (then < now ? 0 : then - now));
230
+ }
231
+
232
+ function scheduleInvocation(invocation) {
233
+ sorted.add(invocations, invocation, sorter);
234
+ prepareNextInvocation();
235
+ const date = invocation.fireDate instanceof CronDate ? invocation.fireDate.toDate() : invocation.fireDate;
236
+ invocation.job.emit('scheduled', date);
237
+ }
238
+
239
+ function prepareNextInvocation() {
240
+ if (invocations.length > 0 && currentInvocation !== invocations[0]) {
241
+ if (currentInvocation !== null) {
242
+ lt.clearTimeout(currentInvocation.timerID);
243
+ currentInvocation.timerID = null;
244
+ currentInvocation = null;
245
+ }
246
+
247
+ currentInvocation = invocations[0];
248
+
249
+ const job = currentInvocation.job;
250
+ const cinv = currentInvocation;
251
+ currentInvocation.timerID = runOnDate(currentInvocation.fireDate, function() {
252
+ currentInvocationFinished();
253
+
254
+ if (job.callback) {
255
+ job.callback();
256
+ }
257
+
258
+ if (cinv.recurrenceRule.recurs || cinv.recurrenceRule._endDate === null) {
259
+ const inv = scheduleNextRecurrence(cinv.recurrenceRule, cinv.job, cinv.fireDate, cinv.endDate);
260
+ if (inv !== null) {
261
+ inv.job.trackInvocation(inv);
262
+ }
263
+ }
264
+
265
+ job.stopTrackingInvocation(cinv);
266
+
267
+ try {
268
+ const result = job.invoke(cinv.fireDate instanceof CronDate ? cinv.fireDate.toDate() : cinv.fireDate);
269
+ job.emit('run');
270
+
271
+ if (result instanceof Promise) {
272
+ result.catch(function (err) {
273
+ job.emit('error', err);
274
+ });
275
+ }
276
+ } catch (err) {
277
+ job.emit('error', err);
278
+ }
279
+
280
+ if (job.isOneTimeJob) {
281
+ job.deleteFromSchedule();
282
+ }
283
+ });
284
+ }
285
+ }
286
+
287
+ function currentInvocationFinished() {
288
+ invocations.shift();
289
+ currentInvocation = null;
290
+ prepareNextInvocation();
291
+ }
292
+
293
+ function cancelInvocation(invocation) {
294
+ const idx = invocations.indexOf(invocation);
295
+ if (idx > -1) {
296
+ invocations.splice(idx, 1);
297
+ if (invocation.timerID !== null) {
298
+ lt.clearTimeout(invocation.timerID);
299
+ }
300
+
301
+ if (currentInvocation === invocation) {
302
+ currentInvocation = null;
303
+ }
304
+
305
+ invocation.job.emit('canceled', invocation.fireDate);
306
+ prepareNextInvocation();
307
+ }
308
+ }
309
+
310
+ /* Recurrence scheduler */
311
+ function scheduleNextRecurrence(rule, job, prevDate, endDate) {
312
+
313
+ prevDate = (prevDate instanceof CronDate) ? prevDate : new CronDate();
314
+
315
+ const date = (rule instanceof RecurrenceRule) ? rule._nextInvocationDate(prevDate) : rule.next();
316
+ if (date === null) {
317
+ return null;
318
+ }
319
+
320
+ if ((endDate instanceof CronDate) && date.getTime() > endDate.getTime()) {
321
+ return null;
322
+ }
323
+
324
+ const inv = new Invocation(job, date, rule, endDate);
325
+ scheduleInvocation(inv);
326
+
327
+ return inv;
328
+ }
329
+
330
+ module.exports = {
331
+ Range,
332
+ RecurrenceRule,
333
+ Invocation,
334
+ cancelInvocation,
335
+ scheduleInvocation,
336
+ scheduleNextRecurrence,
337
+ sorter,
338
+ _invocations: invocations
339
+ }