@pubdate/dayjs-plugin-recurring v0.1.6
dayjs-plugin-recurring
A dayjs plugin to work with recurring dates.
Getting Started
NPM
npm i dayjs @pubdate/dayjs-plugin-recurring
import dayjs from 'dayjs'
import recurring from '@pubdate/dayjs-plugin-recurring'
dayjs.extend(recurring)
CDN
<script src="https://unpkg.com/dayjs"></script>
<script src="https://unpkg.com/@pubdate/dayjs-plugin-recurring"></script>
<script>
dayjs.extend(dayjs_plugin_recurring)
</script>
Options
order
In chronological order (default):
- The first occurrence is always the oldest.
- The sequence continues from the oldest to the newest.
- Arrays of occurrences are always ordered from the oldest to the newest.
dayjs.extend(recurring, { order: 'chronological' })
dayjs('R3/2020-01-01/P1Y').all() // [2020-01-01, 2021-01-01, 2022-01-01, 2023-01-01]
dayjs('R3/P1Y/2023-01-01').all() // [2020-01-01, 2021-01-01, 2022-01-01, 2023-01-01]
In relative order:
- The first occurrence is
start
if provided, orend
if provided, orcontext
(the current dayjs instance). - If
start
is provided orend
is not, the sequence continues from the oldest to the newest. And from the newest to the oldest otherwise. - Arrays of occurrences returned by
all()
/first(n)
are ordered from the first (cf. first point) to the last. - Arrays of occurrences returned by
last(n)
are ordered from the last to the first (cf. first point). - Arrays of occurrences returned by
prev(n)
/next(n)
are ordered from the closest of the current occurrence to the furthest.
dayjs.extend(recurring, { order: 'relative' })
dayjs('R3/2020-01-01/P1Y').all() // [2020-01-01, 2021-01-01, 2022-01-01, 2023-01-01]
dayjs('R3/P1Y/2023-01-01').all() // [2023-01-01, 2022-01-01, 2021-01-01, 2020-01-01]
Comparison:
all/first 1 - 2 - 3 - 4 - 5
chronological ----------------> [1, 2, 3, 4, 5]
relative (start) ----------------> [1, 2, 3, 4, 5]
relative (end) <---------------- [5, 4, 3, 2, 1]
last 1 - 2 - 3 - 4 - 5
chronological ----------------> [1, 2, 3, 4, 5]
relative (start) <---------------- [5, 4, 3, 2, 1]
relative (end) ----------------> [1, 2, 3, 4, 5]
prev 1 - 2 - 3 - 4 - 5
chronological -----> now [1, 2]
relative (start) <----- now [2, 1]
relative (end) now -----> [4, 5]
next 1 - 2 - 3 - 4 - 5
chronological now -----> [4, 5]
relative (start) now -----> [4, 5]
relative (end) <----- now [2, 1]
API
!NOTE Unless explicitly stated otherwise, all examples are in chronological order.
Create a recurring dayjs instance
To create a recurring dayjs instance, simply pass an ISO 8601 recurring time interval string to the dayjs factory.
It will return the occurrence that matches start
if provided, or end
if provided, or now.
dayjs('R10/2020-01-01/P1Y') // 2020-01-01
dayjs('R10/2020-01-01/P1Y').last() // 2030-01-01
dayjs('R10/P1Y/2030-01-01') // 2030-01-01
dayjs('R10/P1Y/2030-01-01').first() // 2020-01-01
dayjs('R10/P1Y') // dayjs()
dayjs('R10/P1Y').last() // dayjs().add(10, 'years')
To generate a recurring instance from a regular instance, call recurring()
with a string or an object.
It will keep the date as current occurrence. And use it as start if the argument has no start
and no end
.
dayjs('2025-01-01').recurring('R10/2020-01-01/P1Y') // 2025-01-01
dayjs('2025-01-01').recurring('R10/2020-01-01/P1Y').last() // 2030-01-01
dayjs('2025-01-01').recurring({ times: 10, start: '2020-01-01', duration: 'P1Y' }) // 2025-01-01
dayjs('2025-01-01').recurring({ times: 10, start: '2020-01-01', duration: 'P1Y' }).last() // 2030-01-01
dayjs('2025-01-01').recurring('R10/P1Y') // 2025-01-01
dayjs('2025-01-01').recurring('R10/P1Y').last() // 2035-01-01
Pass an object with start
and end
but no times
to automatically compute times
.
dayjs('2025-01-01').recurring({ start: '2020-01-01', end: '2030-01-01', duration: 'P1Y' }) // times: 10
dayjs('2025-01-01').recurring({ start: '2020-01-01', duration: 'P1Y' }) // times: undefined
recurring()
Returns the recurring config.
dayjs('R10/2020-01-01/P1Y').recurring().toString({ dateFormat: 'YYYY-MM-DD' }) // 'R10/2020-01-01/P1Y'
isOccurrence()
Checks whether the current date is an occurrence or not.
dayjs('2023-01-01').recurring('R/2020-01-01/P1Y').isOccurrence() // true
dayjs('2023-01-10').recurring('R/2020-01-01/P1Y').isOccurrence() // false
isOccurrence(date, unit = 'milliseconds')
Checks whether date
is an occurrence or not.
If you want to limit the granularity to a unit other than milliseconds, pass it as the second parameter.
dayjs('R/2020-01-01/P1Y').isOccurrence('2023-01-01') // true
dayjs('R/2020-01-01/P1Y').isOccurrence('2023-01-10') // false
dayjs('R/2020-01-01/P1Y').isOccurrence('2023-01-10', 'year') // true
all()
!TIP Use
allBetween
to avoid null errors.
Returns all occurrences.
Or null
when there is no limit.
dayjs('R3/2020-01-01/P1Y').all() // [2020-01-01, 2021-01-01, 2022-01-01, 2023-01-01]
dayjs('R/2020-01-01/P1Y').all() // null
allBetween(a, b, unit = 'milliseconds', inclusion = '()')
Returns all occurrences between a
and b
.
If you want to limit the granularity to a unit other than milliseconds, pass it as the third parameter.
The fourth parameter is about inclusivity. A [
indicates inclusion of a value. A (
indicates exclusion.
dayjs('R/2020-01-01/P1Y').allBetween('2022-01-01', '2025-01-01') // [2023-01-01, 2024-01-01]
dayjs('R/2020-01-01/P1Y').allBetween('2022-01-01', '2025-01-01', undefined, '(]') // [2023-01-01, 2024-01-01, 2025-01-01]
dayjs('R/2020-01-10/P1Y').allBetween('2022-01-01', '2025-01-01', 'month', '(]') // [2023-01-10, 2024-01-10, 2025-01-10]
first()
Returns the first occurrence.
Or null
, in chronological order, when there is no start
and no limit.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').first() // 2020-01-01
dayjs('2025-01-01').recurring('R/P1Y/2030-01-01').first() // null
dayjs('2025-01-01').recurring('R10/P1Y/2030-01-01').first() // 2020-01-01
first(n, query)
Returns the first n
occurrences that match query
.
Or null
, in chronological order, when there is no start
and no limit.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').first(3) // [2020-01-01, 2021-01-01, 2022-01-01]
dayjs('2025-01-01').recurring('R/P1Y/2030-01-01').first(3) // null
dayjs('2025-01-01').recurring('R10/P1Y/2030-01-01').first(3) // [2020-01-01, 2021-01-01, 2022-01-01]
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').first(3, { isBefore: '2022-01-01' }) // [2020-01-01, 2021-01-01]
last()
Returns the first occurrence.
Or null
, in chronological order, when there is no start
and no limit.
Or null
, in relative order, when there is no limit.
dayjs('2025-01-01').recurring('R/P1Y/2030-01-01').last() // 2030-01-01
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').last() // null
dayjs('2025-01-01').recurring('R10/2020-01-01/P1Y').last() // 2030-01-01
last(n, query)
Returns the last n
occurrences that match query
.
Or null
, in chronological order, when there is no end
and no limit.
Or null
, in relative order, when there is no limit.
dayjs('2025-01-01').recurring('R/P1Y/2030-01-01').last(3) // [2028-01-01, 2029-01-01, 2030-01-01]
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').last(3) // null
dayjs('2025-01-01').recurring('R10/2020-01-01/P1Y').last(3) // [2028-01-01, 2029-01-01, 2030-01-01]
dayjs('2025-01-01').recurring('R/P1Y/2030-01-01').last(3, { isAfter: '2028-01-01' }) // [2029-01-01, 2030-01-01]
prev()
Returns the previous occurrence.
Or null
when the current occurrence is the first one.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').prev() // 2024-01-01
dayjs('2020-01-01').recurring('R/2020-01-01/P1Y').prev() // null
prev(n, query)
Returns the n
previous occurrences that match query
.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').prev(2) // [2023-01-01, 2024-01-01]
dayjs('2021-01-01').recurring('R/2020-01-01/P1Y').prev(2) // [2020-01-01]
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').prev(Infinity, { isAfter: '2021-01-01' }) // [2022-01-01, 2023-01-01, 2024-01-01]
next()
Returns the next occurrence.
Or null
when the current occurrence is the last one.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next() // 2026-01-01
dayjs('2030-01-01').recurring('R10/2020-01-01/P1Y').next() // null
next(n, query)
Returns the n
next occurrences that match query
.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next(2) // [2026-01-01, 2027-01-01]
dayjs('2029-01-01').recurring('R10/2020-01-01/P1Y').next(2) // [2030-01-01-01]
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next(Infinity, { isBefore: '2029-01-01' }) // [2026-01-01, 2027-01-01, 2028-01-01]
Query
Some methods accept a query. These methods will stop the moment a date does not match the query. The query can be an object of which the keys are the name of dayjs comparison methods and the values are either a date or the arguments of said methods. It can also be a function that takes a date and returns a boolean.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next(Infinity, { isBefore: '2029-01-01' }) // [2026-01-01, 2027-01-01, 2028-01-01]
dayjs('2025-01-10').recurring('R/2020-01-10/P1Y').next(Infinity, { isBefore: ['2029-01-01', 'year'] }) // [2026-01-10, 2027-01-10, 2028-01-10]
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next(Infinity, date => date.isBefore('2029-01-01')) // [2026-01-01, 2027-01-01, 2028-01-01]
The example below returns an empty array because, even though 2027-01-01
and 2028-01-01
match the query, 2026-01-01
does not.
dayjs('2025-01-01').recurring('R/2020-01-01/P1Y').next(Infinity, { isAfter: '2026-01-01', isBefore: '2029-01-01' }) // []