Skip to content

Commit

Permalink
Introduce the recurrence tester
Browse files Browse the repository at this point in the history
  • Loading branch information
kewisch committed May 11, 2024
1 parent 4c9f94c commit db94e5d
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bower_components
dist/
docs/api/
docs/validator.html
docs/recur-tester.html
tools/vzic/
tools/tzdb/
tools/libical/
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ bugfixing this library, please check if the fix can be upstreamed to libical.
## Sandbox and Validator

If you want to try out ICAL.js right now, there is a
[jsfiddle](http://jsfiddle.net/kewisch/227efboL/) set up and ready to use. Read on for documentation
and example links.
[jsfiddle](http://jsfiddle.net/kewisch/227efboL/) set up and ready to use.

There is also a validator that demonstrates how to use the library in a webpage in the
[tools/](https://github.com/kewisch/ical.js/tree/main/tools) subdirectory.
The ICAL validator demonstrates how to use the library in a webpage, and helps verify iCalendar and
jCal. [Try the validator online](http://kewisch.github.io/ical.js/validator.html)

[Try the validator online](http://kewisch.github.io/ical.js/validator.html), it always uses the
latest release of ICAL.js.
The recurrence tester calculates occurrences based on a RRULE. It can be used to aid in
creating test cases for the recurrence iterator.
[Try the recurrence tester online](https://kewisch.github.io/ical.js/recur-tester.html).

## Installing

Expand Down
10 changes: 10 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import js from "@eslint/js";
import globals from "globals";
import stylistic from '@stylistic/eslint-plugin';
import html from "eslint-plugin-html";

export default [
{
Expand Down Expand Up @@ -374,5 +375,14 @@ export default [
rules: {
"@stylistic/quote-props": ["error", "consistent-as-needed"]
}
},
{
files: ["tools/**/*.html"],
plugins: {
"@html": html
},
languageOptions: {
globals: globals.browser
}
}
];
2 changes: 1 addition & 1 deletion lib/ical/recur_expansion.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ class RecurExpansion {
let maxTries = 500;
let currentTry = 0;

while (true) { // eslint-disable-line no-constant-condition
while (true) {
if (currentTry++ > maxTries) {
throw new Error(
'max tries have occurred, rule may be impossible to fulfill.'
Expand Down
87 changes: 87 additions & 0 deletions package-lock.json

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

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"chai": "^5.1.0",
"clean-jsdoc-theme": "^4.2.18",
"eslint": "^9.0.0",
"eslint-plugin-html": "^8.1.1",
"globals": "^15.0.0",
"jsdoc": "^4.0.2",
"karma": "^6.4.3",
Expand All @@ -57,8 +58,9 @@
"build": "rollup -c",
"lint": "eslint",
"jsdoc": "rm -rf docs/api && jsdoc --configure jsdoc.json --verbose",
"validator": "sed -e 's#unpkg.com/ical.js#unpkg.com/ical.js@'`grep '\"version\"' package.json | cut -d '\"' -f 4`'/dist/ical.js#' tools/validator.html > docs/validator.html && echo 'Validator written to docs/validator.html'",
"ghpages": "npm run jsdoc && npm run validator"
"validator": "node tools/scriptutils.js replace-unpkg tools/validator.html docs/validator.html",
"recurtester": "node tools/scriptutils.js replace-unpkg tools/recur-tester.html docs/recur-tester.html",
"ghpages": "npm run jsdoc && npm run validator && npm run recurtester"
},
"exports": {
"import": "./dist/ical.min.js",
Expand Down
140 changes: 140 additions & 0 deletions tools/recur-tester.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
body {
font-family: 'Verdana', sans-serif;
color: #333;
background-color: #f4f4f4;
padding: 20px;
font-size: 16px;
width: 42em;
}
input[type="submit"] {
font-size: 1.2rem;
border-radius: 5px;
border: 1px solid black;
margin-top: 1ex;
padding: 10px;
}
input[type="text"] {
width: 100%;
font-family: monospace;
padding: 5px;
}
input[type="datetime-local"] {
font-family: monospace;
padding: 5px;
}
form > div {
margin-bottom: 10px;
}
</style>
<script type="module">
import ICAL from "https://unpkg.com/ical.js";

document.addEventListener('DOMContentLoaded', function() {
const now = new Date();
now.setMinutes(0);
now.setSeconds(0);
now.setMilliseconds(0);
const oneMonthLater = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());

// Format dates for input fields
const dtstartValue = now.toISOString().slice(0, 16); // Formats to YYYY-MM-DDTHH:MM
const rangeEndValue = oneMonthLater.toISOString().slice(0, 16); // Formats to YYYY-MM-DDTHH:MM

// Set default values
document.getElementById('dtstart').value = dtstartValue;
document.getElementById('rangeStart').value = dtstartValue;
document.getElementById('rangeEnd').value = rangeEndValue;
});

document.getElementById("form").addEventListener("submit", (event) => {
event.preventDefault();

let dtstart = new Date(document.getElementById('dtstart').value);
let rruleString = document.getElementById('rrule').value;
let rangeStart = new Date(document.getElementById('rangeStart').value);
let rangeEnd = new Date(document.getElementById('rangeEnd').value);

if (isNaN(dtstart.getTime()) || !rruleString || isNaN(rangeStart.getTime()) || isNaN(rangeEnd.getTime())) {
alert('Please fill all fields correctly.');
return;
}

let icDtStart = ICAL.Time.fromJSDate(dtstart);
let icRangeEnd = ICAL.Time.fromJSDate(rangeEnd);
let icRangeStart = ICAL.Time.fromJSDate(rangeStart);

if (rruleString.startsWith("RRULE;")) {
rruleString = rruleString.substring(6);
}

let rrule = ICAL.Recur.fromString(`RRULE;${rruleString}`);
let vevent = new ICAL.Component('vevent');
vevent.addPropertyWithValue('dtstart', icDtStart);
vevent.addPropertyWithValue('rrule', rrule);

let iter = rrule.iterator(icDtStart);

let occurrences = [];
let next;
while ((next = iter.next())) {
if (next.compare(icRangeStart) < 0) {
continue;
} else if (next.compare(icRangeEnd) > 0) {
break;
}

occurrences.push(next.toString());
}

console.log(dtstart, rangeStart);

document.getElementById("testcase").textContent =
`// <describe testcase here>\n` +
`testRRULE('${rruleString}', {\n` +
(dtstart.getTime() == rangeStart.getTime() ? ""
: ` dtStart: '${icRangeStart.toString()}',\n`
) +
" dates: [\n" +
` '${occurrences.join("',\n '")}'\n` +
" ]\n" +
"});";

document.getElementById('occurrences').textContent = occurrences.join('\n');
});
</script>
</head>
<body>
<form id="form" method="post" action="#">
<h1>Recurrence Rule Tester</h1>
<p id="descr">
This tool allows you to calculate occurrences for RRULEs and prepare testcases for them. It
will use ICAL.js from https://unpkg.com/ical.js. <b>Be sure to manually validate the
occurrences, as otherwise it wouldn't be a good test</b>.
</p>
<div>
<label for="dtstart">DTSTART:</label>
<input type="datetime-local" id="dtstart">
</div>
<div>
<label for="rrule">RRULE:</label>
<input type="text" id="rrule">
</div>
<div>
<label for="rangeStart">Expand range:</label>
<input type="datetime-local" id="rangeStart">
<label for="rangeEnd"></label>
<input type="datetime-local" id="rangeEnd">
</div>
<input type="submit" id="calculate" value="Calculate Occurrences"/><br/>
<hr>
<pre id="occurrences"></pre>
<hr>
<pre id="testcase"></pre>
</form>
</body>
</html>
9 changes: 9 additions & 0 deletions tools/scriptutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ async function get_tzdb_version() {
return match[1];
}

async function replace_unpkg(input, output) {
let content = await fs.readFile(input, { encoding: "utf-8" });
let pkg = JSON.parse(await fs.readFile(path.join(import.meta.dirname, "..", "package.json"), { encoding: "utf-8" }));
await fs.writeFile(output, content.replace(/unpkg.com\/ical.js/g, `unpkg.com/ical.js@${pkg.version}/dist/ical.js`));
console.log(`unpkg link from ${input} updated to ${pkg.version} and written to ${output}`);
}

async function main() {
switch (process.argv[2]) {
case "tzdb-version":
Expand All @@ -143,6 +150,8 @@ async function main() {
case "performance-downloader":
await performance_downloader();
break;
case "replace-unpkg":
await replace_unpkg(process.argv[3], process.argv[4]);
}
}
main();
Loading

0 comments on commit db94e5d

Please sign in to comment.