├── example.js ├── .npmignore ├── .gitignore ├── index.d.ts ├── package.json ├── LICENSE ├── test.html ├── README.md ├── test.js └── timexe.js /example.js: -------------------------------------------------------------------------------- 1 | var timexe = require('./timexe'); 2 | var res1=timexe('* * * * * /5', function(){ 3 | console.log('hello The time is now '+(new Date()).toLocaleString()) 4 | }); 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Editor backups 30 | *~ 31 | 32 | notes.txt 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | nyt.txt 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | # Editor backups 32 | *~ 33 | 34 | notes.txt 35 | bug* 36 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace timexe { 2 | type Timex = string; 3 | type ID = number; 4 | type Action = (param?: T) => void; 5 | 6 | interface Result { 7 | result?: string; 8 | error?: string; 9 | id: ID; 10 | } 11 | 12 | interface Item { 13 | timex: string; 14 | action: Action; 15 | param: T; 16 | timeoutShortened: boolean; 17 | } 18 | } 19 | 20 | interface Timexe { 21 | (timex: timexe.Timex, action: timexe.Action, param?: T): timexe.Result; 22 | 23 | add(timex: timexe.Timex, action: timexe.Action, param?: T): timexe.Result; 24 | 25 | remove(id: timexe.ID): timexe.Result; 26 | 27 | get(id?: timexe.ID): timexe.Item | Array>; 28 | 29 | timeResolution: number; 30 | maxTimerDelay: number; 31 | } 32 | 33 | declare const timexe: Timexe; 34 | export as namespace timexe; 35 | export = timexe; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timexe", 3 | "version": "1.0.5", 4 | "description": "Yet another cron clone – but this one is better :o) - new improved syntax – milliseconds resolution – both for node JS and browser", 5 | "main": "timexe.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/paragi/timexe.git" 13 | }, 14 | "keywords": [ 15 | "poller", 16 | "queue", 17 | "task", 18 | "worker", 19 | "job", 20 | "cron", 21 | "extended cron format", 22 | "javascript", 23 | "seconds", 24 | "milliseconds", 25 | "scheduler", 26 | "timer", 27 | "node" 28 | ], 29 | "author": "Simon Riget @Paragi", 30 | "bugs": { 31 | "url": "https://github.com/paragi/timexe/issues" 32 | }, 33 | "maintainers": [ 34 | "paragi " 35 | ], 36 | "runkitExampleFilename": "example.js", 37 | "homepage": "https://github.com/paragi/timexe#readme", 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Ovyerus 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all 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 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 20 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timed execution test page 6 | 7 | 40 | 41 | 42 |

Timexe test page

43 | 44 | 45 |
46 | Short term timer test: 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 |

64 | 65 | 66 |
67 | Time expression syntax test cases:

68 |
69 | 70 |

71 | 72 |
73 | Timer test cases 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Test of deferred timer"Not fired
81 |
82 | 83 | 84 |

85 |
86 | List of active timers: 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Timexe - A Cron-like Timer and scheduler witn milliseconds resolution 2 | ## Also works in a browser 3 | [![License](https://img.shields.io/npm/l/timexe.svg)](https://github.com/paragi/timexe/LICENSE) 4 | [![Downloads per month](http://img.shields.io/npm/dm/timexe.svg)](https://www.npmjs.org/package/timexe) 5 | [![downloads per month](http://img.shields.io/npm/v/timexe.svg)](https://www.npmjs.org/package/timexe) 6 | [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/paragi/timexe/issues) 7 | [![Known Vulnerabilities](https://snyk.io/test/github/paragi/timexe/badge.svg)](https://snyk.io/test/github/paragi/timexe) 8 | 9 | ### Features 10 | * Milliseconds resolution 11 | * Improved cron-like syntax 12 | * Recalculate long running timers, to improve accuracy 13 | * No dependencies 14 | * Works both for node JS and browser inclusion 15 | * Time expressions include ranges, sets, timestamps, weekdays, yeardays and more 16 | * Battle-tested. Very reliable. 17 | 18 | 19 | ### Precission 20 | At present it seems to have an accuracy within 2 ms in node and up to 25 ms i most browsers. 21 | It seems that execution is defered somewhat during process load. 22 | 23 | 24 | ### Example 25 | To add a timed job every day at noon: 26 | 27 | ```javascript 28 | timexe(”* * * 12”, function(){console.log(“hello - it is noon again”)}); 29 | ``` 30 | 31 | The time expression syntax is like cron, but in reverse order: starting with year, month... (where as cron start with minutes, hours...) plus some enhancements. 32 | 33 | ## Time expression Syntax 34 | --- 35 | The basic syntax is a series of fields specifying the time(s): 36 | 37 | ` ...` 38 | 39 | or a time stamp. 40 | 41 | Each field contain wild-cards, ranges, sets, not flags and every flags. Plus some special flags for year days and week days. 42 | 43 | The epoch timestamp is seconds since 1970-01-01 UTC with fractions of second as decimal part: 44 | 45 | @[.] 46 | 47 | ##### Field syntax: `[!][-][-]|[,] | / | *` 48 | ``` 49 | space : field separator 50 | * : all values. Flags will be ignored. 51 | ! : not 52 | / : every (can not be combined with ! and range) 53 | - : Negative values are counted back from the maximum value 54 | a-b : range. both a and b included. 55 | a,b : set of values 56 | 57 | Day field can have the one of the following flags as well 58 | y: day of year 59 | w: day of week 1-7 (1 is Monday) 60 | ``` 61 | Unspecified minor fields are assumed to have the lowest possible value 62 | 63 | ## Note: 64 | - Time expression are in local time where as time stamps are in UTC 65 | - Month and weekday use another offset then the javascript Date function: 66 | - Month 1 is January 67 | - Week day 1-7 starting with Monday 68 | 69 | 70 | ### Examples og timer expressions: 71 | | Time | Time expression | 72 | | --- |:---| 73 | | Every hour | \* \* \* \* | 74 | | Every day at noon | \* \* \* 12 75 | | Every 3th Hour on work days | \* \* w1-5 /3 76 | | Once at a specific epoch time |@1422821601.123 77 | | Once at a specific time | 2014 5 13 18 53 7 300 230 78 | | 2th to last day of the month at noon | \* \* -2 12 79 | | 3th last day of the year | \* \* y-3 80 | | 3 times an hour during work time | \* \* w1-5 9-17 0,20,40 81 | | Every morning at 7:30 but not on weekends | \* \* !6-7 7 30 82 | | Every 10 minutes in the day time | \* \* \* 8-18 /10 83 | 84 | 85 | ## API 86 | --- 87 | ##### timexe(timeExpression, callBack [,parameterToCallBack]) 88 | 89 | Returns a result object: 90 | ``` 91 | { 92 | result: “ok” or null 93 | error: A failure explanation or null 94 | id: integer used to identify the timer 95 | } 96 | ``` 97 | 98 | 99 | ##### timexe.remove(id) 100 | where id is the value returned from timexe 101 | 102 | Returns a result object: 103 | ``` 104 | { 105 | result: “ok” or null 106 | error: A failure explanation or null 107 | } 108 | ``` 109 | 110 | 111 | ##### timexe.get([id]) 112 | where the optional id is the value returned from timexe 113 | 114 | Returnes either a timexe timer object if id is given, or an array of all active timer objects. 115 | 116 | 117 | ### Settings 118 | ##### timexe.timeResolution (integer) 119 | This is the minimum time resolution for an expression. Minimum value is 1 ms. default is 2 ms. 120 | This should be more the the execution time and delays do to load, of the intepreter. 121 | 122 | ##### timexe.maxTimerDelay (integer) 123 | Maximum run time of a setTimeout call. Some javascripts engines cant handle more then 32 bit = 0x7FFFFFF. thats about 28 days. default is 86400000 = 1 day. 124 | When this time have elapsed, the time expression are reevaluated. 125 | 126 | 127 | ## With node JS 128 | --- 129 | #### Install 130 | ```bash 131 | $ npm install timexe 132 | ``` 133 | #### Use 134 | ```js 135 | var timexe = require('timexe'); 136 | 137 | // Add 138 | var res1=timexe(”* * * 12”, function(){console.log(“hello wolrd”)}); 139 | 140 | // Remove 141 | var res2=timexe.remove(res1.id); 142 | ``` 143 | 144 | 145 | ## With HTML & javascript 146 | --- 147 | #### Install 148 | Copy files to folder. 149 | 150 | #### Use 151 | ```html 152 | 153 | 160 | ``` 161 | 162 | ## Change log 163 | 1.0.5 Added types for typescript 164 | 165 | 1.0.3 Bugfix: mismatched ID 166 | 167 | 1.0.2 Temp bugfix of mismatched ID. 168 | 169 | 1.0.1 Documentation 170 | 171 | 1.0.0 Fixed test cases: 172 | 173 | "Cascading carry" failed 174 | 175 | "Only Wildcards = every hour" failed 176 | 177 | Documentation 178 | 179 | 0.9.19 Bug fix. failed when "processs" undefined 180 | 181 | 0.9.18 Documentation update. 182 | 183 | 0.9.14 A quick code review. No bugs repported for 2 years. 184 | 185 | 0.9.13 Minor changes to timex.js 186 | 187 | 0.9.12 Minor changes to comments and reamne.md 188 | 189 | 0.9.11 Minor changes to comments and reamne.md 190 | 191 | 0.9.10 Adapted example to runkit 192 | 193 | 0.9.9 Minor bugfix. timexe.list made into a regular array. 194 | 195 | #### Help 196 | Please don't hesitate to submit an issue on github! It's the only way to make it better. 197 | 198 | But please be prepared to present a test case. 199 | 200 | Contributions of almost any kind are welcome. 201 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /*============================================================================*\ 2 | Check dependencies 3 | \*============================================================================*/ 4 | // For node JS 5 | if(typeof process !== 'undefined'){ 6 | try { 7 | var timexe=require('timexe'); 8 | } 9 | catch(e){ 10 | var timexe=require('./timexe'); 11 | } 12 | 13 | // For HTML 14 | }else{ 15 | if(typeof timexe === 'undefined'){ 16 | console.error("Please include the timexe.js script"); 17 | console.error(''); 18 | } 19 | } 20 | 21 | /*============================================================================*\ 22 | Activate action into the future 23 | \*============================================================================*/ 24 | function timerIn(ms,action,p){ 25 | var ta=new Date(); 26 | ta.setMilliseconds(ta.getMilliseconds()+ms); 27 | ta.getTime(); 28 | var str= Math.floor(ta.getTime()/1000) + "." + ("00" + Math.round(ta.getTime()%1000)).slice(-3); 29 | var res=timexe("@" + str,action,p); 30 | } 31 | 32 | /*============================================================================*\ 33 | Display short term timer pattern 34 | \*============================================================================*/ 35 | function test1(){ 36 | var i=1; 37 | var ii=1; 38 | 39 | function action(p){ 40 | // HTML output 41 | if(typeof process === 'undefined'){ 42 | var bg=document.getElementById('t'+i+ii).style.background; 43 | // Delete 44 | document.getElementById('t'+i+ii).innerHTML=''; 45 | document.getElementById('t'+i+ii).style.background = "#333377"; 46 | i++; 47 | if(i>3){ i=1;ii++}; 48 | if(ii>3){ ii=1}; 49 | 50 | // Write 51 | document.getElementById('t'+i+ii).innerHTML=p; 52 | document.getElementById('t'+i+ii).style.color="#333377"; 53 | document.getElementById('t'+i+ii).style.background="#f0d988"; 54 | 55 | // Node JS 56 | }else{ 57 | process.stdout.write(' '+p); 58 | } 59 | } 60 | 61 | var res=timexe("* * * * * * /50",action,":c)"); 62 | 63 | // deactivate after 5 sec. 64 | var fid=res.id; 65 | timerIn(5155,timexe.remove,fid); 66 | } 67 | 68 | /*============================================================================*\ 69 | Test timer expression syntax 70 | 71 | Test cases 72 | Array format: time expression, epoch time to use as now, 73 | Expected result - timezone oftes 74 | \*============================================================================*/ 75 | var testCase=[ 76 | ["2015 2 1 21 13 21 123",1400000000,1422821601.123,"Simple",false] 77 | ,["2013 2 1 21 13 21 123",1400000000,0,"Passed date",false] 78 | ,["* !/2 -4 12-16",1400000000,1403776800 ,"Mutually exclusive flags",false] 79 | ,["* * * * * 0",1420066799,1420066800,"Cascading carry",false] 80 | ,["* * * * ",1400000000,1400000400,"Only Wildcards = every hour,",false] 81 | ,["* * * * 12",1400000000,1400008320,"Wildcards with one fixed value",false] 82 | ,["**1 12",1400000000,1401624000,"Wildcards with two fixed value",false] 83 | 84 | // basic methods 85 | ,["2014 -1 -2 -3 -4 -5 -600",1400000000,1417211815.4,"Negative values",false] 86 | ,["* * y-3 ",1400000010,1419724800 ,"3th Last day of the year",false] 87 | ,["* * y-0 ",1400000010,1419984000 ,"Last day of the year",false] 88 | ,["* * w-3 ",1400000010,1400112000 ,"3th Last day of the week",false] 89 | ,["* * w-0 ",1400000010,1400371200 ,"Last day of the week",false] 90 | ,["!2014 !1 !1 !0 !0 !0 !0",1400000000,1422838861.001,"Not",false] 91 | ,["/4 /3 /2 /1 /10 /20 /100",1464999001,1465006220,"Every",false] 92 | ,["2015-2114 6-9 12-15 12-23 20-30 10-20 100- 600",1400000000,1434111610.100,"Ranges",false] 93 | ,["* !4-9 !1-15 12-23 20-30 10-20 100-600",1400000000,1413462010.100,"Ranges",false] 94 | ,["* * * * * /5",1432752245,1432752250,"Every 5 seconds",false] 95 | // range 96 | ,["* !10-6 *",1400000000,1404172800,"Not inverse range",false] 97 | ,["* * * * * 10-6 *",1400000827,1400000830,"Range over 0",false] 98 | ,["*-5--4*" ,1400000000,1404172800,"Range with negative start value",false] 99 | ,["*1-12*" ,1400000000,1400025600,"Range covering all values=*",false] 100 | ,["*-3--4*" ,1400000000,1400025600,"Strange wildcard Range coverinf all=*",false] 101 | ,["* 1-1 *" ,1400000000,1420070400,"Range of one value",false] 102 | ,["!2027-2015" ,1400000000,1830297600,"Not Reverse Range",false] 103 | ,["!2027-2014" ,1400000000,1830297600,"Not Reverse Range",false] 104 | ,["2015-6 -8" ,1400000000,1427846400,"Malformed range with negative end value",false] 105 | ,["* 6--3",1400000000,1401580800,"Range with negative value",false] 106 | ,["* 1-12",1400000000,1401580800,"Cover whole range",false] 107 | 108 | // Special day flags 109 | ,["* * y/3 12",1420066899,1420286400,"Every 3th day",false] 110 | ,["* * w/3 12",1420066799,1420286400,"Every wednesday and saturday",false] 111 | ,["* * w6 12",1420066899,1420286400,"Saturdays at noon",false] 112 | ,["* * w1,5 12",1420066899,1420200000,"mon, wedns and fri-day at noot",false] 113 | ,["* * w!1-5 12",1420066899,1420286400,"Not week day at noot",false] 114 | ,["* * y100,200 12",1400000000,1405771200,"Yead day set",false] 115 | ,["* * y!-1--350 12",1400000000,1400068800,"Year day strage range + carry",false] 116 | ,["* * y!-1--350 12",1421097899,1421409600,"Year day strage range + carry",false] 117 | ,["**/y-3",1400000000,1419724800,"Large every yearday",false] 118 | ,["* * y-2" ,1400000000,1419811200,"Negative yearday",false] 119 | ,["* * y-100,300" ,1400000000,1411344000,"Set of yeardays",false] 120 | ,["* 3-5 y343 * * ",1400000000,1418083200,"Ignore month in year day",false] 121 | 122 | // Exceeding constraints 123 | ,["1969 0 0 0 0 0 0 0",1400000000,0,"Under",false] 124 | ,["* 0 0 0 0 0 0 0",1400000000,0,"Under",false] 125 | ,["3000 13 32 24 60 60 1000",1400000000,0,"Over",false] 126 | ,["* 13 32 24 60 60 1000",1400000000,0,"Over",false] 127 | 128 | // Values causing Errors 129 | ,["-2",1400000000,32503680000,"Negative year",false] 130 | ,["/0",1400000000,1420070400,"Every 0 = *",false] 131 | ,["/)+| $ *1234hsd%1234",1400000000,0,"Malformed nonsens",false] 132 | ,[" * * 1,5, 10 , !-3",1400000000,1401616800,"Malformed set",false] 133 | 134 | // Complex 135 | ,["!* 6-8 !w6,7,2,1 !18-8",1400000000,1401699600,"miscellaneous mixture",false] 136 | ,["* * 1,5,7-8 ",1400000000,1401638400,"set negative day that looks like a range",false] 137 | 138 | // Epoch 139 | ,["@1234567890.123",1400000000,1234567890.123,"Epoch with mS",false] 140 | ,["@1234567890",1400000000,1234567890,"Epoch without mS",false] 141 | 142 | // reentry test 143 | ,["* 5 ",1400000010,1430438400 ,"Unused field preset to low limit (reentry test)",false] 144 | 145 | // Debugging cases 146 | ,["* * * * * /1 ",1400000000,1400000001 ,"Every 1 sec = *",false] 147 | ,["* * * * * /5",1433103355,1433103360,"Every 5 seconds",false] 148 | ,["* !1-12",1400000000,0,"All months exclusive",false] 149 | ,["* * w1,7 1 0",1447575482,1447632000,"Pascal's Late sunday error",false] 150 | ,["* * * * 0,5,10,20,30", 1665788401, 1665788700, "Single digit sorting error", false] 151 | 152 | ]; 153 | 154 | function test2(){ 155 | var now=new Date(); 156 | // Adjust for local time zone. All but epoch time stamps is local time. 157 | var of=now.getTimezoneOffset()*60; // Minutes > seconds 158 | var html; 159 | var jt; 160 | // Set other then system timezone 161 | // process.env.TZ='America/New_York' 162 | 163 | // For browser 164 | if(typeof process === 'undefined'){ 165 | html=""; 166 | html+=""; 167 | html+=""; 168 | html+="
Now: " + now.getTime() / 1000 +"Time zone: UTC "+ (of>0?"+":'')+(of!=0?of:'')+"
"; 169 | 170 | // Run test cases 171 | html+=""; 172 | for(var i=0; i"; 177 | html+=""; 178 | // html+=""; 181 | if(result.time==testCase[i][2] 182 | || result.time==jt.getTimezoneOffset()*60+testCase[i][2]) 183 | html+=""; 184 | else 185 | html+=""; 186 | // html+=""; 187 | 188 | 189 | html+=""; 190 | } 191 | 192 | document.getElementById("syntax").innerHTML+=html+"
" + testCase[i][0]+""+( new Date(result.time*1000).toLocaleFormat("%F (%a w%U) %T.")) 179 | html+=""+jt.toLocaleString(); 180 | html+="."+("00" + Math.round(result.time%1*1000)).slice(-3) +"OkFailed:("+result.time+")"+result.error+"" + result.time +"=="+ testCase[i][2] +"
" + testCase[i][3]+"
"; 193 | 194 | // For Node JS 195 | }else{ 196 | for(var i=0; i"; 254 | 255 | for(var ii in list[i]){ 256 | html+="" 257 | if(typeof list[i][ii] === 'function') 258 | html+=""; 262 | } 263 | html+="
"+ii+" [function] "; 259 | else 260 | html+=" "+list[i][ii]+" "; 261 | html+="
" 264 | } 265 | html+=""; 266 | document.getElementById('timer list').innerHTML+=html; 267 | } 268 | 269 | // For browser 270 | if(typeof process === 'undefined'){ 271 | test1(); 272 | test2(); 273 | test3(); 274 | list(); 275 | 276 | // For Node JS 277 | }else{ 278 | console.log("Testing time expression syntax:"); 279 | test1(); 280 | console.log("Testing short term timer pattern:"); 281 | test2(); 282 | console.log("Testing timer:"); 283 | test3(); 284 | } 285 | -------------------------------------------------------------------------------- /timexe.js: -------------------------------------------------------------------------------- 1 | /*============================================================================*\ 2 | timexe - Timed execution for node JS. 3 | 4 | syntax: timexe(