├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── Readme.md ├── benchmark ├── constraint │ ├── next-bench.js │ └── val-bench.js └── core │ └── schedule-bench.js ├── bower.json ├── component.json ├── coverage.html ├── example ├── browser.html ├── modifier.js ├── server.js └── timeperiod.js ├── index-browserify.js ├── index.js ├── later-core.js ├── later-core.min.js ├── later.js ├── later.min.js ├── package.json ├── src ├── array │ ├── array.js │ ├── index.js │ ├── next.js │ ├── nextinvalid.js │ ├── prev.js │ ├── previnvalid.js │ └── sort.js ├── bower.js ├── compat │ ├── index.js │ ├── indexof.js │ └── trim.js ├── component.js ├── constraint │ ├── day.js │ ├── dayofweek.js │ ├── dayofweekcount.js │ ├── dayofyear.js │ ├── fulldate.js │ ├── hour.js │ ├── index.js │ ├── minute.js │ ├── month.js │ ├── second.js │ ├── time.js │ ├── weekofmonth.js │ ├── weekofyear.js │ └── year.js ├── core │ ├── compile.js │ ├── index.js │ ├── schedule.js │ ├── setinterval.js │ └── settimeout.js ├── date │ ├── constant.js │ ├── date.js │ ├── index.js │ ├── next.js │ ├── nextrollover.js │ ├── prev.js │ ├── prevrollover.js │ └── timezone.js ├── end.js ├── later-core.js ├── later.js ├── modifier │ ├── after.js │ ├── before.js │ ├── index.js │ └── modifier.js ├── package.js ├── parse │ ├── cron.js │ ├── ebnf_grammar.txt │ ├── index.js │ ├── parse.js │ ├── recur.js │ └── text.js └── start.js └── test ├── array ├── next-test.js ├── nextinvalid-test.js ├── prev-test.js ├── previnvalid-test.js └── sort-test.js ├── constraint ├── day-test.js ├── dayofweek-test.js ├── dayofweekcount-test.js ├── dayofyear-test.js ├── hour-test.js ├── minute-test.js ├── month-test.js ├── runner.js ├── second-test.js ├── time-test.js ├── weekofmonth-test.js ├── weekofyear-test.js └── year-test.js ├── core ├── compile-test.js ├── schedule-test.js ├── setinterval-test.js └── settimeout-test.js ├── example ├── cron-example-test.js ├── recur-example-test.js └── text-example-test.js ├── modifier ├── after-test.js └── before-test.js └── parse ├── cron-test.js ├── recur-test.js └── text-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src-cov 3 | later-cov.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: "npm test" 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "node" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2013 BunKat 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 13 | 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 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPORTER ?= dot 2 | TESTS ?= $(shell find test -name "*-test.js") 3 | 4 | all: \ 5 | later.js \ 6 | later.min.js \ 7 | later-core.js \ 8 | later-core.min.js \ 9 | component.json \ 10 | bower.json \ 11 | package.json 12 | 13 | .PHONY: clean all test test-cov 14 | 15 | test: later.js 16 | @NODE_ENV=test ./node_modules/.bin/mocha --reporter $(REPORTER) $(TESTS) 17 | 18 | test-cov: later-cov.js 19 | @LATER_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html 20 | 21 | later-cov.js: later.js 22 | @rm -f $@ 23 | @jscoverage --no-highlight src src-cov \ 24 | --no-instrument=later.js \ 25 | --no-instrument=later-core.js \ 26 | --no-instrument=modifier/index.js \ 27 | --no-instrument=array/index.js \ 28 | --no-instrument=date/index.js \ 29 | --no-instrument=constraint/index.js \ 30 | --no-instrument=parse/index.js \ 31 | --no-instrument=core/index.js \ 32 | --no-instrument=compat/index.js \ 33 | --no-instrument=start.js \ 34 | --no-instrument=end.js \ 35 | --no-instrument=component.js \ 36 | --no-instrument=package.js 37 | node_modules/.bin/smash src-cov/later.js > later-cov.js 38 | @chmod a-w $@ 39 | 40 | benchmark: all 41 | @echo 'Constraints --------' 42 | @node benchmark/constraint/next-bench.js 43 | @echo 'Schedules --------' 44 | @node benchmark/core/schedule-bench.js 45 | 46 | later.js: $(shell node_modules/.bin/smash --list src/later.js) 47 | @rm -f $@ 48 | node_modules/.bin/smash src/later.js | node_modules/.bin/uglifyjs - -b indent-level=2 -o $@ 49 | @chmod a-w $@ 50 | 51 | later.min.js: later.js 52 | @rm -f $@ 53 | node_modules/.bin/uglifyjs $< -c -m -o $@ 54 | 55 | later-core.js: $(shell node_modules/.bin/smash --list src/later-core.js) 56 | @rm -f $@ 57 | node_modules/.bin/smash src/later-core.js | node_modules/.bin/uglifyjs - -b indent-level=2 -o $@ 58 | @chmod a-w $@ 59 | 60 | later-core.min.js: later-core.js 61 | @rm -f $@ 62 | node_modules/.bin/uglifyjs $< -c -m -o $@ 63 | 64 | component.json: src/component.js later.js 65 | @rm -f $@ 66 | node src/component.js > $@ 67 | @chmod a-w $@ 68 | 69 | package.json: src/package.js later.js 70 | @rm -f $@ 71 | node src/package.js > $@ 72 | @chmod a-w $@ 73 | 74 | bower.json: src/bower.js later.js 75 | @rm -f $@ 76 | node src/bower.js > $@ 77 | @chmod a-w $@ 78 | 79 | clean: 80 | rm -f later*.js package.json component.json bower.json -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## This project is no longer being maintained. It should be treated as sample code on one technique to calculate future instances of complex schedules. If you are looking to fire a task every 1 minute, use a cron scheduler instead. 2 | 3 | # [Later](http://bunkat.github.io/later/) [![Build Status](https://travis-ci.org/bunkat/later.svg)](https://travis-ci.org/bunkat/later) 4 | 5 | _Later_ is a library for describing recurring schedules and calculating their future occurrences. It supports a very flexible schedule definition including support for composite schedules and schedule exceptions. Create new schedules manually, via Cron expression, via text expressions, or using a fully chainable API. 6 | 7 | Types of schedules supported by _Later_: 8 | 9 | * Run a report on the last day of every month at 12 AM except in December 10 | * Install patches on the 2nd Tuesday of every month at 4 AM 11 | * Gather CPU metrics every 10 mins Mon - Fri and every 30 mins Sat - Sun 12 | * Send out a scary e-mail at 13:13:13 every Friday the 13th 13 | 14 | #### For complete documentation visit [http://bunkat.github.io/later/](http://bunkat.github.io/later/). 15 | 16 | 17 | ## Installation 18 | Using npm: 19 | 20 | $ npm install later 21 | 22 | Using bower: 23 | 24 | $ bower install later 25 | 26 | ## Building 27 | 28 | To build the minified javascript files for _later_, run `npm install` to install dependencies and then: 29 | 30 | $ make all 31 | 32 | ## Running tests 33 | 34 | To run the tests for _later_, run `npm install` to install dependencies and then: 35 | 36 | $ make test 37 | 38 | ## Versioning 39 | 40 | Releases will be numbered with the following format: 41 | 42 | `..` 43 | 44 | And constructed with the following guidelines: 45 | 46 | * Breaking backward compatibility bumps the major (and resets the minor and patch) 47 | * New additions without breaking backward compatibility bumps the minor (and resets the patch) 48 | * Bug fixes and misc changes bumps the patch 49 | 50 | For more information on SemVer, please visit [http://semver.org/](http://semver.org/). 51 | 52 | ## Bug tracker 53 | 54 | Have a bug or a feature request? [Please open a new issue](https://github.com/bunkat/later/issues). 55 | 56 | ## Change Log 57 | 58 | ### Later v1.2.0 59 | * Implemented predefined scheduling definitions for cron 60 | - @yearly, @annually, @monthly, @weekly, @daily, and @hourly are now parsed correctly 61 | - Submitted by pekeler (thanks!) 62 | 63 | ### Later v1.1.8, v1.1.9 64 | 65 | * Fixed npm and bower entry points 66 | 67 | ### Later v1.1.7 68 | 69 | * Various bug fixes 70 | 71 | ### Later v1.1.3 72 | 73 | * Merge consecutive ranges when using composite schedules (fixes issues #27) 74 | 75 | ### Later v1.1.1 and v1.1.2 76 | 77 | * Fixed handling of ranged schedules which never go invalid. End date is undefined for these types of schedules. 78 | 79 | ### Later v1.1.0 80 | 81 | * Implemented fullDate (fd) constraint to specify a specific occurrence (or exception) 82 | - `later.parse.recur().on(new Date(2013,3,21,10,30,0)).fullDate()` 83 | 84 | ### Later v1.0.0 85 | 86 | * Refactored core engine so that it could be better tested 87 | - Added over 41,500 tests and fixed hundreds of edge cases that were unfortunately broken in v0.0.20 88 | * Core engine is now extensible via custom time periods and custom modifiers 89 | - Full examples included in the documentation 90 | * Added support for finding valid ranges as well as valid instances of schedules 91 | - _Later_ can now be used to schedule activities and meetings as well as point in time occurrences 92 | * Improved support for finding past ranges and instances 93 | - Searching forward or backward now produces the same valid occurrences 94 | * No more need to specify a resolution! 95 | - _Later_ now automatically handles this internally, you no longer need to specify your desired resolution. 'Every 5 minutes' now does exactly what you would expect it to :) 96 | * Changing between UTC and local time has changed. 97 | - Use `later.date.UTC()` and `later.date.localTime()` to switch between the two. 98 | * API for parsers has changed. 99 | - Recur is now at `later.parse.recur()` 100 | - Cron is now at `later.parse.cron(expr)` 101 | - Text is now at `later.parse.text(expr)` 102 | * API for calculating occurrences has changed. 103 | - Schedules are now compiled using `later.schedule(schedule)` 104 | - getNext is now `later.schedule(schedule).next(count, start, end)` 105 | - getPrev is now `later.schedule(schedule).prev(count, start, end)` 106 | * `After` meaning 'don't start until after this amount of time' has been deprecated. 107 | - This was a hack since people had a hard time with resolutions. With resolutions gone, this is no longer needed and is deprecated since it produced non-deterministic schedules. 108 | 109 | **Note:** Schedule definitions did not change (unless you were using `after` constraints which have been deprecated). If you stored any schedule definitions from v0.0.20, they should continue to work unchanged in v1.0.0. 110 | -------------------------------------------------------------------------------- /benchmark/constraint/next-bench.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'), 2 | later = require('../../index'), 3 | suite = new Benchmark.Suite('next'); 4 | 5 | suite 6 | .add('year', function() { 7 | later.year.next(new Date(2012, 4, 15, 20, 15, 13), 2014); 8 | }) 9 | .add('month', function() { 10 | later.month.next(new Date(2012, 4, 15, 20, 15, 13), 1); 11 | }) 12 | .add('day', function() { 13 | later.day.next(new Date(2012, 4, 15, 20, 15, 13), 1); 14 | }) 15 | .add('hour', function() { 16 | later.hour.next(new Date(2012, 4, 15, 20, 15, 13), 1); 17 | }) 18 | .add('minute', function() { 19 | later.minute.next(new Date(2012, 4, 15, 20, 15, 13), 1); 20 | }) 21 | .add('second', function() { 22 | later.second.next(new Date(2012, 4, 15, 20, 15, 13), 1); 23 | }) 24 | .add('dayofweek', function() { 25 | later.dayOfWeek.next(new Date(2012, 4, 15, 20, 15, 13), 1); 26 | }) 27 | .add('dayofweekcount', function() { 28 | later.dayOfWeekCount.next(new Date(2012, 4, 15, 20, 15, 13), 1); 29 | }) 30 | .add('dayofyear', function() { 31 | later.dayOfYear.next(new Date(2012, 4, 15, 20, 15, 13), 1); 32 | }) 33 | .add('time', function() { 34 | later.time.next(new Date(2012, 4, 15, 20, 15, 13), 1); 35 | }) 36 | .add('weekofmonth', function() { 37 | later.weekOfMonth.next(new Date(2012, 4, 15, 20, 15, 13), 1); 38 | }) 39 | .add('weekofyear', function() { 40 | later.weekOfYear.next(new Date(2012, 4, 15, 20, 15, 13), 1); 41 | }) 42 | .on('cycle', function(event) { 43 | console.log(String(event.target)); 44 | }) 45 | .run({async: false}); -------------------------------------------------------------------------------- /benchmark/constraint/val-bench.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'), 2 | later = require('../../index'), 3 | suite = new Benchmark.Suite('val'); 4 | 5 | suite 6 | /*.add('year', function() { 7 | later.year.val(new Date(2012, 4, 15, 20, 15, 13)); 8 | }) 9 | .add('month', function() { 10 | later.month.val(new Date(2012, 4, 15, 20, 15, 13)); 11 | }) 12 | .add('day', function() { 13 | later.day.val(new Date(2012, 4, 15, 20, 15, 13)); 14 | }) 15 | .add('hour', function() { 16 | later.hour.val(new Date(2012, 4, 15, 20, 15, 13)); 17 | }) 18 | .add('minute', function() { 19 | later.minute.val(new Date(2012, 4, 15, 20, 15, 13)); 20 | }) 21 | .add('second', function() { 22 | later.second.val(new Date(2012, 4, 15, 20, 15, 13)); 23 | }) 24 | .add('dayofweek', function() { 25 | later.dayOfWeek.val(new Date(2012, 4, 15, 20, 15, 13)); 26 | }) 27 | .add('dayofweekcount', function() { 28 | later.dayOfWeekCount.val(new Date(2012, 4, 15, 20, 15, 13)); 29 | })*/ 30 | .add('dayofyear', function() { 31 | later.dayOfYear.val(new Date(2012, 4, 15, 20, 15, 13)); 32 | }) 33 | /*.add('time', function() { 34 | later.time.val(new Date(2012, 4, 15, 20, 15, 13)); 35 | }) 36 | .add('weekofmonth', function() { 37 | later.weekOfMonth.val(new Date(2012, 4, 15, 20, 15, 13)); 38 | }) 39 | .add('weekofyear', function() { 40 | later.weekOfYear.val(new Date(2012, 4, 15, 20, 15, 13)); 41 | })*/ 42 | .on('cycle', function(event) { 43 | console.log(String(event.target)); 44 | }) 45 | .run({async: true}); -------------------------------------------------------------------------------- /benchmark/core/schedule-bench.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'), 2 | later = require('../../index'), 3 | suite = new Benchmark.Suite('next'); 4 | 5 | var schedSimple = later.parse.cron('* */5 * * * *'), 6 | compiledSimple = later.schedule(schedSimple); 7 | 8 | var schedComplex = later.parse.cron('0 5 15W * ?'), 9 | compiledComplex = later.schedule(schedComplex); 10 | 11 | suite 12 | .add('simple next', function() { 13 | compiledSimple.next(); 14 | }) 15 | .add('complex next', function() { 16 | compiledComplex.next(); 17 | }) 18 | .on('cycle', function(event) { 19 | console.log(String(event.target)); 20 | }) 21 | .run({async: false}); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "later", 3 | "version": "1.2.0", 4 | "description": "Determine later (or previous) occurrences of recurring schedules", 5 | "keywords": [ 6 | "schedule", 7 | "occurrences", 8 | "recur", 9 | "cron" 10 | ], 11 | "author": "BunKat ", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/bunkat/later.git" 15 | }, 16 | "main": "later.js", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "later", 3 | "version": "1.2.0", 4 | "main": "./later.js" 5 | } 6 | -------------------------------------------------------------------------------- /example/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /example/modifier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Modifier 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Example of creating a custom modifier. See 6 | * http://bunkat.github.io/later/modifiers.html#custom for more details. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | var later = require('../index'); 14 | 15 | // create the new modifier 16 | later.modifier.month = later.modifier.m = function(period, values) { 17 | if(period.name !== 'month') { 18 | throw new Error('Month modifier only works with months!'); 19 | } 20 | 21 | return { 22 | name: 'reIndexed ' + period.name, 23 | range: period.range, 24 | val: function(d) { return period.val(d) - 1; }, 25 | isValid: function(d, val) { return period.isValid(d, val+1); }, 26 | extent: function(d) { return [0, 11]; }, 27 | start: period.start, 28 | end: period.end, 29 | next: function(d, val) { return period.next(d, val+1); }, 30 | prev: function(d, val) { return period.prev(d, val+1); } 31 | }; 32 | }; 33 | 34 | // use our new modifier in a schedule 35 | var sched = later.parse.recur().customModifier('m', 2).month(), 36 | next = later.schedule(sched).next(1, new Date(2013, 3, 21)); 37 | 38 | console.log(next.toUTCString()); 39 | // Sat, 01 Mar 2014 00:00:00 GMT -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var later = require('../index'); // require('later') if installed via npm 2 | 3 | 4 | // create the desired schedule 5 | var sched = later.parse.text('every 5 mins on the 30th sec'); 6 | 7 | // calculate the next 5 occurrences using local time 8 | later.date.localTime(); 9 | var results = later.schedule(sched).next(5); 10 | -------------------------------------------------------------------------------- /example/timeperiod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timeperiod 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Example of creating a custom time period. See 6 | * http://bunkat.github.io/later/time-periods.html#custom for more details. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | var later = require('../index'); 14 | 15 | // PartOfDay time period 16 | // 0 = before noon (morning) 17 | // 1 = after noon, before 6pm (afternoon) 18 | // 2 = after 6pm (evening) 19 | later.partOfDay = later.pd = { 20 | 21 | // the name of this time period 22 | name: 'part of day', 23 | 24 | // the minimum amount of seconds that moving from one value to the next 25 | // value will cover. in this case, the minimum is roughly 6 hours 26 | range: later.h.range * 6, 27 | 28 | // return the appropriate val based on the current hour 29 | val: function(d) { 30 | return later.h.val(d) < 12 ? 0 : 31 | later.h.val(d) < 18 ? 1 : 32 | 2; 33 | }, 34 | 35 | // use val(d) to determine if a particular value is valid 36 | isValid: function(d, val) { 37 | return later.pd.val(d) === val; 38 | }, 39 | 40 | // ours is constant since every day will have the same number of ranges 41 | extent: function(d) { return [0, 2]; }, 42 | 43 | // start is the first date that has the same val(d) as the d. in our case 44 | // this is either hour 0, 12, or 18 depending on what part of the day we 45 | // are in 46 | start: function(d) { 47 | var hour = later.pd.val(d) === 0 ? 0 : 48 | later.pd.val(d) === 1 ? 12 : 49 | 18; 50 | 51 | // next is a helper that automatically creates a day in the right timezone 52 | return later.date.next( 53 | later.Y.val(d), 54 | later.M.val(d), 55 | later.D.val(d), 56 | hour 57 | ); 58 | }, 59 | 60 | // end is the last date that has the same val(d) as the d. in our case this 61 | // is the last second of the part of the day we are in 62 | end: function(d) { 63 | var hour = later.pd.val(d) === 0 ? 11 : 64 | later.pd.val(d) === 1 ? 5 : 65 | 23; 66 | 67 | // prev is a helper that automatically creates a day in the right timezone 68 | // with unspecified date parts set to the maximum (this case will set 69 | // minutes and seconds to 59 for us) 70 | return later.date.prev( 71 | later.Y.val(d), 72 | later.M.val(d), 73 | later.D.val(d), 74 | hour 75 | ); 76 | }, 77 | 78 | // move to the next instance of the specified time of day, noting that it 79 | // may occur on the following day 80 | next: function(d, val) { 81 | var hour = val === 0 ? 0 : val === 1 ? 12 : 18; 82 | 83 | return later.date.next( 84 | later.Y.val(d), 85 | later.M.val(d), 86 | // increment the day if we already passed the desired time period 87 | later.D.val(d) + (hour < later.h.val(d) ? 1 : 0), 88 | hour 89 | ); 90 | }, 91 | 92 | // move to the prev instance of the specified time of day, noting that it 93 | // may occur on the previous day 94 | prev: function(d, val) { 95 | var hour = val === 0 ? 11 : val === 1 ? 5 : 23; 96 | 97 | return later.date.prev( 98 | later.Y.val(d), 99 | later.M.val(d), 100 | // decrement the day if we already passed the desired time period 101 | later.D.val(d) + (hour > later.h.val(d) ? -1 : 0), 102 | hour 103 | ); 104 | } 105 | }; 106 | 107 | // use our new time period in a schedule 108 | later.date.localTime(); 109 | var sched = later.parse.recur().every(15).minute().on(2).customPeriod('pd'), 110 | next = later.schedule(sched).next(5, new Date(2013, 3, 21)); 111 | 112 | console.log(next); 113 | //[ Sun Apr 21 2013 18:00:00, 114 | // Sun Apr 21 2013 18:15:00, 115 | // Sun Apr 21 2013 18:30:00, 116 | // Sun Apr 21 2013 18:45:00, 117 | // Sun Apr 21 2013 19:00:00 ] -------------------------------------------------------------------------------- /index-browserify.js: -------------------------------------------------------------------------------- 1 | require("./later"); 2 | module.exports = later; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var globals = ["document", "window", "later"], 2 | globalValues = {}; 3 | 4 | globals.forEach(function(g) { 5 | if (g in global) globalValues[g] = global[g]; 6 | }); 7 | 8 | require(process.env['LATER_COV'] ? "./later-cov" : "./later"); 9 | 10 | module.exports = later; 11 | 12 | globals.forEach(function(g) { 13 | if (g in globalValues) global[g] = globalValues[g]; 14 | else delete global[g]; 15 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "later", 3 | "version": "1.2.0", 4 | "description": "Determine later (or previous) occurrences of recurring schedules", 5 | "keywords": [ 6 | "schedule", 7 | "occurrences", 8 | "recur", 9 | "cron" 10 | ], 11 | "author": "BunKat ", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/bunkat/later.git" 15 | }, 16 | "main": "index.js", 17 | "browserify": "index-browserify.js", 18 | "jam": { 19 | "main": "later.js", 20 | "shim": { 21 | "exports": "later" 22 | } 23 | }, 24 | "devDependencies": { 25 | "smash": "~0.0.8", 26 | "mocha": "*", 27 | "should": ">=0.6.3", 28 | "jslint": "*", 29 | "uglify-js": "*", 30 | "benchmark": "*" 31 | }, 32 | "license": "MIT", 33 | "scripts": { 34 | "test": "./node_modules/.bin/mocha test/**/*-test.js --reporter dot" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/array/array.js: -------------------------------------------------------------------------------- 1 | later.array = {}; -------------------------------------------------------------------------------- /src/array/index.js: -------------------------------------------------------------------------------- 1 | import "array"; 2 | import "sort"; 3 | import "next"; 4 | import "nextinvalid"; 5 | import "prev"; 6 | import "previnvalid"; -------------------------------------------------------------------------------- /src/array/next.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Next 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Returns the next valid value in a range of values, wrapping as needed. Assumes 6 | * the array has already been sorted. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.array.next = function (val, values, extent) { 14 | 15 | var cur, 16 | zeroIsLargest = extent[0] !== 0, 17 | nextIdx = 0; 18 | 19 | for(var i = values.length-1; i > -1; --i) { 20 | cur = values[i]; 21 | 22 | if(cur === val) { 23 | return cur; 24 | } 25 | 26 | if(cur > val || (cur === 0 && zeroIsLargest && extent[1] > val)) { 27 | nextIdx = i; 28 | continue; 29 | } 30 | 31 | break; 32 | } 33 | 34 | return values[nextIdx]; 35 | }; -------------------------------------------------------------------------------- /src/array/nextinvalid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Next Invalid 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Returns the next invalid value in a range of values, wrapping as needed. Assumes 6 | * the array has already been sorted. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.array.nextInvalid = function (val, values, extent) { 14 | 15 | var min = extent[0], max = extent[1], len = values.length, 16 | zeroVal = values[len-1] === 0 && min !== 0 ? max : 0, 17 | next = val, 18 | i = values.indexOf(val), 19 | start = next; 20 | 21 | while(next === (values[i] || zeroVal)) { 22 | 23 | next++; 24 | if(next > max) { 25 | next = min; 26 | } 27 | 28 | i++; 29 | if(i === len) { 30 | i = 0; 31 | } 32 | 33 | if(next === start) { 34 | return undefined; 35 | } 36 | } 37 | 38 | return next; 39 | }; -------------------------------------------------------------------------------- /src/array/prev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Previous 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Returns the previous valid value in a range of values, wrapping as needed. Assumes 6 | * the array has already been sorted. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.array.prev = function (val, values, extent) { 14 | 15 | var cur, len = values.length, 16 | zeroIsLargest = extent[0] !== 0, 17 | prevIdx = len-1; 18 | 19 | for(var i = 0; i < len; i++) { 20 | cur = values[i]; 21 | 22 | if(cur === val) { 23 | return cur; 24 | } 25 | 26 | if(cur < val || (cur === 0 && zeroIsLargest && extent[1] < val)) { 27 | prevIdx = i; 28 | continue; 29 | } 30 | 31 | break; 32 | } 33 | 34 | return values[prevIdx]; 35 | }; -------------------------------------------------------------------------------- /src/array/previnvalid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Previous Invalid 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Returns the previous invalid value in a range of values, wrapping as needed. Assumes 6 | * the array has already been sorted. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.array.prevInvalid = function (val, values, extent) { 14 | 15 | var min = extent[0], max = extent[1], len = values.length, 16 | zeroVal = values[len-1] === 0 && min !== 0 ? max : 0, 17 | next = val, 18 | i = values.indexOf(val), 19 | start = next; 20 | 21 | while(next === (values[i] || zeroVal)) { 22 | next--; 23 | 24 | if(next < min) { 25 | next = max; 26 | } 27 | 28 | i--; 29 | if(i === -1) { 30 | i = len-1; 31 | } 32 | 33 | if(next === start) { 34 | return undefined; 35 | } 36 | } 37 | 38 | return next; 39 | }; -------------------------------------------------------------------------------- /src/array/sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sort 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Sorts an array in natural ascending order, placing zero at the end 6 | * if zeroIsLast is true. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.array.sort = function (arr, zeroIsLast) { 14 | arr.sort(function(a,b) { 15 | return +a - +b; 16 | }); 17 | 18 | if(zeroIsLast && arr[0] === 0) { 19 | arr.push(arr.shift()); 20 | } 21 | }; -------------------------------------------------------------------------------- /src/bower.js: -------------------------------------------------------------------------------- 1 | var later = require("../index"); 2 | 3 | console.log(JSON.stringify({ 4 | "name": "later", 5 | "version": later.version, 6 | "description": "Determine later (or previous) occurrences of recurring schedules", 7 | "keywords": ["schedule", "occurrences", "recur", "cron"], 8 | "author": "BunKat ", 9 | "repository" : { 10 | "type" : "git", 11 | "url" : "git://github.com/bunkat/later.git" 12 | }, 13 | "main": "later.js", 14 | "license": "MIT" 15 | }, null, 2)); -------------------------------------------------------------------------------- /src/compat/index.js: -------------------------------------------------------------------------------- 1 | import "indexof"; 2 | import "trim"; -------------------------------------------------------------------------------- /src/compat/indexof.js: -------------------------------------------------------------------------------- 1 | // indexOf compares searchElement to elements of the Array using strict 2 | // equality (the same method used by the ===, or triple-equals, operator). 3 | // 4 | // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf 5 | // 6 | if (!Array.prototype.indexOf) { 7 | Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { 8 | "use strict"; 9 | if (this == null) { 10 | throw new TypeError(); 11 | } 12 | var t = Object(this); 13 | var len = t.length >>> 0; 14 | if (len === 0) { 15 | return -1; 16 | } 17 | var n = 0; 18 | if (arguments.length > 1) { 19 | n = Number(arguments[1]); 20 | if (n != n) { // shortcut for verifying if it's NaN 21 | n = 0; 22 | } else if (n != 0 && n != Infinity && n != -Infinity) { 23 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); 24 | } 25 | } 26 | if (n >= len) { 27 | return -1; 28 | } 29 | var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 30 | for (; k < len; k++) { 31 | if (k in t && t[k] === searchElement) { 32 | return k; 33 | } 34 | } 35 | return -1; 36 | } 37 | } -------------------------------------------------------------------------------- /src/compat/trim.js: -------------------------------------------------------------------------------- 1 | // The trim method returns the string stripped of whitespace from both ends. 2 | // trim does not affect the value of the string itself. 3 | // 4 | // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim 5 | // 6 | if(!String.prototype.trim) { 7 | String.prototype.trim = function () { 8 | return this.replace(/^\s+|\s+$/g,''); 9 | }; 10 | } -------------------------------------------------------------------------------- /src/component.js: -------------------------------------------------------------------------------- 1 | var later = require("../index"); 2 | 3 | console.log(JSON.stringify({ 4 | "name": "later", 5 | "version": later.version, 6 | "main": "./later.js" 7 | }, null, 2)); -------------------------------------------------------------------------------- /src/constraint/day.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Day Constraint (D) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a day of month (date) constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.day = later.D = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'day', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 86400, 23 | 24 | /** 25 | * The day value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.D || (d.D = later.date.getDate.call(d)); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.D.val(d) === (val || later.D.extent(d)[1]); 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid day values of the month specified. 45 | * Zero to specify the last day of the month. 46 | * 47 | * @param {Date} d: The date indicating the month to find the extent of 48 | */ 49 | extent: function(d) { 50 | if(d.DExtent) return d.DExtent; 51 | 52 | var month = later.M.val(d), 53 | max = later.DAYS_IN_MONTH[month-1]; 54 | 55 | if(month === 2 && later.dy.extent(d)[1] === 366) { 56 | max = max+1; 57 | } 58 | 59 | return (d.DExtent = [1, max]); 60 | }, 61 | 62 | /** 63 | * The start of the day of the specified date. 64 | * 65 | * @param {Date} d: The specified date 66 | */ 67 | start: function(d) { 68 | return d.DStart || (d.DStart = later.date.next( 69 | later.Y.val(d), later.M.val(d), later.D.val(d))); 70 | }, 71 | 72 | /** 73 | * The end of the day of the specified date. 74 | * 75 | * @param {Date} d: The specified date 76 | */ 77 | end: function(d) { 78 | return d.DEnd || (d.DEnd = later.date.prev( 79 | later.Y.val(d), later.M.val(d), later.D.val(d))); 80 | }, 81 | 82 | /** 83 | * Returns the start of the next instance of the day value indicated. Returns 84 | * the first day of the next month if val is greater than the number of 85 | * days in the following month. 86 | * 87 | * @param {Date} d: The starting date 88 | * @param {int} val: The desired value, must be within extent 89 | */ 90 | next: function(d, val) { 91 | val = val > later.D.extent(d)[1] ? 1 : val; 92 | var month = later.date.nextRollover(d, val, later.D, later.M), 93 | DMax = later.D.extent(month)[1]; 94 | 95 | val = val > DMax ? 1 : val || DMax; 96 | 97 | return later.date.next( 98 | later.Y.val(month), 99 | later.M.val(month), 100 | val 101 | ); 102 | }, 103 | 104 | /** 105 | * Returns the end of the previous instance of the day value indicated. Returns 106 | * the last day in the previous month if val is greater than the number of days 107 | * in the previous month. 108 | * 109 | * @param {Date} d: The starting date 110 | * @param {int} val: The desired value, must be within extent 111 | */ 112 | prev: function(d, val) { 113 | var month = later.date.prevRollover(d, val, later.D, later.M), 114 | DMax = later.D.extent(month)[1]; 115 | 116 | return later.date.prev( 117 | later.Y.val(month), 118 | later.M.val(month), 119 | val > DMax ? DMax : val || DMax 120 | ); 121 | } 122 | 123 | }; -------------------------------------------------------------------------------- /src/constraint/dayofweek.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Day of Week Constraint (dw) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a day of week constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.dayOfWeek = later.dw = later.d = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'day of week', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 86400, 23 | 24 | /** 25 | * The day of week value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.dw || (d.dw = later.date.getDay.call(d)+1); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.dw.val(d) === (val || 7); 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid day of week values. Unlike the standard 45 | * Date object, Later day of week goes from 1 to 7. 46 | */ 47 | extent: function() { 48 | return [1, 7]; 49 | }, 50 | 51 | /** 52 | * The start of the day of the specified date. 53 | * 54 | * @param {Date} d: The specified date 55 | */ 56 | start: function(d) { 57 | return later.D.start(d); 58 | }, 59 | 60 | /** 61 | * The end of the day of the specified date. 62 | * 63 | * @param {Date} d: The specified date 64 | */ 65 | end: function(d) { 66 | return later.D.end(d); 67 | }, 68 | 69 | /** 70 | * Returns the start of the next instance of the day of week value indicated. 71 | * 72 | * @param {Date} d: The starting date 73 | * @param {int} val: The desired value, must be within extent 74 | */ 75 | next: function(d, val) { 76 | val = val > 7 ? 1 : val || 7; 77 | 78 | return later.date.next( 79 | later.Y.val(d), 80 | later.M.val(d), 81 | later.D.val(d) + (val - later.dw.val(d)) + (val <= later.dw.val(d) ? 7 : 0)); 82 | }, 83 | 84 | /** 85 | * Returns the end of the previous instance of the day of week value indicated. 86 | * 87 | * @param {Date} d: The starting date 88 | * @param {int} val: The desired value, must be within extent 89 | */ 90 | prev: function(d, val) { 91 | val = val > 7 ? 7 : val || 7; 92 | 93 | return later.date.prev( 94 | later.Y.val(d), 95 | later.M.val(d), 96 | later.D.val(d) + (val - later.dw.val(d)) + (val >= later.dw.val(d) ? -7 : 0)); 97 | } 98 | }; -------------------------------------------------------------------------------- /src/constraint/dayofweekcount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Day of Week Count Constraint (dc) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a day of week count constraint type. This constraint is used 6 | * to specify schedules like '2nd Tuesday of every month'. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | later.dayOfWeekCount = later.dc = { 13 | 14 | /** 15 | * The name of this constraint. 16 | */ 17 | name: 'day of week count', 18 | 19 | /** 20 | * The rough amount of seconds between start and end for this constraint. 21 | * (doesn't need to be exact) 22 | */ 23 | range: 604800, 24 | 25 | /** 26 | * The day of week count value of the specified date. 27 | * 28 | * @param {Date} d: The date to calculate the value of 29 | */ 30 | val: function(d) { 31 | return d.dc || (d.dc = Math.floor((later.D.val(d)-1)/7)+1); 32 | }, 33 | 34 | /** 35 | * Returns true if the val is valid for the date specified. 36 | * 37 | * @param {Date} d: The date to check the value on 38 | * @param {Integer} val: The value to validate 39 | */ 40 | isValid: function(d, val) { 41 | return (later.dc.val(d) === val) || 42 | (val === 0 && later.D.val(d) > later.D.extent(d)[1] - 7); 43 | }, 44 | 45 | /** 46 | * The minimum and maximum valid day values of the month specified. 47 | * Zero to specify the last day of week count of the month. 48 | * 49 | * @param {Date} d: The date indicating the month to find the extent of 50 | */ 51 | extent: function(d) { 52 | return d.dcExtent || (d.dcExtent = [1, Math.ceil(later.D.extent(d)[1] /7)]); 53 | }, 54 | 55 | /** 56 | * The first day of the month with the same day of week count as the date 57 | * specified. 58 | * 59 | * @param {Date} d: The specified date 60 | */ 61 | start: function(d) { 62 | return d.dcStart || (d.dcStart = 63 | later.date.next( 64 | later.Y.val(d), 65 | later.M.val(d), 66 | Math.max(1, ((later.dc.val(d) - 1) * 7) + 1 || 1))); 67 | }, 68 | 69 | /** 70 | * The last day of the month with the same day of week count as the date 71 | * specified. 72 | * 73 | * @param {Date} d: The specified date 74 | */ 75 | end: function(d) { 76 | return d.dcEnd || (d.dcEnd = 77 | later.date.prev( 78 | later.Y.val(d), 79 | later.M.val(d), 80 | Math.min(later.dc.val(d) * 7, later.D.extent(d)[1]))); 81 | }, 82 | 83 | /** 84 | * Returns the next earliest date with the day of week count specified. 85 | * 86 | * @param {Date} d: The starting date 87 | * @param {int} val: The desired value, must be within extent 88 | */ 89 | next: function(d, val) { 90 | val = val > later.dc.extent(d)[1] ? 1 : val; 91 | var month = later.date.nextRollover(d, val, later.dc, later.M), 92 | dcMax = later.dc.extent(month)[1]; 93 | 94 | val = val > dcMax ? 1 : val; 95 | 96 | var next = later.date.next( 97 | later.Y.val(month), 98 | later.M.val(month), 99 | val === 0 ? later.D.extent(month)[1] - 6 : 1 + (7 * (val - 1)) 100 | ); 101 | 102 | if(next.getTime() <= d.getTime()) { 103 | month = later.M.next(d, later.M.val(d)+1); 104 | 105 | return later.date.next( 106 | later.Y.val(month), 107 | later.M.val(month), 108 | val === 0 ? later.D.extent(month)[1] - 6 : 1 + (7 * (val - 1)) 109 | ); 110 | } 111 | 112 | return next; 113 | }, 114 | 115 | /** 116 | * Returns the closest previous date with the day of week count specified. 117 | * 118 | * @param {Date} d: The starting date 119 | * @param {int} val: The desired value, must be within extent 120 | */ 121 | prev: function(d, val) { 122 | var month = later.date.prevRollover(d, val, later.dc, later.M), 123 | dcMax = later.dc.extent(month)[1]; 124 | 125 | val = val > dcMax ? dcMax : val || dcMax; 126 | 127 | return later.dc.end(later.date.prev( 128 | later.Y.val(month), 129 | later.M.val(month), 130 | 1 + (7 * (val - 1)) 131 | )); 132 | } 133 | 134 | }; -------------------------------------------------------------------------------- /src/constraint/dayofyear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Day of Year Constraint (dy) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a day of year constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.dayOfYear = later.dy = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'day of year', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 86400, 23 | 24 | /** 25 | * The day of year value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.dy || (d.dy = 31 | Math.ceil(1 + (later.D.start(d).getTime() - later.Y.start(d).getTime()) / later.DAY)); 32 | }, 33 | 34 | /** 35 | * Returns true if the val is valid for the date specified. 36 | * 37 | * @param {Date} d: The date to check the value on 38 | * @param {Integer} val: The value to validate 39 | */ 40 | isValid: function(d, val) { 41 | return later.dy.val(d) === (val || later.dy.extent(d)[1]); 42 | }, 43 | 44 | /** 45 | * The minimum and maximum valid day of year values of the month specified. 46 | * Zero indicates the last day of the year. 47 | * 48 | * @param {Date} d: The date indicating the month to find the extent of 49 | */ 50 | extent: function(d) { 51 | var year = later.Y.val(d); 52 | 53 | // shortcut on finding leap years since this function gets called a lot 54 | // works between 1901 and 2099 55 | return d.dyExtent || (d.dyExtent = [1, year % 4 ? 365 : 366]); 56 | }, 57 | 58 | /** 59 | * The start of the day of year of the specified date. 60 | * 61 | * @param {Date} d: The specified date 62 | */ 63 | start: function(d) { 64 | return later.D.start(d); 65 | }, 66 | 67 | /** 68 | * The end of the day of year of the specified date. 69 | * 70 | * @param {Date} d: The specified date 71 | */ 72 | end: function(d) { 73 | return later.D.end(d); 74 | }, 75 | 76 | /** 77 | * Returns the start of the next instance of the day of year value indicated. 78 | * 79 | * @param {Date} d: The starting date 80 | * @param {int} val: The desired value, must be within extent 81 | */ 82 | next: function(d, val) { 83 | val = val > later.dy.extent(d)[1] ? 1 : val; 84 | var year = later.date.nextRollover(d, val, later.dy, later.Y), 85 | dyMax = later.dy.extent(year)[1]; 86 | 87 | val = val > dyMax ? 1 : val || dyMax; 88 | 89 | return later.date.next( 90 | later.Y.val(year), 91 | later.M.val(year), 92 | val 93 | ); 94 | 95 | }, 96 | 97 | /** 98 | * Returns the end of the previous instance of the day of year value indicated. 99 | * 100 | * @param {Date} d: The starting date 101 | * @param {int} val: The desired value, must be within extent 102 | */ 103 | prev: function(d, val) { 104 | var year = later.date.prevRollover(d, val, later.dy, later.Y), 105 | dyMax = later.dy.extent(year)[1]; 106 | 107 | val = val > dyMax ? dyMax : val || dyMax; 108 | 109 | return later.date.prev( 110 | later.Y.val(year), 111 | later.M.val(year), 112 | val 113 | ); 114 | } 115 | 116 | }; -------------------------------------------------------------------------------- /src/constraint/fulldate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full date (fd) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for specifying a full date and time. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.fullDate = later.fd = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'full date', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 1, 23 | 24 | /** 25 | * The time value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.fd || (d.fd = d.getTime()); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.fd.val(d) === val; 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid time values. 45 | */ 46 | extent: function() { 47 | return [0, 32503680000000]; 48 | }, 49 | 50 | /** 51 | * Returns the specified date. 52 | * 53 | * @param {Date} d: The specified date 54 | */ 55 | start: function(d) { 56 | return d; 57 | }, 58 | 59 | /** 60 | * Returns the specified date. 61 | * 62 | * @param {Date} d: The specified date 63 | */ 64 | end: function(d) { 65 | return d; 66 | }, 67 | 68 | /** 69 | * Returns the start of the next instance of the time value indicated. 70 | * 71 | * @param {Date} d: The starting date 72 | * @param {int} val: The desired value, must be within extent 73 | */ 74 | next: function(d, val) { 75 | return later.fd.val(d) < val ? new Date(val) : later.NEVER; 76 | }, 77 | 78 | /** 79 | * Returns the end of the previous instance of the time value indicated. 80 | * 81 | * @param {Date} d: The starting date 82 | * @param {int} val: The desired value, must be within extent 83 | */ 84 | prev: function(d, val) { 85 | return later.fd.val(d) > val ? new Date(val) : later.NEVER; 86 | } 87 | 88 | }; -------------------------------------------------------------------------------- /src/constraint/hour.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hour Constraint (H) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a hour constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.hour = later.h = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'hour', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 3600, 23 | 24 | /** 25 | * The hour value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.h || (d.h = later.date.getHour.call(d)); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.h.val(d) === val; 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid hour values. 45 | */ 46 | extent: function() { 47 | return [0, 23]; 48 | }, 49 | 50 | /** 51 | * The start of the hour of the specified date. 52 | * 53 | * @param {Date} d: The specified date 54 | */ 55 | start: function(d) { 56 | return d.hStart || (d.hStart = later.date.next( 57 | later.Y.val(d), later.M.val(d), later.D.val(d), later.h.val(d))); 58 | }, 59 | 60 | /** 61 | * The end of the hour of the specified date. 62 | * 63 | * @param {Date} d: The specified date 64 | */ 65 | end: function(d) { 66 | return d.hEnd || (d.hEnd = later.date.prev( 67 | later.Y.val(d), later.M.val(d), later.D.val(d), later.h.val(d))); 68 | }, 69 | 70 | /** 71 | * Returns the start of the next instance of the hour value indicated. 72 | * 73 | * @param {Date} d: The starting date 74 | * @param {int} val: The desired value, must be within extent 75 | */ 76 | next: function(d, val) { 77 | val = val > 23 ? 0 : val; 78 | 79 | var next = later.date.next( 80 | later.Y.val(d), 81 | later.M.val(d), 82 | later.D.val(d) + (val <= later.h.val(d) ? 1 : 0), 83 | val); 84 | 85 | // correct for passing over a daylight savings boundry 86 | if(!later.date.isUTC && next.getTime() <= d.getTime()) { 87 | next = later.date.next( 88 | later.Y.val(next), 89 | later.M.val(next), 90 | later.D.val(next), 91 | val + 1); 92 | } 93 | 94 | return next; 95 | }, 96 | 97 | /** 98 | * Returns the end of the previous instance of the hour value indicated. 99 | * 100 | * @param {Date} d: The starting date 101 | * @param {int} val: The desired value, must be within extent 102 | */ 103 | prev: function(d, val) { 104 | val = val > 23 ? 23 : val; 105 | 106 | return later.date.prev( 107 | later.Y.val(d), 108 | later.M.val(d), 109 | later.D.val(d) + (val >= later.h.val(d) ? -1 : 0), 110 | val); 111 | } 112 | 113 | }; -------------------------------------------------------------------------------- /src/constraint/index.js: -------------------------------------------------------------------------------- 1 | import "day"; 2 | import "dayofweekcount"; 3 | import "dayofweek"; 4 | import "dayofyear"; 5 | import "hour"; 6 | import "minute"; 7 | import "month"; 8 | import "second"; 9 | import "time"; 10 | import "weekofmonth"; 11 | import "weekofyear"; 12 | import "year"; 13 | import "fulldate"; -------------------------------------------------------------------------------- /src/constraint/minute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minute Constraint (m) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a minute constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.minute = later.m = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'minute', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 60, 23 | 24 | /** 25 | * The minute value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.m || (d.m = later.date.getMin.call(d)); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.m.val(d) === val; 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid minute values. 45 | */ 46 | extent: function(d) { 47 | return [0, 59]; 48 | }, 49 | 50 | /** 51 | * The start of the minute of the specified date. 52 | * 53 | * @param {Date} d: The specified date 54 | */ 55 | start: function(d) { 56 | return d.mStart || (d.mStart = later.date.next( 57 | later.Y.val(d), later.M.val(d), later.D.val(d), later.h.val(d), later.m.val(d))); 58 | }, 59 | 60 | /** 61 | * The end of the minute of the specified date. 62 | * 63 | * @param {Date} d: The specified date 64 | */ 65 | end: function(d) { 66 | return d.mEnd || (d.mEnd = later.date.prev( 67 | later.Y.val(d), later.M.val(d), later.D.val(d), later.h.val(d), later.m.val(d))); 68 | }, 69 | 70 | /** 71 | * Returns the start of the next instance of the minute value indicated. 72 | * 73 | * @param {Date} d: The starting date 74 | * @param {int} val: The desired value, must be within extent 75 | */ 76 | next: function(d, val) { 77 | var m = later.m.val(d), 78 | s = later.s.val(d), 79 | inc = val > 59 ? 60-m : (val <= m ? (60-m) + val : val-m), 80 | next = new Date(d.getTime() + (inc * later.MIN) - (s * later.SEC)); 81 | 82 | // correct for passing over a daylight savings boundry 83 | if(!later.date.isUTC && next.getTime() <= d.getTime()) { 84 | next = new Date(d.getTime() + ((inc + 120) * later.MIN) - (s * later.SEC)); 85 | } 86 | 87 | return next; 88 | }, 89 | 90 | /** 91 | * Returns the end of the previous instance of the minute value indicated. 92 | * 93 | * @param {Date} d: The starting date 94 | * @param {int} val: The desired value, must be within extent 95 | */ 96 | prev: function(d, val) { 97 | val = val > 59 ? 59 : val; 98 | 99 | return later.date.prev( 100 | later.Y.val(d), 101 | later.M.val(d), 102 | later.D.val(d), 103 | later.h.val(d) + (val >= later.m.val(d) ? -1 : 0), 104 | val); 105 | } 106 | 107 | }; -------------------------------------------------------------------------------- /src/constraint/month.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Month Constraint (M) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a month constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.month = later.M = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'month', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 2629740, 23 | 24 | /** 25 | * The month value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.M || (d.M = later.date.getMonth.call(d)+1); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.M.val(d) === (val || 12); 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid month values. Unlike the native date object, 45 | * month values in later are 1 based. 46 | */ 47 | extent: function() { 48 | return [1, 12]; 49 | }, 50 | 51 | /** 52 | * The start of the month of the specified date. 53 | * 54 | * @param {Date} d: The specified date 55 | */ 56 | start: function(d) { 57 | return d.MStart || (d.MStart = later.date.next(later.Y.val(d), later.M.val(d))); 58 | }, 59 | 60 | /** 61 | * The end of the month of the specified date. 62 | * 63 | * @param {Date} d: The specified date 64 | */ 65 | end: function(d) { 66 | return d.MEnd || (d.MEnd = later.date.prev(later.Y.val(d), later.M.val(d))); 67 | }, 68 | 69 | /** 70 | * Returns the start of the next instance of the month value indicated. 71 | * 72 | * @param {Date} d: The starting date 73 | * @param {int} val: The desired value, must be within extent 74 | */ 75 | next: function(d, val) { 76 | val = val > 12 ? 1 : val || 12; 77 | 78 | return later.date.next( 79 | later.Y.val(d) + (val > later.M.val(d) ? 0 : 1), 80 | val); 81 | }, 82 | 83 | /** 84 | * Returns the end of the previous instance of the month value indicated. 85 | * 86 | * @param {Date} d: The starting date 87 | * @param {int} val: The desired value, must be within extent 88 | */ 89 | prev: function(d, val) { 90 | val = val > 12 ? 12 : val || 12; 91 | 92 | return later.date.prev( 93 | later.Y.val(d) - (val >= later.M.val(d) ? 1 : 0), 94 | val); 95 | } 96 | 97 | }; -------------------------------------------------------------------------------- /src/constraint/second.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Second Constraint (s) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a second constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.second = later.s = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'second', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 1, 23 | 24 | /** 25 | * The second value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.s || (d.s = later.date.getSec.call(d)); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.s.val(d) === val; 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid second values. 45 | */ 46 | extent: function() { 47 | return [0, 59]; 48 | }, 49 | 50 | /** 51 | * The start of the second of the specified date. 52 | * 53 | * @param {Date} d: The specified date 54 | */ 55 | start: function(d) { 56 | return d; 57 | }, 58 | 59 | /** 60 | * The end of the second of the specified date. 61 | * 62 | * @param {Date} d: The specified date 63 | */ 64 | end: function(d) { 65 | return d; 66 | }, 67 | 68 | /** 69 | * Returns the start of the next instance of the second value indicated. 70 | * 71 | * @param {Date} d: The starting date 72 | * @param {int} val: The desired value, must be within extent 73 | */ 74 | next: function(d, val) { 75 | var s = later.s.val(d), 76 | inc = val > 59 ? 60-s : (val <= s ? (60-s) + val : val-s), 77 | next = new Date(d.getTime() + (inc * later.SEC)); 78 | 79 | // correct for passing over a daylight savings boundry 80 | if(!later.date.isUTC && next.getTime() <= d.getTime()) { 81 | next = new Date(d.getTime() + ((inc + 7200) * later.SEC)); 82 | } 83 | 84 | return next; 85 | }, 86 | 87 | /** 88 | * Returns the end of the previous instance of the second value indicated. 89 | * 90 | * @param {Date} d: The starting date 91 | * @param {int} val: The desired value, must be within extent 92 | */ 93 | prev: function(d, val, cache) { 94 | val = val > 59 ? 59 : val; 95 | 96 | return later.date.prev( 97 | later.Y.val(d), 98 | later.M.val(d), 99 | later.D.val(d), 100 | later.h.val(d), 101 | later.m.val(d) + (val >= later.s.val(d) ? -1 : 0), 102 | val); 103 | } 104 | 105 | }; -------------------------------------------------------------------------------- /src/constraint/time.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Time Constraint (dy) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a time of day constraint type. Stored as number of seconds 6 | * since midnight to simplify calculations. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | later.time = later.t = { 13 | 14 | /** 15 | * The name of this constraint. 16 | */ 17 | name: 'time', 18 | 19 | /** 20 | * The rough amount of seconds between start and end for this constraint. 21 | * (doesn't need to be exact) 22 | */ 23 | range: 1, 24 | 25 | /** 26 | * The time value of the specified date. 27 | * 28 | * @param {Date} d: The date to calculate the value of 29 | */ 30 | val: function(d) { 31 | return d.t || (d.t = 32 | (later.h.val(d) * 3600) + (later.m.val(d) * 60) + (later.s.val(d))); 33 | }, 34 | 35 | /** 36 | * Returns true if the val is valid for the date specified. 37 | * 38 | * @param {Date} d: The date to check the value on 39 | * @param {Integer} val: The value to validate 40 | */ 41 | isValid: function(d, val) { 42 | return later.t.val(d) === val; 43 | }, 44 | 45 | /** 46 | * The minimum and maximum valid time values. 47 | */ 48 | extent: function() { 49 | return [0, 86399]; 50 | }, 51 | 52 | /** 53 | * Returns the specified date. 54 | * 55 | * @param {Date} d: The specified date 56 | */ 57 | start: function(d) { 58 | return d; 59 | }, 60 | 61 | /** 62 | * Returns the specified date. 63 | * 64 | * @param {Date} d: The specified date 65 | */ 66 | end: function(d) { 67 | return d; 68 | }, 69 | 70 | /** 71 | * Returns the start of the next instance of the time value indicated. 72 | * 73 | * @param {Date} d: The starting date 74 | * @param {int} val: The desired value, must be within extent 75 | */ 76 | next: function(d, val) { 77 | val = val > 86399 ? 0 : val; 78 | 79 | var next = later.date.next( 80 | later.Y.val(d), 81 | later.M.val(d), 82 | later.D.val(d) + (val <= later.t.val(d) ? 1 : 0), 83 | 0, 84 | 0, 85 | val); 86 | 87 | // correct for passing over a daylight savings boundry 88 | if(!later.date.isUTC && next.getTime() < d.getTime()) { 89 | next = later.date.next( 90 | later.Y.val(next), 91 | later.M.val(next), 92 | later.D.val(next), 93 | later.h.val(next), 94 | later.m.val(next), 95 | val + 7200); 96 | } 97 | 98 | return next; 99 | }, 100 | 101 | /** 102 | * Returns the end of the previous instance of the time value indicated. 103 | * 104 | * @param {Date} d: The starting date 105 | * @param {int} val: The desired value, must be within extent 106 | */ 107 | prev: function(d, val) { 108 | val = val > 86399 ? 86399 : val; 109 | 110 | return later.date.next( 111 | later.Y.val(d), 112 | later.M.val(d), 113 | later.D.val(d) + (val >= later.t.val(d) ? -1 : 0), 114 | 0, 115 | 0, 116 | val); 117 | } 118 | 119 | }; -------------------------------------------------------------------------------- /src/constraint/weekofmonth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Week of Month Constraint (wy) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for an week of month constraint type. Week of month treats the 6 | * first of the month as the start of week 1, with each following week starting 7 | * on Sunday. 8 | * 9 | * Later is freely distributable under the MIT license. 10 | * For all details and documentation: 11 | * http://github.com/bunkat/later 12 | */ 13 | later.weekOfMonth = later.wm = { 14 | 15 | /** 16 | * The name of this constraint. 17 | */ 18 | name: 'week of month', 19 | 20 | /** 21 | * The rough amount of seconds between start and end for this constraint. 22 | * (doesn't need to be exact) 23 | */ 24 | range: 604800, 25 | 26 | /** 27 | * The week of month value of the specified date. 28 | * 29 | * @param {Date} d: The date to calculate the value of 30 | */ 31 | val: function(d) { 32 | return d.wm || (d.wm = 33 | (later.D.val(d) + 34 | (later.dw.val(later.M.start(d)) - 1) + (7 - later.dw.val(d))) / 7); 35 | }, 36 | 37 | /** 38 | * Returns true if the val is valid for the date specified. 39 | * 40 | * @param {Date} d: The date to check the value on 41 | * @param {Integer} val: The value to validate 42 | */ 43 | isValid: function(d, val) { 44 | return later.wm.val(d) === (val || later.wm.extent(d)[1]); 45 | }, 46 | 47 | /** 48 | * The minimum and maximum valid week of month values for the month indicated. 49 | * Zero indicates the last week in the month. 50 | * 51 | * @param {Date} d: The date indicating the month to find values for 52 | */ 53 | extent: function(d) { 54 | return d.wmExtent || (d.wmExtent = [1, 55 | (later.D.extent(d)[1] + (later.dw.val(later.M.start(d)) - 1) + 56 | (7 - later.dw.val(later.M.end(d)))) / 7]); 57 | }, 58 | 59 | /** 60 | * The start of the week of the specified date. 61 | * 62 | * @param {Date} d: The specified date 63 | */ 64 | start: function(d) { 65 | return d.wmStart || (d.wmStart = later.date.next( 66 | later.Y.val(d), 67 | later.M.val(d), 68 | Math.max(later.D.val(d) - later.dw.val(d) + 1, 1))); 69 | }, 70 | 71 | /** 72 | * The end of the week of the specified date. 73 | * 74 | * @param {Date} d: The specified date 75 | */ 76 | end: function(d) { 77 | return d.wmEnd || (d.wmEnd = later.date.prev( 78 | later.Y.val(d), 79 | later.M.val(d), 80 | Math.min(later.D.val(d) + (7 - later.dw.val(d)), later.D.extent(d)[1]))); 81 | }, 82 | 83 | /** 84 | * Returns the start of the next instance of the week value indicated. Returns 85 | * the first day of the next month if val is greater than the number of 86 | * days in the following month. 87 | * 88 | * @param {Date} d: The starting date 89 | * @param {int} val: The desired value, must be within extent 90 | */ 91 | next: function(d, val) { 92 | val = val > later.wm.extent(d)[1] ? 1 : val; 93 | 94 | var month = later.date.nextRollover(d, val, later.wm, later.M), 95 | wmMax = later.wm.extent(month)[1]; 96 | 97 | val = val > wmMax ? 1 : val || wmMax; 98 | 99 | // jump to the Sunday of the desired week, set to 1st of month for week 1 100 | return later.date.next( 101 | later.Y.val(month), 102 | later.M.val(month), 103 | Math.max(1, (val-1) * 7 - (later.dw.val(month)-2))); 104 | }, 105 | 106 | /** 107 | * Returns the end of the previous instance of the week value indicated. Returns 108 | * the last day of the previous month if val is greater than the number of 109 | * days in the previous month. 110 | * 111 | * @param {Date} d: The starting date 112 | * @param {int} val: The desired value, must be within extent 113 | */ 114 | prev: function(d, val) { 115 | var month = later.date.prevRollover(d, val, later.wm, later.M), 116 | wmMax = later.wm.extent(month)[1]; 117 | 118 | val = val > wmMax ? wmMax : val || wmMax; 119 | 120 | // jump to the end of Saturday of the desired week 121 | return later.wm.end(later.date.next( 122 | later.Y.val(month), 123 | later.M.val(month), 124 | Math.max(1, (val-1) * 7 - (later.dw.val(month)-2)))); 125 | } 126 | 127 | }; -------------------------------------------------------------------------------- /src/constraint/weekofyear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Week of Year Constraint (wy) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for an ISO 8601 week constraint type. For more information about 6 | * ISO 8601 see http://en.wikipedia.org/wiki/ISO_week_date. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | later.weekOfYear = later.wy = { 13 | 14 | /** 15 | * The name of this constraint. 16 | */ 17 | name: 'week of year (ISO)', 18 | 19 | /** 20 | * The rough amount of seconds between start and end for this constraint. 21 | * (doesn't need to be exact) 22 | */ 23 | range: 604800, 24 | 25 | /** 26 | * The ISO week year value of the specified date. 27 | * 28 | * @param {Date} d: The date to calculate the value of 29 | */ 30 | val: function(d) { 31 | if (d.wy) return d.wy; 32 | 33 | // move to the Thursday in the target week and find Thurs of target year 34 | var wThur = later.dw.next(later.wy.start(d), 5), 35 | YThur = later.dw.next(later.Y.prev(wThur, later.Y.val(wThur)-1), 5); 36 | 37 | // caculate the difference between the two dates in weeks 38 | return (d.wy = 1 + Math.ceil((wThur.getTime() - YThur.getTime()) / later.WEEK)); 39 | }, 40 | 41 | /** 42 | * Returns true if the val is valid for the date specified. 43 | * 44 | * @param {Date} d: The date to check the value on 45 | * @param {Integer} val: The value to validate 46 | */ 47 | isValid: function(d, val) { 48 | return later.wy.val(d) === (val || later.wy.extent(d)[1]); 49 | }, 50 | 51 | /** 52 | * The minimum and maximum valid ISO week values for the year indicated. 53 | * 54 | * @param {Date} d: The date indicating the year to find ISO values for 55 | */ 56 | extent: function(d) { 57 | if (d.wyExtent) return d.wyExtent; 58 | 59 | // go to start of ISO week to get to the right year 60 | var year = later.dw.next(later.wy.start(d), 5), 61 | dwFirst = later.dw.val(later.Y.start(year)), 62 | dwLast = later.dw.val(later.Y.end(year)); 63 | 64 | return (d.wyExtent = [1, dwFirst === 5 || dwLast === 5 ? 53 : 52]); 65 | }, 66 | 67 | /** 68 | * The start of the ISO week of the specified date. 69 | * 70 | * @param {Date} d: The specified date 71 | */ 72 | start: function(d) { 73 | return d.wyStart || (d.wyStart = later.date.next( 74 | later.Y.val(d), 75 | later.M.val(d), 76 | // jump to the Monday of the current week 77 | later.D.val(d) - (later.dw.val(d) > 1 ? later.dw.val(d) - 2 : 6) 78 | )); 79 | }, 80 | 81 | /** 82 | * The end of the ISO week of the specified date. 83 | * 84 | * @param {Date} d: The specified date 85 | */ 86 | end: function(d) { 87 | return d.wyEnd || (d.wyEnd = later.date.prev( 88 | later.Y.val(d), 89 | later.M.val(d), 90 | // jump to the Saturday of the current week 91 | later.D.val(d) + (later.dw.val(d) > 1 ? 8 - later.dw.val(d) : 0) 92 | )); 93 | }, 94 | 95 | /** 96 | * Returns the start of the next instance of the ISO week value indicated. 97 | * 98 | * @param {Date} d: The starting date 99 | * @param {int} val: The desired value, must be within extent 100 | */ 101 | next: function(d, val) { 102 | val = val > later.wy.extent(d)[1] ? 1 : val; 103 | 104 | var wyThur = later.dw.next(later.wy.start(d), 5), 105 | year = later.date.nextRollover(wyThur, val, later.wy, later.Y); 106 | 107 | // handle case where 1st of year is last week of previous month 108 | if(later.wy.val(year) !== 1) { 109 | year = later.dw.next(year, 2); 110 | } 111 | 112 | var wyMax = later.wy.extent(year)[1], 113 | wyStart = later.wy.start(year); 114 | 115 | val = val > wyMax ? 1 : val || wyMax; 116 | 117 | return later.date.next( 118 | later.Y.val(wyStart), 119 | later.M.val(wyStart), 120 | later.D.val(wyStart) + 7 * (val-1) 121 | ); 122 | }, 123 | 124 | /** 125 | * Returns the end of the previous instance of the ISO week value indicated. 126 | * 127 | * @param {Date} d: The starting date 128 | * @param {int} val: The desired value, must be within extent 129 | */ 130 | prev: function(d, val) { 131 | var wyThur = later.dw.next(later.wy.start(d), 5), 132 | year = later.date.prevRollover(wyThur, val, later.wy, later.Y); 133 | 134 | // handle case where 1st of year is last week of previous month 135 | if(later.wy.val(year) !== 1) { 136 | year = later.dw.next(year, 2); 137 | } 138 | 139 | var wyMax = later.wy.extent(year)[1], 140 | wyEnd = later.wy.end(year); 141 | 142 | val = val > wyMax ? wyMax : val || wyMax; 143 | 144 | return later.wy.end(later.date.next( 145 | later.Y.val(wyEnd), 146 | later.M.val(wyEnd), 147 | later.D.val(wyEnd) + 7 * (val-1) 148 | )); 149 | } 150 | }; -------------------------------------------------------------------------------- /src/constraint/year.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Year Constraint (Y) 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Definition for a year constraint type. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | later.year = later.Y = { 12 | 13 | /** 14 | * The name of this constraint. 15 | */ 16 | name: 'year', 17 | 18 | /** 19 | * The rough amount of seconds between start and end for this constraint. 20 | * (doesn't need to be exact) 21 | */ 22 | range: 31556900, 23 | 24 | /** 25 | * The year value of the specified date. 26 | * 27 | * @param {Date} d: The date to calculate the value of 28 | */ 29 | val: function(d) { 30 | return d.Y || (d.Y = later.date.getYear.call(d)); 31 | }, 32 | 33 | /** 34 | * Returns true if the val is valid for the date specified. 35 | * 36 | * @param {Date} d: The date to check the value on 37 | * @param {Integer} val: The value to validate 38 | */ 39 | isValid: function(d, val) { 40 | return later.Y.val(d) === val; 41 | }, 42 | 43 | /** 44 | * The minimum and maximum valid values for the year constraint. 45 | * If max is past 2099, later.D.extent must be fixed to calculate leap years 46 | * correctly. 47 | */ 48 | extent: function() { 49 | return [1970, 2099]; 50 | }, 51 | 52 | /** 53 | * The start of the year of the specified date. 54 | * 55 | * @param {Date} d: The specified date 56 | */ 57 | start: function(d) { 58 | return d.YStart || (d.YStart = later.date.next(later.Y.val(d))); 59 | }, 60 | 61 | /** 62 | * The end of the year of the specified date. 63 | * 64 | * @param {Date} d: The specified date 65 | */ 66 | end: function(d) { 67 | return d.YEnd || (d.YEnd = later.date.prev(later.Y.val(d))); 68 | }, 69 | 70 | /** 71 | * Returns the start of the next instance of the year value indicated. 72 | * 73 | * @param {Date} d: The starting date 74 | * @param {int} val: The desired value, must be within extent 75 | */ 76 | next: function(d, val) { 77 | return val > later.Y.val(d) && val <= later.Y.extent()[1] ? 78 | later.date.next(val) : later.NEVER; 79 | }, 80 | 81 | /** 82 | * Returns the end of the previous instance of the year value indicated. 83 | * 84 | * @param {Date} d: The starting date 85 | * @param {int} val: The desired value, must be within extent 86 | */ 87 | prev: function(d, val) { 88 | return val < later.Y.val(d) && val >= later.Y.extent()[0] ? 89 | later.date.prev(val) : later.NEVER; 90 | } 91 | 92 | }; -------------------------------------------------------------------------------- /src/core/compile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compile 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Compiles a single schedule definition into a form from which instances can be 6 | * efficiently calculated from. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | later.compile = function(schedDef) { 13 | 14 | var constraints = [], 15 | constraintsLen = 0, 16 | tickConstraint; 17 | 18 | for(var key in schedDef) { 19 | var nameParts = key.split('_'), 20 | name = nameParts[0], 21 | mod = nameParts[1], 22 | vals = schedDef[key], 23 | constraint = mod ? later.modifier[mod](later[name], vals) : later[name]; 24 | 25 | constraints.push({constraint: constraint, vals: vals}); 26 | constraintsLen++; 27 | } 28 | 29 | // sort constraints based on their range for best performance (we want to 30 | // always skip the largest block of time possible to find the next valid 31 | // value) 32 | constraints.sort(function(a,b) { 33 | var ra = a.constraint.range, rb = b.constraint.range; 34 | return (rb < ra) ? -1 : (rb > ra) ? 1 : 0; 35 | }); 36 | 37 | // this is the smallest constraint, we use this one to tick the schedule when 38 | // finding multiple instances 39 | tickConstraint = constraints[constraintsLen-1].constraint; 40 | 41 | /** 42 | * Returns a function to use when comparing two dates. Encapsulates the 43 | * difference between searching for instances forward and backwards so that 44 | * the same code can be completely reused for both directions. 45 | * 46 | * @param {String} dir: The direction to use, either 'next' or 'prev' 47 | */ 48 | function compareFn(dir) { 49 | return dir === 'next' ? 50 | function(a,b) { return a.getTime() > b.getTime(); } : 51 | function(a,b) { return b.getTime() > a.getTime(); }; 52 | } 53 | 54 | return { 55 | 56 | /** 57 | * Calculates the start of the next valid occurrence of a particular schedule 58 | * that occurs on or after the specified start time. 59 | * 60 | * @param {String} dir: Direction to search in ('next' or 'prev') 61 | * @param {Date} startDate: The first possible valid occurrence 62 | */ 63 | start: function(dir, startDate) { 64 | var next = startDate, 65 | nextVal = later.array[dir], 66 | maxAttempts = 1000, 67 | done; 68 | 69 | while(maxAttempts-- && !done && next) { 70 | done = true; 71 | 72 | // verify all of the constraints in order since we want to make the 73 | // largest jumps possible to find the first valid value 74 | for(var i = 0; i < constraintsLen; i++) { 75 | 76 | var constraint = constraints[i].constraint, 77 | curVal = constraint.val(next), 78 | extent = constraint.extent(next), 79 | newVal = nextVal(curVal, constraints[i].vals, extent); 80 | 81 | if(!constraint.isValid(next, newVal)) { 82 | next = constraint[dir](next, newVal); 83 | done = false; 84 | break; // need to retest all constraints with new date 85 | } 86 | } 87 | } 88 | 89 | if(next !== later.NEVER) { 90 | next = dir === 'next' ? tickConstraint.start(next) : 91 | tickConstraint.end(next); 92 | } 93 | 94 | // if next, move to start of time period. needed when moving backwards 95 | return next; 96 | }, 97 | 98 | /** 99 | * Given a valid start time, finds the next schedule that is invalid. 100 | * Useful for finding the end of a valid time range. 101 | * 102 | * @param {Date} startDate: The first possible valid occurrence 103 | */ 104 | end: function(dir, startDate) { 105 | 106 | var result, 107 | nextVal = later.array[dir + 'Invalid'], 108 | compare = compareFn(dir); 109 | 110 | for(var i = constraintsLen-1; i >= 0; i--) { 111 | var constraint = constraints[i].constraint, 112 | curVal = constraint.val(startDate), 113 | extent = constraint.extent(startDate), 114 | newVal = nextVal(curVal, constraints[i].vals, extent), 115 | next; 116 | 117 | if(newVal !== undefined) { // constraint has invalid value, use that 118 | next = constraint[dir](startDate, newVal); 119 | if(next && (!result || compare(result, next))) { 120 | result = next; 121 | } 122 | } 123 | } 124 | 125 | return result; 126 | }, 127 | 128 | /** 129 | * Ticks the date by the minimum constraint in this schedule 130 | * 131 | * @param {String} dir: Direction to tick in ('next' or 'prev') 132 | * @param {Date} date: The start date to tick from 133 | */ 134 | tick: function(dir, date) { 135 | return new Date(dir === 'next' ? 136 | tickConstraint.end(date).getTime() + later.SEC : 137 | tickConstraint.start(date).getTime() - later.SEC); 138 | }, 139 | 140 | /** 141 | * Ticks the date to the start of the minimum constraint 142 | * 143 | * @param {Date} date: The start date to tick from 144 | */ 145 | tickStart: function(date) { 146 | return tickConstraint.start(date); 147 | } 148 | 149 | }; 150 | }; -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import "compile"; 2 | import "schedule"; 3 | import "settimeout"; 4 | import "setinterval"; -------------------------------------------------------------------------------- /src/core/setinterval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set Interval 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Works similar to setInterval() but allows you to specify a Later schedule 6 | * instead of milliseconds. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.setInterval = function(fn, sched) { 14 | if (!fn) { 15 | return; 16 | } 17 | 18 | var t = later.setTimeout(scheduleTimeout, sched), 19 | done = t.isDone(); 20 | 21 | /** 22 | * Executes the specified function and then sets the timeout for the next 23 | * interval. 24 | */ 25 | function scheduleTimeout() { 26 | if(!done) { 27 | fn(); 28 | t = later.setTimeout(scheduleTimeout, sched); 29 | } 30 | } 31 | 32 | return { 33 | 34 | isDone: function() { 35 | return t.isDone(); 36 | }, 37 | 38 | /** 39 | * Clears the timeout. 40 | */ 41 | clear: function() { 42 | done = true; 43 | t.clear(); 44 | } 45 | 46 | }; 47 | 48 | }; -------------------------------------------------------------------------------- /src/core/settimeout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set Timeout 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Works similar to setTimeout() but allows you to specify a Later schedule 6 | * instead of milliseconds. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.setTimeout = function(fn, sched) { 14 | 15 | var s = later.schedule(sched), t; 16 | if (fn) { 17 | scheduleTimeout(); 18 | } 19 | 20 | /** 21 | * Schedules the timeout to occur. If the next occurrence is greater than the 22 | * max supported delay (2147483647 ms) than we delay for that amount before 23 | * attempting to schedule the timeout again. 24 | */ 25 | function scheduleTimeout() { 26 | var now = Date.now(), 27 | next = s.next(2, now); 28 | 29 | if (!next[0]) { 30 | t = undefined; 31 | return; 32 | } 33 | 34 | var diff = next[0].getTime() - now; 35 | 36 | // minimum time to fire is one second, use next occurrence instead 37 | if(diff < 1000) { 38 | diff = next[1] ? next[1].getTime() - now : 1000; 39 | } 40 | 41 | if(diff < 2147483647) { 42 | t = setTimeout(fn, diff); 43 | } 44 | else { 45 | t = setTimeout(scheduleTimeout, 2147483647); 46 | } 47 | } 48 | 49 | return { 50 | 51 | isDone: function() { 52 | return !t; 53 | }, 54 | 55 | /** 56 | * Clears the timeout. 57 | */ 58 | clear: function() { 59 | clearTimeout(t); 60 | } 61 | 62 | }; 63 | 64 | }; -------------------------------------------------------------------------------- /src/date/constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Date Constants 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Useful constants for dealing with time conversions. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | 12 | // Time to milliseconds conversion 13 | later.SEC = 1000; 14 | later.MIN = later.SEC * 60; 15 | later.HOUR = later.MIN * 60; 16 | later.DAY = later.HOUR * 24; 17 | later.WEEK = later.DAY * 7; 18 | 19 | // Array of days in each month, must be corrected for leap years 20 | later.DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 21 | 22 | // constant for specifying that a schedule can never occur 23 | later.NEVER = 0; -------------------------------------------------------------------------------- /src/date/date.js: -------------------------------------------------------------------------------- 1 | later.date = {}; 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/date/index.js: -------------------------------------------------------------------------------- 1 | import "date"; 2 | import "timezone"; 3 | import "constant"; 4 | import "next"; 5 | import "nextrollover"; 6 | import "prev"; 7 | import "prevrollover"; -------------------------------------------------------------------------------- /src/date/next.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Next 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Creates a new Date object defaulted to the first second after the specified 6 | * values. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | /** 14 | * Builds and returns a new Date using the specified values. Date 15 | * returned is either using Local time or UTC based on isLocal. 16 | * 17 | * @param {Int} Y: Four digit year 18 | * @param {Int} M: Month between 1 and 12, defaults to 1 19 | * @param {Int} D: Date between 1 and 31, defaults to 1 20 | * @param {Int} h: Hour between 0 and 23, defaults to 0 21 | * @param {Int} m: Minute between 0 and 59, defaults to 0 22 | * @param {Int} s: Second between 0 and 59, defaults to 0 23 | */ 24 | later.date.next = function(Y, M, D, h, m, s) { 25 | 26 | return later.date.build( 27 | Y, 28 | M !== undefined ? M-1 : 0, 29 | D !== undefined ? D : 1, 30 | h || 0, 31 | m || 0, 32 | s || 0); 33 | }; -------------------------------------------------------------------------------- /src/date/nextrollover.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Next Rollover 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Determines if a value will cause a particualr constraint to rollover to the 6 | * next largest time period. Used primarily when a constraint has a 7 | * variable extent. 8 | * 9 | * Later is freely distributable under the MIT license. 10 | * For all details and documentation: 11 | * http://github.com/bunkat/later 12 | */ 13 | 14 | later.date.nextRollover = function(d, val, constraint, period) { 15 | var cur = constraint.val(d), 16 | max = constraint.extent(d)[1]; 17 | 18 | return (((val || max) <= cur) || val > max) ? 19 | new Date(period.end(d).getTime() + later.SEC) : 20 | period.start(d); 21 | }; -------------------------------------------------------------------------------- /src/date/prev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prev 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Creates a new Date object defaulted to the last second after the specified 6 | * values. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | /** 14 | * Builds and returns a new Date using the specified values. Date 15 | * returned is either using Local time or UTC based on isLocal. 16 | * 17 | * @param {Int} Y: Four digit year 18 | * @param {Int} M: Month between 0 and 11, defaults to 11 19 | * @param {Int} D: Date between 1 and 31, defaults to last day of month 20 | * @param {Int} h: Hour between 0 and 23, defaults to 23 21 | * @param {Int} m: Minute between 0 and 59, defaults to 59 22 | * @param {Int} s: Second between 0 and 59, defaults to 59 23 | */ 24 | later.date.prev = function(Y, M, D, h, m, s) { 25 | 26 | var len = arguments.length; 27 | M = len < 2 ? 11 : M-1; 28 | D = len < 3 ? later.D.extent(later.date.next(Y, M+1))[1] : D; 29 | h = len < 4 ? 23 : h; 30 | m = len < 5 ? 59 : m; 31 | s = len < 6 ? 59 : s; 32 | 33 | return later.date.build(Y, M, D, h, m, s); 34 | }; -------------------------------------------------------------------------------- /src/date/prevrollover.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prev Rollover 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Determines if a value will cause a particualr constraint to rollover to the 6 | * previous largest time period. Used primarily when a constraint has a 7 | * variable extent. 8 | * 9 | * Later is freely distributable under the MIT license. 10 | * For all details and documentation: 11 | * http://github.com/bunkat/later 12 | */ 13 | 14 | later.date.prevRollover = function(d, val, constraint, period) { 15 | var cur = constraint.val(d); 16 | 17 | return (val >= cur || !val) ? 18 | period.start(period.prev(d, period.val(d)-1)) : 19 | period.start(d); 20 | }; -------------------------------------------------------------------------------- /src/date/timezone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timezone 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Configures helper functions to switch between useing local time and UTC. Later 6 | * uses UTC time by default. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | later.date.timezone = function(useLocalTime) { 14 | 15 | // configure the date builder used to create new dates in the right timezone 16 | later.date.build = useLocalTime ? 17 | function(Y, M, D, h, m, s) { return new Date(Y, M, D, h, m, s); } : 18 | function(Y, M, D, h, m, s) { return new Date(Date.UTC(Y, M, D, h, m, s)); }; 19 | 20 | // configure the accessor methods 21 | var get = useLocalTime ? 'get' : 'getUTC', 22 | d = Date.prototype; 23 | 24 | later.date.getYear = d[get + 'FullYear']; 25 | later.date.getMonth = d[get + 'Month']; 26 | later.date.getDate = d[get + 'Date']; 27 | later.date.getDay = d[get + 'Day']; 28 | later.date.getHour = d[get + 'Hours']; 29 | later.date.getMin = d[get + 'Minutes']; 30 | later.date.getSec = d[get + 'Seconds']; 31 | 32 | // set the isUTC flag 33 | later.date.isUTC = !useLocalTime; 34 | }; 35 | 36 | // friendly names for available timezones 37 | later.date.UTC = function() { later.date.timezone(false); }; 38 | later.date.localTime = function() { later.date.timezone(true); }; 39 | 40 | // use UTC by default 41 | later.date.UTC(); -------------------------------------------------------------------------------- /src/end.js: -------------------------------------------------------------------------------- 1 | return later; 2 | })(); -------------------------------------------------------------------------------- /src/later-core.js: -------------------------------------------------------------------------------- 1 | import "start"; 2 | import "compat/"; 3 | 4 | import "array/"; 5 | import "constraint/"; 6 | import "modifier/"; 7 | import "core/"; 8 | import "date/"; 9 | 10 | import "end"; -------------------------------------------------------------------------------- /src/later.js: -------------------------------------------------------------------------------- 1 | import "start"; 2 | import "compat/"; 3 | 4 | import "array/"; 5 | import "constraint/"; 6 | import "modifier/"; 7 | import "core/"; 8 | import "date/"; 9 | import "parse/"; 10 | 11 | import "end"; -------------------------------------------------------------------------------- /src/modifier/after.js: -------------------------------------------------------------------------------- 1 | /** 2 | * After Modifier 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Modifies a constraint such that all values that are greater than the 6 | * specified value are considered valid. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | /** 14 | * Creates a new modified constraint. 15 | * 16 | * @param {Constraint} constraint: The constraint to be modified 17 | * @param {Integer} value: The starting value of the after constraint 18 | */ 19 | later.modifier.after = later.modifier.a = function(constraint, values) { 20 | 21 | var value = values[0]; 22 | 23 | return { 24 | 25 | /** 26 | * Returns the name of the constraint with the 'after' modifier. 27 | */ 28 | name: 'after ' + constraint.name, 29 | 30 | /** 31 | * Pass through to the constraint. 32 | */ 33 | range: (constraint.extent(new Date())[1] - value) * constraint.range, 34 | 35 | /** 36 | * The value of the specified date. Returns value for any constraint val 37 | * that is greater than or equal to value. 38 | * 39 | * @param {Date} d: The date to calculate the value of 40 | */ 41 | val: constraint.val, 42 | 43 | /** 44 | * Returns true if the val is valid for the date specified. 45 | * 46 | * @param {Date} d: The date to check the value on 47 | * @param {Integer} val: The value to validate 48 | */ 49 | isValid: function(d, val) { 50 | return this.val(d) >= value; 51 | }, 52 | 53 | /** 54 | * Pass through to the constraint. 55 | */ 56 | extent: constraint.extent, 57 | 58 | /** 59 | * Pass through to the constraint. 60 | */ 61 | start: constraint.start, 62 | 63 | /** 64 | * Pass through to the constraint. 65 | */ 66 | end: constraint.end, 67 | 68 | /** 69 | * Pass through to the constraint. 70 | */ 71 | next: function(startDate, val) { 72 | if(val != value) val = constraint.extent(startDate)[0]; 73 | return constraint.next(startDate, val); 74 | }, 75 | 76 | /** 77 | * Pass through to the constraint. 78 | */ 79 | prev: function(startDate, val) { 80 | val = val === value ? constraint.extent(startDate)[1] : value - 1; 81 | return constraint.prev(startDate, val); 82 | } 83 | 84 | }; 85 | 86 | }; -------------------------------------------------------------------------------- /src/modifier/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Before Modifier 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Modifies a constraint such that all values that are less than the 6 | * specified value are considered valid. 7 | * 8 | * Later is freely distributable under the MIT license. 9 | * For all details and documentation: 10 | * http://github.com/bunkat/later 11 | */ 12 | 13 | /** 14 | * Creates a new modified constraint. 15 | * 16 | * @param {Constraint} constraint: The constraint to be modified 17 | * @param {Integer} value: The starting value of the before constraint 18 | */ 19 | later.modifier.before = later.modifier.b = function(constraint, values) { 20 | 21 | var value = values[values.length-1]; 22 | 23 | return { 24 | 25 | /** 26 | * Returns the name of the constraint with the 'before' modifier. 27 | */ 28 | name: 'before ' + constraint.name, 29 | 30 | /** 31 | * Pass through to the constraint. 32 | */ 33 | range: constraint.range * (value-1), 34 | 35 | /** 36 | * The value of the specified date. Returns value for any constraint val 37 | * that is less than or equal to value. 38 | * 39 | * @param {Date} d: The date to calculate the value of 40 | */ 41 | val: constraint.val, 42 | 43 | /** 44 | * Returns true if the val is valid for the date specified. 45 | * 46 | * @param {Date} d: The date to check the value on 47 | * @param {Integer} val: The value to validate 48 | */ 49 | isValid: function(d, val) { 50 | return this.val(d) < value; 51 | }, 52 | 53 | /** 54 | * Pass through to the constraint. 55 | */ 56 | extent: constraint.extent, 57 | 58 | /** 59 | * Pass through to the constraint. 60 | */ 61 | start: constraint.start, 62 | 63 | /** 64 | * Jump to the end of the range. 65 | */ 66 | end: constraint.end, 67 | 68 | /** 69 | * Pass through to the constraint. 70 | */ 71 | next: function(startDate, val) { 72 | val = val === value ? constraint.extent(startDate)[0] : value; 73 | return constraint.next(startDate, val); 74 | }, 75 | 76 | /** 77 | * Pass through to the constraint. 78 | */ 79 | prev: function(startDate, val) { 80 | val = val === value ? value - 1 : constraint.extent(startDate)[1]; 81 | return constraint.prev(startDate, val); 82 | } 83 | 84 | }; 85 | 86 | }; -------------------------------------------------------------------------------- /src/modifier/index.js: -------------------------------------------------------------------------------- 1 | import "modifier"; 2 | 3 | import "after"; 4 | import "before"; -------------------------------------------------------------------------------- /src/modifier/modifier.js: -------------------------------------------------------------------------------- 1 | 2 | later.modifier = {}; -------------------------------------------------------------------------------- /src/package.js: -------------------------------------------------------------------------------- 1 | var later = require("../index"); 2 | 3 | console.log(JSON.stringify({ 4 | "name": "later", 5 | "version": later.version, 6 | "description": "Determine later (or previous) occurrences of recurring schedules", 7 | "keywords": ["schedule", "occurrences", "recur", "cron"], 8 | "author": "BunKat ", 9 | "repository" : { 10 | "type" : "git", 11 | "url" : "git://github.com/bunkat/later.git" 12 | }, 13 | "main": "index.js", 14 | "browserify": "index-browserify.js", 15 | "jam": { 16 | "main": "later.js", 17 | "shim": { 18 | "exports": "later" 19 | } 20 | }, 21 | "devDependencies": { 22 | "smash": "~0.0.8", 23 | "mocha": "*", 24 | "should": ">=0.6.3", 25 | "jslint": "*", 26 | "uglify-js": "*", 27 | "benchmark": "*" 28 | }, 29 | "license": "MIT", 30 | "scripts": { 31 | "test": "./node_modules/.bin/mocha test/**/*-test.js --reporter dot" 32 | } 33 | }, null, 2)); -------------------------------------------------------------------------------- /src/parse/cron.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cron 3 | * (c) 2013 Bill, BunKat LLC. 4 | * 5 | * Creates a valid Later schedule from a valid cron expression. 6 | * 7 | * Later is freely distributable under the MIT license. 8 | * For all details and documentation: 9 | * http://github.com/bunkat/later 10 | */ 11 | 12 | /** 13 | * Parses a valid cron expression and produces a valid schedule that 14 | * can then be used with Later. 15 | * 16 | * CronParser().parse('* 5 * * * * *', true); 17 | * 18 | * @param {String} expr: The cron expression to parse 19 | * @param {Bool} hasSeconds: True if the expression uses a seconds field 20 | * @api public 21 | */ 22 | later.parse.cron = function (expr, hasSeconds) { 23 | 24 | // Constant array to convert valid names to values 25 | var NAMES = { 26 | JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6, JUL: 7, AUG: 8, 27 | SEP: 9, OCT: 10, NOV: 11, DEC: 12, 28 | SUN: 1, MON: 2, TUE: 3, WED: 4, THU: 5, FRI: 6, SAT: 7 29 | }; 30 | 31 | // Parsable replacements for common expressions 32 | var REPLACEMENTS = { 33 | '* * * * * *': '0/1 * * * * *', 34 | '@YEARLY': '0 0 1 1 *', 35 | '@ANNUALLY': '0 0 1 1 *', 36 | '@MONTHLY': '0 0 1 * *', 37 | '@WEEKLY': '0 0 * * 0', 38 | '@DAILY': '0 0 * * *', 39 | '@HOURLY': '0 * * * *' 40 | }; 41 | 42 | // Contains the index, min, and max for each of the constraints 43 | var FIELDS = { 44 | s: [0, 0, 59], // seconds 45 | m: [1, 0, 59], // minutes 46 | h: [2, 0, 23], // hours 47 | D: [3, 1, 31], // day of month 48 | M: [4, 1, 12], // month 49 | Y: [6, 1970, 2099], // year 50 | d: [5, 1, 7, 1] // day of week 51 | }; 52 | 53 | /** 54 | * Returns the value + offset if value is a number, otherwise it 55 | * attempts to look up the value in the NAMES table and returns 56 | * that result instead. 57 | * 58 | * @param {Int,String} value: The value that should be parsed 59 | * @param {Int} offset: Any offset that must be added to the value 60 | */ 61 | function getValue(value, offset, max) { 62 | return isNaN(value) ? NAMES[value] || null : Math.min(+value + (offset || 0), max || 9999); 63 | } 64 | 65 | /** 66 | * Returns a deep clone of a schedule skipping any day of week 67 | * constraints. 68 | * 69 | * @param {Sched} sched: The schedule that will be cloned 70 | */ 71 | function cloneSchedule(sched) { 72 | var clone = {}, field; 73 | 74 | for(field in sched) { 75 | if (field !== 'dc' && field !== 'd') { 76 | clone[field] = sched[field].slice(0); 77 | } 78 | } 79 | 80 | return clone; 81 | } 82 | 83 | /** 84 | * Adds values to the specified constraint in the current schedule. 85 | * 86 | * @param {Sched} sched: The schedule to add the constraint to 87 | * @param {String} name: Name of constraint to add 88 | * @param {Int} min: Minimum value for this constraint 89 | * @param {Int} max: Maximum value for this constraint 90 | * @param {Int} inc: The increment to use between min and max 91 | */ 92 | function add(sched, name, min, max, inc) { 93 | var i = min; 94 | 95 | if (!sched[name]) { 96 | sched[name] = []; 97 | } 98 | 99 | while (i <= max) { 100 | if (sched[name].indexOf(i) < 0) { 101 | sched[name].push(i); 102 | } 103 | i += inc || 1; 104 | } 105 | 106 | sched[name].sort(function(a,b) { return a - b; }); 107 | } 108 | 109 | /** 110 | * Adds a hash item (of the form x#y or xL) to the schedule. 111 | * 112 | * @param {Schedule} schedules: The current schedule array to add to 113 | * @param {Schedule} curSched: The current schedule to add to 114 | * @param {Int} value: The value to add (x of x#y or xL) 115 | * @param {Int} hash: The hash value to add (y of x#y) 116 | */ 117 | function addHash(schedules, curSched, value, hash) { 118 | // if there are any existing day of week constraints that 119 | // aren't equal to the one we're adding, create a new 120 | // composite schedule 121 | if ((curSched.d && !curSched.dc) || 122 | (curSched.dc && curSched.dc.indexOf(hash) < 0)) { 123 | schedules.push(cloneSchedule(curSched)); 124 | curSched = schedules[schedules.length-1]; 125 | } 126 | 127 | add(curSched, 'd', value, value); 128 | add(curSched, 'dc', hash, hash); 129 | } 130 | 131 | function addWeekday(s, curSched, value) { 132 | var except1 = {}, except2 = {}; 133 | if (value=== 1) { 134 | // cron doesn't pass month boundaries, so if 1st is a 135 | // weekend then we need to use 2nd or 3rd instead 136 | add(curSched, 'D', 1, 3); 137 | add(curSched, 'd', NAMES.MON, NAMES.FRI); 138 | add(except1, 'D', 2, 2); 139 | add(except1, 'd', NAMES.TUE, NAMES.FRI); 140 | add(except2, 'D', 3, 3); 141 | add(except2, 'd', NAMES.TUE, NAMES.FRI); 142 | } else { 143 | // normally you want the closest day, so if v is a 144 | // Saturday, use the previous Friday. If it's a 145 | // sunday, use the following Monday. 146 | add(curSched, 'D', value-1, value+1); 147 | add(curSched, 'd', NAMES.MON, NAMES.FRI); 148 | add(except1, 'D', value-1, value-1); 149 | add(except1, 'd', NAMES.MON, NAMES.THU); 150 | add(except2, 'D', value+1, value+1); 151 | add(except2, 'd', NAMES.TUE, NAMES.FRI); 152 | } 153 | s.exceptions.push(except1); 154 | s.exceptions.push(except2); 155 | } 156 | 157 | /** 158 | * Adds a range item (of the form x-y/z) to the schedule. 159 | * 160 | * @param {String} item: The cron expression item to add 161 | * @param {Schedule} curSched: The current schedule to add to 162 | * @param {String} name: The name to use for this constraint 163 | * @param {Int} min: The min value for the constraint 164 | * @param {Int} max: The max value for the constraint 165 | * @param {Int} offset: The offset to apply to the cron value 166 | */ 167 | function addRange(item, curSched, name, min, max, offset) { 168 | // parse range/x 169 | var incSplit = item.split('/'), 170 | inc = +incSplit[1], 171 | range = incSplit[0]; 172 | 173 | // parse x-y or * or 0 174 | if (range !== '*' && range !== '0') { 175 | var rangeSplit = range.split('-'); 176 | min = getValue(rangeSplit[0], offset, max); 177 | 178 | // fix for issue #13, range may be single digit 179 | max = getValue(rangeSplit[1], offset, max) || max; 180 | } 181 | 182 | add(curSched, name, min, max, inc); 183 | } 184 | 185 | /** 186 | * Parses a particular item within a cron expression. 187 | * 188 | * @param {String} item: The cron expression item to parse 189 | * @param {Schedule} s: The existing set of schedules 190 | * @param {String} name: The name to use for this constraint 191 | * @param {Int} min: The min value for the constraint 192 | * @param {Int} max: The max value for the constraint 193 | * @param {Int} offset: The offset to apply to the cron value 194 | */ 195 | function parse(item, s, name, min, max, offset) { 196 | var value, 197 | split, 198 | schedules = s.schedules, 199 | curSched = schedules[schedules.length-1]; 200 | 201 | // L just means min - 1 (this also makes it work for any field) 202 | if (item === 'L') { 203 | item = min - 1; 204 | } 205 | 206 | // parse x 207 | if ((value = getValue(item, offset, max)) !== null) { 208 | add(curSched, name, value, value); 209 | } 210 | // parse xW 211 | else if ((value = getValue(item.replace('W', ''), offset, max)) !== null) { 212 | addWeekday(s, curSched, value); 213 | } 214 | // parse xL 215 | else if ((value = getValue(item.replace('L', ''), offset, max)) !== null) { 216 | addHash(schedules, curSched, value, min-1); 217 | } 218 | // parse x#y 219 | else if ((split = item.split('#')).length === 2) { 220 | value = getValue(split[0], offset, max); 221 | addHash(schedules, curSched, value, getValue(split[1])); 222 | } 223 | // parse x-y or x-y/z or */z or 0/z 224 | else { 225 | addRange(item, curSched, name, min, max, offset); 226 | } 227 | } 228 | 229 | /** 230 | * Returns true if the item is either of the form x#y or xL. 231 | * 232 | * @param {String} item: The expression item to check 233 | */ 234 | function isHash(item) { 235 | return item.indexOf('#') > -1 || item.indexOf('L') > 0; 236 | } 237 | 238 | 239 | function itemSorter(a,b) { 240 | return isHash(a) && !isHash(b) ? 1 : a - b; 241 | } 242 | 243 | /** 244 | * Parses each of the fields in a cron expression. The expression must 245 | * include the seconds field, the year field is optional. 246 | * 247 | * @param {String} expr: The cron expression to parse 248 | */ 249 | function parseExpr(expr) { 250 | var schedule = {schedules: [{}], exceptions: []}, 251 | components = expr.replace(/(\s)+/g, ' ').split(' '), 252 | field, f, component, items; 253 | 254 | for(field in FIELDS) { 255 | f = FIELDS[field]; 256 | component = components[f[0]]; 257 | if (component && component !== '*' && component !== '?') { 258 | // need to sort so that any #'s come last, otherwise 259 | // schedule clones to handle # won't contain all of the 260 | // other constraints 261 | items = component.split(',').sort(itemSorter); 262 | var i, length = items.length; 263 | for (i = 0; i < length; i++) { 264 | parse(items[i], schedule, field, f[1], f[2], f[3]); 265 | } 266 | } 267 | } 268 | 269 | return schedule; 270 | } 271 | 272 | /** 273 | * Make cron expression parsable. 274 | * 275 | * @param {String} expr: The cron expression to prepare 276 | */ 277 | function prepareExpr(expr) { 278 | var prepared = expr.toUpperCase(); 279 | return REPLACEMENTS[prepared] || prepared; 280 | } 281 | 282 | var e = prepareExpr(expr); 283 | return parseExpr(hasSeconds ? e : '0 ' + e); 284 | }; -------------------------------------------------------------------------------- /src/parse/ebnf_grammar.txt: -------------------------------------------------------------------------------- 1 | Composite ::= Schedule ('also' Schedule)* (('except' Schedule) ('also' Schedule)*)? 2 | 3 | Schedule ::= ( 4 | ('on the' ('first' | 'last' | Number_Range) Period) | 5 | ('every' ('weekend' | 'weekday' | ((Number Period)) (('starting on the' Number Period) | ('between the' Number 'and' Number))?)) | 6 | ('after' ((Number Period) | Time)) | 7 | ('before' ((Number Period) | Time)) | 8 | ('at' Time ('and' Time)*) | 9 | ('on' Day (Day_Range)?) | 10 | ('of' Month (Month_Range)?) | 11 | ('in' Year (Year_Range)?) 12 | 13 | )+ 14 | 15 | Range ::= (('-' | 'through') Value) | ((',' | 'and') Value)+ -------------------------------------------------------------------------------- /src/parse/index.js: -------------------------------------------------------------------------------- 1 | import "parse"; 2 | import "cron"; 3 | import "recur"; 4 | import "text"; -------------------------------------------------------------------------------- /src/parse/parse.js: -------------------------------------------------------------------------------- 1 | later.parse = {}; -------------------------------------------------------------------------------- /src/parse/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses an English string expression and produces a schedule that is 3 | * compatible with Later.js. 4 | * 5 | * Examples: 6 | * 7 | * every 5 minutes between the 1st and 30th minute 8 | * at 10:00 am on tues of may in 2012 9 | * on the 15-20th day of march-dec 10 | * every 20 seconds every 5 minutes every 4 hours between the 10th and 20th hour 11 | */ 12 | later.parse.text = function(str) { 13 | 14 | var recur = later.parse.recur, 15 | pos = 0, 16 | input = '', 17 | error; 18 | 19 | // Regex expressions for all of the valid tokens 20 | var TOKENTYPES = { 21 | eof: /^$/, 22 | rank: /^((\d+)(st|nd|rd|th)?)\b/, 23 | time: /^((([0]?[1-9]|1[0-2]):[0-5]\d(\s)?(am|pm))|(([0]?\d|1\d|2[0-3]):[0-5]\d))\b/, 24 | dayName: /^((sun|mon|tue(s)?|wed(nes)?|thu(r(s)?)?|fri|sat(ur)?)(day)?)\b/, 25 | monthName: /^(jan(uary)?|feb(ruary)?|ma((r(ch)?)?|y)|apr(il)?|ju(ly|ne)|aug(ust)?|oct(ober)?|(sept|nov|dec)(ember)?)\b/, 26 | yearIndex: /^(\d\d\d\d)\b/, 27 | every: /^every\b/, 28 | after: /^after\b/, 29 | before: /^before\b/, 30 | second: /^(s|sec(ond)?(s)?)\b/, 31 | minute: /^(m|min(ute)?(s)?)\b/, 32 | hour: /^(h|hour(s)?)\b/, 33 | day: /^(day(s)?( of the month)?)\b/, 34 | dayInstance: /^day instance\b/, 35 | dayOfWeek: /^day(s)? of the week\b/, 36 | dayOfYear: /^day(s)? of the year\b/, 37 | weekOfYear: /^week(s)?( of the year)?\b/, 38 | weekOfMonth: /^week(s)? of the month\b/, 39 | weekday: /^weekday\b/, 40 | weekend: /^weekend\b/, 41 | month: /^month(s)?\b/, 42 | year: /^year(s)?\b/, 43 | between: /^between (the)?\b/, 44 | start: /^(start(ing)? (at|on( the)?)?)\b/, 45 | at: /^(at|@)\b/, 46 | and: /^(,|and\b)/, 47 | except: /^(except\b)/, 48 | also: /(also)\b/, 49 | first: /^(first)\b/, 50 | last: /^last\b/, 51 | "in": /^in\b/, 52 | of: /^of\b/, 53 | onthe: /^on the\b/, 54 | on: /^on\b/, 55 | through: /(-|^(to|through)\b)/ 56 | }; 57 | 58 | // Array to convert string names to valid numerical values 59 | var NAMES = { jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, jul: 7, 60 | aug: 8, sep: 9, oct: 10, nov: 11, dec: 12, sun: 1, mon: 2, tue: 3, 61 | wed: 4, thu: 5, fri: 6, sat: 7, '1st': 1, fir: 1, '2nd': 2, sec: 2, 62 | '3rd': 3, thi: 3, '4th': 4, 'for': 4 63 | }; 64 | 65 | /** 66 | * Bundles up the results of the peek operation into a token. 67 | * 68 | * @param {Int} start: The start position of the token 69 | * @param {Int} end: The end position of the token 70 | * @param {String} text: The actual text that was parsed 71 | * @param {TokenType} type: The TokenType of the token 72 | */ 73 | function t(start, end, text, type) { 74 | return {startPos: start, endPos: end, text: text, type: type}; 75 | } 76 | 77 | /** 78 | * Peeks forward to see if the next token is the expected token and 79 | * returns the token if found. Pos is not moved during a Peek operation. 80 | * 81 | * @param {TokenType} exepected: The types of token to scan for 82 | */ 83 | function peek(expected) { 84 | var scanTokens = expected instanceof Array ? expected : [expected], 85 | whiteSpace = /\s+/, 86 | token, curInput, m, scanToken, start, len; 87 | 88 | scanTokens.push(whiteSpace); 89 | 90 | // loop past any skipped tokens and only look for expected tokens 91 | start = pos; 92 | while (!token || token.type === whiteSpace) { 93 | len = -1; 94 | curInput = input.substring(start); 95 | token = t(start, start, input.split(whiteSpace)[0]); 96 | 97 | var i, length = scanTokens.length; 98 | for(i = 0; i < length; i++) { 99 | scanToken = scanTokens[i]; 100 | m = scanToken.exec(curInput); 101 | if (m && m.index === 0 && m[0].length > len) { 102 | len = m[0].length; 103 | token = t(start, start + len, curInput.substring(0, len), scanToken); 104 | } 105 | } 106 | 107 | // update the start position if this token should be skipped 108 | if (token.type === whiteSpace) { 109 | start = token.endPos; 110 | } 111 | } 112 | 113 | return token; 114 | } 115 | 116 | /** 117 | * Moves pos to the end of the expectedToken if it is found. 118 | * 119 | * @param {TokenType} exepectedToken: The types of token to scan for 120 | */ 121 | function scan(expectedToken) { 122 | var token = peek(expectedToken); 123 | pos = token.endPos; 124 | return token; 125 | } 126 | 127 | /** 128 | * Parses the next 'y-z' expression and returns the resulting valid 129 | * value array. 130 | * 131 | * @param {TokenType} tokenType: The type of range values allowed 132 | */ 133 | function parseThroughExpr(tokenType) { 134 | var start = +parseTokenValue(tokenType), 135 | end = checkAndParse(TOKENTYPES.through) ? +parseTokenValue(tokenType) : start, 136 | nums = []; 137 | 138 | for (var i = start; i <= end; i++) { 139 | nums.push(i); 140 | } 141 | 142 | return nums; 143 | } 144 | 145 | /** 146 | * Parses the next 'x,y-z' expression and returns the resulting valid 147 | * value array. 148 | * 149 | * @param {TokenType} tokenType: The type of range values allowed 150 | */ 151 | function parseRanges(tokenType) { 152 | var nums = parseThroughExpr(tokenType); 153 | while (checkAndParse(TOKENTYPES.and)) { 154 | nums = nums.concat(parseThroughExpr(tokenType)); 155 | } 156 | return nums; 157 | } 158 | 159 | /** 160 | * Parses the next 'every (weekend|weekday|x) (starting on|between)' expression. 161 | * 162 | * @param {Recur} r: The recurrence to add the expression to 163 | */ 164 | function parseEvery(r) { 165 | var num, period, start, end; 166 | 167 | if (checkAndParse(TOKENTYPES.weekend)) { 168 | r.on(NAMES.sun,NAMES.sat).dayOfWeek(); 169 | } 170 | else if (checkAndParse(TOKENTYPES.weekday)) { 171 | r.on(NAMES.mon,NAMES.tue,NAMES.wed,NAMES.thu,NAMES.fri).dayOfWeek(); 172 | } 173 | else { 174 | num = parseTokenValue(TOKENTYPES.rank); 175 | r.every(num); 176 | period = parseTimePeriod(r); 177 | 178 | if (checkAndParse(TOKENTYPES.start)) { 179 | num = parseTokenValue(TOKENTYPES.rank); 180 | r.startingOn(num); 181 | parseToken(period.type); 182 | } 183 | else if (checkAndParse(TOKENTYPES.between)) { 184 | start = parseTokenValue(TOKENTYPES.rank); 185 | if (checkAndParse(TOKENTYPES.and)) { 186 | end = parseTokenValue(TOKENTYPES.rank); 187 | r.between(start,end); 188 | } 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * Parses the next 'on the (first|last|x,y-z)' expression. 195 | * 196 | * @param {Recur} r: The recurrence to add the expression to 197 | */ 198 | function parseOnThe(r) { 199 | if (checkAndParse(TOKENTYPES.first)) { 200 | r.first(); 201 | } 202 | else if (checkAndParse(TOKENTYPES.last)) { 203 | r.last(); 204 | } 205 | else { 206 | r.on(parseRanges(TOKENTYPES.rank)); 207 | } 208 | 209 | parseTimePeriod(r); 210 | } 211 | 212 | /** 213 | * Parses the schedule expression and returns the resulting schedules, 214 | * and exceptions. Error will return the position in the string where 215 | * an error occurred, will be null if no errors were found in the 216 | * expression. 217 | * 218 | * @param {String} str: The schedule expression to parse 219 | */ 220 | function parseScheduleExpr(str) { 221 | pos = 0; 222 | input = str; 223 | error = -1; 224 | 225 | var r = recur(); 226 | while (pos < input.length && error < 0) { 227 | 228 | var token = parseToken([TOKENTYPES.every, TOKENTYPES.after, TOKENTYPES.before, 229 | TOKENTYPES.onthe, TOKENTYPES.on, TOKENTYPES.of, TOKENTYPES["in"], 230 | TOKENTYPES.at, TOKENTYPES.and, TOKENTYPES.except, 231 | TOKENTYPES.also]); 232 | 233 | switch (token.type) { 234 | case TOKENTYPES.every: 235 | parseEvery(r); 236 | break; 237 | case TOKENTYPES.after: 238 | if(peek(TOKENTYPES.time).type !== undefined) { 239 | r.after(parseTokenValue(TOKENTYPES.time)); 240 | r.time(); 241 | } 242 | else { 243 | r.after(parseTokenValue(TOKENTYPES.rank)); 244 | parseTimePeriod(r); 245 | } 246 | break; 247 | case TOKENTYPES.before: 248 | if(peek(TOKENTYPES.time).type !== undefined) { 249 | r.before(parseTokenValue(TOKENTYPES.time)); 250 | r.time(); 251 | } 252 | else { 253 | r.before(parseTokenValue(TOKENTYPES.rank)); 254 | parseTimePeriod(r); 255 | } 256 | break; 257 | case TOKENTYPES.onthe: 258 | parseOnThe(r); 259 | break; 260 | case TOKENTYPES.on: 261 | r.on(parseRanges(TOKENTYPES.dayName)).dayOfWeek(); 262 | break; 263 | case TOKENTYPES.of: 264 | r.on(parseRanges(TOKENTYPES.monthName)).month(); 265 | break; 266 | case TOKENTYPES["in"]: 267 | r.on(parseRanges(TOKENTYPES.yearIndex)).year(); 268 | break; 269 | case TOKENTYPES.at: 270 | r.on(parseTokenValue(TOKENTYPES.time)).time(); 271 | while (checkAndParse(TOKENTYPES.and)) { 272 | r.on(parseTokenValue(TOKENTYPES.time)).time(); 273 | } 274 | break; 275 | case TOKENTYPES.and: 276 | break; 277 | case TOKENTYPES.also: 278 | r.and(); 279 | break; 280 | case TOKENTYPES.except: 281 | r.except(); 282 | break; 283 | default: 284 | error = pos; 285 | } 286 | } 287 | 288 | return {schedules: r.schedules, exceptions: r.exceptions, error: error}; 289 | } 290 | 291 | /** 292 | * Parses the next token representing a time period and adds it to 293 | * the provided recur object. 294 | * 295 | * @param {Recur} r: The recurrence to add the time period to 296 | */ 297 | function parseTimePeriod(r) { 298 | var timePeriod = parseToken([TOKENTYPES.second, TOKENTYPES.minute, 299 | TOKENTYPES.hour, TOKENTYPES.dayOfYear, TOKENTYPES.dayOfWeek, 300 | TOKENTYPES.dayInstance, TOKENTYPES.day, TOKENTYPES.month, 301 | TOKENTYPES.year, TOKENTYPES.weekOfMonth, TOKENTYPES.weekOfYear]); 302 | 303 | switch (timePeriod.type) { 304 | case TOKENTYPES.second: 305 | r.second(); 306 | break; 307 | case TOKENTYPES.minute: 308 | r.minute(); 309 | break; 310 | case TOKENTYPES.hour: 311 | r.hour(); 312 | break; 313 | case TOKENTYPES.dayOfYear: 314 | r.dayOfYear(); 315 | break; 316 | case TOKENTYPES.dayOfWeek: 317 | r.dayOfWeek(); 318 | break; 319 | case TOKENTYPES.dayInstance: 320 | r.dayOfWeekCount(); 321 | break; 322 | case TOKENTYPES.day: 323 | r.dayOfMonth(); 324 | break; 325 | case TOKENTYPES.weekOfMonth: 326 | r.weekOfMonth(); 327 | break; 328 | case TOKENTYPES.weekOfYear: 329 | r.weekOfYear(); 330 | break; 331 | case TOKENTYPES.month: 332 | r.month(); 333 | break; 334 | case TOKENTYPES.year: 335 | r.year(); 336 | break; 337 | default: 338 | error = pos; 339 | } 340 | 341 | return timePeriod; 342 | } 343 | 344 | /** 345 | * Checks the next token to see if it is of tokenType. Returns true if 346 | * it is and discards the token. Returns false otherwise. 347 | * 348 | * @param {TokenType} tokenType: The type or types of token to parse 349 | */ 350 | function checkAndParse(tokenType) { 351 | var found = (peek(tokenType)).type === tokenType; 352 | if (found) { 353 | scan(tokenType); 354 | } 355 | return found; 356 | } 357 | 358 | /** 359 | * Parses and returns the next token. 360 | * 361 | * @param {TokenType} tokenType: The type or types of token to parse 362 | */ 363 | function parseToken(tokenType) { 364 | var t = scan(tokenType); 365 | if (t.type) { 366 | t.text = convertString(t.text, tokenType); 367 | } 368 | else { 369 | error = pos; 370 | } 371 | return t; 372 | } 373 | 374 | /** 375 | * Returns the text value of the token that was parsed. 376 | * 377 | * @param {TokenType} tokenType: The type of token to parse 378 | */ 379 | function parseTokenValue(tokenType) { 380 | return (parseToken(tokenType)).text; 381 | } 382 | 383 | /** 384 | * Converts a string value to a numerical value based on the type of 385 | * token that was parsed. 386 | * 387 | * @param {String} str: The schedule string to parse 388 | * @param {TokenType} tokenType: The type of token to convert 389 | */ 390 | function convertString(str, tokenType) { 391 | var output = str; 392 | 393 | switch (tokenType) { 394 | case TOKENTYPES.time: 395 | var parts = str.split(/(:|am|pm)/), 396 | hour = parts[3] === 'pm' && parts[0] < 12 ? parseInt(parts[0],10) + 12 : parts[0], 397 | min = parts[2].trim(); 398 | 399 | output = (hour.length === 1 ? '0' : '') + hour + ":" + min; 400 | break; 401 | 402 | case TOKENTYPES.rank: 403 | output = parseInt((/^\d+/.exec(str))[0],10); 404 | break; 405 | 406 | case TOKENTYPES.monthName: 407 | case TOKENTYPES.dayName: 408 | output = NAMES[str.substring(0,3)]; 409 | break; 410 | } 411 | 412 | return output; 413 | } 414 | 415 | return parseScheduleExpr(str.toLowerCase()); 416 | }; 417 | -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Later.js 3 | * (c) 2015 Bill, Levelstory, Inc. 4 | * Later is freely distributable under the MIT license. 5 | * For all details and documentation: 6 | * http://bunkat.github.com/later 7 | */ 8 | later = (function() { 9 | 'use strict'; 10 | 11 | var later = {version: "1.2.0"}; // semver -------------------------------------------------------------------------------- /test/array/next-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Later.array.next', function() { 5 | 6 | it('should exist', function() { 7 | should.exist(later.array.next); 8 | }); 9 | 10 | it('should return the next highest value', function() { 11 | var arr = [1,2,4,5], 12 | cur = 3, 13 | extent = [1,5], 14 | expected = 4, 15 | actual = later.array.next(cur, arr, extent); 16 | 17 | actual.should.eql(expected); 18 | }); 19 | 20 | it('should return the next highest value with array size of 1', function() { 21 | var arr = [1], 22 | cur = 3, 23 | extent = [1,5], 24 | expected = 1, 25 | actual = later.array.next(cur, arr, extent); 26 | 27 | actual.should.eql(expected); 28 | }); 29 | 30 | it('should return the next highest value with array size of 1 with same value', function() { 31 | var arr = [1], 32 | cur = 1, 33 | extent = [1,5], 34 | expected = 1, 35 | actual = later.array.next(cur, arr, extent); 36 | 37 | actual.should.eql(expected); 38 | }); 39 | 40 | it('should return the next highest value with array size of 1 with zero value', function() { 41 | var arr = [0], 42 | cur = 30, 43 | extent = [1,31], 44 | expected = 0, 45 | actual = later.array.next(cur, arr, extent); 46 | 47 | actual.should.eql(expected); 48 | }); 49 | 50 | it('should return the next highest value which might be the first value', function() { 51 | var arr = [1,2,3,4,5], 52 | cur = 0, 53 | extent = [1,5], 54 | expected = 1, 55 | actual = later.array.next(cur, arr, extent); 56 | 57 | actual.should.eql(expected); 58 | }); 59 | 60 | it('should return the next highest value, wrapping if needed', function() { 61 | var arr = [0,1,2,3,4,5], 62 | cur = 6, 63 | extent = [0,5], 64 | expected = 0, 65 | actual = later.array.next(cur, arr, extent); 66 | 67 | actual.should.eql(expected); 68 | }); 69 | 70 | it('should return the next highest value, which might be zero', function() { 71 | var arr = [1,2,3,4,5,0], 72 | cur = 6, 73 | extent = [1,10], 74 | expected = 0, 75 | actual = later.array.next(cur, arr, extent); 76 | 77 | actual.should.eql(expected); 78 | }); 79 | 80 | it('should return current value when it is in the list', function() { 81 | var arr = [1,2,4,5,0], 82 | cur = 4, 83 | extent = [1,10], 84 | expected = 4, 85 | actual = later.array.next(cur, arr, extent); 86 | 87 | actual.should.eql(expected); 88 | }); 89 | 90 | it('should return the next highest value when cur is greater than last value', function() { 91 | var arr = [1,2,4,5,0], 92 | cur = 12, 93 | extent = [1,10], 94 | expected = 1, 95 | actual = later.array.next(cur, arr, extent); 96 | 97 | actual.should.eql(expected); 98 | }); 99 | 100 | }); -------------------------------------------------------------------------------- /test/array/nextinvalid-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Later.array.nextInvalid', function() { 5 | 6 | it('should exist', function() { 7 | should.exist(later.array.nextInvalid); 8 | }); 9 | 10 | it('should return the next invalid value', function() { 11 | var arr = [1,2,5], 12 | extent = [1,5], 13 | cur = 2, 14 | expected = 3, 15 | actual = later.array.nextInvalid(cur, arr, extent); 16 | 17 | actual.should.eql(expected); 18 | }); 19 | 20 | it('should return the next invalid value when greater than arr', function() { 21 | var arr = [1,2,5], 22 | extent = [1,10], 23 | cur = 5, 24 | expected = 6, 25 | actual = later.array.nextInvalid(cur, arr, extent); 26 | 27 | actual.should.eql(expected); 28 | }); 29 | 30 | it('should return the next invalid value when zero value is largest', function() { 31 | var arr = [1,2,5, 0], 32 | extent = [1,31], 33 | cur = 31, 34 | expected = 3, 35 | actual = later.array.nextInvalid(cur, arr, extent); 36 | 37 | actual.should.eql(expected); 38 | }); 39 | 40 | it('should return the next invalid value when zero value is smallest', function() { 41 | var arr = [0,1,2,5,10], 42 | extent = [0,10], 43 | cur = 10, 44 | expected = 3, 45 | actual = later.array.nextInvalid(cur, arr, extent); 46 | 47 | actual.should.eql(expected); 48 | }); 49 | 50 | it('should return the current value if it is invalid', function() { 51 | var arr = [0,1,2,5,10], 52 | extent = [0,10], 53 | cur = 4, 54 | expected = 4, 55 | actual = later.array.nextInvalid(cur, arr, extent); 56 | 57 | actual.should.eql(expected); 58 | }); 59 | }); -------------------------------------------------------------------------------- /test/array/prev-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Later.array.prev', function() { 5 | 6 | it('should exist', function() { 7 | should.exist(later.array.prev); 8 | }); 9 | 10 | it('should return the prev highest value', function() { 11 | var arr = [1,2,4,5], 12 | cur = 3, 13 | extent = [1,5], 14 | expected = 2, 15 | actual = later.array.prev(cur, arr, extent); 16 | 17 | actual.should.eql(expected); 18 | }); 19 | 20 | it('should return the prev highest value with array size of 1', function() { 21 | var arr = [1], 22 | cur = 3, 23 | extent = [1,5], 24 | expected = 1, 25 | actual = later.array.prev(cur, arr, extent); 26 | 27 | actual.should.eql(expected); 28 | }); 29 | 30 | it('should return the prev highest value with array size of 1 with zero value', function() { 31 | var arr = [0], 32 | cur = 10, 33 | extent = [1,5], 34 | expected = 0, 35 | actual = later.array.prev(cur, arr, extent); 36 | 37 | actual.should.eql(expected); 38 | }); 39 | 40 | it('should return the prev highest value which might be the last value', function() { 41 | var arr = [1,2,3,4,5], 42 | cur = 6, 43 | extent = [1,5], 44 | expected = 5, 45 | actual = later.array.prev(cur, arr, extent); 46 | 47 | actual.should.eql(expected); 48 | }); 49 | 50 | it('should return the prev highest value, wrapping if needed', function() { 51 | var arr = [1,2,3,4,5], 52 | cur = 0, 53 | extent = [0,5], 54 | expected = 5, 55 | actual = later.array.prev(cur, arr, extent); 56 | 57 | actual.should.eql(expected); 58 | }); 59 | 60 | it('should return the prev highest value, which might be zero value', function() { 61 | var arr = [2,3,4,5,0], 62 | cur = 1, 63 | extent = [1,10], 64 | expected = 0, 65 | actual = later.array.prev(cur, arr, extent); 66 | 67 | actual.should.eql(expected); 68 | }); 69 | 70 | it('should return current value when it is in the list', function() { 71 | var arr = [1,2,4,5,0], 72 | cur = 4, 73 | extent = [1,10], 74 | expected = 4, 75 | actual = later.array.prev(cur, arr, extent); 76 | 77 | actual.should.eql(expected); 78 | }); 79 | 80 | it('should return the prev highest value when cur is greater than last value', function() { 81 | var arr = [1,2,4,5,0], 82 | cur = 12, 83 | extent = [1,10], 84 | expected = 0, 85 | actual = later.array.prev(cur, arr, extent); 86 | 87 | actual.should.eql(expected); 88 | }); 89 | 90 | }); -------------------------------------------------------------------------------- /test/array/previnvalid-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Later.array.prevInvalid', function() { 5 | 6 | it('should exist', function() { 7 | should.exist(later.array.prevInvalid); 8 | }); 9 | 10 | it('should return the previous invalid value', function() { 11 | var arr = [1,2,5], 12 | extent = [1,5], 13 | cur = 5, 14 | expected = 4, 15 | actual = later.array.prevInvalid(cur, arr, extent); 16 | 17 | actual.should.eql(expected); 18 | }); 19 | 20 | it('should return the previous invalid value when less than arr', function() { 21 | var arr = [2,3,5], 22 | extent = [1,10], 23 | cur = 3, 24 | expected = 1, 25 | actual = later.array.prevInvalid(cur, arr, extent); 26 | 27 | actual.should.eql(expected); 28 | }); 29 | 30 | it('should return the previous invalid value when zero value is largest', function() { 31 | var arr = [1,2,5,0], 32 | extent = [1,31], 33 | cur = 31, 34 | expected = 30, 35 | actual = later.array.prevInvalid(cur, arr, extent); 36 | 37 | actual.should.eql(expected); 38 | }); 39 | 40 | it('should return the previous invalid value when zero value is smallest', function() { 41 | var arr = [0,1,2,5,10], 42 | extent = [0,10], 43 | cur = 2, 44 | expected = 9, 45 | actual = later.array.prevInvalid(cur, arr, extent); 46 | 47 | actual.should.eql(expected); 48 | }); 49 | 50 | it('should return the current value if it is invalid', function() { 51 | var arr = [0,1,2,5,10], 52 | extent = [0,10], 53 | cur = 4, 54 | expected = 4, 55 | actual = later.array.prevInvalid(cur, arr, extent); 56 | 57 | actual.should.eql(expected); 58 | }); 59 | 60 | it('should return undefined if there is no invalid value', function() { 61 | var arr = [0,1,2,3,4,5], 62 | extent = [0,5], 63 | cur = 4; 64 | 65 | should.not.exist(later.array.prevInvalid(cur, arr, extent)); 66 | }); 67 | }); -------------------------------------------------------------------------------- /test/array/sort-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Later.array.sort', function() { 5 | 6 | it('should exist', function() { 7 | should.exist(later.array.sort); 8 | }); 9 | 10 | it('should not modify arrays that are already sorted', function() { 11 | var arr = [1,2,3,4,5], 12 | expected = [1,2,3,4,5]; 13 | 14 | later.array.sort(arr); 15 | arr.should.eql(expected); 16 | }); 17 | 18 | it('should sort in natural order', function() { 19 | var arr = [6,9,2,4,3], 20 | expected = [2,3,4,6,9]; 21 | 22 | later.array.sort(arr); 23 | arr.should.eql(expected); 24 | }); 25 | 26 | it('should put zero at the front by default', function() { 27 | var arr = [6,9,2,0,4,3], 28 | expected = [0,2,3,4,6,9]; 29 | 30 | later.array.sort(arr); 31 | arr.should.eql(expected); 32 | }); 33 | 34 | it('should put zero at the end if zeroIsLast is true', function() { 35 | var arr = [6,9,2,0,4,3], 36 | expected = [2,3,4,6,9,0]; 37 | 38 | later.array.sort(arr, true); 39 | arr.should.eql(expected); 40 | }); 41 | 42 | }); -------------------------------------------------------------------------------- /test/constraint/day-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.day), 3 | should = require('should'); 4 | 5 | describe('Later.day', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1,31], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 31, 20 | extent: [1,31], 21 | start: new Date(2009, 11, 31), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 1, 28 | extent: [1,31], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 30, 36 | extent: [1,30], 37 | start: new Date(2011, 3, 30), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 28, 44 | extent: [1,29], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 29, 52 | extent: [1,29], 53 | start: new Date(2012, 1, 29), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 8, 60 | extent: [1,30], 61 | start: new Date(2012, 10, 8), 62 | end: new Date(2012, 10, 8, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 10, 68 | extent: [1,31], 69 | start: new Date(2013, 2, 10), 70 | end: new Date(2013, 2, 10, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 3, 76 | extent: [1,30], 77 | start: new Date(2013, 10, 3), 78 | end: new Date(2013, 10, 3, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 22, 84 | extent: [1,28], 85 | start: new Date(2014, 1, 22), 86 | end: new Date(2014, 1, 22, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 19, 92 | extent: [1,30], 93 | start: new Date(2015, 5, 19), 94 | end: new Date(2015, 5, 19, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 29, 100 | extent: [1,31], 101 | start: new Date(2016, 7, 29), 102 | end: new Date(2016, 7, 29, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 4, 108 | extent: [1,30], 109 | start: new Date(2017, 8, 4), 110 | end: new Date(2017, 8, 4, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/dayofweek-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.dayOfWeek), 3 | should = require('should'); 4 | 5 | describe('Later.dayOfWeek', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 3, 12 | extent: [1,7], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 5, 20 | extent: [1,7], 21 | start: new Date(2009, 11, 31), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 1, 28 | extent: [1,7], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 7, 36 | extent: [1,7], 37 | start: new Date(2011, 3, 30), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 3, 44 | extent: [1,7], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 4, 52 | extent: [1,7], 53 | start: new Date(2012, 1, 29), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 5, 60 | extent: [1,7], 61 | start: new Date(2012, 10, 8), 62 | end: new Date(2012, 10, 8, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 1, 68 | extent: [1,7], 69 | start: new Date(2013, 2, 10), 70 | end: new Date(2013, 2, 10, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 1, 76 | extent: [1,7], 77 | start: new Date(2013, 10, 3), 78 | end: new Date(2013, 10, 3, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 7, 84 | extent: [1,7], 85 | start: new Date(2014, 1, 22), 86 | end: new Date(2014, 1, 22, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 6, 92 | extent: [1,7], 93 | start: new Date(2015, 5, 19), 94 | end: new Date(2015, 5, 19, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 2, 100 | extent: [1,7], 101 | start: new Date(2016, 7, 29), 102 | end: new Date(2016, 7, 29, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 2, 108 | extent: [1,7], 109 | start: new Date(2017, 8, 4), 110 | end: new Date(2017, 8, 4, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/dayofweekcount-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.dayOfWeekCount), 3 | should = require('should'); 4 | 5 | describe('Later.dayOfWeekCount', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1,5], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 7, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 5, 20 | extent: [1,5], 21 | start: new Date(2009, 11, 29), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 1, 28 | extent: [1,5], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 7, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 5, 36 | extent: [1,5], 37 | start: new Date(2011, 3, 29), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 4, 44 | extent: [1,5], 45 | start: new Date(2012, 1, 22), 46 | end: new Date(2012, 1, 28, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 5, 52 | extent: [1,5], 53 | start: new Date(2012, 1, 29), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 2, 60 | extent: [1,5], 61 | start: new Date(2012, 10, 8), 62 | end: new Date(2012, 10, 14, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 2, 68 | extent: [1,5], 69 | start: new Date(2013, 2, 8), 70 | end: new Date(2013, 2, 14, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 1, 76 | extent: [1,5], 77 | start: new Date(2013, 10, 1), 78 | end: new Date(2013, 10, 7, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 4, 84 | extent: [1,4], 85 | start: new Date(2014, 1, 22), 86 | end: new Date(2014, 1, 28, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 3, 92 | extent: [1,5], 93 | start: new Date(2015, 5, 15), 94 | end: new Date(2015, 5, 21, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 5, 100 | extent: [1,5], 101 | start: new Date(2016, 7, 29), 102 | end: new Date(2016, 7, 31, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 1, 108 | extent: [1,5], 109 | start: new Date(2017, 8, 1), 110 | end: new Date(2017, 8, 7, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/dayofyear-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.dayOfYear), 3 | should = require('should'); 4 | 5 | describe('Later.dayOfYear', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1,366], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 365, 20 | extent: [1,365], 21 | start: new Date(2009, 11, 31), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 213, 28 | extent: [1,365], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 120, 36 | extent: [1,365], 37 | start: new Date(2011, 3, 30), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 59, 44 | extent: [1,366], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 60, 52 | extent: [1,366], 53 | start: new Date(2012, 1, 29), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 313, 60 | extent: [1,366], 61 | start: new Date(2012, 10, 8), 62 | end: new Date(2012, 10, 8, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 69, 68 | extent: [1,365], 69 | start: new Date(2013, 2, 10), 70 | end: new Date(2013, 2, 10, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 307, 76 | extent: [1,365], 77 | start: new Date(2013, 10, 3), 78 | end: new Date(2013, 10, 3, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 53, 84 | extent: [1,365], 85 | start: new Date(2014, 1, 22), 86 | end: new Date(2014, 1, 22, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 170, 92 | extent: [1,365], 93 | start: new Date(2015, 5, 19), 94 | end: new Date(2015, 5, 19, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 242, 100 | extent: [1,366], 101 | start: new Date(2016, 7, 29), 102 | end: new Date(2016, 7, 29, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 247, 108 | extent: [1,365], 109 | start: new Date(2017, 8, 4), 110 | end: new Date(2017, 8, 4, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/hour-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.hour), 3 | should = require('should'); 4 | 5 | describe('Later.hour', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 0, 12 | extent: [0, 23], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1, 0, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 23, 20 | extent: [0, 23], 21 | start: new Date(2009, 11, 31, 23), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 0, 28 | extent: [0, 23], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1, 0, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 23, 36 | extent: [0, 23], 37 | start: new Date(2011, 3, 30, 23), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 0, 44 | extent: [0, 23], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28, 0, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 23, 52 | extent: [0, 23], 53 | start: new Date(2012, 1, 29, 23), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 14, 60 | extent: [0, 23], 61 | start: new Date(2012, 10, 8, 14), 62 | end: new Date(2012, 10, 8, 14, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 1, 68 | extent: [0, 23], 69 | start: new Date(2013, 2, 10, 1), 70 | end: new Date(2013, 2, 10, 1, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 2, 76 | extent: [0, 23], 77 | start: new Date(2013, 10, 3, 2), 78 | end: new Date(2013, 10, 3, 2, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 6, 84 | extent: [0, 23], 85 | start: new Date(2014, 1, 22, 6), 86 | end: new Date(2014, 1, 22, 6, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 18, 92 | extent: [0, 23], 93 | start: new Date(2015, 5, 19, 18), 94 | end: new Date(2015, 5, 19, 18, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 2, 100 | extent: [0, 23], 101 | start: new Date(2016, 7, 29, 2), 102 | end: new Date(2016, 7, 29, 2, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 10, 108 | extent: [0, 23], 109 | start: new Date(2017, 8, 4, 10), 110 | end: new Date(2017, 8, 4, 10, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/minute-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.minute), 3 | should = require('should'); 4 | 5 | describe('Later.minute', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 0, 12 | extent: [0, 59], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1, 0, 0, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 59, 20 | extent: [0, 59], 21 | start: new Date(2009, 11, 31, 23, 59), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 0, 28 | extent: [0, 59], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1, 0, 0, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 59, 36 | extent: [0, 59], 37 | start: new Date(2011, 3, 30, 23, 59), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 0, 44 | extent: [0, 59], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28, 0, 0, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 59, 52 | extent: [0, 59], 53 | start: new Date(2012, 1, 29, 23, 59), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 0, 60 | extent: [0, 59], 61 | start: new Date(2012, 10, 8, 14), 62 | end: new Date(2012, 10, 8, 14, 0, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 59, 68 | extent: [0, 59], 69 | start: new Date(2013, 2, 10, 1, 59), 70 | end: new Date(2013, 2, 10, 1, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 0, 76 | extent: [0, 59], 77 | start: new Date(2013, 10, 3, 2), 78 | end: new Date(2013, 10, 3, 2, 0, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 59, 84 | extent: [0, 59], 85 | start: new Date(2014, 1, 22, 6, 59), 86 | end: new Date(2014, 1, 22, 6, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 22, 92 | extent: [0, 59], 93 | start: new Date(2015, 5, 19, 18, 22), 94 | end: new Date(2015, 5, 19, 18, 22, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 56, 100 | extent: [0, 59], 101 | start: new Date(2016, 7, 29, 2, 56), 102 | end: new Date(2016, 7, 29, 2, 56, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 31, 108 | extent: [0, 59], 109 | start: new Date(2017, 8, 4, 10, 31), 110 | end: new Date(2017, 8, 4, 10, 31, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/month-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.month), 3 | should = require('should'); 4 | 5 | describe('Later.month', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1, 12], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 31, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 12, 20 | extent: [1, 12], 21 | start: new Date(2009, 11, 1), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 8, 28 | extent: [1, 12], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 31, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 4, 36 | extent: [1, 12], 37 | start: new Date(2011, 3, 1), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 2, 44 | extent: [1, 12], 45 | start: new Date(2012, 1, 1), 46 | end: new Date(2012, 1, 29, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 2, 52 | extent: [1, 12], 53 | start: new Date(2012, 1, 1), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 11, 60 | extent: [1, 12], 61 | start: new Date(2012, 10, 1), 62 | end: new Date(2012, 10, 30, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 3, 68 | extent: [1, 12], 69 | start: new Date(2013, 2, 1), 70 | end: new Date(2013, 2, 31, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 11, 76 | extent: [1, 12], 77 | start: new Date(2013, 10, 1), 78 | end: new Date(2013, 10, 30, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 2, 84 | extent: [1, 12], 85 | start: new Date(2014, 1, 1), 86 | end: new Date(2014, 1, 28, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 6, 92 | extent: [1, 12], 93 | start: new Date(2015, 5, 1), 94 | end: new Date(2015, 5, 30, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 8, 100 | extent: [1, 12], 101 | start: new Date(2016, 7, 1), 102 | end: new Date(2016, 7, 31, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 9, 108 | extent: [1, 12], 109 | start: new Date(2017, 8, 1), 110 | end: new Date(2017, 8, 30, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/runner.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | 3 | module.exports = runner = function (later, constraint) { 4 | 5 | function convertToUTC(d) { 6 | return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), 7 | d.getHours(), d.getMinutes(), d.getSeconds())); 8 | } 9 | 10 | function runSingleTest(fn, data, utc) { 11 | var date = utc ? convertToUTC(data.date) : data.date, 12 | dateString = utc ? date.toUTCString() : date, 13 | ex = utc && (data[fn] instanceof Date) ? convertToUTC(data[fn]) : data[fn], 14 | exString = utc && (ex instanceof Date) ? ex.toUTCString() : ex; 15 | 16 | it('should return ' + exString + ' for ' + dateString, function() { 17 | if(utc) later.date.UTC(); else later.date.localTime(); 18 | var actual = constraint[fn](date); 19 | actual = actual instanceof Date ? actual.getTime() : actual; 20 | ex = ex instanceof Date ? ex.getTime() : ex; 21 | actual.should.eql(ex); 22 | }); 23 | } 24 | 25 | function runSweepTest(fn, data, utc) { 26 | var min = data.extent[0] === 1 ? 0 : data.extent[0], 27 | max = data.extent[1] + 1, 28 | inc = Math.ceil((max-min)/200); // max 200 tests per constraint 29 | 30 | for(var i = min; i <= max; i = i + inc) { // test for overbounds 31 | if(fn === 'next') { 32 | testNext(data, i, utc); // test all values for amt 33 | } 34 | else { 35 | testPrev(data, i, utc); // test all values for amt 36 | } 37 | } 38 | } 39 | 40 | function testNext(data, amt, utc) { 41 | var date = utc ? convertToUTC(data.date) : data.date, 42 | dateString = utc ? date.toUTCString() : date; 43 | 44 | it('should return first date after ' + dateString + ' with val ' + amt, function() { 45 | if(utc) later.date.UTC(); else later.date.localTime(); 46 | 47 | var next = constraint.next(date, amt), 48 | ex = amt, 49 | outOfBounds = ex > constraint.extent(date)[1] || ex > constraint.extent(next)[1]; 50 | 51 | // if amt is outside of extent, the constraint should rollover to the 52 | // first value of the following time period 53 | if (outOfBounds) ex = constraint.extent(next)[0]; 54 | 55 | // hack to pass hour test that crosses DST 56 | if (ex === 2 && constraint.val(next) === 3 && next.getTimezoneOffset() !== date.getTimezoneOffset()) { 57 | ex = 3; 58 | } 59 | 60 | // result should match ex, should be greater than date, and should 61 | // be at the start of the time period 62 | // if check is hack to support year constraints which can return undefined 63 | if(constraint.name === 'year' && (amt <= constraint.val(date) || amt > later.Y.extent()[1])) { 64 | next.should.eql(later.NEVER); 65 | } 66 | else { 67 | constraint.isValid(next, ex).should.eql(true); 68 | next.getTime().should.be.above(date.getTime()); 69 | 70 | // need to special case day of week count since the last nth day may 71 | // not fall on a week boundary 72 | if(constraint.name !== 'day of week count' || amt !== 0) { 73 | constraint.start(next).getTime().should.eql(next.getTime()); 74 | } 75 | } 76 | 77 | }); 78 | } 79 | 80 | function testPrev(data, amt, utc) { 81 | var date = utc ? convertToUTC(data.date) : data.date, 82 | dateString = utc ? date.toUTCString() : date; 83 | 84 | it('should return first date before ' + dateString + ' with val ' + amt, function() { 85 | if(utc) later.date.UTC(); else later.date.localTime(); 86 | 87 | var prev = constraint.prev(date, amt), 88 | ex = amt, 89 | outOfBounds = ex > constraint.extent(prev)[1]; 90 | 91 | // if amt is outside of extent, the constraint should rollover to the 92 | // first value of the following time period 93 | if (outOfBounds) ex = constraint.extent(prev)[1]; 94 | 95 | // result should match ex, should be greater than date, and should 96 | // be at the start of the time period 97 | // if check is hack to support year constraints which can return undefined 98 | if(constraint.name === 'year' && (amt >= constraint.val(date) || amt < later.Y.extent()[0])) { 99 | prev.should.eql(later.NEVER); 100 | } 101 | else { 102 | constraint.isValid(prev, ex).should.eql(true); 103 | prev.getTime().should.be.below(date.getTime()); 104 | constraint.end(prev).getTime().should.eql(prev.getTime()); 105 | } 106 | }); 107 | } 108 | 109 | return { 110 | 111 | run: function (data, isYear) { 112 | var i = 0, len = data.length; 113 | 114 | // test both UTC and local times for all functions 115 | [true, false].forEach(function (utc) { 116 | 117 | // simple tests have the expected value passed in as data 118 | ['val', 'extent', 'start', 'end'].forEach(function (fn) { 119 | describe(fn, function() { 120 | for(i = 0; i < len; i++) { 121 | runSingleTest(fn, data[i], utc); 122 | } 123 | }); 124 | }); 125 | 126 | // complex tests do a sweep across all values and validate results 127 | // using checks verified by the simple tests 128 | ['next', 'prev'].forEach(function (fn) { 129 | describe(fn, function() { 130 | for(i = 0; i < len; i++) { 131 | runSweepTest(fn, data[i], utc); 132 | } 133 | }); 134 | }); 135 | 136 | }); 137 | 138 | } 139 | 140 | }; 141 | 142 | }; -------------------------------------------------------------------------------- /test/constraint/second-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.second), 3 | should = require('should'); 4 | 5 | describe('Later.second', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 0, 12 | extent: [0, 59], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 59, 20 | extent: [0, 59], 21 | start: new Date(2009, 11, 31, 23, 59, 59), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 0, 28 | extent: [0, 59], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 59, 36 | extent: [0, 59], 37 | start: new Date(2011, 3, 30, 23, 59, 59), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 0, 44 | extent: [0, 59], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 59, 52 | extent: [0, 59], 53 | start: new Date(2012, 1, 29, 23, 59, 59), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 0, 60 | extent: [0, 59], 61 | start: new Date(2012, 10, 8, 14), 62 | end: new Date(2012, 10, 8, 14) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 59, 68 | extent: [0, 59], 69 | start: new Date(2013, 2, 10, 1, 59, 59), 70 | end: new Date(2013, 2, 10, 1, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 0, 76 | extent: [0, 59], 77 | start: new Date(2013, 10, 3, 2), 78 | end: new Date(2013, 10, 3, 2) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 59, 84 | extent: [0, 59], 85 | start: new Date(2014, 1, 22, 6, 59, 59), 86 | end: new Date(2014, 1, 22, 6, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 0, 92 | extent: [0, 59], 93 | start: new Date(2015, 5, 19, 18, 22), 94 | end: new Date(2015, 5, 19, 18, 22) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 59, 100 | extent: [0, 59], 101 | start: new Date(2016, 7, 29, 2, 56, 59), 102 | end: new Date(2016, 7, 29, 2, 56, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 22, 108 | extent: [0, 59], 109 | start: new Date(2017, 8, 4, 10, 31, 22), 110 | end: new Date(2017, 8, 4, 10, 31, 22) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/time-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.time), 3 | should = require('should'); 4 | 5 | describe('Later.time', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 0, 12 | extent: [0, 86399], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 1) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 86399, 20 | extent: [0, 86399], 21 | start: new Date(2009, 11, 31, 23, 59, 59), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 0, 28 | extent: [0, 86399], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 1) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 86399, 36 | extent: [0, 86399], 37 | start: new Date(2011, 3, 30, 23, 59, 59), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 0, 44 | extent: [0, 86399], 45 | start: new Date(2012, 1, 28), 46 | end: new Date(2012, 1, 28) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 86399, 52 | extent: [0, 86399], 53 | start: new Date(2012, 1, 29, 23, 59, 59), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 50400, 60 | extent: [0, 86399], 61 | start: new Date(2012, 10, 8, 14), 62 | end: new Date(2012, 10, 8, 14) 63 | }, 64 | /* { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 7199, 68 | extent: [0, 86399], 69 | start: new Date(2013, 2, 10, 1, 59, 59), 70 | end: new Date(2013, 2, 10, 1, 59, 59) 71 | },*/ 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 7200, 76 | extent: [0, 86399], 77 | start: new Date(2013, 10, 3, 2), 78 | end: new Date(2013, 10, 3, 2) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 25199, 84 | extent: [0, 86399], 85 | start: new Date(2014, 1, 22, 6, 59, 59), 86 | end: new Date(2014, 1, 22, 6, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 66120, 92 | extent: [0, 86399], 93 | start: new Date(2015, 5, 19, 18, 22), 94 | end: new Date(2015, 5, 19, 18, 22) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 10619, 100 | extent: [0, 86399], 101 | start: new Date(2016, 7, 29, 2, 56, 59), 102 | end: new Date(2016, 7, 29, 2, 56, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 37882, 108 | extent: [0, 86399], 109 | start: new Date(2017, 8, 4, 10, 31, 22), 110 | end: new Date(2017, 8, 4, 10, 31, 22) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/weekofmonth-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.weekOfMonth), 3 | should = require('should'); 4 | 5 | describe('Later.weekOfMonth', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1,5], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 0, 5, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 5, 20 | extent: [1,5], 21 | start: new Date(2009, 11, 27), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 1, 28 | extent: [1,5], 29 | start: new Date(2010, 7, 1), 30 | end: new Date(2010, 7, 7, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 5, 36 | extent: [1,5], 37 | start: new Date(2011, 3, 24), 38 | end: new Date(2011, 3, 30, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 5, 44 | extent: [1,5], 45 | start: new Date(2012, 1, 26), 46 | end: new Date(2012, 1, 29, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 5, 52 | extent: [1,5], 53 | start: new Date(2012, 1, 26), 54 | end: new Date(2012, 1, 29, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 2, 60 | extent: [1,5], 61 | start: new Date(2012, 10, 4), 62 | end: new Date(2012, 10, 10, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 3, 68 | extent: [1,6], 69 | start: new Date(2013, 2, 10), 70 | end: new Date(2013, 2, 16, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 2, 76 | extent: [1,5], 77 | start: new Date(2013, 10, 3), 78 | end: new Date(2013, 10, 9, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 4, 84 | extent: [1,5], 85 | start: new Date(2014, 1, 16), 86 | end: new Date(2014, 1, 22, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 3, 92 | extent: [1,5], 93 | start: new Date(2015, 5, 14), 94 | end: new Date(2015, 5, 20, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 5, 100 | extent: [1,5], 101 | start: new Date(2016, 7, 28), 102 | end: new Date(2016, 7, 31, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 2, 108 | extent: [1, 5], 109 | start: new Date(2017, 8, 3), 110 | end: new Date(2017, 8, 9, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/weekofyear-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.weekOfYear), 3 | should = require('should'); 4 | 5 | describe('Later.weekOfYear', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 1, 12 | extent: [1,52], 13 | start: new Date(2007, 11, 31), 14 | end: new Date(2008, 0, 6, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 53, 20 | extent: [1,53], 21 | start: new Date(2009, 11, 28), 22 | end: new Date(2010, 0, 3, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 30, 28 | extent: [1,52], 29 | start: new Date(2010, 6, 26), 30 | end: new Date(2010, 7, 1, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 17, 36 | extent: [1,52], 37 | start: new Date(2011, 3, 25), 38 | end: new Date(2011, 4, 1, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 9, 44 | extent: [1,52], 45 | start: new Date(2012, 1, 27), 46 | end: new Date(2012, 2, 4, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 9, 52 | extent: [1,52], 53 | start: new Date(2012, 1, 27), 54 | end: new Date(2012, 2, 4, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 45, 60 | extent: [1,52], 61 | start: new Date(2012, 10, 5), 62 | end: new Date(2012, 10, 11, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 10, 68 | extent: [1,52], 69 | start: new Date(2013, 2, 4), 70 | end: new Date(2013, 2, 10, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 44, 76 | extent: [1,52], 77 | start: new Date(2013, 9, 28), 78 | end: new Date(2013, 10, 3, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 8, 84 | extent: [1,52], 85 | start: new Date(2014, 1, 17), 86 | end: new Date(2014, 1, 23, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 25, 92 | extent: [1,53], 93 | start: new Date(2015, 5, 15), 94 | end: new Date(2015, 5, 21, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 35, 100 | extent: [1,52], 101 | start: new Date(2016, 7, 29), 102 | end: new Date(2016, 8, 4, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 36, 108 | extent: [1, 52], 109 | start: new Date(2017, 8, 4), 110 | end: new Date(2017, 8, 10, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests); 115 | 116 | }); -------------------------------------------------------------------------------- /test/constraint/year-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | runner = require('./runner')(later, later.year), 3 | should = require('should'); 4 | 5 | describe('Later.year', function() { 6 | 7 | var tests = [ 8 | { 9 | // first second of year 10 | date: new Date(2008, 0, 1), 11 | val: 2008, 12 | extent: [1970, 2099], 13 | start: new Date(2008, 0, 1), 14 | end: new Date(2008, 11, 31, 23, 59, 59) 15 | }, 16 | { 17 | // last second of year 18 | date: new Date(2009, 11, 31, 23, 59, 59), 19 | val: 2009, 20 | extent: [1970, 2099], 21 | start: new Date(2009, 0, 1), 22 | end: new Date(2009, 11, 31, 23, 59, 59) 23 | }, 24 | { 25 | // first second of month starting on Sunday 26 | date: new Date(2010, 7, 1), 27 | val: 2010, 28 | extent: [1970, 2099], 29 | start: new Date(2010, 0, 1), 30 | end: new Date(2010, 11, 31, 23, 59, 59) 31 | }, 32 | { 33 | // last second of month ending on Saturday 34 | date: new Date(2011, 3, 30, 23, 59, 59), 35 | val: 2011, 36 | extent: [1970, 2099], 37 | start: new Date(2011, 0, 1), 38 | end: new Date(2011, 11, 31, 23, 59, 59) 39 | }, 40 | { 41 | // first second of day 42 | date: new Date(2012, 1, 28), 43 | val: 2012, 44 | extent: [1970, 2099], 45 | start: new Date(2012, 0, 1), 46 | end: new Date(2012, 11, 31, 23, 59, 59) 47 | }, 48 | { 49 | // last second of day on leap day 50 | date: new Date(2012, 1, 29, 23, 59, 59), 51 | val: 2012, 52 | extent: [1970, 2099], 53 | start: new Date(2012, 0, 1), 54 | end: new Date(2012, 11, 31, 23, 59, 59) 55 | }, 56 | { 57 | // first second of hour 58 | date: new Date(2012, 10, 8, 14), 59 | val: 2012, 60 | extent: [1970, 2099], 61 | start: new Date(2012, 0, 1), 62 | end: new Date(2012, 11, 31, 23, 59, 59) 63 | }, 64 | { 65 | // last second of hour (start DST) 66 | date: new Date(2013, 2, 10, 1, 59, 59), 67 | val: 2013, 68 | extent: [1970, 2099], 69 | start: new Date(2013, 0, 1), 70 | end: new Date(2013, 11, 31, 23, 59, 59) 71 | }, 72 | { 73 | // first second of hour (end DST) 74 | date: new Date(2013, 10, 3, 2), 75 | val: 2013, 76 | extent: [1970, 2099], 77 | start: new Date(2013, 0, 1), 78 | end: new Date(2013, 11, 31, 23, 59, 59) 79 | }, 80 | { 81 | // last second of hour 82 | date: new Date(2014, 1, 22, 6, 59, 59), 83 | val: 2014, 84 | extent: [1970, 2099], 85 | start: new Date(2014, 0, 1), 86 | end: new Date(2014, 11, 31, 23, 59, 59) 87 | }, 88 | { 89 | // first second of minute 90 | date: new Date(2015, 5, 19, 18, 22), 91 | val: 2015, 92 | extent: [1970, 2099], 93 | start: new Date(2015, 0, 1), 94 | end: new Date(2015, 11, 31, 23, 59, 59) 95 | }, 96 | { 97 | // last second of minute 98 | date: new Date(2016, 7, 29, 2, 56, 59), 99 | val: 2016, 100 | extent: [1970, 2099], 101 | start: new Date(2016, 0, 1), 102 | end: new Date(2016, 11, 31, 23, 59, 59) 103 | }, 104 | { 105 | // second 106 | date: new Date(2017, 8, 4, 10, 31, 22), 107 | val: 2017, 108 | extent: [1970, 2099], 109 | start: new Date(2017, 0, 1), 110 | end: new Date(2017, 11, 31, 23, 59, 59) 111 | } 112 | ]; 113 | 114 | runner.run(tests, true); 115 | 116 | }); -------------------------------------------------------------------------------- /test/core/compile-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Compile', function() { 5 | 6 | describe('start', function() { 7 | var d = new Date('2013-03-21T00:00:05Z'); 8 | 9 | describe('next', function() { 10 | 11 | it('should return start date if start is valid', function() { 12 | later.date.UTC(); 13 | later.compile({Y:[2013], M:[3], D:[21], s:[5]}).start('next', d).should.eql(d); 14 | }); 15 | 16 | it('should return start date if after modifier is used', function() { 17 | later.date.UTC(); 18 | later.compile({M: [3], Y_a:[2012]}).start('next', d).should.eql(new Date('2013-03-01T00:00:00Z')); 19 | }); 20 | 21 | it('should return next valid occurrence if invalid', function() { 22 | later.date.UTC(); 23 | later.compile({Y:[2013], M:[4,5]}).start('next', d).should.eql(new Date('2013-04-01T00:00:00Z')); 24 | }); 25 | 26 | it('should validate all constraints on a rollover', function() { 27 | later.date.UTC(); 28 | later.compile({Y:[2013,2015], M:[1]}).start('next', d).should.eql(new Date('2015-01-01T00:00:00Z')); 29 | }); 30 | }); 31 | 32 | describe('prev', function() { 33 | 34 | it('should return start date if start is valid', function() { 35 | later.date.UTC(); 36 | later.compile({Y:[2013], s:[5]}).start('prev', d).should.eql(d); 37 | }); 38 | 39 | it('should return previous valid occurrence if invalid', function() { 40 | later.date.UTC(); 41 | later.compile({Y:[2012]}).start('prev', d).should.eql(new Date('2012-12-31T23:59:59Z')); 42 | }); 43 | 44 | }); 45 | 46 | }); 47 | 48 | describe('end', function() { 49 | var d = new Date('2013-03-21T00:00:05Z'); 50 | 51 | it('should return next invalid occurrence if valid', function() { 52 | later.date.UTC(); 53 | later.compile({Y:[2013,2014]}).end('next', d).should.eql(new Date('2015-01-01T00:00:00Z')); 54 | }); 55 | 56 | it('should return prev invalid occurrence if valid', function() { 57 | later.date.UTC(); 58 | later.compile({Y:[2013,2014]}).end('prev', d).should.eql(new Date('2012-12-31T23:59:59Z')); 59 | }); 60 | 61 | }); 62 | 63 | describe('tick', function() { 64 | var d = new Date('2013-03-21T00:00:05Z'); 65 | 66 | describe('next', function() { 67 | 68 | it('should tick the smallest constraint with only one', function() { 69 | later.date.UTC(); 70 | later.compile({M:[3,5]}).tick('next', d).should.eql(new Date('2013-04-01T00:00:00Z')); 71 | }); 72 | 73 | it('should tick the smallest constraint with multiple', function() { 74 | later.date.UTC(); 75 | later.compile({Y:[2013,2014], s: [10, 20]}).tick('next', d).should.eql(new Date('2013-03-21T00:00:06Z')); 76 | }); 77 | 78 | }); 79 | 80 | describe('prev', function() { 81 | 82 | it('should tick the smallest constraint with only one', function() { 83 | later.date.UTC(); 84 | later.compile({M:[3,5]}).tick('prev', d).should.eql(new Date('2013-02-28T23:59:59Z')); 85 | }); 86 | 87 | it('should tick the smallest constraint with multiple', function() { 88 | later.date.UTC(); 89 | later.compile({Y:[2013,2014], s: [10, 20]}).tick('prev', d).should.eql(new Date('2013-03-21T00:00:04Z')); 90 | }); 91 | 92 | }); 93 | 94 | }); 95 | 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /test/core/schedule-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | schedule = later.schedule, 3 | should = require('should'); 4 | 5 | describe('Schedule', function() { 6 | later.date.UTC(); 7 | 8 | describe('isValid', function() { 9 | var d = new Date('2013-03-21T00:00:05Z'); 10 | 11 | it('should return true if date is valid', function() { 12 | var s = {schedules: [{Y:[2013], M:[3], D:[21], s:[5]}]}; 13 | schedule(s).isValid(d).should.eql(true); 14 | }); 15 | 16 | it('should return false if date is invalid', function() { 17 | var s = {schedules: [{Y:[2012]}]}; 18 | schedule(s).isValid(d).should.eql(false); 19 | }); 20 | 21 | }); 22 | 23 | describe('next', function() { 24 | var d = new Date('2013-03-21T00:00:05Z'), 25 | e = new Date('2016-01-01T00:00:05Z'); 26 | 27 | it('should return the start date if it is valid', function() { 28 | var s = {schedules: [{Y:[2013], M:[3], D:[21], s:[5]}]}; 29 | schedule(s).next(1, d).should.eql(d); 30 | }); 31 | 32 | it('should return next valid date if one exists', function() { 33 | var s = {schedules: [{Y:[2015]}]}; 34 | schedule(s).next(1, d).should.eql(new Date('2015-01-01T00:00:00Z')); 35 | }); 36 | 37 | it('should return next valid date if one exists with composite', function() { 38 | var s = {schedules: [{Y:[2017]},{Y:[2015]}]}; 39 | schedule(s).next(1, d).should.eql(new Date('2015-01-01T00:00:00Z')); 40 | }); 41 | 42 | it('should return next valid date if one exists with exceptions', function() { 43 | var s = {schedules: [{Y:[2015,2016,2017]}], exceptions: [{Y:[2015]}]}; 44 | schedule(s).next(1, d).should.eql(new Date('2016-01-01T00:00:00Z')); 45 | }); 46 | 47 | it('should return count valid dates if they exist', function() { 48 | var s = {schedules: [{Y:[2015,2016,2017]}]}; 49 | schedule(s).next(3, d).should.eql([ 50 | new Date('2015-01-01T00:00:00Z'), 51 | new Date('2016-01-01T00:00:00Z'), 52 | new Date('2017-01-01T00:00:00Z') 53 | ]); 54 | }); 55 | 56 | it('should return later.NEVER if no next valid date exists', function() { 57 | var s = {schedules: [{Y:[2012]}]}; 58 | should.equal(schedule(s).next(1, d), later.NEVER); 59 | }); 60 | 61 | it('should return later.NEVER if end date precludes a valid schedule', function() { 62 | var s = {schedules: [{Y:[2017]}]}; 63 | should.equal(schedule(s).next(1, d, e), later.NEVER); 64 | }); 65 | 66 | }); 67 | 68 | describe('prev', function() { 69 | var d = new Date('2013-03-21T00:00:05Z'), 70 | e = new Date('2010-01-01T00:00:05Z'); 71 | 72 | it('should return the start date if it is valid', function() { 73 | var s = {schedules: [{Y:[2013], M:[3], D:[21], s:[5]}]}; 74 | schedule(s).prev(1,d).should.eql(d); 75 | }); 76 | 77 | it('should return prev valid date if one exists', function() { 78 | var s = {schedules: [{Y:[2012]}]}; 79 | schedule(s).prev(1,d).should.eql(new Date('2012-01-01T00:00:00Z')); 80 | }); 81 | 82 | it('should return prev valid date if one exists with exceptions', function() { 83 | var s = {schedules: [{Y:[2012,2013,2014]}], exceptions: [{Y:[2013]}]}; 84 | schedule(s).prev(1,d).should.eql(new Date('2012-01-01T00:00:00Z')); 85 | }); 86 | 87 | it('should return count valid dates if they exist', function() { 88 | var s = {schedules: [{Y:[2010, 2011,2012]}]}; 89 | schedule(s).prev(3,d).should.eql([ 90 | new Date('2012-01-01T00:00:00Z'), 91 | new Date('2011-01-01T00:00:00Z'), 92 | new Date('2010-01-01T00:00:00Z') 93 | ]); 94 | }); 95 | 96 | it('should return later.NEVER if no prev valid date exists', function() { 97 | var s = {schedules: [{Y:[2017]}]}; 98 | should.equal(schedule(s).prev(1,d), later.NEVER); 99 | }); 100 | 101 | it('should return later.NEVER if end date precludes a valid schedule', function() { 102 | var s = {schedules: [{Y:[2009]}]}; 103 | should.equal(schedule(s).prev(1,d, e), later.NEVER); 104 | }); 105 | 106 | }); 107 | 108 | describe('nextRange', function() { 109 | it('should return next valid range if one exists', function() { 110 | var d = new Date('2013-03-21T00:00:05Z'), 111 | e = new Date('2016-01-01T00:00:05Z'); 112 | 113 | var s = {schedules: [{Y:[2015,2016,2017]}]}; 114 | schedule(s).nextRange(1, d).should.eql([ 115 | new Date('2015-01-01T00:00:00Z'), 116 | new Date('2018-01-01T00:00:00Z') 117 | ]); 118 | }); 119 | 120 | it('should correctly calculate ranges', function() { 121 | var d = new Date('2013-03-21T00:00:05Z'); 122 | 123 | var s = { 124 | schedules: [ { dw: [ 2, 3, 4, 5, 6 ], h_a: [ 8 ], h_b: [ 16 ] } ], 125 | exceptions: 126 | [ { fd_a: [ 1362420000000 ], fd_b: [ 1362434400000 ] }, 127 | { fd_a: [ 1363852800000 ], fd_b: [ 1363860000000 ] }, 128 | { fd_a: [ 1364499200000 ], fd_b: [ 1364516000000 ] } ] 129 | }; 130 | 131 | schedule(s).nextRange(1, d).should.eql([ 132 | new Date('2013-03-21T10:00:00Z'), 133 | new Date('2013-03-21T16:00:00Z') 134 | ]); 135 | 136 | }); 137 | 138 | it('should return undefined as end if there is no end date', function() { 139 | var d = new Date('2013-03-21T00:00:05Z'); 140 | 141 | var s = { 142 | schedules: [ { fd_a: [ 1363824005000 ] } ] 143 | }; 144 | 145 | schedule(s).nextRange(3, d).should.eql([ 146 | [new Date('2013-03-21T00:00:05Z'), undefined] 147 | ]); 148 | }); 149 | 150 | // issue #27 151 | it('should merge valid ranges across anded schedule definitions', function() { 152 | var d = new Date("Sat Sep 28 2013 11:00:00 GMT+0600 (YEKT)"); 153 | 154 | var s = later.parse.recur() 155 | .every().hour().between(0,8).onWeekday() 156 | .and() 157 | .onWeekend(); 158 | 159 | schedule(s).nextRange(2, d).should.eql([ 160 | [new Date('2013-09-28T05:00:00Z'), new Date('2013-09-30T09:00:00Z')], 161 | [new Date('2013-10-01T00:00:00Z'), new Date('2013-10-01T09:00:00Z')] 162 | ]); 163 | }); 164 | }); 165 | 166 | describe('prevRange', function() { 167 | var d = new Date('2013-03-21T00:00:05Z'), 168 | e = new Date('2016-01-01T00:00:05Z'); 169 | 170 | it('should return next valid range if one exists', function() { 171 | var s = {schedules: [{Y:[2011,2012]}]}; 172 | schedule(s).prevRange(1, d).should.eql([ 173 | new Date('2011-01-01T00:00:00Z'), 174 | new Date('2013-01-01T00:00:00Z') 175 | ]); 176 | }); 177 | 178 | it('should return undefined as end if there is no end date', function() { 179 | var d = new Date('2013-03-21T00:00:05Z'); 180 | 181 | var s = { 182 | schedules: [ { fd_b: [ 1363824005000 ] } ] 183 | }; 184 | 185 | schedule(s).prevRange(3, d).should.eql([ 186 | [undefined, new Date('2013-03-21T00:00:05Z')] 187 | ]); 188 | }); 189 | 190 | // issue #27 191 | it('should merge valid ranges across anded schedule definitions', function() { 192 | var d = new Date("2013-09-30T09:00:00Z"); 193 | 194 | var s = later.parse.recur() 195 | .every().hour().between(0,8).onWeekday() 196 | .and() 197 | .onWeekend(); 198 | 199 | schedule(s).prevRange(2, d).should.eql([ 200 | [new Date('2013-09-28T00:00:00Z'), new Date('2013-09-30T09:00:00Z')], 201 | [new Date('2013-09-27T00:00:00Z'), new Date('2013-09-27T09:00:00Z')] 202 | ]); 203 | }); 204 | }); 205 | 206 | }); -------------------------------------------------------------------------------- /test/core/setinterval-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Set interval', function() { 5 | 6 | it('should execute a callback after the specified amount of time', function(done) { 7 | this.timeout(0); 8 | 9 | var s = later.parse.recur().every(1).second(), 10 | t = later.setInterval(test, s), 11 | count = 0; 12 | 13 | function test() { 14 | later.schedule(s).isValid(new Date()).should.eql(true); 15 | count++; 16 | if(count > 2) { 17 | t.clear(); 18 | done(); 19 | } 20 | } 21 | 22 | }); 23 | 24 | it('should allow clearing of the timeout', function(done) { 25 | this.timeout(0); 26 | 27 | var s = later.parse.recur().every(2).second(); 28 | 29 | function test() { 30 | should.not.exist(true); 31 | } 32 | 33 | var t = later.setInterval(test, s); 34 | t.clear(); 35 | 36 | setTimeout(done, 3000); 37 | }); 38 | 39 | }); -------------------------------------------------------------------------------- /test/core/settimeout-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Set timeout', function() { 5 | 6 | it('should execute a callback after the specified amount of time', function(done) { 7 | this.timeout(3000); 8 | 9 | var s = later.parse.recur().every(2).second(); 10 | 11 | function test() { 12 | later.schedule(s).isValid(new Date()).should.eql(true); 13 | done(); 14 | } 15 | 16 | later.setTimeout(test, s); 17 | }); 18 | 19 | it('should allow clearing of the timeout', function(done) { 20 | this.timeout(3000); 21 | 22 | var s = later.parse.recur().every(1).second(); 23 | 24 | function test() { 25 | should.not.exist(true); 26 | } 27 | 28 | var t = later.setTimeout(test, s); 29 | t.clear(); 30 | 31 | setTimeout(done, 2000); 32 | }); 33 | 34 | 35 | it('should not execute a far out schedule immediately', function(done) { 36 | this.timeout(3000); 37 | 38 | var s = later.parse.recur().on(2017).year(); 39 | 40 | function test() { 41 | should.not.exist(true); 42 | } 43 | 44 | var t = later.setTimeout(test, s); 45 | 46 | setTimeout(function() { t.clear(); done(); }, 2000); 47 | }); 48 | 49 | it('should execute a callback for a one-time occurrence after the specified amount of time', function(done) { 50 | this.timeout(3000); 51 | 52 | var offsetInMilliseconds = 2000; 53 | var now = new Date() 54 | var nowOffset = now.getTime() + offsetInMilliseconds 55 | var s = later.parse.recur().on(new Date(nowOffset)).fullDate(); 56 | 57 | function test() { 58 | done(); 59 | } 60 | 61 | later.setTimeout(test, s); 62 | }); 63 | 64 | }); -------------------------------------------------------------------------------- /test/modifier/after-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Modifier After', function() { 5 | 6 | describe('name', function() { 7 | 8 | it('should append "after" before a minute constraint', function() { 9 | later.date.UTC(); 10 | var after = later.modifier.after(later.m, [15]); 11 | after.name.should.equal('after ' + later.m.name); 12 | }); 13 | 14 | it('should append "after" before a time constraint', function() { 15 | later.date.UTC(); 16 | var after = later.modifier.after(later.t, [15]); 17 | after.name.should.equal('after ' + later.t.name); 18 | }); 19 | 20 | }); 21 | 22 | describe('range', function() { 23 | 24 | it('should be the number of seconds covered by the minutes range', function() { 25 | later.date.UTC(); 26 | var after = later.modifier.after(later.m, [15]); 27 | after.range.should.equal(44 * 60); 28 | }); 29 | 30 | it('should be the number of seconds covered by the time range', function() { 31 | later.date.UTC(); 32 | var after = later.modifier.after(later.t, [60000]); 33 | after.range.should.equal(26399); 34 | }); 35 | 36 | }); 37 | 38 | describe('val', function() { 39 | 40 | var d = new Date('2013-03-21T03:10:00Z'); 41 | 42 | it('should return the correct minutes value when less than constraint', function() { 43 | later.date.UTC(); 44 | var after = later.modifier.after(later.m, [5]); 45 | after.val(d).should.equal(10); 46 | }); 47 | 48 | it('should return the correct minutes value when greater than constraint', function() { 49 | later.date.UTC(); 50 | var after = later.modifier.after(later.m, [15]); 51 | after.val(d).should.equal(10); 52 | }); 53 | 54 | it('should be the number of seconds covered by the time range when less than constraint', function() { 55 | later.date.UTC(); 56 | var after = later.modifier.after(later.t, [10000]); 57 | after.val(d).should.equal(11400); 58 | }); 59 | 60 | it('should be the number of seconds covered by the time range when greater than constraint', function() { 61 | later.date.UTC(); 62 | var after = later.modifier.after(later.t, [20000]); 63 | after.val(d).should.equal(11400); 64 | }); 65 | 66 | }); 67 | 68 | describe('isValid', function() { 69 | 70 | var d = new Date('2013-03-21T03:10:00Z'); 71 | 72 | it('should return true if the current minute val is greater than constraint', function() { 73 | later.date.UTC(); 74 | var after = later.modifier.after(later.m, [5]); 75 | after.isValid(d, 10).should.equal(true); 76 | }); 77 | 78 | it('should return true if the current minute val is equal to the constraint', function() { 79 | later.date.UTC(); 80 | var after = later.modifier.after(later.m, [10]); 81 | after.isValid(d, 5).should.equal(true); 82 | }); 83 | 84 | it('should return false if the current minute val is less than constraint', function() { 85 | later.date.UTC(); 86 | var after = later.modifier.after(later.m, [15]); 87 | after.isValid(d, 2).should.equal(false); 88 | }); 89 | 90 | it('should return true if the current time val is greater than constraint', function() { 91 | later.date.UTC(); 92 | var after = later.modifier.after(later.t, [10000]); 93 | after.isValid(d, 30000).should.equal(true); 94 | }); 95 | 96 | it('should return true if the current time val is equal to the constraint', function() { 97 | later.date.UTC(); 98 | var after = later.modifier.after(later.t, [11400]); 99 | after.isValid(d, 20000).should.equal(true); 100 | }); 101 | 102 | it('should return false if the current time val is less than constraint', function() { 103 | later.date.UTC(); 104 | var after = later.modifier.after(later.t, [20000]); 105 | after.isValid(d, 15000).should.equal(false); 106 | }); 107 | 108 | }); 109 | 110 | describe('extent', function() { 111 | 112 | var d = new Date('2013-03-21T03:10:00Z'); 113 | 114 | it('should return the minute extent', function() { 115 | later.date.UTC(); 116 | var after = later.modifier.after(later.m, [15]); 117 | after.extent(d).should.eql(later.m.extent(d)); 118 | }); 119 | 120 | it('should return the time extent', function() { 121 | later.date.UTC(); 122 | var after = later.modifier.after(later.t, [60000]); 123 | after.extent(d).should.eql(later.t.extent(d)); 124 | }); 125 | 126 | }); 127 | 128 | describe('start', function() { 129 | 130 | var d = new Date('2013-03-21T03:10:00Z'); 131 | 132 | it('should return the minute start', function() { 133 | later.date.UTC(); 134 | var after = later.modifier.after(later.m, [15]); 135 | after.start(d).should.eql(later.m.start(d)); 136 | }); 137 | 138 | it('should return the time start', function() { 139 | later.date.UTC(); 140 | var after = later.modifier.after(later.t, [60000]); 141 | after.start(d).should.eql(later.t.start(d)); 142 | }); 143 | 144 | }); 145 | 146 | describe('end', function() { 147 | 148 | var d = new Date('2013-03-21T03:10:00Z'); 149 | 150 | it('should return the minute end', function() { 151 | later.date.UTC(); 152 | var after = later.modifier.after(later.m, [15]); 153 | after.end(d).should.eql(later.m.end(d)); 154 | }); 155 | 156 | it('should return the time end', function() { 157 | later.date.UTC(); 158 | var after = later.modifier.after(later.t, [60000]); 159 | after.end(d).should.eql(later.t.end(d)); 160 | }); 161 | 162 | }); 163 | 164 | describe('next', function() { 165 | 166 | var d = new Date('2013-03-21T03:10:00Z'); 167 | 168 | it('should return start of range if val equals minute constraint', function() { 169 | later.date.UTC(); 170 | var after = later.modifier.after(later.m, [15]); 171 | after.next(d, 15).should.eql(new Date('2013-03-21T03:15:00Z')); 172 | }); 173 | 174 | it('should return start of extent if val does not equal minute constraint', function() { 175 | later.date.UTC(); 176 | var after = later.modifier.after(later.m, [10]); 177 | after.next(d, 5).should.eql(new Date('2013-03-21T04:00:00Z')); 178 | }); 179 | 180 | it('should return start of range if val equals time constraint', function() { 181 | later.date.UTC(); 182 | var after = later.modifier.after(later.t, [11520]); 183 | after.next(d, 11520).should.eql(new Date('2013-03-21T03:12:00Z')); 184 | }); 185 | 186 | it('should return start of extent if val does not equal time constraint', function() { 187 | later.date.UTC(); 188 | var after = later.modifier.after(later.t, [11520]); 189 | after.next(d, 11521).should.eql(new Date('2013-03-22T00:00:00Z')); 190 | }); 191 | 192 | }); 193 | 194 | describe('prev', function() { 195 | 196 | var d = new Date('2013-03-21T03:10:00Z'); 197 | 198 | it('should return end of range if val equals minute constraint', function() { 199 | later.date.UTC(); 200 | var after = later.modifier.after(later.m, [15]); 201 | after.prev(d, 15).should.eql(new Date('2013-03-21T02:59:59Z')); 202 | }); 203 | 204 | it('should return start of range - 1 if val does not equal minute constraint', function() { 205 | later.date.UTC(); 206 | var after = later.modifier.after(later.m, [10]); 207 | after.prev(d, 5).should.eql(new Date('2013-03-21T03:09:59Z')); 208 | }); 209 | 210 | it('should return end of range if val equals time constraint', function() { 211 | later.date.UTC(); 212 | var after = later.modifier.after(later.t, [11520]); 213 | after.prev(d, 11520).should.eql(new Date('2013-03-20T23:59:59Z')); 214 | }); 215 | 216 | it('should return start of range - 1 if val does not equal time constraint', function() { 217 | later.date.UTC(); 218 | var after = later.modifier.after(later.t, [11400]); 219 | after.prev(d, 11521).should.eql(new Date('2013-03-21T03:09:59Z')); 220 | }); 221 | 222 | }); 223 | 224 | 225 | describe('compiled minute schedule', function() { 226 | 227 | var c = later.compile({m_a: [30]}); 228 | 229 | it('should tick to next consecutive minutes', function() { 230 | later.date.UTC(); 231 | var d = new Date('2013-03-21T03:31:00Z'), 232 | expected = new Date('2013-03-21T03:32:00Z'), 233 | actual = c.tick('next', d); 234 | 235 | actual.should.eql(expected); 236 | }); 237 | 238 | it('should tick to prev consecutive minutes', function() { 239 | later.date.UTC(); 240 | var d = new Date('2013-03-21T03:26:00Z'), 241 | expected = new Date('2013-03-21T03:25:59Z'), 242 | actual = c.tick('prev', d); 243 | 244 | actual.should.eql(expected); 245 | }); 246 | 247 | it('should go the next valid value when invalid', function() { 248 | later.date.UTC(); 249 | var d = new Date('2013-03-21T03:25:00Z'), 250 | expected = new Date('2013-03-21T03:30:00Z'), 251 | actual = c.start('next', d); 252 | 253 | actual.should.eql(expected); 254 | }); 255 | 256 | it('should go the prev valid value when invalid', function() { 257 | later.date.UTC(); 258 | var d = new Date('2013-03-21T03:25:00Z'), 259 | expected = new Date('2013-03-21T02:59:59Z'), 260 | actual = c.start('prev', d); 261 | 262 | actual.should.eql(expected); 263 | }); 264 | 265 | it('should go the end of constraint value when valid for prev', function() { 266 | later.date.UTC(); 267 | var d = new Date('2013-03-21T03:45:10Z'), 268 | expected = new Date('2013-03-21T03:45:59Z'), 269 | actual = c.start('prev', d); 270 | 271 | actual.should.eql(expected); 272 | }); 273 | 274 | it('should go the start of constraint value when valid for next', function() { 275 | later.date.UTC(); 276 | var d = new Date('2013-03-21T03:45:10Z'), 277 | expected = new Date('2013-03-21T03:45:00Z'), 278 | actual = c.start('next', d); 279 | 280 | actual.should.eql(expected); 281 | }); 282 | 283 | it('should go the end of the constraint', function() { 284 | later.date.UTC(); 285 | var d = new Date('2013-03-21T03:45:10Z'), 286 | expected = new Date('2013-03-21T04:00:00Z'), 287 | actual = c.end('next', d); 288 | 289 | actual.should.eql(expected); 290 | }); 291 | 292 | }); 293 | 294 | describe('compiled time schedule', function() { 295 | 296 | var c = later.compile({t_a: [11400]}); 297 | 298 | it('should tick to next consecutive minutes', function() { 299 | later.date.UTC(); 300 | var d = new Date('2013-03-21T03:31:00Z'), 301 | expected = new Date('2013-03-21T03:31:01Z'), 302 | actual = c.tick('next', d); 303 | 304 | actual.should.eql(expected); 305 | }); 306 | 307 | it('should tick to prev consecutive minutes', function() { 308 | later.date.UTC(); 309 | var d = new Date('2013-03-21T03:26:00Z'), 310 | expected = new Date('2013-03-21T03:25:59Z'), 311 | actual = c.tick('prev', d); 312 | 313 | actual.should.eql(expected); 314 | }); 315 | 316 | it('should go the next valid value when invalid', function() { 317 | later.date.UTC(); 318 | var d = new Date('2013-03-21T03:05:00Z'), 319 | expected = new Date('2013-03-21T03:10:00Z'), 320 | actual = c.start('next', d); 321 | 322 | actual.getTime().should.eql(expected.getTime()); 323 | }); 324 | 325 | it('should go the prev valid value when invalid', function() { 326 | later.date.UTC(); 327 | var d = new Date('2013-03-21T03:05:00Z'), 328 | expected = new Date('2013-03-20T23:59:59Z'), 329 | actual = c.start('prev', d); 330 | 331 | actual.getTime().should.eql(expected.getTime()); 332 | }); 333 | 334 | it('should go the start of constraint value when valid for prev', function() { 335 | later.date.UTC(); 336 | var d = new Date('2013-03-21T03:10:10Z'), 337 | expected = new Date('2013-03-21T03:10:10Z'), 338 | actual = c.start('prev', d); 339 | 340 | actual.getTime().should.eql(expected.getTime()); 341 | }); 342 | 343 | it('should go the start of constraint value when valid for next', function() { 344 | later.date.UTC(); 345 | var d = new Date('2013-03-21T03:10:10Z'), 346 | expected = new Date('2013-03-21T03:10:10Z'), 347 | actual = c.start('next', d); 348 | 349 | actual.getTime().should.eql(expected.getTime()); 350 | }); 351 | 352 | it('should go the end of the constraint', function() { 353 | later.date.UTC(); 354 | var d = new Date('2013-03-21T03:45:10Z'), 355 | expected = new Date('2013-03-22T00:00:00Z'), 356 | actual = c.end('next', d); 357 | 358 | actual.getTime().should.eql(expected.getTime()); 359 | }); 360 | 361 | }); 362 | }); -------------------------------------------------------------------------------- /test/modifier/before-test.js: -------------------------------------------------------------------------------- 1 | var later = require('../../index'), 2 | should = require('should'); 3 | 4 | describe('Modifier Before', function() { 5 | 6 | describe('name', function() { 7 | 8 | it('should append "before" before a minute constraint', function() { 9 | later.date.UTC(); 10 | var before = later.modifier.before(later.m, [15]); 11 | before.name.should.equal('before ' + later.m.name); 12 | }); 13 | 14 | it('should append "before" before a time constraint', function() { 15 | later.date.UTC(); 16 | var before = later.modifier.before(later.t, [15]); 17 | before.name.should.equal('before ' + later.t.name); 18 | }); 19 | 20 | }); 21 | 22 | describe('range', function() { 23 | 24 | it('should be the number of seconds covered by the minutes range', function() { 25 | later.date.UTC(); 26 | var before = later.modifier.before(later.m, [15]); 27 | before.range.should.equal(14 * 60); 28 | }); 29 | 30 | it('should be the number of seconds covered by the time range', function() { 31 | later.date.UTC(); 32 | var before = later.modifier.before(later.t, [60000]); 33 | before.range.should.equal(59999); 34 | }); 35 | 36 | }); 37 | 38 | describe('val', function() { 39 | 40 | var d = new Date('2013-03-21T03:10:00Z'); 41 | 42 | it('should return the correct minutes value when less than constraint', function() { 43 | later.date.UTC(); 44 | var before = later.modifier.before(later.m, [5]); 45 | before.val(d).should.equal(10); 46 | }); 47 | 48 | it('should return the correct minutes value when greater than constraint', function() { 49 | later.date.UTC(); 50 | var before = later.modifier.before(later.m, [15]); 51 | before.val(d).should.equal(10); 52 | }); 53 | 54 | it('should be the number of seconds covered by the time range when less than constraint', function() { 55 | later.date.UTC(); 56 | var before = later.modifier.before(later.t, [10000]); 57 | before.val(d).should.equal(11400); 58 | }); 59 | 60 | it('should be the number of seconds covered by the time range when greater than constraint', function() { 61 | later.date.UTC(); 62 | var before = later.modifier.before(later.t, [20000]); 63 | before.val(d).should.equal(11400); 64 | }); 65 | 66 | }); 67 | 68 | describe('isValid', function() { 69 | 70 | var d = new Date('2013-03-21T03:10:00Z'); 71 | 72 | it('should return false if the current minute val is greater than constraint', function() { 73 | later.date.UTC(); 74 | var before = later.modifier.before(later.m, [5]); 75 | before.isValid(d, 10).should.equal(false); 76 | }); 77 | 78 | it('should return false if the current minute val is equal to the constraint', function() { 79 | later.date.UTC(); 80 | var before = later.modifier.before(later.m, [10]); 81 | before.isValid(d, 5).should.equal(false); 82 | }); 83 | 84 | it('should return true if the current minute val is less than constraint', function() { 85 | later.date.UTC(); 86 | var before = later.modifier.before(later.m, [15]); 87 | before.isValid(d, 2).should.equal(true); 88 | }); 89 | 90 | it('should return false if the current time val is greater than constraint', function() { 91 | later.date.UTC(); 92 | var before = later.modifier.before(later.t, [10000]); 93 | before.isValid(d, 30000).should.equal(false); 94 | }); 95 | 96 | it('should return false if the current time val is equal to the constraint', function() { 97 | later.date.UTC(); 98 | var before = later.modifier.before(later.t, [11400]); 99 | before.isValid(d, 20000).should.equal(false); 100 | }); 101 | 102 | it('should return true if the current time val is less than constraint', function() { 103 | later.date.UTC(); 104 | var before = later.modifier.before(later.t, [20000]); 105 | before.isValid(d, 15000).should.equal(true); 106 | }); 107 | 108 | }); 109 | 110 | describe('extent', function() { 111 | 112 | var d = new Date('2013-03-21T03:10:00Z'); 113 | 114 | it('should return the minute extent', function() { 115 | later.date.UTC(); 116 | var before = later.modifier.before(later.m, [15]); 117 | before.extent(d).should.eql(later.m.extent(d)); 118 | }); 119 | 120 | it('should return the time extent', function() { 121 | later.date.UTC(); 122 | var before = later.modifier.before(later.t, [60000]); 123 | before.extent(d).should.eql(later.t.extent(d)); 124 | }); 125 | 126 | }); 127 | 128 | describe('start', function() { 129 | 130 | var d = new Date('2013-03-21T03:10:00Z'); 131 | 132 | it('should return the minute start', function() { 133 | later.date.UTC(); 134 | var before = later.modifier.before(later.m, [15]); 135 | before.start(d).should.eql(later.m.start(d)); 136 | }); 137 | 138 | it('should return the time start', function() { 139 | later.date.UTC(); 140 | var before = later.modifier.before(later.t, [60000]); 141 | before.start(d).should.eql(later.t.start(d)); 142 | }); 143 | 144 | }); 145 | 146 | describe('end', function() { 147 | 148 | var d = new Date('2013-03-21T03:10:00Z'); 149 | 150 | it('should return the minute end', function() { 151 | later.date.UTC(); 152 | var before = later.modifier.before(later.m, [15]); 153 | before.end(d).should.eql(later.m.end(d)); 154 | }); 155 | 156 | it('should return the time end', function() { 157 | later.date.UTC(); 158 | var before = later.modifier.before(later.t, [60000]); 159 | before.end(d).should.eql(later.t.end(d)); 160 | }); 161 | 162 | }); 163 | 164 | describe('next', function() { 165 | 166 | var d = new Date('2013-03-21T03:10:00Z'); 167 | 168 | it('should return start of range if val equals minute constraint', function() { 169 | later.date.UTC(); 170 | var before = later.modifier.before(later.m, [5]); 171 | before.next(d, 5).should.eql(new Date('2013-03-21T04:00:00Z')); 172 | }); 173 | 174 | it('should return end of valid if val does not equal minute constraint', function() { 175 | later.date.UTC(); 176 | var before = later.modifier.before(later.m, [20]); 177 | before.next(d, 5).should.eql(new Date('2013-03-21T03:20:00Z')); 178 | }); 179 | 180 | it('should return start of range if val equals time constraint', function() { 181 | later.date.UTC(); 182 | var before = later.modifier.before(later.t, [5520]); 183 | before.next(d, 5520).should.eql(new Date('2013-03-22T00:00:00Z')); 184 | }); 185 | 186 | it('should return end of valid if val does not equal time constraint', function() { 187 | later.date.UTC(); 188 | var before = later.modifier.before(later.t, [11520]); 189 | before.next(d, 11521).should.eql(new Date('2013-03-21T03:12:00Z')); 190 | }); 191 | 192 | }); 193 | 194 | describe('prev', function() { 195 | 196 | var d = new Date('2013-03-21T03:30:00Z'); 197 | 198 | it('should return end of range if val equals minute constraint', function() { 199 | later.date.UTC(); 200 | var before = later.modifier.before(later.m, [15]); 201 | before.prev(d, 15).should.eql(new Date('2013-03-21T03:14:59Z')); 202 | }); 203 | 204 | it('should return start of range - 1 if val does not equal minute constraint', function() { 205 | later.date.UTC(); 206 | var before = later.modifier.before(later.m, [45]); 207 | before.prev(d, 5).should.eql(new Date('2013-03-21T02:59:59Z')); 208 | }); 209 | 210 | it('should return end of range if val equals time constraint', function() { 211 | later.date.UTC(); 212 | var before = later.modifier.before(later.t, [11520]); 213 | before.prev(d, 11520).should.eql(new Date('2013-03-21T03:11:59Z')); 214 | }); 215 | 216 | it('should return start of range - 1 if val does not equal time constraint', function() { 217 | later.date.UTC(); 218 | var before = later.modifier.before(later.t, [11400]); 219 | before.prev(d, 11521).should.eql(new Date('2013-03-20T23:59:59Z')); 220 | }); 221 | 222 | }); 223 | 224 | }); -------------------------------------------------------------------------------- /test/parse/recur-test.js: -------------------------------------------------------------------------------- 1 | var recur = require('../../index').parse.recur; 2 | var should = require('should'); 3 | 4 | describe('Parse Recur', function() { 5 | 6 | describe('time periods', function() { 7 | 8 | it('should store second constraints with name s', function() { 9 | var r = recur().on(1).second(); 10 | r.schedules[0].should.eql({s: [1]}); 11 | }); 12 | 13 | it('should store minute constraints with name m', function() { 14 | var r = recur().on(1).minute(); 15 | r.schedules[0].should.eql({m: [1]}); 16 | }); 17 | 18 | it('should store hour constraints with name h', function() { 19 | var r = recur().on(1).hour(); 20 | r.schedules[0].should.eql({h: [1]}); 21 | }); 22 | 23 | it('should store day of month constraints with name D', function() { 24 | var r = recur().on(1).dayOfMonth(); 25 | r.schedules[0].should.eql({D: [1]}); 26 | }); 27 | 28 | it('should store day of week constraints with name d', function() { 29 | var r = recur().on(1).dayOfWeek(); 30 | r.schedules[0].should.eql({d: [1]}); 31 | }); 32 | 33 | it('should store day of week count constraints with name dc', function() { 34 | var r = recur().on(1).dayOfWeekCount(); 35 | r.schedules[0].should.eql({dc: [1]}); 36 | }); 37 | 38 | it('should store day of year constraints with name dy', function() { 39 | var r = recur().on(1).dayOfYear(); 40 | r.schedules[0].should.eql({dy: [1]}); 41 | }); 42 | 43 | it('should store week of month constraints with name wm', function() { 44 | var r = recur().on(1).weekOfMonth(); 45 | r.schedules[0].should.eql({wm: [1]}); 46 | }); 47 | 48 | it('should store week of year constraints with name wy', function() { 49 | var r = recur().on(1).weekOfYear(); 50 | r.schedules[0].should.eql({wy: [1]}); 51 | }); 52 | 53 | it('should store month constraints with name M', function() { 54 | var r = recur().on(1).month(); 55 | r.schedules[0].should.eql({M: [1]}); 56 | }); 57 | 58 | it('should store year constraints with name Y', function() { 59 | var r = recur().on(1).year(); 60 | r.schedules[0].should.eql({Y: [1]}); 61 | }); 62 | 63 | }); 64 | 65 | describe('on', function() { 66 | 67 | it('should store a single constraint', function() { 68 | var r = recur().on(2).minute(); 69 | r.schedules[0].m.should.eql([2]); 70 | }); 71 | 72 | it('should store multiple constraints', function() { 73 | var r = recur().on(2, 3).minute(); 74 | r.schedules[0].m.should.eql([2, 3]); 75 | }); 76 | 77 | }); 78 | 79 | describe('every', function() { 80 | 81 | it('should calculate the appropriate minute constraint', function() { 82 | var r = recur().every(15).minute(); 83 | r.schedules[0].m.should.eql([0, 15, 30, 45]); 84 | }); 85 | 86 | it('should calculate the appropriate week of year constraint', function() { 87 | var r = recur().every(10).weekOfYear(); 88 | r.schedules[0].wy.should.eql([1, 11, 21, 31, 41, 51]); 89 | }); 90 | 91 | }); 92 | 93 | describe('after', function() { 94 | 95 | it('should store the appropriate minute constraint', function() { 96 | var r = recur().after(15).minute(); 97 | r.schedules[0].m_a.should.eql([15]); 98 | }); 99 | 100 | it('should store the appropriate week of year constraint', function() { 101 | var r = recur().after(10).weekOfYear(); 102 | r.schedules[0].wy_a.should.eql([10]); 103 | }); 104 | 105 | it('should store the appropriate year constraint', function() { 106 | var r = recur().after(10).year(); 107 | r.schedules[0].Y_a.should.eql([10]); 108 | }); 109 | 110 | }); 111 | 112 | describe('before', function() { 113 | 114 | it('should store the appropriate minute constraint', function() { 115 | var r = recur().before(15).minute(); 116 | r.schedules[0].m_b.should.eql([15]); 117 | }); 118 | 119 | it('should store the appropriate week of year constraint', function() { 120 | var r = recur().before(10).weekOfYear(); 121 | r.schedules[0].wy_b.should.eql([10]); 122 | }); 123 | 124 | it('should store the appropriate year constraint', function() { 125 | var r = recur().before(10).year(); 126 | r.schedules[0].Y_b.should.eql([10]); 127 | }); 128 | 129 | }); 130 | 131 | describe('first', function() { 132 | 133 | it('should calculate the appropriate constraint', function() { 134 | var r = recur().first().minute(); 135 | r.schedules[0].m.should.eql([0]); 136 | }); 137 | 138 | }); 139 | 140 | describe('last', function() { 141 | 142 | it('should calculate the appropriate constraint', function() { 143 | var r = recur().last().minute(); 144 | r.schedules[0].m.should.eql([59]); 145 | }); 146 | 147 | }); 148 | 149 | 150 | describe('before time', function() { 151 | 152 | it('should store a single before time constraint', function() { 153 | var r = recur().before('08:30:00').time(); 154 | r.schedules[0].t_b.should.eql([30600]); 155 | }); 156 | 157 | }); 158 | 159 | describe('after time', function() { 160 | 161 | it('should store a single after time constraint', function() { 162 | var r = recur().after('08:30:00').time(); 163 | r.schedules[0].t_a.should.eql([30600]); 164 | }); 165 | 166 | }); 167 | 168 | describe('starting on', function() { 169 | 170 | it('should offset the start of an every constraint', function() { 171 | var r = recur().every(20).second().startingOn(8); 172 | r.schedules[0].s.should.eql([8, 28, 48]); 173 | }); 174 | 175 | it('should ignore offsets that are too large', function() { 176 | var r = recur().every(20).second().startingOn(68); 177 | r.schedules[0].s.should.eql([]); 178 | }); 179 | 180 | }); 181 | 182 | describe('between', function() { 183 | 184 | it('should limit the start and end of an every constraint', function() { 185 | var r = recur().every(20).second().between(5, 40); 186 | r.schedules[0].s.should.eql([5, 25]); 187 | }); 188 | 189 | }); 190 | 191 | describe('and', function() { 192 | 193 | it('should create a composite schedule', function() { 194 | var r = recur().every(20).second().and().on(5).minute(); 195 | r.schedules[0].s.should.eql([0, 20, 40]); 196 | r.schedules[1].m.should.eql([5]); 197 | }); 198 | 199 | it('should create multiple composite schedules', function() { 200 | var r = recur().every(20).second().and().on(5).minute(); 201 | r.and().on(0).month(); 202 | r.schedules[0].s.should.eql([0, 20, 40]); 203 | r.schedules[1].m.should.eql([5]); 204 | r.schedules[2].M.should.eql([0]); 205 | }); 206 | 207 | }); 208 | 209 | describe('except', function() { 210 | 211 | it('should create an exception schedule', function() { 212 | var r = recur().except().on(5).minute(); 213 | r.exceptions[0].m.should.eql([5]); 214 | }); 215 | 216 | it('should create composite exception schedule', function() { 217 | var r = recur().except().on(5).minute().and().on(0).month(); 218 | r.exceptions[0].m.should.eql([5]); 219 | r.exceptions[1].M.should.eql([0]); 220 | }); 221 | 222 | }); 223 | 224 | }); --------------------------------------------------------------------------------