├── .editorconfig ├── .eslintrc.json ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yaml │ ├── release.yaml │ ├── semantic-pr-title.yaml │ └── test.yaml ├── .gitignore ├── .prettierignore ├── .releaserc ├── LICENSE ├── README.md ├── SS.png ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── index.ts └── lib │ ├── at.spec.ts │ ├── at.ts │ ├── between.spec.ts │ ├── between.ts │ ├── combine.spec.ts │ ├── combine.ts │ ├── every.spec.ts │ ├── every.ts │ ├── expression.spec.ts │ ├── expression.ts │ ├── inMonth.spec.ts │ ├── inMonth.ts │ ├── on.spec.ts │ ├── on.ts │ └── types.ts ├── tsconfig.json └── tsconfig.module.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "project": "./tsconfig.json" }, 5 | "env": { "es6": true }, 6 | "ignorePatterns": ["node_modules", "build", "coverage"], 7 | "plugins": ["import", "eslint-comments"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:eslint-comments/recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:import/typescript", 13 | "prettier", 14 | "prettier/@typescript-eslint" 15 | ], 16 | "globals": { "BigInt": true, "console": true, "WebAssembly": true }, 17 | "rules": { 18 | "@typescript-eslint/explicit-module-boundary-types": "off", 19 | "eslint-comments/disable-enable-pair": [ 20 | "error", 21 | { "allowWholeFile": true } 22 | ], 23 | "eslint-comments/no-unused-disable": "error", 24 | "import/order": [ 25 | "error", 26 | { "newlines-between": "always", "alphabetize": { "order": "asc" } } 27 | ], 28 | "sort-imports": [ 29 | "error", 30 | { "ignoreDeclarationSort": true, "ignoreCase": true } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Example Contributing Guidelines 2 | 3 | This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information. 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | - **Summary** 8 | 9 | - **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | - **What is the current behavior?** (You can also link to an open issue here) 4 | 5 | - **What is the new behavior (if this is a feature change)?** 6 | 7 | - **Other information**: 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-18.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 16 19 | 20 | - uses: actions/cache@v2 21 | with: 22 | path: '**/node_modules' 23 | key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Build 29 | run: npm run build 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-18.04 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 16 20 | 21 | - uses: actions/cache@v2 22 | with: 23 | path: '**/node_modules' 24 | key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Build 30 | run: npm run build 31 | 32 | - name: Release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | GIT_AUTHOR_NAME: 'kbariotis' 37 | GIT_AUTHOR_EMAIL: 'konmpar@gmail.com' 38 | GIT_COMMITTER_NAME: 'kbariotis' 39 | GIT_COMMITTER_EMAIL: 'konmpar@gmail.com' 40 | run: npm run semantic-release 41 | 42 | - name: Build docs 43 | run: npm run doc 44 | 45 | - name: Deploy to GH Pages 46 | uses: peaceiris/actions-gh-pages@v3 47 | with: 48 | github_token: ${{ secrets.GH_TOKEN }} 49 | publish_dir: ./build/docs 50 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pr-title.yaml: -------------------------------------------------------------------------------- 1 | name: Semantic PR title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, edited] 6 | 7 | jobs: 8 | semantic: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: amannn/action-semantic-pull-request@v3.0.0 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-18.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 16 19 | 20 | - uses: actions/cache@v2 21 | with: 22 | path: '**/node_modules' 23 | key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Test 29 | run: npm run test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build/ 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main" 4 | ], 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/changelog", 9 | "@semantic-release/npm", 10 | [ 11 | "@semantic-release/github", 12 | { 13 | "assets": [] 14 | } 15 | ], 16 | [ 17 | "@semantic-release/git", 18 | { 19 | "assets": [ 20 | "package.json" 21 | ], 22 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 23 | } 24 | ] 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kbariotis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-cron-expression 2 | 3 | [![npm](https://img.shields.io/npm/l/node-cron-expression.svg)](https://github.com/merencia/node-cron-expression/blob/master/LICENSE.md) 4 | [![npm](https://img.shields.io/npm/v/node-cron-expression.svg)](https://img.shields.io/npm/v/node-cron-expression.svg) 5 | 6 | **Declarative functional cron expression builder. Use it with tools like [node-cron](https://github.com/node-cron/node-cron) or [bull](https://github.com/OptimalBits/bull)** 7 | 8 | ![Screenshot of node-cron-expression](https://raw.githubusercontent.com/kbariotis/node-cron-expression/main/SS.png) 9 | 10 | ## Getting Started 11 | 12 | Install `node-cron-expression` using npm: 13 | 14 | ```console 15 | $ npm install --save node-cron-expression 16 | ``` 17 | 18 | Import `node-cron-expression` and build an expression 19 | 20 | ```javascript 21 | const { onDayOfTheWeek, every, everyHour } = require('node-cron-expression'); 22 | 23 | console.log(onDayOfTheWeek(6).toString()); // 0 0 * * 6 24 | console.log(everyHour().toString()); // 0 * * * * 25 | console.log(every(8).hours().toString()); // 0 */8 * * * 26 | ``` 27 | 28 | ## Documentation 29 | 30 | Find all available methods with examples [here](https://kbariotis.github.io/node-cron-expression). 31 | 32 | ## Issues 33 | 34 | Feel free to submit issues and enhancement requests [here](https://github.com/kbariotis/node-cron-expression/issues). 35 | 36 | ## Contributing 37 | 38 | In general, we follow the "fork-and-pull" Git workflow. 39 | 40 | - Fork the repo on GitHub; 41 | - Commit changes to a branch in your fork; 42 | - Pull request "upstream" with your changes; 43 | 44 | NOTE: Be sure to merge the latest from "upstream" before making a pull request! 45 | 46 | Please do not contribute code you did not write yourself, unless you are certain you have the legal ability to do so. Also ensure all contributed code can be distributed under the ISC License. 47 | 48 | ## License 49 | 50 | node-cron-expression is under [MIT License](./LICENSE). 51 | -------------------------------------------------------------------------------- /SS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbariotis/node-cron-expression/d09f828e00aa2f9bc34176e7a3df3dd89d89f42c/SS.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-cron-expression", 3 | "version": "1.3.1", 4 | "description": "Declarative functional crontab expression builder", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "repository": "https://github.com/kbariotis/node-cron-expression", 9 | "license": "MIT", 10 | "keywords": [ 11 | "cron", 12 | "crontab", 13 | "nodejs" 14 | ], 15 | "scripts": { 16 | "build": "run-p build:*", 17 | "build:main": "tsc -p tsconfig.json", 18 | "build:module": "tsc -p tsconfig.module.json", 19 | "fix": "run-s fix:*", 20 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 21 | "fix:lint": "eslint src --ext .ts --fix", 22 | "test": "run-s build test:*", 23 | "test:lint": "eslint src --ext .ts", 24 | "test:prettier": "prettier \"src/**/*.ts\" --list-different", 25 | "test:unit": "jest src/", 26 | "watch:build": "tsc -p tsconfig.json -w", 27 | "watch:test": "jest --watch src/", 28 | "doc": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 29 | "semantic-release": "semantic-release" 30 | }, 31 | "engines": { 32 | "node": ">=10" 33 | }, 34 | "devDependencies": { 35 | "@semantic-release/changelog": "^6.0.1", 36 | "@semantic-release/git": "^10.0.1", 37 | "@types/jest": "^27.4.0", 38 | "@typescript-eslint/eslint-plugin": "^4.0.1", 39 | "@typescript-eslint/parser": "^4.0.1", 40 | "eslint": "^7.8.0", 41 | "eslint-config-prettier": "^6.11.0", 42 | "eslint-plugin-eslint-comments": "^3.2.0", 43 | "eslint-plugin-import": "^2.22.0", 44 | "gh-pages": "^3.1.0", 45 | "jest": "^27.4.5", 46 | "npm-run-all": "^4.1.5", 47 | "prettier": "^2.1.1", 48 | "semantic-release": "^18.0.1", 49 | "standard-version": "^9.0.0", 50 | "ts-jest": "^27.1.2", 51 | "ts-node": "^9.0.0", 52 | "typedoc": "^0.19.0", 53 | "typescript": "^4.0.2" 54 | }, 55 | "files": [ 56 | "build/main", 57 | "build/module", 58 | "!**/*.spec.*", 59 | "!**/*.json", 60 | "CHANGELOG.md", 61 | "LICENSE", 62 | "README.md" 63 | ], 64 | "prettier": { 65 | "singleQuote": true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/at'; 2 | export * from './lib/every'; 3 | export * from './lib/combine'; 4 | export * from './lib/on'; 5 | export * from './lib/inMonth'; 6 | export * from './lib/between'; 7 | -------------------------------------------------------------------------------- /src/lib/at.spec.ts: -------------------------------------------------------------------------------- 1 | import { atMinute } from './at'; 2 | 3 | describe('at', () => { 4 | it('should run at minute', () => { 5 | expect(atMinute(23).toString()).toBe('23 * * * *'); 6 | }); 7 | 8 | it('should run at minute', () => { 9 | expect(atMinute([23, 32, 56]).toString()).toBe('23,32,56 * * * *'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/lib/at.ts: -------------------------------------------------------------------------------- 1 | import { HourExpression, MinuteExpression } from './expression'; 2 | 3 | /** 4 | * Set to run in a specific minute 5 | * 6 | * ``` 7 | * atMinute(2); // 2 * * * * 8 | * ``` 9 | */ 10 | export const atMinute = (minute: Minute | Minute[]) => { 11 | return new MinuteExpression({ 12 | minute: Array.isArray(minute) ? minute.join(',') : `${minute}`, 13 | }); 14 | }; 15 | 16 | /** 17 | * Set to run in a specific hour. Defaults to the first minute 18 | * of that hour 19 | * 20 | * ``` 21 | * atHour(3); // 0 3 * * * 22 | * ``` 23 | */ 24 | export const atHour = (hour: Hour | Hour[]) => { 25 | return new HourExpression({ 26 | hour: `${Array.isArray(hour) ? hour.join(',') : hour}`, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/lib/between.spec.ts: -------------------------------------------------------------------------------- 1 | import { atHour, atMinute } from './at'; 2 | import { between } from './between'; 3 | import { inMonth } from './inMonth'; 4 | import { onDayOfTheMonth, onDayOfTheWeek } from './on'; 5 | 6 | describe('between', () => { 7 | it('should provide minutes range', () => { 8 | expect(between(atMinute(23), atMinute(33)).toString()).toBe( 9 | '23-33 * * * *' 10 | ); 11 | }); 12 | 13 | it('should provide hours range', () => { 14 | expect(between(atHour(2), atHour(11)).toString()).toBe('* 2-11 * * *'); 15 | }); 16 | 17 | it('should provide month range', () => { 18 | expect(between(inMonth(2), inMonth(11)).toString()).toBe('* * * 2-11 *'); 19 | }); 20 | 21 | it('should provide days of the week, monday to friday', () => { 22 | expect(between(onDayOfTheWeek(0), onDayOfTheWeek(4)).toString()).toBe( 23 | '* * * * 0-4' 24 | ); 25 | }); 26 | 27 | it('should throw an error if different types', () => { 28 | expect(() => 29 | between(onDayOfTheWeek(0), onDayOfTheMonth(4)).toString() 30 | ).toThrow(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/lib/between.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DayOfTheMonthExpression, 3 | DayOfTheWeekExpression, 4 | Expression, 5 | HourExpression, 6 | MinuteExpression, 7 | MonthExpression, 8 | } from './expression'; 9 | 10 | /** 11 | * Provide two time points of the same type 12 | * to create a range 13 | * 14 | * ``` 15 | * between(atMinute(2), atMinute(20)); // 2-20 * * * * 16 | * ``` 17 | * 18 | */ 19 | export function between( 20 | startExpression: T, 21 | endExpression: T 22 | ) { 23 | if ( 24 | startExpression instanceof MinuteExpression && 25 | endExpression instanceof MinuteExpression 26 | ) { 27 | return new Expression({ 28 | minute: `${startExpression.getMinute()}-${endExpression.getMinute()}`, 29 | }); 30 | } 31 | if ( 32 | startExpression instanceof HourExpression && 33 | endExpression instanceof HourExpression 34 | ) { 35 | return new Expression({ 36 | hour: `${startExpression.getHour()}-${endExpression.getHour()}`, 37 | }); 38 | } 39 | if ( 40 | startExpression instanceof MonthExpression && 41 | endExpression instanceof MonthExpression 42 | ) { 43 | return new Expression({ 44 | month: `${startExpression.getMonth()}-${endExpression.getMonth()}`, 45 | }); 46 | } 47 | if ( 48 | startExpression instanceof DayOfTheMonthExpression && 49 | endExpression instanceof DayOfTheMonthExpression 50 | ) { 51 | return new Expression({ 52 | dayOfTheMonth: `${startExpression.getDayOfTheMonth()}-${endExpression.getDayOfTheMonth()}`, 53 | }); 54 | } 55 | if ( 56 | startExpression instanceof DayOfTheWeekExpression && 57 | endExpression instanceof DayOfTheWeekExpression 58 | ) { 59 | return new Expression({ 60 | dayOfTheWeek: `${startExpression.getDayOfTheWeek()}-${endExpression.getDayOfTheWeek()}`, 61 | }); 62 | } 63 | 64 | throw new Error('Unrecognized class or input params of different type'); 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/combine.spec.ts: -------------------------------------------------------------------------------- 1 | import { atHour, atMinute } from './at'; 2 | import { combine } from './combine'; 3 | import { every } from './every'; 4 | import { inMonth } from './inMonth'; 5 | import { onDayOfTheMonth, onDayOfTheWeek } from './on'; 6 | 7 | describe('combine', () => { 8 | it('should provide every hour at 30 minutes', () => { 9 | expect(combine(every(2).hours(), atMinute(30)).toString()).toBe( 10 | '30 */2 * * *' 11 | ); 12 | }); 13 | 14 | it('should provide every day at midnight', () => { 15 | expect(combine(atHour(0), atMinute(0)).toString()).toBe('0 0 * * *'); 16 | }); 17 | 18 | it('should provide every Thursday at midnight in July', () => { 19 | expect( 20 | combine(onDayOfTheWeek(5), atHour(0), inMonth(7), atMinute(0)).toString() 21 | ).toBe('0 0 * 7 5'); 22 | }); 23 | 24 | it('should provide every day at 2am', () => { 25 | expect(combine(atHour(2), atMinute(0)).toString()).toBe('0 2 * * *'); 26 | }); 27 | 28 | it('should provide every sunday at 2am', () => { 29 | expect(combine(onDayOfTheWeek(6), atHour(2), atMinute(0)).toString()).toBe( 30 | '0 2 * * 6' 31 | ); 32 | }); 33 | 34 | it('should combine different functions with different order', () => { 35 | const expectedValue = '32 2 15 * 6'; 36 | expect( 37 | combine( 38 | onDayOfTheWeek(6), 39 | onDayOfTheMonth(15), 40 | atHour(2), 41 | atMinute(32) 42 | ).toString() 43 | ).toBe(expectedValue); 44 | 45 | expect( 46 | combine( 47 | atHour(2), 48 | atMinute(32), 49 | onDayOfTheMonth(15), 50 | onDayOfTheWeek(6) 51 | ).toString() 52 | ).toBe(expectedValue); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/lib/combine.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DayOfTheMonthExpression, 3 | DayOfTheWeekExpression, 4 | Expression, 5 | HourExpression, 6 | MinuteExpression, 7 | MonthExpression, 8 | } from './expression'; 9 | 10 | /** 11 | * Combine different time points 12 | * 13 | * ``` 14 | * combine(atMinute(30), between(atHour(2), atHour(4))); // 30 2-4 * * * 15 | * ``` 16 | */ 17 | function combine( 18 | ...expressions: Array< 19 | | DayOfTheWeekExpression 20 | | MonthExpression 21 | | DayOfTheMonthExpression 22 | | HourExpression 23 | | MinuteExpression 24 | > 25 | ) { 26 | return expressions.reduce((initial, current) => { 27 | if (current instanceof MinuteExpression) { 28 | initial.setMinute(current.getMinute()); 29 | } 30 | if (current instanceof HourExpression) { 31 | initial.setHour(current.getHour()); 32 | } 33 | if (current instanceof DayOfTheMonthExpression) { 34 | initial.setDayOfTheMonth(current.getDayOfTheMonth()); 35 | } 36 | if (current instanceof MonthExpression) { 37 | initial.setMonth(current.getMonth()); 38 | } 39 | if (current instanceof DayOfTheWeekExpression) { 40 | initial.setDayOfTheWeek(current.getDayOfTheWeek()); 41 | } 42 | 43 | return initial; 44 | }, new Expression()); 45 | } 46 | 47 | export { combine }; 48 | -------------------------------------------------------------------------------- /src/lib/every.spec.ts: -------------------------------------------------------------------------------- 1 | import { every, everyHour, everyMinute, everyMonth } from './every'; 2 | 3 | describe('every', () => { 4 | it('should provide every minute', () => { 5 | expect(everyMinute().toString()).toBe('* * * * *'); 6 | }); 7 | 8 | it('should provide on specific minute', () => { 9 | expect(everyMinute(5).toString()).toBe('5 * * * *'); 10 | }); 11 | 12 | it('should provide on specific month', () => { 13 | expect(everyMonth(5).toString()).toBe('0 0 1 5 *'); 14 | }); 15 | 16 | it('should provide every 2 minutes', () => { 17 | expect(every(2).minutes().toString()).toBe('*/2 * * * *'); 18 | }); 19 | 20 | it('should provide every even minute', () => { 21 | expect(every(2).minutes().toString()).toBe('*/2 * * * *'); 22 | }); 23 | 24 | it('should provide every 5 minutes', () => { 25 | expect(every(5).minutes().toString()).toBe('*/5 * * * *'); 26 | }); 27 | 28 | it('should provide every quarter hour', () => { 29 | expect(every(15).minutes().toString()).toBe('*/15 * * * *'); 30 | }); 31 | 32 | it('should provide every half hour', () => { 33 | expect(every(30).minutes().toString()).toBe('*/30 * * * *'); 34 | }); 35 | 36 | it('should provide every hour', () => { 37 | expect(everyHour().toString()).toBe('0 * * * *'); 38 | }); 39 | 40 | it('should provide every 1 hour', () => { 41 | expect(every(1).hours().toString()).toBe('0 */1 * * *'); 42 | }); 43 | 44 | it('should provide every 2 hours', () => { 45 | expect(every(2).hours().toString()).toBe('0 */2 * * *'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/lib/every.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HourExpression, 3 | MinuteExpression, 4 | MonthExpression, 5 | } from './expression'; 6 | 7 | /** 8 | * Every single minute or every X minute after current date time 9 | * 10 | * ``` 11 | * everyMinute(3); // *\/3 * * * * 12 | * ``` 13 | */ 14 | export const everyMinute = (minute?: Minute) => { 15 | return new MinuteExpression({ 16 | minute: minute !== undefined ? `${minute}` : undefined, 17 | }); 18 | }; 19 | 20 | /** 21 | * Every single hour or every X hours after current date time. Defaults 22 | * to the first minute of that hour. 23 | * 24 | * ``` 25 | * everyHour(3); // 0 *\/3 * * * 26 | * ``` 27 | */ 28 | export const everyHour = (hour?: Hour) => { 29 | return new HourExpression({ 30 | minute: '0', 31 | hour: hour !== undefined ? `${hour}` : undefined, 32 | }); 33 | }; 34 | 35 | /** 36 | * Every single month or every X months after current date time. Defaults 37 | * to the first minute, first hour and first day of the month. 38 | * 39 | * ``` 40 | * everyHour(3); // 0 *\/3 * * * 41 | * ``` 42 | */ 43 | export const everyMonth = (month?: Month) => { 44 | return new MonthExpression({ 45 | minute: '0', 46 | hour: '0', 47 | dayOfTheMonth: '1', 48 | month: month !== undefined ? `${month}` : month, 49 | }); 50 | }; 51 | 52 | /** 53 | * Every X time point 54 | * 55 | * ``` 56 | * every(3).minutes(); // *\/3 * * * 57 | * every(3).hours(); // 0 *\/3 * * * 58 | * every(3).months(); // 0 0 1 *\/3 * 59 | * ``` 60 | */ 61 | export const every = (input: number) => { 62 | return { 63 | minutes: () => 64 | new MinuteExpression({ 65 | minute: `*/${input}`, 66 | }), 67 | hours: () => 68 | new HourExpression({ 69 | minute: '0', 70 | hour: `*/${input}`, 71 | }), 72 | months: () => 73 | new MonthExpression({ 74 | minute: '0', 75 | hour: '0', 76 | dayOfTheMonth: '1', 77 | month: `*/${input}`, 78 | }), 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /src/lib/expression.spec.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from './expression'; 2 | 3 | describe('expression', () => { 4 | it('should provide should run every minute', () => { 5 | const expression = new Expression(); 6 | expect(expression.toString()).toBe('* * * * *'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/lib/expression.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create an expression with default values. Every value defaults to `*`. 3 | * 4 | * ``` 5 | * const expression = new Expression({hour: '1'}); // * 1 * * * 6 | * ``` 7 | */ 8 | export class Expression { 9 | private minute: string; 10 | private hour: string; 11 | private dayOfTheMonth: string; 12 | private month: string; 13 | private dayOfTheWeek: string; 14 | 15 | constructor(params?: { 16 | minute?: string; 17 | hour?: string; 18 | dayOfTheMonth?: string; 19 | month?: string; 20 | dayOfTheWeek?: string; 21 | }) { 22 | this.minute = (params && params.minute) || '*'; 23 | this.hour = (params && params.hour) || '*'; 24 | this.dayOfTheMonth = (params && params.dayOfTheMonth) || '*'; 25 | this.month = (params && params.month) || '*'; 26 | this.dayOfTheWeek = (params && params.dayOfTheWeek) || '*'; 27 | } 28 | 29 | public setMinute(minute: string) { 30 | this.minute = minute; 31 | } 32 | public setHour(hour: string) { 33 | this.hour = hour; 34 | } 35 | public setDayOfTheMonth(dayOfTheMonth: string) { 36 | this.dayOfTheMonth = dayOfTheMonth; 37 | } 38 | public setMonth(month: string) { 39 | this.month = month; 40 | } 41 | public setDayOfTheWeek(dayOfTheWeek: string) { 42 | this.dayOfTheWeek = dayOfTheWeek; 43 | } 44 | 45 | public getMinute() { 46 | return this.minute; 47 | } 48 | public getHour() { 49 | return this.hour; 50 | } 51 | public getDayOfTheMonth() { 52 | return this.dayOfTheMonth; 53 | } 54 | public getMonth() { 55 | return this.month; 56 | } 57 | public getDayOfTheWeek() { 58 | return this.dayOfTheWeek; 59 | } 60 | 61 | public toString() { 62 | return `${this.minute} ${this.hour} ${this.dayOfTheMonth} ${this.month} ${this.dayOfTheWeek}`; 63 | } 64 | } 65 | 66 | /** 67 | * Alias of Expression to infer the return type 68 | */ 69 | export class MinuteExpression extends Expression {} 70 | 71 | /** 72 | * Alias of Expression to infer the return type 73 | */ 74 | export class HourExpression extends Expression {} 75 | 76 | /** 77 | * Alias of Expression to infer the return type 78 | */ 79 | export class DayOfTheMonthExpression extends Expression {} 80 | 81 | /** 82 | * Alias of Expression to infer the return type 83 | */ 84 | export class MonthExpression extends Expression {} 85 | 86 | /** 87 | * Alias of Expression to infer the return type 88 | */ 89 | export class DayOfTheWeekExpression extends Expression {} 90 | -------------------------------------------------------------------------------- /src/lib/inMonth.spec.ts: -------------------------------------------------------------------------------- 1 | import { inMonth } from './inMonth'; 2 | 3 | describe('inMonth', () => { 4 | it('should provide in January', () => { 5 | expect(inMonth(1).toString()).toBe('* * * 1 *'); 6 | }); 7 | 8 | it('should provide in January and December', () => { 9 | expect(inMonth([1, 12]).toString()).toBe('* * * 1,12 *'); 10 | }); 11 | 12 | it('should provide in January and December using literals', () => { 13 | expect(inMonth(['January', 'December']).toString()).toBe('* * * 1,12 *'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/lib/inMonth.ts: -------------------------------------------------------------------------------- 1 | import { MonthExpression } from './expression'; 2 | 3 | const map = { 4 | January: 1, 5 | February: 2, 6 | March: 3, 7 | April: 4, 8 | May: 5, 9 | June: 6, 10 | July: 7, 11 | August: 8, 12 | September: 9, 13 | October: 10, 14 | November: 11, 15 | December: 12, 16 | }; 17 | 18 | /** 19 | * In specific month or multiple specific months 20 | * 21 | * ``` 22 | * inMonth(3); // * * * 3 * 23 | * inMonth([3,5]); // * * * 3,5 * 24 | * 25 | * inMonth('January'); // * * * 1 * 26 | * inMonth(['January', 'December']); // * * * 1,12 * 27 | * ``` 28 | * 29 | */ 30 | export const inMonth = ( 31 | month: Month | Month[] | MonthLiteral | MonthLiteral[] 32 | ) => { 33 | const arrayInput = Array.isArray(month) ? month : [month]; 34 | 35 | if (typeof arrayInput[0] === 'string') { 36 | return new MonthExpression({ 37 | month: (arrayInput as MonthLiteral[]) 38 | .map((month) => map[month]) 39 | .join(','), 40 | }); 41 | } 42 | 43 | return new MonthExpression({ 44 | month: arrayInput.join(','), 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/lib/on.spec.ts: -------------------------------------------------------------------------------- 1 | import { onDayOfTheMonth, onDayOfTheWeek } from './on'; 2 | 3 | describe('on', () => { 4 | it('should provide on Sunday', () => { 5 | expect(onDayOfTheWeek(6).toString()).toBe('0 0 * * 6'); 6 | }); 7 | 8 | it('should provide on 3rd day of the month', () => { 9 | expect(onDayOfTheMonth(3).toString()).toBe('0 0 3 * *'); 10 | }); 11 | 12 | it('should provide day of the week using literals', () => { 13 | expect(onDayOfTheWeek('Sunday').toString()).toBe('0 0 * * 0'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/lib/on.ts: -------------------------------------------------------------------------------- 1 | import { DayOfTheMonthExpression, DayOfTheWeekExpression } from './expression'; 2 | 3 | /** 4 | * In specific day of the month 5 | * 6 | * ``` 7 | * onDayOfTheMonth(3); // * * 3 * * 8 | * onDayOfTheMonth([3,5]); // * * 3,5 * * 9 | * ``` 10 | * 11 | */ 12 | export const onDayOfTheMonth = ( 13 | dayOfTheMonth: DayOfTheMonth | DayOfTheMonth[] 14 | ) => { 15 | return new DayOfTheMonthExpression({ 16 | minute: '0', 17 | hour: '0', 18 | dayOfTheMonth: `${ 19 | Array.isArray(dayOfTheMonth) ? dayOfTheMonth.join(',') : dayOfTheMonth 20 | }`, 21 | }); 22 | }; 23 | 24 | const map = { 25 | Monday: 1, 26 | Tuesday: 2, 27 | Wednesday: 3, 28 | Thursday: 4, 29 | Friday: 5, 30 | Saturday: 6, 31 | Sunday: 0, 32 | }; 33 | 34 | /** 35 | * In specific day of the week 36 | * 37 | * ``` 38 | * onDayOfTheWeek(3); // 0 0 * * 3 39 | * onDayOfTheWeek([3,5]); // 0 0 * * 3,5 40 | * 41 | * onDayOfTheWeek('Monday'); // 0 0 * * 1 42 | * onDayOfTheWeek(['Sunday', 'Thursday']); // 0 0 * * 0,4 43 | * ``` 44 | */ 45 | export const onDayOfTheWeek = ( 46 | dayOfTheWeek: 47 | | DayOfTheWeek 48 | | DayOfTheWeek[] 49 | | DayOfTheWeekLiteral 50 | | DayOfTheWeekLiteral[] 51 | ) => { 52 | const arrayInput = Array.isArray(dayOfTheWeek) 53 | ? dayOfTheWeek 54 | : [dayOfTheWeek]; 55 | 56 | if (typeof arrayInput[0] === 'string') { 57 | return new DayOfTheWeekExpression({ 58 | minute: '0', 59 | hour: '0', 60 | dayOfTheWeek: (arrayInput as DayOfTheWeekLiteral[]) 61 | .map((day) => map[day]) 62 | .join(','), 63 | }); 64 | } 65 | 66 | return new DayOfTheWeekExpression({ 67 | minute: '0', 68 | hour: '0', 69 | dayOfTheWeek: arrayInput.join(','), 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | type Minute = 3 | | 0 4 | | 1 5 | | 2 6 | | 3 7 | | 4 8 | | 5 9 | | 6 10 | | 7 11 | | 8 12 | | 9 13 | | 10 14 | | 11 15 | | 12 16 | | 13 17 | | 14 18 | | 15 19 | | 16 20 | | 17 21 | | 18 22 | | 19 23 | | 20 24 | | 21 25 | | 22 26 | | 23 27 | | 24 28 | | 25 29 | | 26 30 | | 27 31 | | 28 32 | | 29 33 | | 30 34 | | 31 35 | | 32 36 | | 33 37 | | 34 38 | | 35 39 | | 36 40 | | 37 41 | | 38 42 | | 39 43 | | 40 44 | | 41 45 | | 42 46 | | 43 47 | | 44 48 | | 45 49 | | 46 50 | | 47 51 | | 48 52 | | 49 53 | | 50 54 | | 51 55 | | 52 56 | | 53 57 | | 54 58 | | 55 59 | | 56 60 | | 57 61 | | 58 62 | | 59; 63 | 64 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 65 | type Hour = 66 | | 0 67 | | 1 68 | | 2 69 | | 3 70 | | 4 71 | | 5 72 | | 6 73 | | 7 74 | | 8 75 | | 9 76 | | 10 77 | | 11 78 | | 12 79 | | 13 80 | | 14 81 | | 15 82 | | 16 83 | | 17 84 | | 18 85 | | 19 86 | | 20 87 | | 21 88 | | 22 89 | | 23; 90 | 91 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 92 | type Month = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; 93 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 94 | type MonthLiteral = 95 | | 'January' 96 | | 'February' 97 | | 'March' 98 | | 'April' 99 | | 'May' 100 | | 'June' 101 | | 'July' 102 | | 'August' 103 | | 'September' 104 | | 'October' 105 | | 'November' 106 | | 'December'; 107 | 108 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 109 | type DayOfTheMonth = 110 | | 1 111 | | 2 112 | | 3 113 | | 4 114 | | 5 115 | | 6 116 | | 7 117 | | 8 118 | | 9 119 | | 10 120 | | 11 121 | | 12 122 | | 13 123 | | 14 124 | | 15 125 | | 16 126 | | 17 127 | | 18 128 | | 19 129 | | 20 130 | | 21 131 | | 22 132 | | 23 133 | | 24 134 | | 25 135 | | 26 136 | | 27 137 | | 28 138 | | 29 139 | | 30 140 | | 31; 141 | 142 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 143 | type DayOfTheWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6; 144 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 145 | type DayOfTheWeekLiteral = 146 | | 'Monday' 147 | | 'Tuesday' 148 | | 'Wednesday' 149 | | 'Thursday' 150 | | 'Friday' 151 | | 'Saturday' 152 | | 'Sunday'; 153 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2017", 5 | "outDir": "build/main", 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "declaration": true, 10 | "inlineSourceMap": true, 11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 12 | "resolveJsonModule": true /* Include modules imported with .json extension. */, 13 | 14 | "strict": true /* Enable all strict type-checking options. */, 15 | 16 | /* Strict Type-Checking Options */ 17 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 18 | // "strictNullChecks": true /* Enable strict null checks. */, 19 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 20 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 21 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 22 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 23 | 24 | /* Additional Checks */ 25 | "noUnusedLocals": true /* Report errors on unused locals. */, 26 | "noUnusedParameters": true /* Report errors on unused parameters. */, 27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 29 | 30 | /* Debugging Options */ 31 | "traceResolution": false /* Report module resolution log messages. */, 32 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 33 | "listFiles": false /* Print names of files part of the compilation. */, 34 | "pretty": true /* Stylize errors and messages using color and context. */, 35 | 36 | /* Experimental Options */ 37 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 38 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 39 | 40 | "lib": ["es2017"], 41 | "types": ["jest", "node"], 42 | "typeRoots": ["node_modules/@types", "src/types"] 43 | }, 44 | "include": ["src/**/*.ts"], 45 | "exclude": ["node_modules/**"], 46 | "compileOnSave": false 47 | } 48 | -------------------------------------------------------------------------------- /tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "outDir": "build/module", 6 | "module": "esnext" 7 | }, 8 | "exclude": [ 9 | "node_modules/**" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------