Skip to content

Commit 47e64e6

Browse files
committed
feat!: UNIX standard alignments (kelektiv#667)
1 parent 7471e95 commit 47e64e6

File tree

3 files changed

+136
-46
lines changed

3 files changed

+136
-46
lines changed

README.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ Cron is a tool that allows you to execute _something_ on a schedule. This is typ
2727
npm install cron
2828
```
2929

30+
## Migrating from v2 to v3
31+
32+
In version 3 of this library, we aligned our format for the cron patterns with the UNIX format. See below for the changes you need to make when upgrading:
33+
34+
<details>
35+
<summary>Migrating from v2 to v3</summary>
36+
37+
### Month & day-of-week indexing changes
38+
39+
**Month indexing went from `0-11` to `1-12`. So you need to increment all numeric months by 1.**
40+
41+
For day-of-week indexing, we only added support for `7` as Sunday, so you don't need to change anything !
42+
43+
</details>
44+
3045
## Versions and Backwards compatibility breaks
3146

3247
As goes with semver, breaking backwards compatibility should be explicit in the versioning of your library. As such, we'll upgrade the version of this module in accordance with breaking changes (We're not always great about doing it this way so if you notice that there are breaking changes that haven't been bumped appropriately please let us know).
@@ -65,14 +80,21 @@ There are tools that help when constructing your cronjobs. You might find someth
6580

6681
### Cron Ranges
6782

68-
When specifying your cron values you'll need to make sure that your values fall within the ranges. For instance, some cron's use a 0-7 range for the day of week where both 0 and 7 represent Sunday. We do not. And that is an optimisation.
83+
This library follows the [UNIX Cron format](https://man7.org/linux/man-pages/man5/crontab.5.html), with an added field at the beginning for second granularity.
84+
85+
```
86+
field allowed values
87+
----- --------------
88+
second 0-59
89+
minute 0-59
90+
hour 0-23
91+
day of month 1-31
92+
month 1-12 (or names, see below)
93+
day of week 0-7 (0 or 7 is Sunday, or use names)
94+
```
6995

70-
- Seconds: 0-59
71-
- Minutes: 0-59
72-
- Hours: 0-23
73-
- Day of Month: 1-31
74-
- Months: 0-11 (Jan-Dec) <-- currently different from Unix `cron`!
75-
- Day of Week: 0-6 (Sun-Sat)
96+
> Names can also be used for the 'month' and 'day of week' fields. Use the first three letters of the particular day or month (case does not matter). Ranges and lists of names are allowed.
97+
> Examples: "mon,wed,fri", "jan-mar".
7698
7799
## Gotchas
78100

lib/time.js

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ const CONSTRAINTS = [
33
[0, 59],
44
[0, 23],
55
[1, 31],
6-
[0, 11],
7-
[0, 6]
6+
[1, 12],
7+
[0, 7]
88
];
99
const MONTH_CONSTRAINTS = [
1010
31,
@@ -22,18 +22,18 @@ const MONTH_CONSTRAINTS = [
2222
];
2323
const PARSE_DEFAULTS = ['0', '*', '*', '*', '*', '*'];
2424
const ALIASES = {
25-
jan: 0,
26-
feb: 1,
27-
mar: 2,
28-
apr: 3,
29-
may: 4,
30-
jun: 5,
31-
jul: 6,
32-
aug: 7,
33-
sep: 8,
34-
oct: 9,
35-
nov: 10,
36-
dec: 11,
25+
jan: 1,
26+
feb: 2,
27+
mar: 3,
28+
apr: 4,
29+
may: 5,
30+
jun: 6,
31+
jul: 7,
32+
aug: 8,
33+
sep: 9,
34+
oct: 10,
35+
nov: 11,
36+
dec: 12,
3737
sun: 0,
3838
mon: 1,
3939
tue: 2,
@@ -42,17 +42,18 @@ const ALIASES = {
4242
fri: 5,
4343
sat: 6
4444
};
45-
const TIME_UNITS = [
46-
'second',
47-
'minute',
48-
'hour',
49-
'dayOfMonth',
50-
'month',
51-
'dayOfWeek'
52-
];
45+
const TIME_UNITS_MAP = {
46+
SECOND: 'second',
47+
MINUTE: 'minute',
48+
HOUR: 'hour',
49+
DAY_OF_MONTH: 'dayOfMonth',
50+
MONTH: 'month',
51+
DAY_OF_WEEK: 'dayOfWeek'
52+
};
53+
const TIME_UNITS = Object.values(TIME_UNITS_MAP);
5354
const TIME_UNITS_LEN = TIME_UNITS.length;
5455
const PRESETS = {
55-
'@yearly': '0 0 0 1 0 *',
56+
'@yearly': '0 0 0 1 1 *',
5657
'@monthly': '0 0 0 1 * *',
5758
'@weekly': '0 0 0 * * 0',
5859
'@daily': '0 0 0 * * *',
@@ -112,7 +113,7 @@ function CronTime(luxon) {
112113
let lastWrongMonth = NaN;
113114
for (let i = 0; i < months.length; i++) {
114115
const m = months[i];
115-
const con = MONTH_CONSTRAINTS[parseInt(m, 10)];
116+
const con = MONTH_CONSTRAINTS[parseInt(m, 10) - 1];
116117

117118
for (let j = 0; j < dom.length; j++) {
118119
const day = dom[j];
@@ -130,7 +131,7 @@ function CronTime(luxon) {
130131

131132
// infinite loop detected (dayOfMonth is not found in all months)
132133
if (!ok) {
133-
const notOkCon = MONTH_CONSTRAINTS[parseInt(lastWrongMonth, 10)];
134+
const notOkCon = MONTH_CONSTRAINTS[parseInt(lastWrongMonth, 10) - 1];
134135
for (let k = 0; k < dom.length; k++) {
135136
const notOkDay = dom[k];
136137
if (notOkDay > notOkCon) {
@@ -278,7 +279,7 @@ function CronTime(luxon) {
278279
}
279280

280281
if (
281-
!(date.month - 1 in this.month) &&
282+
!(date.month in this.month) &&
282283
Object.keys(this.month).length !== 12
283284
) {
284285
date = date.plus({ months: 1 });
@@ -471,7 +472,7 @@ function CronTime(luxon) {
471472
const beforeJumpingPoint = afterJumpingPoint.minus({ second: 1 });
472473

473474
if (
474-
date.month in this.month &&
475+
date.month + 1 in this.month &&
475476
date.day in this.dayOfMonth &&
476477
date.getWeekDay() in this.dayOfWeek
477478
) {
@@ -671,8 +672,13 @@ function CronTime(luxon) {
671672

672673
_hasAll: function (type) {
673674
const constraints = CONSTRAINTS[TIME_UNITS.indexOf(type)];
675+
const low = constraints[0];
676+
const high =
677+
type === TIME_UNITS_MAP.DAY_OF_WEEK
678+
? constraints[1] - 1
679+
: constraints[1];
674680

675-
for (let i = constraints[0], n = constraints[1]; i < n; i++) {
681+
for (let i = low, n = high; i < n; i++) {
676682
if (!(i in this[type])) {
677683
return false;
678684
}
@@ -803,6 +809,13 @@ function CronTime(luxon) {
803809
typeObj[pointer] = true; // mutates the field objects values inside CronTime
804810
pointer += step;
805811
} while (pointer <= upper);
812+
813+
// merge day 7 into day 0 (both Sunday), and remove day 7
814+
// since we work with day-of-week 0-6 under the hood
815+
if (type === 'dayOfWeek') {
816+
if (!typeObj[0] && !!typeObj[7]) typeObj[0] = typeObj[7];
817+
delete typeObj[7];
818+
}
806819
});
807820
} else {
808821
throw new Error(`Field (${type}) cannot be parsed`);

tests/crontime.test.js

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ describe('crontime', () => {
6464
}).not.toThrow();
6565
});
6666

67-
it('should test all hyphens (0-10 0-10 1-10 1-10 0-6 0-1)', () => {
67+
it('should test all hyphens (0-10 0-10 1-10 1-10 1-7 0-1)', () => {
6868
expect(() => {
69-
new cron.CronTime('0-10 0-10 1-10 1-10 0-6 0-1');
69+
new cron.CronTime('0-10 0-10 1-10 1-10 1-7 0-1');
7070
}).not.toThrow();
7171
});
7272

@@ -82,9 +82,9 @@ describe('crontime', () => {
8282
}).not.toThrow();
8383
});
8484

85-
it('should test all commas (0,10 0,10 1,10 1,10 0,6 0,1)', () => {
85+
it('should test all commas (0,10 0,10 1,10 1,10 1,7 0,1)', () => {
8686
expect(() => {
87-
new cron.CronTime('0,10 0,10 1,10 1,10 0,6 0,1');
87+
new cron.CronTime('0,10 0,10 1,10 1,10 1,7 0,1');
8888
}).not.toThrow();
8989
});
9090

@@ -118,6 +118,12 @@ describe('crontime', () => {
118118
}).toThrow();
119119
});
120120

121+
it('should be case-insensitive for aliases (* * * * JAN,FEB MON,TUE)', () => {
122+
expect(() => {
123+
new cron.CronTime('* * * * JAN,FEB MON,TUE', null, null);
124+
}).not.toThrow();
125+
});
126+
121127
it('should test too few fields', () => {
122128
expect(() => {
123129
new cron.CronTime('* * * *', null, null);
@@ -130,10 +136,59 @@ describe('crontime', () => {
130136
}).toThrow();
131137
});
132138

133-
it('should test out of range values', () => {
134-
expect(() => {
135-
new cron.CronTime('* * * * 1234', null, null);
136-
}).toThrow();
139+
it('should return the same object with 0 & 7 as Sunday (except "source" prop)', () => {
140+
const sunday0 = new cron.CronTime('* * * * 0', null, null);
141+
const sunday7 = new cron.CronTime('* * * * 7', null, null);
142+
delete sunday0.source;
143+
delete sunday7.source;
144+
expect(sunday7).toEqual(sunday0);
145+
});
146+
147+
describe('should test out of range values', () => {
148+
it('should test out of range minute', () => {
149+
expect(() => {
150+
new cron.CronTime('-1 * * * *', null, null);
151+
}).toThrow();
152+
expect(() => {
153+
new cron.CronTime('60 * * * *', null, null);
154+
}).toThrow();
155+
});
156+
157+
it('should test out of range hour', () => {
158+
expect(() => {
159+
new cron.CronTime('* -1 * * *', null, null);
160+
}).toThrow();
161+
expect(() => {
162+
new cron.CronTime('* 24 * * *', null, null);
163+
}).toThrow();
164+
});
165+
166+
it('should test out of range day-of-month', () => {
167+
expect(() => {
168+
new cron.CronTime('* * 0 * *', null, null);
169+
}).toThrow();
170+
expect(() => {
171+
new cron.CronTime('* * 32 * *', null, null);
172+
}).toThrow();
173+
});
174+
175+
it('should test out of range month', () => {
176+
expect(() => {
177+
new cron.CronTime('* * * 0 *', null, null);
178+
}).toThrow();
179+
expect(() => {
180+
new cron.CronTime('* * * 13 *', null, null);
181+
}).toThrow();
182+
});
183+
184+
it('should test out of range day-of-week', () => {
185+
expect(() => {
186+
new cron.CronTime('* * * * -1', null, null);
187+
}).toThrow();
188+
expect(() => {
189+
new cron.CronTime('* * * * 8', null, null);
190+
}).toThrow();
191+
});
137192
});
138193

139194
it('should test invalid wildcard expression', () => {
@@ -257,7 +312,7 @@ describe('crontime', () => {
257312

258313
it('should parse @yearly', () => {
259314
const cronTime = new cron.CronTime('@yearly');
260-
expect(cronTime.toString()).toEqual('0 0 0 1 0 *');
315+
expect(cronTime.toString()).toEqual('0 0 0 1 1 *');
261316
});
262317
});
263318

@@ -564,8 +619,8 @@ describe('crontime', () => {
564619
currentDate = nextDate;
565620
}
566621
});
567-
it('should test valid range of months (*/15 * * 6-11 *)', () => {
568-
const cronTime = new cron.CronTime('*/15 * * 6-11 *');
622+
it('should test valid range of months (*/15 * * 7-12 *)', () => {
623+
const cronTime = new cron.CronTime('*/15 * * 7-12 *');
569624
const previousDate1 = new Date(Date.UTC(2018, 3, 0, 0, 0));
570625
const nextDate1 = cronTime._getNextDateFrom(previousDate1, 'UTC');
571626
expect(new Date(nextDate1).toUTCString()).toEqual(

0 commit comments

Comments
 (0)