node-cronにスケジュール書式を渡しすぎると値がずれ想定外のスケジュールになる

# 結論

書式が足りない部分はデフォルトの値が適用される。
簡単に言えば、書式設定が右にずれる。
多すぎた場合はこの少なかったとき用の計算が意図せず働き、設定が左にずれる。

例えば、

書式が5個と通常よりひとつ少なかった場合。
`1 2 3 4 5`

本来秒数指定の場所の左端の値(1)は、右にずれて分数指定の記述として扱われる。
(秒数指定にはデフォルト値の0が指定される)
`0 1 2 3 4 5`

書式が7個と通常よりひとつ多かった場合。
`1 2 3 4 5 6 7`
本来日数として指定された値は、左にずれて時数指定の記述として扱われる。
(週の指定にはデフォルト値の`*`が指定される)
`2 3 4 5 6 7 *`

**Linuxなどのcrontabは値を5つ指定する(standard cron syntaxと表現)=node-cronではできる秒数指定ができない。**
**node-cronにstandard cron syntaxが指定されたとき、エラーと意図の相違なくパースさせる処理が、渡しすぎた場合にエラーなく意図と相違あるパースになってしまった。**

# ライブラリ

[kelektiv/node-cron: Cron for NodeJS.](https://github.com/kelektiv/node-cron)

>Cron Ranges
==
>
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.
>
 * Seconds: 0-59
 * Minutes: 0-59
 * Hours: 0-23
 * Day of Month: 1-31
 * Months: 0-11 (Jan-Dec)
 * Day of Week: 0-6 (Sun-Sat)

指定できる場所は6個あることが特徴です。


## バージョン

`"cron": "^1.4.0"`

# コード

[node-cron/cron.js at 1ec1e81eba34fc0b2c034ea65c943ba62b3e16b4 · kelektiv/node-cron](https://github.com/kelektiv/node-cron/blob/1ec1e81eba34fc0b2c034ea65c943ba62b3e16b4/lib/cron.js#L357-L363)

```js
	var timeUnits = [
		'second',
		'minute',
		'hour',
		'dayOfMonth',
		'month',
		'dayOfWeek'
];


	CronTime.parseDefaults = ['0', '*', '*', '*', '*', '*'];

	CronTime.prototype = {
		_parse: function() {
			var aliases = CronTime.aliases;
			var source = this.source.replace(/[a-z]{1,3}/gi, function(alias) {
				alias = alias.toLowerCase();

				if (alias in aliases) {
					return aliases[alias];
				}

				throw new Error('Unknown alias: ' + alias);
			});
			var split = source.replace(/^\s\s*|\s\s*$/g, '').split(/\s+/);
			var cur;
			var i = 0;
			var len = timeUnits.length;

			for (; i < timeUnits.length; i++) {
				// If the split source string doesn't contain all digits,
				// assume defaults for first n missing digits.
				// This adds support for 5-digit standard cron syntax
				cur = split[i - (len - split.length)] || CronTime.parseDefaults[i];
				this._parseField(cur, timeUnits[i], CronTime.constraints[i]);
			}
		},

```


`source`が渡した書式です。


`* * * * *`
ならStandard cron syntax。左端は**分**です。

`* * * * * *`
ならcron-node用のsyntax。左端は**秒**です。

`0 0 0 * * * *`
これが今回誤って渡していた書式です。毎日0時に実行してほしいつもりが、毎時実行になっていました。

`cur = split[i - (len - split.length)] || CronTime.parseDefaults[i];`
このコード、`len`は6で固定です。

node-cronのsyntaxが渡された場合、`(len - split.length)`は常に0なので、そのままの値が順番に渡されます。

***

Standard cron syntaxが渡されたとき、`i = 0`、秒数のパースの際は、


`cur = split[0 - (6 - 5)] || CronTime.parseDefaults[0];`

と`split[-1]`が参照されることになり、当然`undefined`なので`parseDefaults[0]`が適用されます。

以降、Standard cron syntaxで指定された分数指定、split[0]は、`i = 1`のとき、

`cur = split[1 - (6 - 5)] || CronTime.parseDefaults[1];`
となり`split[0]`がnode-cronのsyntaxでの分数(`[1]`)として入り、整合性がとれます。

***


`0 0 0 * * * *`
を渡した場合。
`split.length`は7で、`(len - split.length)`は常に`-1`。
`- (len - split.length)`なので常に`+1`されることになります。

`i = 0`の場合、

`cur = split[0 + 1] || CronTime.parseDefaults[0];`
で秒数にsplit[1]、分と考えていたものがズレて入りました。

時を指定する`i = 2`では
`cur = split[2 + 1] || CronTime.parseDefaults[2];`
で`split[3] = *`が時間の値として処理され、毎時実行になりました。

***

コードのコメントにあるように、この引き算は`5-digit standard cron syntax`をサポートするためのものです。
なので、`7-digit cron syntax`などという不正な書式はエラーにしてしまうことが、本来は正しいのかもしれません。

***

[CronTime day of week is non-standard (Sunday is 1, not 0) · Issue #36 · kelektiv/node-cron](https://github.com/kelektiv/node-cron/issues/36)

サポートが入った経緯。