Giter VIP home page Giter VIP logo

hexagon / croner Goto Github PK

View Code? Open in Web Editor NEW
1.9K 10.0 46.0 3.25 MB

Trigger functions or evaluate cron expressions in JavaScript or TypeScript. No dependencies. Most features. Node. Deno. Bun. Browser.

Home Page: https://croner.56k.guru

License: MIT License

JavaScript 91.83% TypeScript 8.17%
cron scheduling crontab crontab-syntax javascript-cron-parser javascript-cron-scheduler scheduler javascript timer parser

croner's Introduction

Croner
Trigger functions or evaluate cron expressions in JavaScript or TypeScript. No dependencies. All features. Node. Deno. Bun. Browser.

Try it live on jsfiddle, and check out the full documentation on croner.56k.guru.

Croner - Cron for JavaScript and TypeScript

npm version Codacy Badge NPM Downloads No dependencies MIT License

  • Trigger functions in JavaScript using Cron syntax.
  • Evaluate cron expressions and get a list of upcoming run times.
  • Uses Vixie-cron pattern, with additional features such as L for last day and weekday of month and # for nth weekday of month.
  • Works in Node.js >=18 (both require and import), Deno >=1.16 and Bun >=1.0.0.
  • Works in browsers as standalone, UMD or ES-module.
  • Target different time zones.
  • Built-in overrun protection
  • Built-in error handling
  • Includes TypeScript typings.
  • Support for asynchronous functions.
  • Pause, resume, or stop execution after a task is scheduled.
  • Operates in-memory, with no need for a database or configuration files.
  • Zero dependencies.

Quick examples:

// Basic: Run a function at the interval defined by a cron expression
const job = Cron('*/5 * * * * *', () => {
	console.log('This will run every fifth second');
});

// Enumeration: What dates do the next 100 sundays occur on?
const nextSundays = Cron('0 0 0 * * 7').nextRuns(100);
console.log(nextSundays);

// Days left to a specific date
const msLeft = Cron('59 59 23 24 DEC *').nextRun() - new Date();
console.log(Math.floor(msLeft/1000/3600/24) + " days left to next christmas eve");

// Run a function at a specific date/time using a non-local timezone (time is ISO 8601 local time)
// This will run 2024-01-23 00:00:00 according to the time in Asia/Kolkata
Cron('2024-01-23T00:00:00', { timezone: 'Asia/Kolkata' }, () => { console.log('Yay!') });

More examples...

Installation

Full documentation on installation and usage is found at https://croner.56k.guru

Note If you are migrating from a different library such as cron or node-cron, or upgrading from a older version of croner, see the migration section of the manual.

Install croner using your favorite package manager or CDN. then include it in you project:

Using Node.js or Bun

// ESM Import ...
import { Cron } from "croner";

// ... or CommonJS Require, destructure to add type hints
const { Cron } = require("croner");

Using Deno

// From deno.land/x
import { Cron } from "https://deno.land/x/[email protected]/dist/croner.js";

// ... or jsr.io
import { Cron } from "jsr:@hexagon/[email protected]";

In a webpage using the UMD-module

<script src="https://cdn.jsdelivr.net/npm/croner@8/dist/croner.umd.min.js"></script>

Documentation

Signature

Cron takes three arguments

// Parameters
// - First: Cron pattern, js date object (fire once), or ISO 8601 time string (fire once)
// - Second: Options (optional)
// - Third: Function run trigger (optional)
const job = Cron("* * * * * *", { maxRuns: 1 }, () => {} );

// If function is omitted in constructor, it can be scheduled later
job.schedule(job, /* optional */ context) => {});

The job will be sceduled to run at next matching time unless you supply option { paused: true }. The Cron(...) constructor will return a Cron instance, later called job, which have a couple of methods and properties listed below.

Status

job.nextRun( /*optional*/ startFromDate );	// Get a Date object representing the next run.
job.nextRuns(10, /*optional*/ startFromDate ); // Get an array of Dates, containing the next n runs.
job.msToNext( /*optional*/ startFromDate ); // Get the milliseconds left until the next execution.
job.currentRun(); 		// Get a Date object showing when the current (or last) run was started.
job.previousRun( ); 		// Get a Date object showing when the previous job was started.

job.isRunning(); 	// Indicates if the job is scheduled and not paused or killed (true or false).
job.isStopped(); 	// Indicates if the job is permanently stopped using `stop()` (true or false).
job.isBusy(); 		// Indicates if the job is currently busy doing work (true or false).

job.getPattern(); 	// Returns the original pattern string

Control functions

job.trigger();		// Force a trigger instantly
job.pause();		// Pause trigger
job.resume();		// Resume trigger
job.stop();		// Stop the job completely. It is not possible to resume after this.
				// Note that this also removes named jobs from the exported `scheduledJobs` array.

Properties

job.name 			// Optional job name, populated if a name were passed to options

Options

Key Default value Data type Remarks
name undefined String If you specify a name for the job, Croner will keep a reference to the job in the exported array scheduledJobs. The reference will be removed on .stop().
maxRuns Infinite Number
catch false Boolean|Function Catch unhandled errors in triggered function. Passing true will silently ignore errors. Passing a callback function will trigger this callback on error.
timezone undefined String Timezone in Europe/Stockholm format
startAt undefined String ISO 8601 formatted datetime (2021-10-17T23:43:00)
in local time (according to timezone parameter if passed)
stopAt undefined String ISO 8601 formatted datetime (2021-10-17T23:43:00)
in local time (according to timezone parameter if passed)
interval 0 Number Minimum number of seconds between triggers.
paused false Boolean If the job should be paused from start.
context undefined Any Passed as the second parameter to triggered function
legacyMode true boolean Combine day-of-month and day-of-week using true = OR, false = AND
unref false boolean Setting this to true unrefs the internal timer, which allows the process to exit even if a cron job is running.
utcOffset undefined number Schedule using a specific utc offset in minutes. This does not take care of daylight savings time, you probably want to use option timezone instead.
protect undefined boolean|Function Enabled over-run protection. Will block new triggers as long as an old trigger is in progress. Pass either true or a callback function to enable

Warning Unreferencing timers (option unref) is only supported by Node.js and Deno. Browsers have not yet implemented this feature, and it does not make sense to use it in a browser environment.

Pattern

The expressions used by Croner are very similar to those of Vixie Cron, but with a few additions and changes as outlined below:

// ┌──────────────── (optional) second (0 - 59)
// │ ┌────────────── minute (0 - 59)
// │ │ ┌──────────── hour (0 - 23)
// │ │ │ ┌────────── day of month (1 - 31)
// │ │ │ │ ┌──────── month (1 - 12, JAN-DEC)
// │ │ │ │ │ ┌────── day of week (0 - 6, SUN-Mon) 
// │ │ │ │ │ │       (0 to 6 are Sunday to Saturday; 7 is Sunday, the same as 0)
// │ │ │ │ │ │
// * * * * * *
  • Croner expressions have the following additional modifiers:

    • ?: The question mark is substituted with the time of initialization. For example, ? ? * * * * would be substituted with 25 8 * * * * if the time is :08:25 at the time of new Cron('? ? * * * *', <...>). The question mark can be used in any field.
    • L: The letter 'L' can be used in the day of the month field to indicate the last day of the month. When used in the day of the week field in conjunction with the # character, it denotes the last specific weekday of the month. For example, 5#L represents the last Friday of the month.
    • #: The # character specifies the "nth" occurrence of a particular day within a month. For example, supplying 5#2 in the day of week field signifies the second Friday of the month. This can be combined with ranges and supports day names. For instance, MON-FRI#2 would match the Monday through Friday of the second week of the month.
  • Croner allows you to pass a JavaScript Date object or an ISO 8601 formatted string as a pattern. The scheduled function will trigger at the specified date/time and only once. If you use a timezone different from the local timezone, you should pass the ISO 8601 local time in the target location and specify the timezone using the options (2nd parameter).

  • Croner also allows you to change how the day-of-week and day-of-month conditions are combined. By default, Croner (and Vixie cron) will trigger when either the day-of-month OR the day-of-week conditions match. For example, 0 20 1 * MON will trigger on the first of the month as well as each Monday. If you want to use AND (so that it only triggers on Mondays that are also the first of the month), you can pass { legacyMode: false }. For more information, see issue #53.

Field Required Allowed values Allowed special characters Remarks
Seconds Optional 0-59 * , - / ?
Minutes Yes 0-59 * , - / ?
Hours Yes 0-23 * , - / ?
Day of Month Yes 1-31 * , - / ? L
Month Yes 1-12 or JAN-DEC * , - / ?
Day of Week Yes 0-7 or SUN-MON * , - / ? L # 0 to 6 are Sunday to Saturday
7 is Sunday, the same as 0
# is used to specify nth occurrence of a weekday

Note Weekday and month names are case-insensitive. Both MON and mon work. When using L in the Day of Week field, it affects all specified weekdays. For example, 5-6#L means the last Friday and Saturday in the month." The # character can be used to specify the "nth" weekday of the month. For example, 5#2 represents the second Friday of the month.

It is also possible to use the following "nicknames" as pattern.

Nickname Description
@yearly Run once a year, ie. "0 0 1 1 *".
@annually Run once a year, ie. "0 0 1 1 *".
@monthly Run once a month, ie. "0 0 1 * *".
@weekly Run once a week, ie. "0 0 * * 0".
@daily Run once a day, ie. "0 0 * * *".
@hourly Run once an hour, ie. "0 * * * *".

Why another JavaScript cron implementation

Because the existing ones are not good enough. They have serious bugs, use bloated dependencies, do not work in all environments, and/or simply do not work as expected.

croner cronosjs node-cron cron node-schedule
Platforms
Node.js (CommonJS)
Browser (ESMCommonJS)
Deno (ESM)
Features
Over-run protection
Error handling
Typescript typings
Unref timers (optional)
dom-OR-dow
dom-AND-dow (optional)
Next run
Next n runs
Timezone
Minimum interval
Controls (stop/resume)
Range (0-13)
Stepping (*/5)
Last day of month (L)
Nth weekday of month (#)
In depth comparison of various libraries
croner cronosjs node-cron cron node-schedule
Size
Minified size (KB) 17.0 14.9 15.2 85.4 ⚠️ 100.5 ⚠️
Bundlephobia minzip (KB) 5.0 5.1 5.7 25.8 29.2 ⚠️
Dependencies 0 0 1 1 3 ⚠️
Popularity
Downloads/week 1 2019K 31K 447K 1366K 1046K
Quality
Issues 1 0 2 133 ⚠️ 13 145 ⚠️
Code coverage 99% 98% 100% 81% 94%
Performance
Ops/s 1 2 3 4 5 6 160 651 55 593 N/A ❌ 6 313 ⚠️ 2 726 ⚠️
Ops/s 0 0 0 29 2 * 176 714 67 920 N/A ❌ 3 104 ⚠️ 737 ⚠️
Tests 11/11 10/11 0/11 2 7/11 ⚠️ 9/11

Note

  • Table last updated at 2023-10-10
  • node-cron has no interface to predict when the function will run, so tests cannot be carried out.
  • All tests and benchmarks were carried out using https://github.com/Hexagon/cron-comparison

Development

Master branch

Node.js CI Deno CI Bun CI

This branch contains the latest stable code, released on npm's default channel latest. You can install the latest stable revision by running the command below.

npm install croner --save

Dev branch

Node.js CI Deno CI Bun CI

This branch contains code currently being tested, and is released at channel dev on npm. You can install the latest revision of the development branch by running the command below.

npm install croner@dev

Warning Expect breaking changes if you do not pin to a specific version.

A list of fixes and features currently released in the dev branch is available here

Contributing & Support

Croner is founded and actively maintained by Hexagon. If you find value in Croner and want to contribute:

Your trust, support, and contributions drive the project. Every bit, irrespective of its size, is deeply appreciated.

License

MIT License

Footnotes

  1. As of 2023-10-10 2

  2. Node-cron has no way of showing next run time.

croner's People

Contributors

egfx-notifications avatar enzom-uy avatar gilles-crealp avatar hexagon avatar hophamlam avatar iisalazar avatar lorrding avatar maricn avatar mceachen avatar mryellow avatar mzpl avatar rsk2 avatar unkelpehr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

croner's Issues

TypeError: croner_1.Cron is not a constructor

I tried run the following code in TypeScript (specifically in a Nest.js service):

import {Cron} from 'croner'

const job: Cron = new Cron('* * * * * *', () => {
	console.log('This will run every second.')
})

But it fails with TypeError: croner_1.Cron is not a constructor.

If I remove new keyword, it fails with TypeError: croner_1.Cron is not a function (obviously).

I have no idea why this fails.


MWE example Nest.js service:

import {Injectable, Logger} from '@nestjs/common'
import {Cron} from 'croner'

@Injectable()
export class CronService {
	private readonly logger = new Logger(CronService.name)
	private jobs: Cron[] = []

	createJob(time: string) {
		// time = '*/1 * * * * *'
		const job: Cron = new Cron(time, () => {
			this.logger.debug('This will run every second.')
		})

		this.jobs.push(job)
		return job
	}
}

In CommonJS, it works as expected.


This might not be an issue with croner, but with Nest.js and how it does in the background (see SO answer).


jmcdo29 at Nest.js Discord Channel found out that the issue is in croner’s typing, citing:

Looks like it's bad typings on croner's part. I had to do

import * as Cron from 'croner';
(Cron as any)('* * * * * *', () => console.log('run every second'));

To get it to work

This expression is not callable

Thanks for your efforts. Here replacing cron package with croner.
I saw also PM2 recently migrated to croner. Congrats.

Using croner 4.3.6, with VSCode 1.65.2, I noted this messages in Problems panel:

This expression is not callable.
  Type 'typeof import("[...]/node_modules/croner/types/croner.single")' has no call signatures.

A function with a name starting with an uppercase letter should only be used as a constructor.
const Cron = require("croner");

Cron("1 25 5 * * *", {
    timezone: "Europe/Rome"
}, () => {
    run();
});

Ok, eslint error depends on new-cap setting.
The surprise was about ts lint message

Of course I can suppress the messages with the following directives:

// @ts-ignore
Cron("1 25 5 * * *", { // eslint-disable-line
    timezone: "Europe/Rome"
}, () => {
    run();
});

Any hint?

Providing invalid date results in an infinite loop

const cron2 = new Cron('0 * * * *', {
  legacyMode: true,
});
cron2.next(new Date('pizza'));

I think I also had an infinite loop with a valid date, but I'm still working on a concise way to reproduce it.

New feature: Option for legacy dom-OR-dow

By default, croner work differently from the standard refered to by @vixie. If you specify both day-of-month and day-of-week ( 0 0 1 * MON) croner would only run when there is a monday at the 1st of any month. This enable you to set patterns that run on the first monday of any month (0 0 1-7 * MON) and other clever stuff.

In classic cron (Vixie, for example), day-of-month and day-of-week are ORed, which makes 0 0 1 * MON run at the 1st of any month AND at mondays.

	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
	 * like many bizarre things, it's the standard.
	 */

Croner should support this with options, maybe

{ 
   legacyMode: true
}

Feature request: onComplete callback

This is more of a question and/or possible feature request. Does Croner have an onComplete type callback ability? Essentially I'm looking to run this job x amount of times using maxRuns however on the last run if my tasks are not complete I will need to perform other tasks. So without keeping an internal count of the runs and then running if it hits its max I would like to use the maxRuns option and then pass another callback function to on if maxRuns has reached it's end. Thanks!

Problem with timezone override and question mark

If using question mark in expression, and a timezone different from local, question mark is substituted with local value instead of target timezone value. An example:

Cron("0 0 ? * * *", {timezone: "UTC"}) will be evaluated to Cron("0 0 0 * * *", {timezone: "UTC"}) if time is 00:xx:yy in local timezone (but something other in UTC).

Package subpath './package.json' is not defined by "exports"

Hello!
I encountered the error:
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './package.json' is not defined by "exports" in /..../backend/node_modules/croner/package.json

Added line solved the problem:

"exports": {
"./package.json": "./package.json",
".": {
"import": "./src/croner.js",
"require": "./dist/croner.cjs",
"browser": "./dist/croner.min.js"
},

Can not specify range from any weekday to sunday

A pattern like * * * * FRI-SUN will not be valid because SUN is replaced with 0 and thus the following error is thrown

TypeError: CronPattern: From value is larger than to value: '5-0'

Since alphabetical weekdays are non-standard syntax anyway, would you be willing to have a fix for this edge case added?

A likely candidate could be here

croner/src/pattern.js

Lines 315 to 324 in 9f23fae

CronPattern.prototype.replaceAlphaDays = function (conf) {
return conf
.replace(/sun/gi, "0")
.replace(/mon/gi, "1")
.replace(/tue/gi, "2")
.replace(/wed/gi, "3")
.replace(/thu/gi, "4")
.replace(/fri/gi, "5")
.replace(/sat/gi, "6");
};

I guess something like this should work, but did not run all tests yet

CronPattern.prototype.replaceAlphaDays = function (conf) {
	return conf
		.replace(/-sun/gi, "-7")
		.replace(/sun/gi, "0")
		.replace(/mon/gi, "1")
		.replace(/tue/gi, "2")
		.replace(/wed/gi, "3")
		.replace(/thu/gi, "4")
		.replace(/fri/gi, "5")
		.replace(/sat/gi, "6");
};

This way we keep 0 as the standard for sundays, but automatically choose 7 for upper values of ranges.

The following would be an even cleaner alternative, but lookahead and lookbehind in regex are sometimes unsupported in older systems, so depending on what should still be supported that is not an option

CronPattern.prototype.replaceAlphaDays = function (conf) {
	return conf
		.replace(/(?<=-)sun/gi, "7")
		.replace(/sun/gi, "0")
		.replace(/mon/gi, "1")
		.replace(/tue/gi, "2")
		.replace(/wed/gi, "3")
		.replace(/thu/gi, "4")
		.replace(/fri/gi, "5")
		.replace(/sat/gi, "6");
};

Chore: Review code

Have a look at PR #15 , full repo review.

Pay special attention to src/*,

pay no attention to docs/* (as these are auto-generated files)

Incorrect next date when using legacy mode

const cron = new Cron('15 9 * * mon', {
  legacyMode: true,
});
const borked = cron.next(new Date('2022-02-28 23:59:00'));
console.log(borked);

Expected: Something in March.
Actual: Mon Feb 28 2022 09:15:00

Cron job every five months, next not calculated correctly

Description

Hi
I'm trying to run a cron every 5 months at 10 am. But the next date is not calculated correctly.

const job = new Cron(`0 10 1 */5 *`, {timezone: "Europe/Rome"}, () => {
// My Job
});
console.log((new Date()).toISOString(), job.next()?.toISOString());

Output:
Now: 2022-02-15T09:05:05.829Z
next: 2022-05-01T08:00:00.000Z

I expect the next is: 2022-07-01T08:00:00.000Z

My tests:
0 10 1 */1 * -> (next: 2022-03-01T09:00:00.000Z)

0 10 1 */2 * -> (next: 2022-04-01T08:00:00.000Z)

0 10 1 */3 * <- Not work (next: 2022-03-01T09:00:00.000Z)

0 10 1 */4 * <- Not work (next: 2022-04-01T08:00:00.000Z)

0 10 1 */5 * <- Not work (next: 2022-05-01T08:00:00.000Z )

I've also tried setting the startAt option, but it doesn't change anything:

{
  startAt: new Date(),
  timezone: "Europe/Rome"
}

Enumerate does not care about option maxRuns

job.enumerate(10); is expected to list next 10 run times, which it does.

However - if option maxRuns is set to 5, the job is only triggered five times. So in this case job.enumerate(10) should only list the first five runs.

test.ts

import { Cron } from "croner";
// Or in deno: import { Cron } from "https://deno.land/x/[email protected]/src/croner.js";

const job = new Cron("* * * * * *", { maxRuns: 5 }, () => {
  console.log("This will run five times.");
});

console.log(job.enumerate(10));

Output:

[
  2022-09-21T18:54:04.000Z,
  2022-09-21T18:54:05.000Z,
  2022-09-21T18:54:06.000Z,
  2022-09-21T18:54:07.000Z,
  2022-09-21T18:54:08.000Z,
  2022-09-21T18:54:09.000Z,
  2022-09-21T18:54:10.000Z,
  2022-09-21T18:54:11.000Z,
  2022-09-21T18:54:12.000Z,
  2022-09-21T18:54:13.000Z
]
This will run five times.
This will run five times.
This will run five times.
This will run five times.
This will run five times.

Duplicate executing

Hello, i have duplicate executing issue with croner,
image
importing
image
code
image
results
as i understand right, i place maxRuns:1 and interval in options, but same result its sending a lot of request, but needed to send only one
same situation with cron, node-schedule

Croner & PM2: Croner job is stopping at 00:00

Hello,
I'm using pm2 to keep my services up and running and centralize logs.

I'm running actually a really simple piece of code in a docker container (basically theses commands appears in my Dockerfile):

FROM node:16.15-bullseye-slim
COPY release /opt/src/publisher
CMD pm2 start /opt/src/bot/main.js --time  -o /var/log/publisher/publisher.log -e /var/log/publisher/publisher.log ; pm2 logs

Here is my NodeJS code:

const dayjs = require('dayjs');
const Cron = require("croner");

//Simple match wildcard function like: "aa" will match "a*" or "*a" or "**" but not "bb" or "b*"
function matchWildcard(str1, str2) {
    if (typeof str1 != "string" || typeof str2 != "string")
        return false;
    if (str1.length != str2.length)
        return false;
    for (let i = 0; i < str1.length; i++) {
        if (str1.at(i) != "*" && str2.at(i) != "*" && str1.at(i) != str2.at(i)) {
            console.debug(`${str1} matchs ${str2} ?: FALSE`);
            return false;
        }
    }
    console.debug(`${str1} matchs ${str2} ?: TRUE`);
    return true;
}

//My cron task running every minute
const job = Cron("0 */1 * ? * *", async () => {
    let now = dayjs();
    let dayKey = now.format("HH:mm.DD-MM");
    matchWildcard("12:00.27-**", dayKey);
});

So the croner job is starting but stops at 00:00 and I do not know why this append:
image

Proper types support for ESM exports when moduleResolution is set to NodeNext (^TS 4.7)

Hi,

first of all thanks for this package. I noticed that the package itself is already a module which is really nice. But unfortunately there are some minor things which needs to be updated to support NodeNext as moduleResolution.

The problem is easy to reproduce:

  • Create a TypeScript project
  • Have a tsconfig in place and set the moduleResolution to NodeNext
  • Install croner
  • import croner in index.ts

As you will notice, the types cannot be resolved.

This configuration in TypeScript is really strict about some settings and cannot detect types in a fallback manner (types field). Therefore you need to update the package.json like documented here: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-rc/#package-json-exports-imports-and-self-referencing

This would mean the package.json should look like this:

 {
  "type": "module",
  "main": "./dist/croner.cjs",
  "browser": "./dist/croner.min.js",
  "module": "./src/croner.js",
  "types": "types/croner.d.ts",
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "import": {
        "types": "./types/croner.d.ts",
        "default": "./src/croner.js"
      },
      "require": "./dist/croner.cjs",
      "browser": "./dist/croner.min.js"
    },
    "./minified": {
      "import": "./dist/croner.min.mjs",
      "require": "./dist/croner.min.js",
      "browser": "./dist/croner.min.js",
      "types": "./types/croner.d.ts"
    }
  }
}

So instead of relying on the old field types we need to define the types explicit for the import mapping. This way the types can be properly resolved (but only when defined as first prop in the import object).

I tested the changes locally and they work well. Hope it can be fixed pretty soon, but for now I just add the types via include in the tsconfig file as workaround.

Cron.next() returned incorrect calculation sometimes depending on the system time.

Supposed the system time is 12:35:42 for the following examples, the date is irrelevant in this case

  • * 17 * * * expected 17:00:00 but 17:36:00 was returned
  • */5 * 15 * * * expected 15:00:00 but 15:35:45 was returned
  • * * 15 * * * expected 15:00:00 but 15:35:00 was returned
  • 42 * * * * returned 12:42:00 as expected.
  • */5 * 11 * * * returned 11:00:00 in the next day as expected.
  • * 17 * 1 * returned 17:00:00 as expected

Generally, the miscalculation occurred when you used the * pattern in the second, minute, or hour slot but completely went away when the minute, day, or month slot has number literal or when the resulting date sits at least tomorrow in the future.

Programmatically stop croner

Thanks for the library!

Is there any way to programmatically stop croner? My Jest tests hangs until I manually kill tests process

Feature: Support for minimum interval

Croner should allow you to set a minimum interval. This would as an example allow triggering each 90th second, while still allowing to restrict runs as usual.

Example:

Cron(
  "* * * 1 * *", 
  {
    interval: 90
  },
  () => { 
    console.log('This will run every 90th second, but only at the first day of every month');
  }
};

Croner & PM2: try to "date set" make the callback be called 2 times

Hello,
I'm using pm2 to keep my services up and running and centralize logs.

I'm running actually a really simple piece of code in a docker container (basically theses commands appears in my Dockerfile):

FROM node:16.15-bullseye-slim
COPY main.js /opt/src/publisher/main.js
COPY package-lock.json /opt/src/publisher/package-lock.json
COPY package.json /opt/src/publisher/package.json
WORKDIR /opt/src/publisher
RUN npm ci --only=prod && npm install -g pm2
CMD pm2 start /opt/src/publisher/main.js --time  -o /var/log/publisher/publisher.log -e /var/log/publisher/publisher.log ; pm2 logs

Here is my NodeJS code:

const dayjs = require('dayjs');
const Cron = require("croner");

//Simple match wildcard function like: "aa" will match "a*" or "*a" or "**" but not "bb" or "b*"
function matchWildcard(str1, str2) {
    if (typeof str1 != "string" || typeof str2 != "string")
        return false;
    if (str1.length != str2.length)
        return false;
    for (let i = 0; i < str1.length; i++) {
        if (str1.at(i) != "*" && str2.at(i) != "*" && str1.at(i) != str2.at(i)) {
            console.debug(`${str1} matchs ${str2} ?: FALSE`);
            return false;
        }
    }
    console.debug(`${str1} matchs ${str2} ?: TRUE`);
    return true;
}

//My cron task running every minute
const job = Cron("0 */1 * ? * *", async () => {
    let now = dayjs();
    let dayKey = now.format("HH:mm.DD-MM");
    matchWildcard("12:00.27-**", dayKey);
});

So I met this really strange error where if I run a bash in my docker and try to change the date (unsuccessfully), the croner job is going to start to execute the callback at 32:00 min and 32:59min (like one ate 00sec and one at 59 sec) or starting to has 1sec late.

# date -s "23:55:00"
date: cannot set date: Operation not permitted
Mon May 30 23:55:00 +14 2022

image

I know this is a really specific case but I was wondering if it could hide something more deep.

Day skipping fix breaks calculation on DST start

I was trying to figure out what changes have been made to croner before updating to the current release. I read f8b2d3e and got the impression that you rely on the difference between the local timezone and the provided one to get the next valid time. Assuming that I understood this correctly I figured that there would still be an issue if the local and target timezone start and end daylight saving time at the same time or if you just explicitly state your local timezone.
To verify my assumption I wrote the following test:

	test("0 30 2 * * * with 365 iterations should return 365 days from now in Europe/Berlin", function () {
		let scheduler = new Cron("0 30 2 * * *", { timezone: "Europe/Berlin" }),
			prevRun = new Date(),
			nextRun,
			iterations = 365,
			compareDay = new Date();
			
		compareDay.setDate(compareDay.getDate() + iterations );
		
		while(iterations-->0) {
			nextRun = scheduler.next(prevRun),
			prevRun = nextRun;
			console.log(nextRun.toLocaleString("en-US"));
		}

		// Set seconds, minutes and hours to 00:00:00
		compareDay.setMilliseconds(0);
		compareDay.setSeconds(0);
		compareDay.setMinutes(0);
		compareDay.setHours(0);

		// Do comparison
		assert.equal(Math.abs(nextRun.getTime()-compareDay.getTime())<13*60*60*1000, true);

	});

And I also ran this with 0 30 2 * * * and Europe/Budapest as well as 0 30 1 * * * and Europe/London. Since I'm based in Germany all of those tests failed for me, to reproduce you'd have to find a timezone matching your local one and specify the correct time that would be skipped on a DST change.

The tests produce the following output at croner v4.4.0

~/p/croner ((4.4.0)) > npm run test

> [email protected] test
> uvu test test.croner.js

node/js/test.croner.js
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 10/27/2022, 2:30:00 AM
10/28/2022, 2:30:00 AM
10/29/2022, 2:30:00 AM
10/30/2022, 2:30:00 AM
10/31/2022, 2:30:00 AM
11/1/2022, 2:30:00 AM
11/2/2022, 2:30:00 AM
11/3/2022, 2:30:00 AM
11/4/2022, 2:30:00 AM
11/5/2022, 2:30:00 AM
11/6/2022, 2:30:00 AM
11/7/2022, 2:30:00 AM
11/8/2022, 2:30:00 AM
11/9/2022, 2:30:00 AM
11/10/2022, 2:30:00 AM
11/11/2022, 2:30:00 AM
11/12/2022, 2:30:00 AM
11/13/2022, 2:30:00 AM
11/14/2022, 2:30:00 AM
11/15/2022, 2:30:00 AM
11/16/2022, 2:30:00 AM
11/17/2022, 2:30:00 AM
11/18/2022, 2:30:00 AM
11/19/2022, 2:30:00 AM
11/20/2022, 2:30:00 AM
11/21/2022, 2:30:00 AM
11/22/2022, 2:30:00 AM
11/23/2022, 2:30:00 AM
11/24/2022, 2:30:00 AM
11/25/2022, 2:30:00 AM
11/26/2022, 2:30:00 AM
11/27/2022, 2:30:00 AM
11/28/2022, 2:30:00 AM
11/29/2022, 2:30:00 AM
11/30/2022, 2:30:00 AM
12/1/2022, 2:30:00 AM
12/2/2022, 2:30:00 AM
12/3/2022, 2:30:00 AM
12/4/2022, 2:30:00 AM
12/5/2022, 2:30:00 AM
12/6/2022, 2:30:00 AM
12/7/2022, 2:30:00 AM
12/8/2022, 2:30:00 AM
12/9/2022, 2:30:00 AM
12/10/2022, 2:30:00 AM
12/11/2022, 2:30:00 AM
12/12/2022, 2:30:00 AM
12/13/2022, 2:30:00 AM
12/14/2022, 2:30:00 AM
12/15/2022, 2:30:00 AM
12/16/2022, 2:30:00 AM
12/17/2022, 2:30:00 AM
12/18/2022, 2:30:00 AM
12/19/2022, 2:30:00 AM
12/20/2022, 2:30:00 AM
12/21/2022, 2:30:00 AM
12/22/2022, 2:30:00 AM
12/23/2022, 2:30:00 AM
12/24/2022, 2:30:00 AM
12/25/2022, 2:30:00 AM
12/26/2022, 2:30:00 AM
12/27/2022, 2:30:00 AM
12/28/2022, 2:30:00 AM
12/29/2022, 2:30:00 AM
12/30/2022, 2:30:00 AM
12/31/2022, 2:30:00 AM
1/1/2023, 2:30:00 AM
1/2/2023, 2:30:00 AM
1/3/2023, 2:30:00 AM
1/4/2023, 2:30:00 AM
1/5/2023, 2:30:00 AM
1/6/2023, 2:30:00 AM
1/7/2023, 2:30:00 AM
1/8/2023, 2:30:00 AM
1/9/2023, 2:30:00 AM
1/10/2023, 2:30:00 AM
1/11/2023, 2:30:00 AM
1/12/2023, 2:30:00 AM
1/13/2023, 2:30:00 AM
1/14/2023, 2:30:00 AM
1/15/2023, 2:30:00 AM
1/16/2023, 2:30:00 AM
1/17/2023, 2:30:00 AM
1/18/2023, 2:30:00 AM
1/19/2023, 2:30:00 AM
1/20/2023, 2:30:00 AM
1/21/2023, 2:30:00 AM
1/22/2023, 2:30:00 AM
1/23/2023, 2:30:00 AM
1/24/2023, 2:30:00 AM
1/25/2023, 2:30:00 AM
1/26/2023, 2:30:00 AM
1/27/2023, 2:30:00 AM
1/28/2023, 2:30:00 AM
1/29/2023, 2:30:00 AM
1/30/2023, 2:30:00 AM
1/31/2023, 2:30:00 AM
2/1/2023, 2:30:00 AM
2/2/2023, 2:30:00 AM
2/3/2023, 2:30:00 AM
2/4/2023, 2:30:00 AM
2/5/2023, 2:30:00 AM
2/6/2023, 2:30:00 AM
2/7/2023, 2:30:00 AM
2/8/2023, 2:30:00 AM
2/9/2023, 2:30:00 AM
2/10/2023, 2:30:00 AM
2/11/2023, 2:30:00 AM
2/12/2023, 2:30:00 AM
2/13/2023, 2:30:00 AM
2/14/2023, 2:30:00 AM
2/15/2023, 2:30:00 AM
2/16/2023, 2:30:00 AM
2/17/2023, 2:30:00 AM
2/18/2023, 2:30:00 AM
2/19/2023, 2:30:00 AM
2/20/2023, 2:30:00 AM
2/21/2023, 2:30:00 AM
2/22/2023, 2:30:00 AM
2/23/2023, 2:30:00 AM
2/24/2023, 2:30:00 AM
2/25/2023, 2:30:00 AM
2/26/2023, 2:30:00 AM
2/27/2023, 2:30:00 AM
2/28/2023, 2:30:00 AM
3/1/2023, 2:30:00 AM
3/2/2023, 2:30:00 AM
3/3/2023, 2:30:00 AM
3/4/2023, 2:30:00 AM
3/5/2023, 2:30:00 AM
3/6/2023, 2:30:00 AM
3/7/2023, 2:30:00 AM
3/8/2023, 2:30:00 AM
3/9/2023, 2:30:00 AM
3/10/2023, 2:30:00 AM
3/11/2023, 2:30:00 AM
3/12/2023, 2:30:00 AM
3/13/2023, 2:30:00 AM
3/14/2023, 2:30:00 AM
3/15/2023, 2:30:00 AM
3/16/2023, 2:30:00 AM
3/17/2023, 2:30:00 AM
3/18/2023, 2:30:00 AM
3/19/2023, 2:30:00 AM
3/20/2023, 2:30:00 AM
3/21/2023, 2:30:00 AM
3/22/2023, 2:30:00 AM
3/23/2023, 2:30:00 AM
3/24/2023, 2:30:00 AM
3/25/2023, 2:30:00 AM
3/27/2023, 2:30:00 AM
3/28/2023, 2:30:00 AM
3/29/2023, 2:30:00 AM
3/30/2023, 2:30:00 AM
3/31/2023, 2:30:00 AM
4/1/2023, 2:30:00 AM
4/2/2023, 2:30:00 AM
4/3/2023, 2:30:00 AM
4/4/2023, 2:30:00 AM
4/5/2023, 2:30:00 AM
4/6/2023, 2:30:00 AM
4/7/2023, 2:30:00 AM
4/8/2023, 2:30:00 AM
4/9/2023, 2:30:00 AM
4/10/2023, 2:30:00 AM
4/11/2023, 2:30:00 AM
4/12/2023, 2:30:00 AM
4/13/2023, 2:30:00 AM
4/14/2023, 2:30:00 AM
4/15/2023, 2:30:00 AM
4/16/2023, 2:30:00 AM
4/17/2023, 2:30:00 AM
4/18/2023, 2:30:00 AM
4/19/2023, 2:30:00 AM
4/20/2023, 2:30:00 AM
4/21/2023, 2:30:00 AM
4/22/2023, 2:30:00 AM
4/23/2023, 2:30:00 AM
4/24/2023, 2:30:00 AM
4/25/2023, 2:30:00 AM
4/26/2023, 2:30:00 AM
4/27/2023, 2:30:00 AM
4/28/2023, 2:30:00 AM
4/29/2023, 2:30:00 AM
4/30/2023, 2:30:00 AM
5/1/2023, 2:30:00 AM
5/2/2023, 2:30:00 AM
5/3/2023, 2:30:00 AM
5/4/2023, 2:30:00 AM
5/5/2023, 2:30:00 AM
5/6/2023, 2:30:00 AM
5/7/2023, 2:30:00 AM
5/8/2023, 2:30:00 AM
5/9/2023, 2:30:00 AM
5/10/2023, 2:30:00 AM
5/11/2023, 2:30:00 AM
5/12/2023, 2:30:00 AM
5/13/2023, 2:30:00 AM
5/14/2023, 2:30:00 AM
5/15/2023, 2:30:00 AM
5/16/2023, 2:30:00 AM
5/17/2023, 2:30:00 AM
5/18/2023, 2:30:00 AM
5/19/2023, 2:30:00 AM
5/20/2023, 2:30:00 AM
5/21/2023, 2:30:00 AM
5/22/2023, 2:30:00 AM
5/23/2023, 2:30:00 AM
5/24/2023, 2:30:00 AM
5/25/2023, 2:30:00 AM
5/26/2023, 2:30:00 AM
5/27/2023, 2:30:00 AM
5/28/2023, 2:30:00 AM
5/29/2023, 2:30:00 AM
5/30/2023, 2:30:00 AM
5/31/2023, 2:30:00 AM
6/1/2023, 2:30:00 AM
6/2/2023, 2:30:00 AM
6/3/2023, 2:30:00 AM
6/4/2023, 2:30:00 AM
6/5/2023, 2:30:00 AM
6/6/2023, 2:30:00 AM
6/7/2023, 2:30:00 AM
6/8/2023, 2:30:00 AM
6/9/2023, 2:30:00 AM
6/10/2023, 2:30:00 AM
6/11/2023, 2:30:00 AM
6/12/2023, 2:30:00 AM
6/13/2023, 2:30:00 AM
6/14/2023, 2:30:00 AM
6/15/2023, 2:30:00 AM
6/16/2023, 2:30:00 AM
6/17/2023, 2:30:00 AM
6/18/2023, 2:30:00 AM
6/19/2023, 2:30:00 AM
6/20/2023, 2:30:00 AM
6/21/2023, 2:30:00 AM
6/22/2023, 2:30:00 AM
6/23/2023, 2:30:00 AM
6/24/2023, 2:30:00 AM
6/25/2023, 2:30:00 AM
6/26/2023, 2:30:00 AM
6/27/2023, 2:30:00 AM
6/28/2023, 2:30:00 AM
6/29/2023, 2:30:00 AM
6/30/2023, 2:30:00 AM
7/1/2023, 2:30:00 AM
7/2/2023, 2:30:00 AM
7/3/2023, 2:30:00 AM
7/4/2023, 2:30:00 AM
7/5/2023, 2:30:00 AM
7/6/2023, 2:30:00 AM
7/7/2023, 2:30:00 AM
7/8/2023, 2:30:00 AM
7/9/2023, 2:30:00 AM
7/10/2023, 2:30:00 AM
7/11/2023, 2:30:00 AM
7/12/2023, 2:30:00 AM
7/13/2023, 2:30:00 AM
7/14/2023, 2:30:00 AM
7/15/2023, 2:30:00 AM
7/16/2023, 2:30:00 AM
7/17/2023, 2:30:00 AM
7/18/2023, 2:30:00 AM
7/19/2023, 2:30:00 AM
7/20/2023, 2:30:00 AM
7/21/2023, 2:30:00 AM
7/22/2023, 2:30:00 AM
7/23/2023, 2:30:00 AM
7/24/2023, 2:30:00 AM
7/25/2023, 2:30:00 AM
7/26/2023, 2:30:00 AM
7/27/2023, 2:30:00 AM
7/28/2023, 2:30:00 AM
7/29/2023, 2:30:00 AM
7/30/2023, 2:30:00 AM
7/31/2023, 2:30:00 AM
8/1/2023, 2:30:00 AM
8/2/2023, 2:30:00 AM
8/3/2023, 2:30:00 AM
8/4/2023, 2:30:00 AM
8/5/2023, 2:30:00 AM
8/6/2023, 2:30:00 AM
8/7/2023, 2:30:00 AM
8/8/2023, 2:30:00 AM
8/9/2023, 2:30:00 AM
8/10/2023, 2:30:00 AM
8/11/2023, 2:30:00 AM
8/12/2023, 2:30:00 AM
8/13/2023, 2:30:00 AM
8/14/2023, 2:30:00 AM
8/15/2023, 2:30:00 AM
8/16/2023, 2:30:00 AM
8/17/2023, 2:30:00 AM
8/18/2023, 2:30:00 AM
8/19/2023, 2:30:00 AM
8/20/2023, 2:30:00 AM
8/21/2023, 2:30:00 AM
8/22/2023, 2:30:00 AM
8/23/2023, 2:30:00 AM
8/24/2023, 2:30:00 AM
8/25/2023, 2:30:00 AM
8/26/2023, 2:30:00 AM
8/27/2023, 2:30:00 AM
8/28/2023, 2:30:00 AM
8/29/2023, 2:30:00 AM
8/30/2023, 2:30:00 AM
8/31/2023, 2:30:00 AM
9/1/2023, 2:30:00 AM
9/2/2023, 2:30:00 AM
9/3/2023, 2:30:00 AM
9/4/2023, 2:30:00 AM
9/5/2023, 2:30:00 AM
9/6/2023, 2:30:00 AM
9/7/2023, 2:30:00 AM
9/8/2023, 2:30:00 AM
9/9/2023, 2:30:00 AM
9/10/2023, 2:30:00 AM
9/11/2023, 2:30:00 AM
9/12/2023, 2:30:00 AM
9/13/2023, 2:30:00 AM
9/14/2023, 2:30:00 AM
9/15/2023, 2:30:00 AM
9/16/2023, 2:30:00 AM
9/17/2023, 2:30:00 AM
9/18/2023, 2:30:00 AM
9/19/2023, 2:30:00 AM
9/20/2023, 2:30:00 AM
9/21/2023, 2:30:00 AM
9/22/2023, 2:30:00 AM
9/23/2023, 2:30:00 AM
9/24/2023, 2:30:00 AM
9/25/2023, 2:30:00 AM
9/26/2023, 2:30:00 AM
9/27/2023, 2:30:00 AM
9/28/2023, 2:30:00 AM
9/29/2023, 2:30:00 AM
9/30/2023, 2:30:00 AM
10/1/2023, 2:30:00 AM
10/2/2023, 2:30:00 AM
10/3/2023, 2:30:00 AM
10/4/2023, 2:30:00 AM
10/5/2023, 2:30:00 AM
10/6/2023, 2:30:00 AM
10/7/2023, 2:30:00 AM
10/8/2023, 2:30:00 AM
10/9/2023, 2:30:00 AM
10/10/2023, 2:30:00 AM
10/11/2023, 2:30:00 AM
10/12/2023, 2:30:00 AM
10/13/2023, 2:30:00 AM
10/14/2023, 2:30:00 AM
10/15/2023, 2:30:00 AM
10/16/2023, 2:30:00 AM
10/17/2023, 2:30:00 AM
10/18/2023, 2:30:00 AM
10/19/2023, 2:30:00 AM
10/20/2023, 2:30:00 AM
10/21/2023, 2:30:00 AM
10/22/2023, 2:30:00 AM
10/23/2023, 2:30:00 AM
10/24/2023, 2:30:00 AM
10/25/2023, 2:30:00 AM
10/26/2023, 2:30:00 AM
10/27/2023, 2:30:00 AM
✘   (161 / 162)

   FAIL  "0 30 2 * * * with 365 iterations should return 365 days from now in Europe/Berlin"
    Expected values to be deeply equal:  (equal)

        ++true     (Expected)
        --false    (Actual)

March 26th 2023 is missing from the output, March 26th 2023 2:30 AM does not exist as we move our clocks from 2AM to 3AM.

Seeing that you did some further refactoring with minitz since then I also ran the same test with the current release croner v5.3.1

~/p/croner (master)> npm run test

> [email protected] test
> uvu test test.croner.js

node/js/test.croner.js
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 10/27/2022, 2:30:00 AM
10/28/2022, 2:30:00 AM
10/29/2022, 2:30:00 AM
10/30/2022, 2:30:00 AM
10/31/2022, 2:30:00 AM
11/1/2022, 2:30:00 AM
11/2/2022, 2:30:00 AM
11/3/2022, 2:30:00 AM
11/4/2022, 2:30:00 AM
11/5/2022, 2:30:00 AM
11/6/2022, 2:30:00 AM
11/7/2022, 2:30:00 AM
11/8/2022, 2:30:00 AM
11/9/2022, 2:30:00 AM
11/10/2022, 2:30:00 AM
11/11/2022, 2:30:00 AM
11/12/2022, 2:30:00 AM
11/13/2022, 2:30:00 AM
11/14/2022, 2:30:00 AM
11/15/2022, 2:30:00 AM
11/16/2022, 2:30:00 AM
11/17/2022, 2:30:00 AM
11/18/2022, 2:30:00 AM
11/19/2022, 2:30:00 AM
11/20/2022, 2:30:00 AM
11/21/2022, 2:30:00 AM
11/22/2022, 2:30:00 AM
11/23/2022, 2:30:00 AM
11/24/2022, 2:30:00 AM
11/25/2022, 2:30:00 AM
11/26/2022, 2:30:00 AM
11/27/2022, 2:30:00 AM
11/28/2022, 2:30:00 AM
11/29/2022, 2:30:00 AM
11/30/2022, 2:30:00 AM
12/1/2022, 2:30:00 AM
12/2/2022, 2:30:00 AM
12/3/2022, 2:30:00 AM
12/4/2022, 2:30:00 AM
12/5/2022, 2:30:00 AM
12/6/2022, 2:30:00 AM
12/7/2022, 2:30:00 AM
12/8/2022, 2:30:00 AM
12/9/2022, 2:30:00 AM
12/10/2022, 2:30:00 AM
12/11/2022, 2:30:00 AM
12/12/2022, 2:30:00 AM
12/13/2022, 2:30:00 AM
12/14/2022, 2:30:00 AM
12/15/2022, 2:30:00 AM
12/16/2022, 2:30:00 AM
12/17/2022, 2:30:00 AM
12/18/2022, 2:30:00 AM
12/19/2022, 2:30:00 AM
12/20/2022, 2:30:00 AM
12/21/2022, 2:30:00 AM
12/22/2022, 2:30:00 AM
12/23/2022, 2:30:00 AM
12/24/2022, 2:30:00 AM
12/25/2022, 2:30:00 AM
12/26/2022, 2:30:00 AM
12/27/2022, 2:30:00 AM
12/28/2022, 2:30:00 AM
12/29/2022, 2:30:00 AM
12/30/2022, 2:30:00 AM
12/31/2022, 2:30:00 AM
1/1/2023, 2:30:00 AM
1/2/2023, 2:30:00 AM
1/3/2023, 2:30:00 AM
1/4/2023, 2:30:00 AM
1/5/2023, 2:30:00 AM
1/6/2023, 2:30:00 AM
1/7/2023, 2:30:00 AM
1/8/2023, 2:30:00 AM
1/9/2023, 2:30:00 AM
1/10/2023, 2:30:00 AM
1/11/2023, 2:30:00 AM
1/12/2023, 2:30:00 AM
1/13/2023, 2:30:00 AM
1/14/2023, 2:30:00 AM
1/15/2023, 2:30:00 AM
1/16/2023, 2:30:00 AM
1/17/2023, 2:30:00 AM
1/18/2023, 2:30:00 AM
1/19/2023, 2:30:00 AM
1/20/2023, 2:30:00 AM
1/21/2023, 2:30:00 AM
1/22/2023, 2:30:00 AM
1/23/2023, 2:30:00 AM
1/24/2023, 2:30:00 AM
1/25/2023, 2:30:00 AM
1/26/2023, 2:30:00 AM
1/27/2023, 2:30:00 AM
1/28/2023, 2:30:00 AM
1/29/2023, 2:30:00 AM
1/30/2023, 2:30:00 AM
1/31/2023, 2:30:00 AM
2/1/2023, 2:30:00 AM
2/2/2023, 2:30:00 AM
2/3/2023, 2:30:00 AM
2/4/2023, 2:30:00 AM
2/5/2023, 2:30:00 AM
2/6/2023, 2:30:00 AM
2/7/2023, 2:30:00 AM
2/8/2023, 2:30:00 AM
2/9/2023, 2:30:00 AM
2/10/2023, 2:30:00 AM
2/11/2023, 2:30:00 AM
2/12/2023, 2:30:00 AM
2/13/2023, 2:30:00 AM
2/14/2023, 2:30:00 AM
2/15/2023, 2:30:00 AM
2/16/2023, 2:30:00 AM
2/17/2023, 2:30:00 AM
2/18/2023, 2:30:00 AM
2/19/2023, 2:30:00 AM
2/20/2023, 2:30:00 AM
2/21/2023, 2:30:00 AM
2/22/2023, 2:30:00 AM
2/23/2023, 2:30:00 AM
2/24/2023, 2:30:00 AM
2/25/2023, 2:30:00 AM
2/26/2023, 2:30:00 AM
2/27/2023, 2:30:00 AM
2/28/2023, 2:30:00 AM
3/1/2023, 2:30:00 AM
3/2/2023, 2:30:00 AM
3/3/2023, 2:30:00 AM
3/4/2023, 2:30:00 AM
3/5/2023, 2:30:00 AM
3/6/2023, 2:30:00 AM
3/7/2023, 2:30:00 AM
3/8/2023, 2:30:00 AM
3/9/2023, 2:30:00 AM
3/10/2023, 2:30:00 AM
3/11/2023, 2:30:00 AM
3/12/2023, 2:30:00 AM
3/13/2023, 2:30:00 AM
3/14/2023, 2:30:00 AM
3/15/2023, 2:30:00 AM
3/16/2023, 2:30:00 AM
3/17/2023, 2:30:00 AM
3/18/2023, 2:30:00 AM
3/19/2023, 2:30:00 AM
3/20/2023, 2:30:00 AM
3/21/2023, 2:30:00 AM
3/22/2023, 2:30:00 AM
3/23/2023, 2:30:00 AM
3/24/2023, 2:30:00 AM
3/25/2023, 2:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
3/26/2023, 1:30:00 AM
✘   (166 / 167)

   FAIL  "0 30 2 * * * with 365 iterations should return 365 days from now in Europe/Berlin"
    Expected values to be deeply equal:  (equal)

        ++true     (Expected)
        --false    (Actual)

I haven't yet been able to check how the calculation did change from 4.4.0 to 5.3.1, but this seems very broken to me. It seems croner is unable to get a next valid date after the invalid date March 26th 2023 1:30AM. I'd rather keep skipping a day than hanging indefinitely on DST start.
That being said, timezones and DST are challenging and I can not provide a better solution or even a suggestion how to make this reliably testable on all developer machines across different timezones. I hope we can somehow get to a better solution than the current one, in the meantime I'm just going to stick with our current croner release.

Question: Named schedules

Hey I am trying to create multiple schedules for a discord bot.
The user can choose on which date they want to schedule the command, that will get saved in the database if the bot turns off cause of an update or what ever reason.
The database will then get requested every 10 minutes to check if a new schedule has been add to the database.
So I try to call it and create multiple schedules based on that but your croner seems like it can only run if I say that it has a variable to it,
or did I missunderstood the documentation.

Got `Invalid Date` with timezone on JSC

croner version: v5.3.2

new Cron('* * * * *', { timezone: 'UTC' }).next()
// Got `Invalid Date`

I just confirm it happened in JSC with the following platforms:

  • Safari 16.1
  • React Native v0.69 iOS

It works fine on Chrome / Firebox.

Also, I'm using [email protected] in production and it works fine.

Running multiple times

My production env has 2 instances of application running (Running app over PM2 )
Currently, this cron running 2 times in my application how to limit to 1?

Wrong evaluation of dayOfWeek AND dayOfMonth

Expression 0 0 0 13 * 5 does not generate right dates.
This is basically expression for https://en.wikipedia.org/wiki/Friday_the_13th.

Croner generates (for 2023 - source)

0: Fri Jan 06 2023
1: Fri Jan 13 2023
2: Fri Jan 20 2023
...
6: Mon Feb 13 2023

but expected would be (for 2023 - source)

0: Fri, Jan 13 2023
1: Fri, Oct 13 2023
2: Fri, Sep 13 2024
3: Fri, Dec 13 2024
4: Fri, Jun 13 2025
...

As you can see from examples dayOfMonth and dayOfWeek combination behaves as OR instead of AND.

Infinite loop with specific crontab

const cron = new Cron('0 * * * mon,tue,wed,fri,sat,sun', {
  legacyMode: true,
});
cron.next(new Date('2022-03-31 11:40:34'));

Results in an infinite loop.

Code size vs readability

It has been a few months since I was last able to keep track of the developments in croner. Reading up on the changes (and there have been a lot of nice improvements!) I'd like to debate whether the changes to variable names in 390ebca are really a good trade-off.
While it certainly makes the code size smaller, I think it also makes the code less readable. I always thought of croner as a very well written, easy to understand library. So I'm afraid this change will introduce some tribal knowledge to the code which makes it harder for future contributors to get up to speed. Being a backend developer, I do not usually care that much about binary size, so maybe it is worth the reduction in readability and I'm just wrong. Still I'm wondering if there would not be a better solution for this (uglify, etc.) which provides small code size for shipping the code while keeping the code nice and clear for development. At least we could make sure that all those shorthand property names have solid doc comments.

Incorrect future day calculation when using weekdays

"croner": "~4.1.96"

const cron = new Cron('0 0 * * sun');
const previousRun = new Date('2022-02-17');
const nextRun = cron.next(previousRun);

Expected: Sun Feb 20 2022 00:00:00
Actual: Sun Jan 01 2023 00:00:00

Skipping 1 day

I downloaded the project and ran the test before making any change and I found an issue with the following test:

test("0 0 0 * * * with 40 iterations should return 40 days from now", function () {

   FAIL  "0 0 0 * * * with 40 iterations should return 40 days from now"                             
    Expected values to be deeply equal:  (equal)                                                     

        ++1664506800000    (Expected)
        --1664593200000    (Actual)

    at assert (D:/Development/WebStorm/cronerRaw/node_modules/uvu/assert/index.js:33:8)
    at Object.equal (D:/Development/WebStorm/cronerRaw/node_modules/uvu/assert/index.js:45:2)
    at Object.handler (D:/Development/WebStorm/cronerRaw/test/node/js/src/suites/basics.cjs:529:10)
    at Number.runner (D:/Development/WebStorm/cronerRaw/node_modules/uvu/dist/index.js:78:16)
    at async Module.exec (file:///D:/Development/WebStorm/cronerRaw/node_modules/uvu/dist/index.mjs:141:33)
    at async Module.run (file:///D:/Development/WebStorm/cronerRaw/node_modules/uvu/run/index.mjs:12:2)
    at async D:/Development/WebStorm/cronerRaw/node_modules/uvu/bin.js:26:5

After running the test with many different initial dates and changing the iteration number to 1460 (4 years) I found out it is skipping specific dates: The first Sunday of September after September first

Skipping Sun Sep 5, 2021
image

Skipping Sun Sep 4, 2022
image

Skipping Sun Sep 3, 2023
image

Skipping Sun Sep 8, 2024
image

Skipping Sun Sep 7, 2025
image

Duplicated jobs

Hello!
I created a cron job to send a POST request to my backend with axios.
I have the following cron setting:
image

There is my function content:
image

Still get duplicated tasks:
image

Could you please help that is going on?

Type error

node_modules/croner/types/croner.d.ts:59:35 - error TS2304: Cannot find name 'date'.

Incorrect handling of comma-separated values with ranges and/or stepping

Hi, I'm currently evaluating cron-related libraries for usage in my project, including reviewing the code of any candidates before deciding which one to use.
Let me first say that I'm very pleased with the quality of your code and that I'm very likely to choose this project to handle cron schedules in my application.

During code review I stumbled upon a few lines regarding the handling of a pattern which as far as I know reject patterns that would be perfectly valid in cron syntax and would also successfully parse by just removing those checks. These lines were introduced with 62c0289

croner/src/pattern.js

Lines 141 to 163 in 12cc992

const split = conf.split(",");
if( split.length > 1 ) {
for( let i = 0; i < split.length; i++ ) {
this.partToArray(type, split[i], valueIndexOffset, true);
}
// Handle range with stepping (x-y/z)
} else if( conf.indexOf("-") !== -1 && conf.indexOf("/") !== -1 ) {
if (recursed) throw new Error("CronPattern: Range with stepping cannot coexist with ,");
this.handleRangeWithStepping(conf, type, valueIndexOffset);
// Handle range
} else if( conf.indexOf("-") !== -1 ) {
if (recursed) throw new Error("CronPattern: Range with stepping cannot coexist with ,");
this.handleRange(conf, type, valueIndexOffset);
// Handle stepping
} else if( conf.indexOf("/") !== -1 ) {
if (recursed) throw new Error("CronPattern: Range with stepping cannot coexist with ,");
this.handleStepping(conf, type, valueIndexOffset);

As you can see in the snippet above, in the function CronPattern.partToArray() you first split parts of the current pattern part by comma, but then reject any ranges or steppings if they are evaluated after this split, in the recursion. Contrary to the statement of the thrown error, comma-separated values can perfectly coexist with ranges and stepping.

I entered the examples from your tests for Cronitor and it parses well:
https://crontab.guru/#1,2/5__**
https://crontab.guru/#1-15/5,6__**
https://crontab.guru/#1-15,17__**

As you can see from the website's explanation these patterns are perfectly fine and if you'd just remove those checks they would indeed parse as intended. Actually you could get rid of the recursed parameter altogether.

Is there any reason I didn't see yet why you reject such patterns? If you want to accept these patterns as Cronitor does, I can submit a PR as I already made some tests locally. Just want to hear your opinion on it first.

Inconsistent bug prone timezone argument order

Cron('2023-01-23T00:00:00', { timezone: 'Asia/Kolkata' }, () => { console.log('Yay') });

is inconsistent vs default usage such as

const job = Cron('*/5 * * * * *', () => {
	console.log('This will run every fifth second');
});

timezone should come as 3rd argument, not 2nd to minimize bugs:

Cron('2023-01-23T00:00:00', () => { console.log('Yay') }, { timezone: 'Asia/Kolkata' });

Alternatively, if wishing to follow callback as the last argument convention, make it two arguments only (object + callback), like:

Cron({time: '2023-01-23T00:00:00', timezone: 'Asia/Kolkata' }, () => { console.log('Yay') });

4.3.12 timing is wrong on some schedule steps

OS: RHEL 8/7
Node: 16.15.0

Running the below on 4.3.11 gives expected results (1 run per 5 seconds)

const cron = new Cron('*/5 * * * * *');
cron.schedule(() => console.log(`running: ${new Date()}:${Date.now()}`));

Output:

running: Wed Jun 15 2022 15:00:25 GMT-0500 (Central Daylight Time):1655323225001
running: Wed Jun 15 2022 15:00:30 GMT-0500 (Central Daylight Time):1655323230001
running: Wed Jun 15 2022 15:00:35 GMT-0500 (Central Daylight Time):1655323235001
running: Wed Jun 15 2022 15:00:40 GMT-0500 (Central Daylight Time):1655323240000
running: Wed Jun 15 2022 15:00:45 GMT-0500 (Central Daylight Time):1655323245000
running: Wed Jun 15 2022 15:00:49 GMT-0500 (Central Daylight Time):1655323249999
running: Wed Jun 15 2022 15:00:50 GMT-0500 (Central Daylight Time):1655323250001
running: Wed Jun 15 2022 15:00:55 GMT-0500 (Central Daylight Time):1655323255001
running: Wed Jun 15 2022 15:00:59 GMT-0500 (Central Daylight Time):1655323259999
running: Wed Jun 15 2022 15:01:05 GMT-0500 (Central Daylight Time):1655323265000

However, 4.3.12
gives the below output, this seems to happen with all schedules at some random frequency.

running: Wed Jun 15 2022 14:56:40 GMT-0500 (Central Daylight Time):1655323000001

---- 14:56:45 is ran almost 5 seconds late immediately followed by the 14:56:50 run
**running: Wed Jun 15 2022 14:56:49 GMT-0500 (Central Daylight Time):1655323009997**
**running: Wed Jun 15 2022 14:56:50 GMT-0500 (Central Daylight Time):1655323010001**

running: Wed Jun 15 2022 14:56:55 GMT-0500 (Central Daylight Time):1655323015000
running: Wed Jun 15 2022 14:57:00 GMT-0500 (Central Daylight Time):1655323020000
running: Wed Jun 15 2022 14:57:05 GMT-0500 (Central Daylight Time):1655323025001
running: Wed Jun 15 2022 14:57:10 GMT-0500 (Central Daylight Time):1655323030001
running: Wed Jun 15 2022 14:57:15 GMT-0500 (Central Daylight Time):1655323035000

---- 14:57:20 is ran almost 5 seconds late immediately followed by the 14:57:25 run
**running: Wed Jun 15 2022 14:57:24 GMT-0500 (Central Daylight Time):1655323044999**
**running: Wed Jun 15 2022 14:57:25 GMT-0500 (Central Daylight Time):1655323045000**

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.