Skip to content

Commit ebe9953

Browse files
mikecoteZacqary
andauthored
[8.17] [ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650) (elastic#205830)
# Backport This will backport the following commits from `main` to `8.17`: - [[ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650)](elastic#205650) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Zacqary Adam Xeper","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-01-07T19:32:43Z","message":"[ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650)\n\n## Summary\r\n\r\nCloses https://github.com/elastic/kibana/issues/205558\r\n\r\nUpdates the RRule library to correctly handle some scenarios with\r\ninvalid parameters that would either cause it to return strange\r\nrecurrence data or to infinitely loop. Specifically:\r\n\r\n- On `RRule` object creation, removes and ignores any `bymonth`,\r\n`bymonthday`, `byweekday`, or `byyearday` value that's out of bounds,\r\ne.g. less than 0 or greater than the number of possible months, days,\r\nweekdays, etc.\r\n- Successfully ignores cases of `BYMONTH=2, BYMONTHDAY=30` (February\r\n30th), an input that's complicated to invalidate but still won't ever\r\noccur\r\n\r\nAllowing these values to go unhandled led to unpredictable behavior. The\r\nRRule library uses Moment.js to compare dates, but Moment.js months,\r\ndays, and other values generally start at `0` while RRule values start\r\nat `1`. That led to several circumstances where we passed Moment.js a\r\nvalue of `-1`, which Moment.js interpreted as moving to the\r\n***previous*** year, month, or other period of time.\r\n\r\nAt worst, this could cause an infinite loop because the RRule library\r\nwas constantly iterating through the wrong year, never reaching the date\r\nit was supposed to end on.\r\n\r\nIn addition to making the RRule library more able to handle these cases,\r\nthis PR also gives it a hard 100,000 iteration limit to prevent any\r\npossible infinite loops we've missed.\r\n\r\nLastly, the Snooze Schedule APIs also come with additional validation to\r\nhopefully prevent out of bounds dates from ever being set.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: adcoelho <[email protected]>","sha":"b30210929be0824f684f0b7d9d13bc936c1cbd22","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:ResponseOps","v9.0.0","Feature:Alerting/RulesFramework","backport:version","v8.18.0","v8.16.3","v8.17.1"],"number":205650,"url":"https://github.com/elastic/kibana/pull/205650","mergeCommit":{"message":"[ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650)\n\n## Summary\r\n\r\nCloses https://github.com/elastic/kibana/issues/205558\r\n\r\nUpdates the RRule library to correctly handle some scenarios with\r\ninvalid parameters that would either cause it to return strange\r\nrecurrence data or to infinitely loop. Specifically:\r\n\r\n- On `RRule` object creation, removes and ignores any `bymonth`,\r\n`bymonthday`, `byweekday`, or `byyearday` value that's out of bounds,\r\ne.g. less than 0 or greater than the number of possible months, days,\r\nweekdays, etc.\r\n- Successfully ignores cases of `BYMONTH=2, BYMONTHDAY=30` (February\r\n30th), an input that's complicated to invalidate but still won't ever\r\noccur\r\n\r\nAllowing these values to go unhandled led to unpredictable behavior. The\r\nRRule library uses Moment.js to compare dates, but Moment.js months,\r\ndays, and other values generally start at `0` while RRule values start\r\nat `1`. That led to several circumstances where we passed Moment.js a\r\nvalue of `-1`, which Moment.js interpreted as moving to the\r\n***previous*** year, month, or other period of time.\r\n\r\nAt worst, this could cause an infinite loop because the RRule library\r\nwas constantly iterating through the wrong year, never reaching the date\r\nit was supposed to end on.\r\n\r\nIn addition to making the RRule library more able to handle these cases,\r\nthis PR also gives it a hard 100,000 iteration limit to prevent any\r\npossible infinite loops we've missed.\r\n\r\nLastly, the Snooze Schedule APIs also come with additional validation to\r\nhopefully prevent out of bounds dates from ever being set.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: adcoelho <[email protected]>","sha":"b30210929be0824f684f0b7d9d13bc936c1cbd22"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/205650","number":205650,"mergeCommit":{"message":"[ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650)\n\n## Summary\r\n\r\nCloses https://github.com/elastic/kibana/issues/205558\r\n\r\nUpdates the RRule library to correctly handle some scenarios with\r\ninvalid parameters that would either cause it to return strange\r\nrecurrence data or to infinitely loop. Specifically:\r\n\r\n- On `RRule` object creation, removes and ignores any `bymonth`,\r\n`bymonthday`, `byweekday`, or `byyearday` value that's out of bounds,\r\ne.g. less than 0 or greater than the number of possible months, days,\r\nweekdays, etc.\r\n- Successfully ignores cases of `BYMONTH=2, BYMONTHDAY=30` (February\r\n30th), an input that's complicated to invalidate but still won't ever\r\noccur\r\n\r\nAllowing these values to go unhandled led to unpredictable behavior. The\r\nRRule library uses Moment.js to compare dates, but Moment.js months,\r\ndays, and other values generally start at `0` while RRule values start\r\nat `1`. That led to several circumstances where we passed Moment.js a\r\nvalue of `-1`, which Moment.js interpreted as moving to the\r\n***previous*** year, month, or other period of time.\r\n\r\nAt worst, this could cause an infinite loop because the RRule library\r\nwas constantly iterating through the wrong year, never reaching the date\r\nit was supposed to end on.\r\n\r\nIn addition to making the RRule library more able to handle these cases,\r\nthis PR also gives it a hard 100,000 iteration limit to prevent any\r\npossible infinite loops we've missed.\r\n\r\nLastly, the Snooze Schedule APIs also come with additional validation to\r\nhopefully prevent out of bounds dates from ever being set.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: Janki Salvi <[email protected]>\r\nCo-authored-by: adcoelho <[email protected]>","sha":"b30210929be0824f684f0b7d9d13bc936c1cbd22"}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/205803","number":205803,"state":"MERGED","mergeCommit":{"sha":"a02fcb232faed2f385ce9b97fbdb323ccbf8ca45","message":"[8.x] [ResponseOps] [Alerting] Handle invalid RRule params and prevent infinite looping (elastic#205650) (elastic#205803)\n\n# Backport\n\nThis will backport the following commits from `main` to `8.x`:\n- [[ResponseOps] [Alerting] Handle invalid RRule params and prevent\ninfinite looping\n(elastic#205650)](https://github.com/elastic/kibana/pull/205650)\n\n<!--- Backport version: 9.4.3 -->\n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT [{\"author\":{\"name\":\"Zacqary Adam\nXeper\",\"email\":\"[email protected]\"},\"sourceCommit\":{\"committedDate\":\"2025-01-07T19:32:43Z\",\"message\":\"[ResponseOps]\n[Alerting] Handle invalid RRule params and prevent infinite looping\n(elastic#205650)\\n\\n## Summary\\r\\n\\r\\nCloses\nhttps://github.com/elastic/issues/205558\\r\\n\\r\\nUpdates the RRule\nlibrary to correctly handle some scenarios with\\r\\ninvalid parameters\nthat would either cause it to return strange\\r\\nrecurrence data or to\ninfinitely loop. Specifically:\\r\\n\\r\\n- On `RRule` object creation,\nremoves and ignores any `bymonth`,\\r\\n`bymonthday`, `byweekday`, or\n`byyearday` value that's out of bounds,\\r\\ne.g. less than 0 or greater\nthan the number of possible months, days,\\r\\nweekdays, etc.\\r\\n-\nSuccessfully ignores cases of `BYMONTH=2, BYMONTHDAY=30`\n(February\\r\\n30th), an input that's complicated to invalidate but still\nwon't ever\\r\\noccur\\r\\n\\r\\nAllowing these values to go unhandled led to\nunpredictable behavior. The\\r\\nRRule library uses Moment.js to compare\ndates, but Moment.js months,\\r\\ndays, and other values generally start\nat `0` while RRule values start\\r\\nat `1`. That led to several\ncircumstances where we passed Moment.js a\\r\\nvalue of `-1`, which\nMoment.js interpreted as moving to the\\r\\n***previous*** year, month, or\nother period of time.\\r\\n\\r\\nAt worst, this could cause an infinite loop\nbecause the RRule library\\r\\nwas constantly iterating through the wrong\nyear, never reaching the date\\r\\nit was supposed to end on.\\r\\n\\r\\nIn\naddition to making the RRule library more able to handle these\ncases,\\r\\nthis PR also gives it a hard 100,000 iteration limit to\nprevent any\\r\\npossible infinite loops we've missed.\\r\\n\\r\\nLastly, the\nSnooze Schedule APIs also come with additional validation\nto\\r\\nhopefully prevent out of bounds dates from ever being\nset.\\r\\n\\r\\n### Checklist\\r\\n\\r\\n- [x] [Unit or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi <[email protected]>\\r\\nCo-authored-by: adcoelho\n<[email protected]>\",\"sha\":\"b30210929be0824f684f0b7d9d13bc936c1cbd22\",\"branchLabelMapping\":{\"^v9.0.0$\":\"main\",\"^v8.18.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:fix\",\"Team:ResponseOps\",\"v9.0.0\",\"Feature:Alerting/RulesFramework\",\"backport:version\",\"v8.18.0\",\"v8.16.3\",\"v8.17.1\"],\"title\":\"[ResponseOps]\n[Alerting] Handle invalid RRule params and prevent infinite\nlooping\",\"number\":205650,\"url\":\"https://github.com/elastic/kibana/pull/205650\",\"mergeCommit\":{\"message\":\"[ResponseOps]\n[Alerting] Handle invalid RRule params and prevent infinite looping\n(elastic#205650)\\n\\n## Summary\\r\\n\\r\\nCloses\nhttps://github.com/elastic/issues/205558\\r\\n\\r\\nUpdates the RRule\nlibrary to correctly handle some scenarios with\\r\\ninvalid parameters\nthat would either cause it to return strange\\r\\nrecurrence data or to\ninfinitely loop. Specifically:\\r\\n\\r\\n- On `RRule` object creation,\nremoves and ignores any `bymonth`,\\r\\n`bymonthday`, `byweekday`, or\n`byyearday` value that's out of bounds,\\r\\ne.g. less than 0 or greater\nthan the number of possible months, days,\\r\\nweekdays, etc.\\r\\n-\nSuccessfully ignores cases of `BYMONTH=2, BYMONTHDAY=30`\n(February\\r\\n30th), an input that's complicated to invalidate but still\nwon't ever\\r\\noccur\\r\\n\\r\\nAllowing these values to go unhandled led to\nunpredictable behavior. The\\r\\nRRule library uses Moment.js to compare\ndates, but Moment.js months,\\r\\ndays, and other values generally start\nat `0` while RRule values start\\r\\nat `1`. That led to several\ncircumstances where we passed Moment.js a\\r\\nvalue of `-1`, which\nMoment.js interpreted as moving to the\\r\\n***previous*** year, month, or\nother period of time.\\r\\n\\r\\nAt worst, this could cause an infinite loop\nbecause the RRule library\\r\\nwas constantly iterating through the wrong\nyear, never reaching the date\\r\\nit was supposed to end on.\\r\\n\\r\\nIn\naddition to making the RRule library more able to handle these\ncases,\\r\\nthis PR also gives it a hard 100,000 iteration limit to\nprevent any\\r\\npossible infinite loops we've missed.\\r\\n\\r\\nLastly, the\nSnooze Schedule APIs also come with additional validation\nto\\r\\nhopefully prevent out of bounds dates from ever being\nset.\\r\\n\\r\\n### Checklist\\r\\n\\r\\n- [x] [Unit or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi <[email protected]>\\r\\nCo-authored-by: adcoelho\n<[email protected]>\",\"sha\":\"b30210929be0824f684f0b7d9d13bc936c1cbd22\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"8.x\",\"8.16\",\"8.17\"],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v9.0.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/205650\",\"number\":205650,\"mergeCommit\":{\"message\":\"[ResponseOps]\n[Alerting] Handle invalid RRule params and prevent infinite looping\n(elastic#205650)\\n\\n## Summary\\r\\n\\r\\nCloses\nhttps://github.com/elastic/issues/205558\\r\\n\\r\\nUpdates the RRule\nlibrary to correctly handle some scenarios with\\r\\ninvalid parameters\nthat would either cause it to return strange\\r\\nrecurrence data or to\ninfinitely loop. Specifically:\\r\\n\\r\\n- On `RRule` object creation,\nremoves and ignores any `bymonth`,\\r\\n`bymonthday`, `byweekday`, or\n`byyearday` value that's out of bounds,\\r\\ne.g. less than 0 or greater\nthan the number of possible months, days,\\r\\nweekdays, etc.\\r\\n-\nSuccessfully ignores cases of `BYMONTH=2, BYMONTHDAY=30`\n(February\\r\\n30th), an input that's complicated to invalidate but still\nwon't ever\\r\\noccur\\r\\n\\r\\nAllowing these values to go unhandled led to\nunpredictable behavior. The\\r\\nRRule library uses Moment.js to compare\ndates, but Moment.js months,\\r\\ndays, and other values generally start\nat `0` while RRule values start\\r\\nat `1`. That led to several\ncircumstances where we passed Moment.js a\\r\\nvalue of `-1`, which\nMoment.js interpreted as moving to the\\r\\n***previous*** year, month, or\nother period of time.\\r\\n\\r\\nAt worst, this could cause an infinite loop\nbecause the RRule library\\r\\nwas constantly iterating through the wrong\nyear, never reaching the date\\r\\nit was supposed to end on.\\r\\n\\r\\nIn\naddition to making the RRule library more able to handle these\ncases,\\r\\nthis PR also gives it a hard 100,000 iteration limit to\nprevent any\\r\\npossible infinite loops we've missed.\\r\\n\\r\\nLastly, the\nSnooze Schedule APIs also come with additional validation\nto\\r\\nhopefully prevent out of bounds dates from ever being\nset.\\r\\n\\r\\n### Checklist\\r\\n\\r\\n- [x] [Unit or\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi\n<[email protected]>\\r\\nCo-authored-by:\nJanki Salvi <[email protected]>\\r\\nCo-authored-by: adcoelho\n<[email protected]>\",\"sha\":\"b30210929be0824f684f0b7d9d13bc936c1cbd22\"}},{\"branch\":\"8.x\",\"label\":\"v8.18.0\",\"branchLabelMappingKey\":\"^v8.18.0$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"8.16\",\"label\":\"v8.16.3\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"8.17\",\"label\":\"v8.17.1\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"}]}]\nBACKPORT-->\n\nCo-authored-by: Zacqary Adam Xeper <[email protected]>"}},{"branch":"8.16","label":"v8.16.3","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Zacqary Adam Xeper <[email protected]>
1 parent 97b5be4 commit ebe9953

File tree

9 files changed

+615
-68
lines changed

9 files changed

+615
-68
lines changed

packages/kbn-rrule/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
export { RRule, Frequency, Weekday } from './rrule';
11-
export type { Options } from './rrule';
12-
export declare type WeekdayStr = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU';
10+
export { RRule } from './rrule';
11+
export type { Options, WeekdayStr } from './types';
12+
export { Frequency, Weekday } from './types';

packages/kbn-rrule/rrule.test.ts

+276-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
*/
99

1010
import sinon from 'sinon';
11-
import { RRule, Frequency, Weekday } from './rrule';
11+
import { RRule } from './rrule';
12+
import { Frequency, Weekday } from './types';
1213

1314
const DATE_2019 = '2019-01-01T00:00:00.000Z';
1415
const DATE_2019_DECEMBER_19 = '2019-12-19T00:00:00.000Z';
@@ -730,6 +731,228 @@ describe('RRule', () => {
730731
]
731732
`);
732733
});
734+
it('ignores invalid byweekday values', () => {
735+
const rule = new RRule({
736+
dtstart: new Date(DATE_2019_DECEMBER_19),
737+
freq: Frequency.WEEKLY,
738+
interval: 1,
739+
tzid: 'UTC',
740+
byweekday: [Weekday.TH, 0, -2],
741+
});
742+
expect(rule.all(14)).toMatchInlineSnapshot(`
743+
Array [
744+
2019-12-19T00:00:00.000Z,
745+
2019-12-26T00:00:00.000Z,
746+
2020-01-02T00:00:00.000Z,
747+
2020-01-09T00:00:00.000Z,
748+
2020-01-16T00:00:00.000Z,
749+
2020-01-23T00:00:00.000Z,
750+
2020-01-30T00:00:00.000Z,
751+
2020-02-06T00:00:00.000Z,
752+
2020-02-13T00:00:00.000Z,
753+
2020-02-20T00:00:00.000Z,
754+
2020-02-27T00:00:00.000Z,
755+
2020-03-05T00:00:00.000Z,
756+
2020-03-12T00:00:00.000Z,
757+
2020-03-19T00:00:00.000Z,
758+
]
759+
`);
760+
761+
const rule2 = new RRule({
762+
dtstart: new Date(DATE_2019),
763+
freq: Frequency.WEEKLY,
764+
interval: 1,
765+
tzid: 'UTC',
766+
byweekday: [Weekday.SA, Weekday.SU, Weekday.MO, 0],
767+
});
768+
769+
expect(rule2.all(9)).toMatchInlineSnapshot(`
770+
Array [
771+
2019-01-05T00:00:00.000Z,
772+
2019-01-06T00:00:00.000Z,
773+
2019-01-07T00:00:00.000Z,
774+
2019-01-12T00:00:00.000Z,
775+
2019-01-13T00:00:00.000Z,
776+
2019-01-14T00:00:00.000Z,
777+
2019-01-19T00:00:00.000Z,
778+
2019-01-20T00:00:00.000Z,
779+
2019-01-21T00:00:00.000Z,
780+
]
781+
`);
782+
});
783+
});
784+
785+
describe('bymonth', () => {
786+
it('works with yearly frequency', () => {
787+
const rule = new RRule({
788+
dtstart: new Date(DATE_2019_DECEMBER_19),
789+
freq: Frequency.YEARLY,
790+
interval: 1,
791+
tzid: 'UTC',
792+
bymonth: [2, 5],
793+
});
794+
expect(rule.all(14)).toMatchInlineSnapshot(`
795+
Array [
796+
2020-02-19T00:00:00.000Z,
797+
2020-05-19T00:00:00.000Z,
798+
2021-02-19T00:00:00.000Z,
799+
2021-05-19T00:00:00.000Z,
800+
2022-02-19T00:00:00.000Z,
801+
2022-05-19T00:00:00.000Z,
802+
2023-02-19T00:00:00.000Z,
803+
2023-05-19T00:00:00.000Z,
804+
2024-02-19T00:00:00.000Z,
805+
2024-05-19T00:00:00.000Z,
806+
2025-02-19T00:00:00.000Z,
807+
2025-05-19T00:00:00.000Z,
808+
2026-02-19T00:00:00.000Z,
809+
2026-05-19T00:00:00.000Z,
810+
]
811+
`);
812+
});
813+
it('ignores invalid bymonth values', () => {
814+
const rule = new RRule({
815+
dtstart: new Date(DATE_2019_DECEMBER_19),
816+
freq: Frequency.YEARLY,
817+
interval: 1,
818+
tzid: 'UTC',
819+
bymonth: [0],
820+
});
821+
expect(rule.all(14)).toMatchInlineSnapshot(`
822+
Array [
823+
2019-12-19T00:00:00.000Z,
824+
2020-12-19T00:00:00.000Z,
825+
2021-12-19T00:00:00.000Z,
826+
2022-12-19T00:00:00.000Z,
827+
2023-12-19T00:00:00.000Z,
828+
2024-12-19T00:00:00.000Z,
829+
2025-12-19T00:00:00.000Z,
830+
2026-12-19T00:00:00.000Z,
831+
2027-12-19T00:00:00.000Z,
832+
2028-12-19T00:00:00.000Z,
833+
2029-12-19T00:00:00.000Z,
834+
2030-12-19T00:00:00.000Z,
835+
2031-12-19T00:00:00.000Z,
836+
2032-12-19T00:00:00.000Z,
837+
]
838+
`);
839+
});
840+
});
841+
842+
describe('bymonthday', () => {
843+
it('works with monthly frequency', () => {
844+
const rule = new RRule({
845+
dtstart: new Date(DATE_2019_DECEMBER_19),
846+
freq: Frequency.MONTHLY,
847+
interval: 1,
848+
tzid: 'UTC',
849+
bymonthday: [1, 15],
850+
});
851+
expect(rule.all(14)).toMatchInlineSnapshot(`
852+
Array [
853+
2020-01-01T00:00:00.000Z,
854+
2020-01-15T00:00:00.000Z,
855+
2020-02-01T00:00:00.000Z,
856+
2020-02-15T00:00:00.000Z,
857+
2020-03-01T00:00:00.000Z,
858+
2020-03-15T00:00:00.000Z,
859+
2020-04-01T00:00:00.000Z,
860+
2020-04-15T00:00:00.000Z,
861+
2020-05-01T00:00:00.000Z,
862+
2020-05-15T00:00:00.000Z,
863+
2020-06-01T00:00:00.000Z,
864+
2020-06-15T00:00:00.000Z,
865+
2020-07-01T00:00:00.000Z,
866+
2020-07-15T00:00:00.000Z,
867+
]
868+
`);
869+
});
870+
it('ignores invalid bymonthday values', () => {
871+
const rule = new RRule({
872+
dtstart: new Date(DATE_2019_DECEMBER_19),
873+
freq: Frequency.MONTHLY,
874+
interval: 1,
875+
tzid: 'UTC',
876+
bymonthday: [0, -1, 32],
877+
});
878+
expect(rule.all(14)).toMatchInlineSnapshot(`
879+
Array [
880+
2019-12-19T00:00:00.000Z,
881+
2020-01-19T00:00:00.000Z,
882+
2020-02-19T00:00:00.000Z,
883+
2020-03-19T00:00:00.000Z,
884+
2020-04-19T00:00:00.000Z,
885+
2020-05-19T00:00:00.000Z,
886+
2020-06-19T00:00:00.000Z,
887+
2020-07-19T00:00:00.000Z,
888+
2020-08-19T00:00:00.000Z,
889+
2020-09-19T00:00:00.000Z,
890+
2020-10-19T00:00:00.000Z,
891+
2020-11-19T00:00:00.000Z,
892+
2020-12-19T00:00:00.000Z,
893+
2021-01-19T00:00:00.000Z,
894+
]
895+
`);
896+
});
897+
});
898+
899+
describe('bymonth, bymonthday', () => {
900+
it('works with yearly frequency', () => {
901+
const rule = new RRule({
902+
dtstart: new Date(DATE_2019_DECEMBER_19),
903+
freq: Frequency.YEARLY,
904+
interval: 1,
905+
tzid: 'UTC',
906+
bymonth: [2, 5],
907+
bymonthday: [8],
908+
});
909+
expect(rule.all(14)).toMatchInlineSnapshot(`
910+
Array [
911+
2020-02-08T00:00:00.000Z,
912+
2020-05-08T00:00:00.000Z,
913+
2021-02-08T00:00:00.000Z,
914+
2021-05-08T00:00:00.000Z,
915+
2022-02-08T00:00:00.000Z,
916+
2022-05-08T00:00:00.000Z,
917+
2023-02-08T00:00:00.000Z,
918+
2023-05-08T00:00:00.000Z,
919+
2024-02-08T00:00:00.000Z,
920+
2024-05-08T00:00:00.000Z,
921+
2025-02-08T00:00:00.000Z,
922+
2025-05-08T00:00:00.000Z,
923+
2026-02-08T00:00:00.000Z,
924+
2026-05-08T00:00:00.000Z,
925+
]
926+
`);
927+
});
928+
it('ignores valid dates that do not exist e.g. February 30th', () => {
929+
const rule = new RRule({
930+
dtstart: new Date(DATE_2019_DECEMBER_19),
931+
freq: Frequency.YEARLY,
932+
interval: 1,
933+
tzid: 'UTC',
934+
bymonth: [2, 5],
935+
bymonthday: [30],
936+
});
937+
expect(rule.all(14)).toMatchInlineSnapshot(`
938+
Array [
939+
2020-05-30T00:00:00.000Z,
940+
2021-05-30T00:00:00.000Z,
941+
2022-05-30T00:00:00.000Z,
942+
2023-05-30T00:00:00.000Z,
943+
2024-05-30T00:00:00.000Z,
944+
2025-05-30T00:00:00.000Z,
945+
2026-05-30T00:00:00.000Z,
946+
2027-05-30T00:00:00.000Z,
947+
2028-05-30T00:00:00.000Z,
948+
2029-05-30T00:00:00.000Z,
949+
2030-05-30T00:00:00.000Z,
950+
2031-05-30T00:00:00.000Z,
951+
2032-05-30T00:00:00.000Z,
952+
2033-05-30T00:00:00.000Z,
953+
]
954+
`);
955+
});
733956
});
734957

735958
describe('byhour, byminute, bysecond', () => {
@@ -844,6 +1067,30 @@ describe('RRule', () => {
8441067
]
8451068
`);
8461069
});
1070+
it('ignores invalid byyearday values', () => {
1071+
const rule = new RRule({
1072+
dtstart: new Date(DATE_2020),
1073+
freq: Frequency.YEARLY,
1074+
byyearday: [0, -1],
1075+
interval: 1,
1076+
tzid: 'UTC',
1077+
});
1078+
1079+
expect(rule.all(10)).toMatchInlineSnapshot(`
1080+
Array [
1081+
2020-01-01T00:00:00.000Z,
1082+
2021-01-01T00:00:00.000Z,
1083+
2022-01-01T00:00:00.000Z,
1084+
2023-01-01T00:00:00.000Z,
1085+
2024-01-01T00:00:00.000Z,
1086+
2025-01-01T00:00:00.000Z,
1087+
2026-01-01T00:00:00.000Z,
1088+
2027-01-01T00:00:00.000Z,
1089+
2028-01-01T00:00:00.000Z,
1090+
2029-01-01T00:00:00.000Z,
1091+
]
1092+
`);
1093+
});
8471094
});
8481095

8491096
describe('error handling', () => {
@@ -872,5 +1119,33 @@ describe('RRule', () => {
8721119
`"Cannot create RRule: until is an invalid date"`
8731120
);
8741121
});
1122+
1123+
it('throws an error on an interval of 0', () => {
1124+
const testFn = () =>
1125+
new RRule({
1126+
dtstart: new Date(DATE_2020),
1127+
freq: Frequency.HOURLY,
1128+
interval: 0,
1129+
tzid: 'UTC',
1130+
});
1131+
expect(testFn).toThrowErrorMatchingInlineSnapshot(
1132+
`"Cannot create RRule: interval must be greater than 0"`
1133+
);
1134+
});
1135+
1136+
it('throws an error when exceeding the iteration limit', () => {
1137+
const testFn = () => {
1138+
const rule = new RRule({
1139+
dtstart: new Date(DATE_2020),
1140+
freq: Frequency.YEARLY,
1141+
byyearday: [1],
1142+
interval: 1,
1143+
tzid: 'UTC',
1144+
});
1145+
rule.all(100001);
1146+
};
1147+
1148+
expect(testFn).toThrowErrorMatchingInlineSnapshot(`"RRule iteration limit exceeded"`);
1149+
});
8751150
});
8761151
});

0 commit comments

Comments
 (0)