Skip to content

Commit

Permalink
Accept dates in CronosTask constructor (Closes #4)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaclarke committed Aug 7, 2019
1 parent da8ebfa commit e942128
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 13 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [1.4.0] - 07 Aug 2019
### Added
- Support for providing a date, array of dates, or a custom date sequence to `new CronosTask()` instead of a `CronosExpression` object

## [1.3.0] - 30 Jul 2019
### Added
- Support wrap-around ranges for cyclic type fields (ie. *Second*, *Minute*, *Hour*, *Month* and *Day of Week*)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cronosjs",
"version": "1.3.0",
"version": "1.4.0",
"description": "A cron based task scheduler for node and the browser, with extended syntax and timezone support.",
"keywords": [
"cron",
Expand Down
35 changes: 31 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ task
console.log(`No more dates matching expression`)
})
.start()

// schedule tasks from a list of dates
const taskFromDates = new CronosTask([
new Date(2020, 7, 23, 9, 45, 0),
1555847845000,
'5 Oct 2019 17:32',
])

taskFromDates
.on('run', (timestamp) => {
console.log(`Task triggered at ${timestamp}`)
})
.on('ended', () => {
console.log(`No more dates in list`)
})
.start()
```


Expand Down Expand Up @@ -283,10 +299,21 @@ import {
### CronosTask
```class CronosTask```

#### Constructor
```new CronosTask(expression)```
- `expression: CronosExpression`
An instance of [CronosExpression](#cronosexpression)
#### Constructor (3 overloads)
- ```new CronosTask(sequence)```
- `sequence: DateSequence`
Either an instance of [CronosExpression](#cronosexpression) or any other object that implements the `DateSequence` interface
```typescript
interface DateSequence {
nextDate: (afterDate: Date) => Date | null
}
```
- ```new CronosTask(date)```
- `date: Date | string | number`
Either a `Date`, a timestamp, or a string repesenting a valid date, parsable by `new Date()`
- ```new CronosTask(dates)```
- `dates: (Date | string | number)[]`
An array of dates accepted valid in above constructor

#### Properties
- `nextRun: Date | null` (readonly)
Expand Down
3 changes: 2 additions & 1 deletion src/expression.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { CronosDaysExpression, _parse } from './parser'
import { CronosDate, CronosTimezone } from './date'
import { DateSequence } from './scheduler'
import { sortAsc } from './utils'

const hourinms = 60 * 60 * 1000
const findFirstFrom = (from: number, list: number[]) => list.findIndex(n => n >= from)

export class CronosExpression {
export class CronosExpression implements DateSequence {
private timezone?: CronosTimezone
private skipRepeatedHour = true
private missingHour: 'insert' | 'offset' | 'skip' = 'insert'
Expand Down
42 changes: 36 additions & 6 deletions src/scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { CronosExpression } from './expression'

const maxTimeout = Math.pow(2, 31) - 1
const scheduledTasks: CronosTask[] = []

Expand Down Expand Up @@ -46,13 +44,36 @@ function runScheduledTasks() {
} else runningTimer = null
}

export interface DateSequence {
nextDate: (afterDate: Date) => Date | null
}

class DateArraySequence implements DateSequence {
private _dates: Date[]

constructor(dateLikes: DateLike[]) {
this._dates = dateLikes.map(dateLike => {
const date = new Date(dateLike)
if (isNaN(date.getTime())) throw new Error('Invalid date')
return date
}).sort((a, b) => a.getTime() - b.getTime())
}

nextDate(afterDate: Date) {
const nextIndex = this._dates.findIndex(d => d > afterDate)
return nextIndex === -1 ? null : this._dates[nextIndex]
}
}

type CronosTaskListeners = {
'started': () => void
'stopped': () => void
'run': (timestamp: number) => void
'ended': () => void
}

type DateLike = Date | string | number

export class CronosTask {
private _listeners: {
[K in keyof CronosTaskListeners]: Set<CronosTaskListeners[K]>
Expand All @@ -63,12 +84,21 @@ export class CronosTask {
'ended': new Set(),
}
private _timestamp?: number
private _expression: CronosExpression
private _sequence: DateSequence

constructor(sequence: DateSequence)
constructor(dates: DateLike[])
constructor(date: DateLike)
constructor(
expression: CronosExpression,
sequenceOrDates: DateSequence | DateLike[] | DateLike,
) {
this._expression = expression
if (Array.isArray(sequenceOrDates)) this._sequence = new DateArraySequence(sequenceOrDates)
else if (
typeof sequenceOrDates === 'string' ||
typeof sequenceOrDates === 'number' ||
sequenceOrDates instanceof Date
) this._sequence = new DateArraySequence([sequenceOrDates])
else this._sequence = sequenceOrDates
}

start() {
Expand Down Expand Up @@ -99,7 +129,7 @@ export class CronosTask {
}

private _updateTimestamp() {
const nextDate = this._expression.nextDate(
const nextDate = this._sequence.nextDate(
this._timestamp ? new Date(this._timestamp) : new Date()
)

Expand Down
36 changes: 36 additions & 0 deletions tests/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,42 @@ describe('Scheduling tests', () => {
expect(task.isRunning).toEqual(false)
})

test('CronosTask with array of dates', () => {
const startedCallback = jest.fn()
const runCallback = jest.fn()

const fromDate = '2019-04-21T11:23:45Z'
mockDate(fromDate)

const task = new CronosTask(
[new Date(2020, 7, 23, 9, 45, 0), 1555847845000, '5 Oct 2019 17:32']
)

task
.on('started', startedCallback)
.on('run', runCallback)

expect(startedCallback).not.toBeCalled()

task.start()

expect(startedCallback).toBeCalled()

mockDate('2019-04-21T11:57:25Z')
jest.runOnlyPendingTimers()

expect(runCallback).toHaveBeenCalledTimes(1)
expect(runCallback).toHaveBeenLastCalledWith( getTimestamp('2019-04-21T11:57:25Z') )

task.off('run', runCallback)
})

test('CronosTask with invalid date', () => {
expect(
() => new CronosTask('invalid')
).toThrow()
})

})

describe('CronosExpression.cronString and .toString()', () => {
Expand Down

0 comments on commit e942128

Please sign in to comment.