From 52cf3c538785ebcc03839a74cf46265662862fd8 Mon Sep 17 00:00:00 2001 From: boyska Date: Wed, 2 Feb 2022 10:16:12 +0100 Subject: [PATCH] getNext --- calendar.js | 145 +++++++++++++++++++++++----- test/example-full-ondarossa.test.js | 29 ++++++ 2 files changed, 149 insertions(+), 25 deletions(-) diff --git a/calendar.js b/calendar.js index 7b9b6e9..86555ec 100644 --- a/calendar.js +++ b/calendar.js @@ -1,6 +1,11 @@ const ICAL = require('ical.js') const shows = require('./shows.js') +function max(a, b) { + if (a 1) - console.log("More than one event right now?!") - return ev_now[0] - } - - /** - * @returns {RadioShow} if nothing is going on right now, `null` is returned - */ - getNowShow(now) { - var ev = this.getNowEvent(now) - + getShowByEvent(ev) { if (ev === null) return null if (this.radio !== undefined) { const showid = RadioSchedule.veventGetShowID(ev) @@ -54,19 +42,59 @@ class RadioSchedule { } /** - * @todo To be implemented + * @returns {external:ICAL~Component} if nothing is going on right now, `null` is returned + */ + getNowEvent(now) { + var ev_now = this.getEvents().filter(function(vevent) { + const ev = new ICAL.Event(vevent) + return isNow(ev, now) + }) + ev_now.sort((e1, e2) => { return this.veventGetPriority(e1) - this.veventGetPriority(e2) }) + if(ev_now.length === 0) + return null + return ev_now[0] + } + + /** + * @returns {RadioShow} if nothing is going on right now, `null` is returned + */ + getNowShow(now) { + const ev = this.getNowEvent(now) + return this.getShowByEvent(ev) + } + + /** + * @returns {NextEvent} if there is none, `null` is returned */ getNextEvent(now) { - // XXX + var nowEvent = this.getNowEvent(now) + let future_events = this.getEvents().filter((e) => { return e != nowEvent }) + .map((e) => { + const vEvent = new ICAL.Event(e) + return {event: e, time: getNext(vEvent, now)} + }) + .filter((x) => { return x.time !== null && x.time !== undefined }) + // since ".sort()" is guaranteed to be stable, we can sort by priority, then by date, so that two events + // starting at the same time will be sorted observing priority + future_events.sort((x1, x2) => this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event)) + future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime()) + + if (future_events.length === 0) { + return null + } + return future_events[0] } /** - * @todo To be implemented - * @returns {NextShow} if there is no next show, nothing will be returned + * @returns {NextShow} if there is no next show, null will be returned */ getNextShow(now) { - // XXX + const next = this.getNextEvent(now) + if (next === null) return null + const ev = next.event + const show = this.getShowByEvent(ev) + return {show: show, time: next.time} } static veventGetSummary(vevent) { return vevent.getFirstProperty('summary').getFirstValue() @@ -75,6 +103,24 @@ class RadioSchedule { return RadioSchedule.veventGetSummary(vevent) // XXX: X-Show-ID } + /** + * @return {integer} a normalized version of priority, easier to compare + */ + veventGetPriority(ev) { + const prop = ev.getFirstProperty('priority') + let prio; + if (prop === null) { + prio = null + } else { + prio = prop.getFirstValue() + } + if (prio === null || prio === 0) { + prio = 100 + } + return prio + } + + } @@ -85,7 +131,7 @@ function isNow(vEvent, now) { if (vEvent.isRecurring()) { return isNowRecurring(vEvent, now) } - return (now < vEvent.endDate) && (now > event.startDate); + return (now < vEvent.endDate) && (now > vEvent.startDate); } @@ -105,6 +151,49 @@ function isNowRecurring(vEvent, now) { } +/* + * @private + * @param {external:ICAL~Component} vEvent a _recurring_ vEvent + * @param {external:ICAL~Time} [now] + * @return {external:ICAL~Time} first future occurrence of this event + */ +function getNext(vEvent, now) { + if (now === undefined) { + now = ICAL.Time.now() + } + if (vEvent.isRecurring()) { + return getNextRecurring(vEvent, now) + } + if (vEvent.endDate > now) { + const val = max(now, vEvent.startDate) + return val + } + return null +} + +/* + * @private + * @param {external:ICAL~Component} vEvent a _recurring_ vEvent + * @param {external:ICAL~Time} now + * @return {external:ICAL~Time} first future occurrence of this event + */ +function getNextRecurring(vEvent, now) { + var expand = vEvent.iterator(vEvent.startDate) + var next, next_end; + + while ((next = expand.next())) { + const start = next.clone() + next_end = start.clone() + next_end.addDuration(vEvent.duration) + if (next_end <= now) { + continue + } + return max(start, now) + } + return null +} + + async function get(manifest) { if (manifest.scheduleURL) { let resp = null @@ -151,6 +240,12 @@ module.exports = { * @property {external:ICAL~Time} time When it will start */ +/** + * @typedef {Object} NextEvent + * @property {RadioShow} event The next show scheduled + * @property {external:ICAL~Time} time When it will start + */ + /** * @external ICAL * @see https://mozilla-comm.github.io/ical.js/api/index.html diff --git a/test/example-full-ondarossa.test.js b/test/example-full-ondarossa.test.js index 9d5e113..7734c43 100644 --- a/test/example-full-ondarossa.test.js +++ b/test/example-full-ondarossa.test.js @@ -72,6 +72,35 @@ describe('examples/' + exampleName, () => { assert.equal(show.getFeed(), testFeed) }) + it('monday at 7PM, "Entropia Massima" is next', async () => { + const rm = await radiomanifest.get(url) + const rs = rm.getSchedule() + const now = new ICAL.Time({ + year: 2022, + month: 1, + day: 31, + hour: 19, + minute: 10, + second: 0, + isDate: false + }, + ); + const vevent = rs.getNowEvent(now) + assert.notEqual(vevent, null) + const show = rs.getNowShow(now) + assert.notEqual(show, null) + assert.equal(show.getName(), 'Baraonda') + + const next_event = rs.getNextEvent(now) + assert.notEqual(next_event, null) + assert.notEqual(next_event.event, null) + const next_show = rs.getNextShow(now) + assert.notEqual(next_show, null) + assert.notEqual(next_show.show, null) + assert.equal(next_show.show.getName(), testShowName) + assert.equal(next_show.show.getFeed(), testFeed) + }) + }) })