First batch of extra files

This commit is contained in:
2025-10-28 18:39:57 +01:00
parent d8c35077cb
commit 2bb22c7583
890 changed files with 40738 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta content="pandoc" name="generator" />
<meta content="width=device-width, initial-scale=1.0, user-scalable=yes" name="viewport" />
<meta content="Dave Morriss" name="author" />
<title>iCalendar Hacking (HPR Show 1286)</title>
<style type="text/css">code{white-space: pre;}</style>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href="http://hackerpublicradio.org/css/hpr.css" rel="stylesheet" />
</head>
<body id="home">
<div class="shadow" id="container"><header><h1 class="title">iCalendar Hacking (HPR Show 1286)</h1>
<h2 class="subtitle">Creating iCalendar rules by hand and with a Perl script</h2>
<h2 class="author">Dave Morriss</h2>
<hr /></header> <main id="maincontent"> <article> <header><h1>Table of Contents</h1><nav id="TOC"><ul>
<li><a href="#editors-note-2020-01-02">Editors Note 2020-01-02</a></li>
<li><a href="#the-problem">The Problem</a></li>
<li><a href="#research">Research</a></li>
<li><a href="#experiment-1">Experiment 1</a></li>
<li><a href="#experiment-2">Experiment 2</a></li>
<li><a href="#experiment-3">Experiment 3</a></li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#links">Links</a></li>
</ul></nav> </header><h2 id="editors-note-2020-01-02">Editors Note 2020-01-02</h2>
<p>The notes for this episode have been reformatted, particularly the long-form notes. This was done to make them more readable. Also, the original Git repository has been changed from <em>Gitorious</em> to <em>GitLab</em>.</p>
<p>In 2019 an iCalendar file was placed on the HPR server at <a href="http://hackerpublicradio.org/HPR_Community_News_schedule.ics">http://hackerpublicradio.org/HPR_Community_News_schedule.ics</a> which you can use in your own calendar application. The file contains the recording times of 12 months of Community News shows and is updated monthly.</p>
<h2 id="the-problem">The Problem</h2>
<p>Back in 2012 Ken Fallon tried to use Google Calendar to set up an event for the recording of the monthly <em>Community News</em> shows on HPR. He wanted to set these on the Saturday before the first Monday of the month. Surprisingly he didnt find a way to do this and ended up deleting the attempt.</p>
<p>I looked at the calendaring application I use: <em>Thunderbird</em> with the <em>Lightning</em> calendar plugin, to see if I could manage it. I also couldnt find a way.</p>
<p>This episode documents my journey to find a way to make the calendar entries we need.</p>
<h2 id="research">Research</h2>
<p>I was aware that calendars like Google Calendar and many more use iCalendar to represent and communicate events, and so I thought I would try and find out more. I have often wondered how iCalendar calendaring works, so I grabbed a copy of <strong>RFC 5545</strong> and absorbed enough to get a vague idea of how it defines recurrent entries. If youd like to read this yourself have a look at <a class="uri" href="http://www.ietf.org/rfc/rfc5545.txt">http://www.ietf.org/rfc/rfc5545.txt</a></p>
<p>There are two primary methods of defining recurrent events within iCalendar: <strong>RRULE</strong> and <strong>RDATE</strong>. The RRULE property is the more powerful of the two and the more complex. The description of RRULE is long and involved, but in the context of this problem I could see how to define the first Monday of every month:</p><pre><code>RRULE:FREQ=MONTHLY;BYDAY=1MO</code></pre><ul>
<li><strong><code>FREQ=MONTHLY</code></strong> simply means the rule repeats every month.</li>
<li><strong><code>BYDAY=1MO</code></strong> then means that every month the first Monday is selected.</li>
</ul>
<p>Most calendar applications are well able to deal with this sort of specification, and it seems to be the way in which most recurrent events are defined.</p>
<h2 id="experiment-1">Experiment 1</h2>
<p>However, this is not what we want. We need the Saturday before the first Monday, but the iCalendar syntax doesnt have any obvious way of subtracting 2 days to get the Saturday before, especially when it could be in the previous month.</p>
<p>The definition of the <strong>BYDAY</strong> rule part specifies a comma separated list of days of the week (MO, TU, WE, TH, FR, SA, SU). As we have seen these weekday specifications can also be preceded by a digit as in 1MO.</p>
<p>There is also a rule part <strong>BYSETPOS</strong> which modifies the BYDAY rule part. It is followed by a comma separated list of values which corresponds to the nth occurrence within the set of events specified by the rule.</p>
<p>This led me to believe that I could make a rule as follows:</p><pre><code>RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1</code></pre><ul>
<li><strong><code>FREQ=MONTHLY</code></strong> as before means the rule repeats every month.</li>
<li><strong><code>BYDAY=SA,SU,1MO</code></strong> then means that every month the weekend before the first Monday is selected.</li>
<li><strong><code>BYSETPOS=1</code></strong> means to select the first first day of the group, the Saturday</li>
</ul>
<p>I was rather surprised to find that this actually worked, but soon discovered that it has a fatal flaw. If the three days in BYDAY are all in the same month it works fine, but if either the Saturday or Sunday are in the previous month it cant backtrack far enough and drops the event on the wrong day.</p>
<p>Even if this worked, I suspect many calendar applications couldnt define it anyway. <em>Thunderbird+Lightning</em> cannot for certain. The user interface is just not able to specify this amount of detail.</p>
<p>The following is the full iCalendar entry that I plugged into <em>Thunderbird</em>:</p><pre><code>BEGIN:VCALENDAR
PRODID:MyCal
VERSION:2.0
BEGIN:VEVENT
DTSTART:20130629T190000Z
DTEND:20130629T210000Z
LOCATION:mumble.openspeak.cc port: 64747
RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1
SUMMARY:HPR Community News
END:VEVENT
END:VCALENDAR</code></pre><h2 id="experiment-2">Experiment 2</h2>
<p>However, I discovered there is an alternative way through the <strong>RDATE</strong> specification. With it you can define a number of events by pre-computing them. I was able to build a test calendar containing the next twelve <em>Community News</em> events (theres naturally a plug-in for Vim which recognises the syntax!), load it into <em>Thunderbird</em> and make it send out invitations.</p>
<p>In true Hacker style I wrote a Perl <a href="make_meeting.html">script</a><a class="footnote-ref" href="#fn1" id="fnref1"><sup>1</sup></a> to generate the necessary <strong>RDATE</strong> dates. The script uses the Perl module <em>Date::Calc</em> to perform date calculations and <em>Data::ICal</em> to generate iCalendar data.</p>
<p>Running the script in the following way:</p><pre><code>./make_meeting &gt; experiment2.ics</code></pre><p>generates a file containing 12 appointments that can be loaded into Thunderbird (and presumably any other iCalendar-based calendar).</p><pre><code>BEGIN:VCALENDAR
VERSION:2.0
PRODID:Data::ICal 0.20
X-WR-CALNAME:Hacker Public Radio
X-WR-TIMEZONE:Europe/London
BEGIN:VEVENT
DESCRIPTION:This is a test\, building an iCalendar file and loading it into
Thunderbird.\n-----------------------------------------\nMumble settings\
nServer Name: Anything you like\nServer Address: mumble.openspeak.cc \nPor
t: 64747\nName: Your name or alias is fine\n\nDon&#39;t have mumble\, setup in
structions can be found on our wiki -\nhttp://linuxbasix.com/tiki-index.ph
p?page=Linux+Basix+Mumble\n
DTEND:20130803T210000Z
DTSTART:20130803T190000Z
LOCATION:mumble.openspeak.cc port: 64747
RDATE;VALUE=DATE-TIME:20130803T190000Z
RDATE;VALUE=DATE-TIME:20130831T190000Z
RDATE;VALUE=DATE-TIME:20131005T190000Z
RDATE;VALUE=DATE-TIME:20131102T190000Z
RDATE;VALUE=DATE-TIME:20131130T190000Z
RDATE;VALUE=DATE-TIME:20140104T190000Z
RDATE;VALUE=DATE-TIME:20140201T190000Z
RDATE;VALUE=DATE-TIME:20140301T190000Z
RDATE;VALUE=DATE-TIME:20140405T190000Z
RDATE;VALUE=DATE-TIME:20140503T190000Z
RDATE;VALUE=DATE-TIME:20140531T190000Z
RDATE;VALUE=DATE-TIME:20140705T190000Z
SUMMARY:HPR Community News
END:VEVENT
END:VCALENDAR</code></pre><p>Thunderbirds event dialog will not let you edit the sub-events, just delete them, but the idea works, albeit in a rather clunky way.</p>
<p>I dont have access to many other calendaring systems, except for Korganizer. It sees the multiple dates as multiple discrete events rather than a single recurring event.</p>
<h2 id="experiment-3">Experiment 3</h2>
<p>Other calendaring systems that do not use iCalendar can handle this problem more effectively. For many years I have used a tool called <em>pcal</em> (<a class="uri" href="http://pcal.sourceforge.net/">http://pcal.sourceforge.net/</a>) that generates PostScript calendars which I print and hang on the wall. It can reliably specify the Saturday before the first Monday of each month with the expression:</p><pre><code>Saturday before first Monday in all HPR Community News (19:00 - 21:00)</code></pre><p>Another tool which can do this is <em>Remind</em> (<a class="uri" href="http://www.roaringpenguin.com/products/remind">http://www.roaringpenguin.com/products/remind</a>, <a class="uri" href="http://www.linuxjournal.com/article/3529">http://www.linuxjournal.com/article/3529</a>). With this the following expression achieves the required result:</p><pre><code>REM Mon 1 --2 AT 19:00 MSG HPR Community News (19:00 - 21:00)</code></pre><p>Remind comes with a tool which can generate iCalendar data, called <em>rem2ics</em>. It expects output from the <em>remind</em> command from which it generates data. The following example generates 12 meetings from the above reminder which is in the file <em>.reminders</em>.</p><pre><code>remind -s12 .reminders | TZ=UTC rem2ics -do &gt;reminders.ics</code></pre><p>The result uses the RDATE specification as discussed in Experiment 2, with the same result.</p><pre><code>BEGIN:VCALENDAR
VERSION:2.0
PRODID:http://mark.atwood.name/code/rem2ics rem2ics 0.93
BEGIN:VEVENT
UID:rem2ics.50a24176.3f5d.1@localhost
SUMMARY:HPR Community News (19\:00 - 21\:00)
DTSTART;TZID=UTC:20121103T190000
RDATE;TZID=UTC:20121103T190000,20121201T190000,20130105T190000,20130202T
190000,20130302T190000,20130330T190000,20130504T190000,20130601T190000,2
0130629T190000,20130803T190000,20130831T190000,20131005T190000
DTSTAMP:20121113Z124750Z
COMMENT: generated by rem2ics 0.93\n http://mark.atwood.name/code/rem2ic
s\n data[1]=|2012/11/03 * * * 1140 7\:00pm HPR Community News (19\:00 -
21\:00)|
END:VEVENT
END:VCALENDAR</code></pre><h2 id="conclusion">Conclusion</h2>
<p>It seems that the iCalendar specification <strong>should</strong> be able to generate the appointments we need using the compact RRULE specification. However, in the (admittedly small) sample of calendaring applications checked this does not seem to have been implemented properly.</p>
<p>Other tools that do not use iCalendar have less difficulty representing such events but are not as widely adopted.</p>
<p>If anyone has any ideas about how this problem could be solved more effectively then please let me know!</p>
<h2 id="links">Links</h2>
<ul>
<li>Detailed show notes: <a href="full_shownotes.html">full_shownotes.html</a></li>
<li>My <em>make_meeting</em> script for download - <a class="uri" href="https://gitlab.com/davmo/hprmisc/blob/master/make_meeting">https://gitlab.com/davmo/hprmisc/blob/master/make_meeting</a></li>
<li>Wikipedia iCalendar entry - <a class="uri" href="http://en.wikipedia.org/wiki/ICalendar">http://en.wikipedia.org/wiki/ICalendar</a></li>
<li>iCalendar RFC - <a class="uri" href="http://www.ietf.org/rfc/rfc5545.txt">http://www.ietf.org/rfc/rfc5545.txt</a></li>
<li>pcal PostScript calendar generator - <a class="uri" href="http://pcal.sourceforge.net/">http://pcal.sourceforge.net/</a></li>
<li>Remind - <a class="uri" href="http://www.roaringpenguin.com/products/remind">http://www.roaringpenguin.com/products/remind</a>, <a class="uri" href="http://www.linuxjournal.com/article/3529">http://www.linuxjournal.com/article/3529</a></li>
</ul><section class="footnotes"><hr />
<ol>
<li id="fn1">
<p>Note, this is a viewable HTML version of the script, but a downloadable version exists in a Git repository. See the <b>Links</b> section for details.<a class="footnote-back" href="#fnref1"></a></p>
</li>
</ol></section> </article> </main></div>
</body>
</html>

249
eps/hpr1286/make_meeting.html Executable file
View File

@@ -0,0 +1,249 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- Generated by perltidy on Mon Jul 1 13:37:55 2013 -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>make_meeting</title>
<style type="text/css">
<!--
/* default style sheet generated by perltidy */
body {background: #FFFFFF; color: #000000}
pre { color: #000000;
background: #FFFFFF;
font-family: courier;
}
.c { color: #228B22;} /* comment */
.cm { color: #000000;} /* comma */
.co { color: #000000;} /* colon */
.h { color: #CD5555; font-weight:bold;} /* here-doc-target */
.hh { color: #CD5555; font-style:italic;} /* here-doc-text */
.i { color: #00688B;} /* identifier */
.j { color: #CD5555; font-weight:bold;} /* label */
.k { color: #8B008B; font-weight:bold;} /* keyword */
.m { color: #FF0000; font-weight:bold;} /* subroutine */
.n { color: #B452CD;} /* numeric */
.p { color: #000000;} /* paren */
.pd { color: #228B22; font-style:italic;} /* pod-text */
.pu { color: #000000;} /* punctuation */
.q { color: #CD5555;} /* quote */
.s { color: #000000;} /* structure */
.sc { color: #000000;} /* semicolon */
.v { color: #B452CD;} /* v-string */
.w { color: #000000;} /* bareword */
-->
</style>
</head>
<body>
<a name="-top-"></a>
<h1>make_meeting</h1>
<!-- BEGIN CODE INDEX --><a name="code-index"></a>
<ul>
<li><a href="#make_date-">package main</a>
<ul>
<li><a href="#make_date-">make_date</a></li>
</ul>
</li>
<li><a href="#EOF-">EOF</a></li>
</ul>
<!-- END CODE INDEX -->
<hr />
<!-- contents of filename: make_meeting -->
<pre>
#!/usr/bin/perl
<span class="c">#===============================================================================</span>
<span class="c">#</span>
<span class="c"># FILE: make_meeting</span>
<span class="c">#</span>
<span class="c"># USAGE: ./make_meeting</span>
<span class="c">#</span>
<span class="c"># DESCRIPTION: Makes a recurrent iCalendar meeting to be loaded into</span>
<span class="c"># a calendar. This is apparently necessary when the &#39;RRULE&#39;</span>
<span class="c"># recurrence description is not adequate.</span>
<span class="c">#</span>
<span class="c"># OPTIONS: None</span>
<span class="c"># REQUIREMENTS: Needs modules Data::ICal and Date::Calc</span>
<span class="c"># BUGS: ---</span>
<span class="c"># NOTES: Distributed with the HPR episode &quot;iCalendar Hacking&quot;</span>
<span class="c"># AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com</span>
<span class="c"># LICENCE: Copyright (c) year 2012, Dave Morriss</span>
<span class="c"># VERSION: 1.0</span>
<span class="c"># CREATED: 13/10/2012 15:34:01</span>
<span class="c"># REVISION: 16/11/2012 16:04:37</span>
<span class="c">#</span>
<span class="c">#===============================================================================</span>
<span class="c"># This program is free software: you can redistribute it and/or modify it</span>
<span class="c"># under the terms of the GNU General Public License as published by the Free</span>
<span class="c"># Software Foundation, either version 3 of the License, or (at your option)</span>
<span class="c"># any later version.</span>
<span class="c"># </span>
<span class="c"># This program is distributed in the hope that it will be useful, but WITHOUT</span>
<span class="c"># ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or</span>
<span class="c"># FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for</span>
<span class="c"># more details.</span>
<span class="c"># </span>
<span class="c"># You should have received a copy of the GNU General Public License along with</span>
<span class="c"># this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</span>
<span class="c">#===============================================================================</span>
<span class="k">use</span> <span class="n">5.010</span><span class="sc">;</span>
<span class="k">use</span> <span class="w">strict</span><span class="sc">;</span>
<span class="k">use</span> <span class="w">warnings</span><span class="sc">;</span>
<span class="k">use</span> <span class="w">Data::ICal</span><span class="sc">;</span>
<span class="k">use</span> <span class="w">Data::ICal::Entry::Event</span><span class="sc">;</span>
<span class="k">use</span> <span class="w">Date::Calc</span> <span class="q">qw{</span>
<span class="q">Today Day_of_Year Add_Delta_YMD Nth_Weekday_of_Month_Year</span>
<span class="q">}</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Date and time values</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">@today</span> = <span class="i">Today</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">@startdate</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">@rdate</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">$monday</span> = <span class="n">1</span><span class="sc">;</span> <span class="c"># Day of week number 1-7, Monday-Sunday</span>
<span class="k">my</span> <span class="i">@starttime</span> = <span class="s">(</span> <span class="n">19</span><span class="cm">,</span> <span class="n">00</span><span class="cm">,</span> <span class="n">00</span> <span class="s">)</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">@endtime</span> = <span class="s">(</span> <span class="n">21</span><span class="cm">,</span> <span class="n">00</span><span class="cm">,</span> <span class="n">00</span> <span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Format of an ISO UTC datetime</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">$fmt</span> = <span class="q">&quot;%02d%02d%02dT%02d%02d%02dZ&quot;</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Constants for the event</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">$calname</span> = <span class="q">&#39;Hacker Public Radio&#39;</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">$timezone</span> = <span class="q">&#39;Europe/London&#39;</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">$location</span> = <span class="q">&#39;mumble.openspeak.cc port: 64747&#39;</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">$summary</span> = <span class="q">&#39;HPR Community News&#39;</span><span class="sc">;</span>
<span class="k">my</span> <span class="i">$description</span> = <span class="h">&lt;&lt;ENDDESC</span><span class="sc">;</span>
<span class="hh">This is a test, building an iCalendar file and loading it into Thunderbird.</span>
<span class="hh">-----------------------------------------</span>
<span class="hh">Mumble settings</span>
<span class="hh">Server Name: Anything you like</span>
<span class="hh">Server Address: mumble.openspeak.cc </span>
<span class="hh">Port: 64747</span>
<span class="hh">Name: Your name or alias is fine</span>
<span class="hh">Don&#39;t have mumble, setup instructions can be found on our wiki -</span>
<span class="hh">http://linuxbasix.com/tiki-index.php?page=Linux+Basix+Mumble</span>
<span class="h">ENDDESC</span>
<span class="c">#</span>
<span class="c"># Compute the next meeting date from now</span>
<span class="c">#</span>
<span class="i">@startdate</span> = <span class="i">make_date</span><span class="s">(</span> \<span class="i">@today</span><span class="cm">,</span> <span class="i">$monday</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">-2</span> <span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Create the calendar object</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">$calendar</span> = <span class="w">Data::ICal</span><span class="w">-&gt;new</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Some calendar properties</span>
<span class="c">#</span>
<span class="i">$calendar</span><span class="i">-&gt;add_properties</span><span class="s">(</span>
<span class="q">&#39;X-WR-CALNAME&#39;</span> <span class="cm">=&gt;</span> <span class="i">$calname</span><span class="cm">,</span>
<span class="q">&#39;X-WR-TIMEZONE&#39;</span> <span class="cm">=&gt;</span> <span class="i">$timezone</span><span class="cm">,</span>
<span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Create the event object</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">$vevent</span> = <span class="w">Data::ICal::Entry::Event</span><span class="w">-&gt;new</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Add some event properties</span>
<span class="c">#</span>
<span class="i">$vevent</span><span class="i">-&gt;add_properties</span><span class="s">(</span>
<span class="w">summary</span> <span class="cm">=&gt;</span> <span class="i">$summary</span><span class="cm">,</span>
<span class="w">location</span> <span class="cm">=&gt;</span> <span class="i">$location</span><span class="cm">,</span>
<span class="w">description</span> <span class="cm">=&gt;</span> <span class="i">$description</span><span class="cm">,</span>
<span class="w">dtstart</span> <span class="cm">=&gt;</span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@startdate</span><span class="cm">,</span> <span class="i">@starttime</span> <span class="s">)</span><span class="cm">,</span>
<span class="w">dtend</span> <span class="cm">=&gt;</span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@startdate</span><span class="cm">,</span> <span class="i">@endtime</span> <span class="s">)</span><span class="cm">,</span>
<span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Add 12 recurring dates. (Note that this generates 12 RDATE entries rather</span>
<span class="c"># than 1 entry with multiple dates; this is because this module doesn&#39;t seem</span>
<span class="c"># to have the ability to generated the concatenated entry. The two modes of</span>
<span class="c"># expressing the repeated dates seem to be equivalent.)</span>
<span class="c">#</span>
<span class="k">for</span> <span class="k">my</span> <span class="i">$i</span> <span class="s">(</span> <span class="n">1</span> .. <span class="n">12</span> <span class="s">)</span> <span class="s">{</span>
<span class="i">@today</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@today</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">0</span> <span class="s">)</span><span class="sc">;</span>
<span class="i">@rdate</span> = <span class="i">make_date</span><span class="s">(</span> \<span class="i">@today</span><span class="cm">,</span> <span class="i">$monday</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">-2</span> <span class="s">)</span><span class="sc">;</span>
<span class="i">$vevent</span><span class="i">-&gt;add_property</span><span class="s">(</span> <span class="w">rdate</span> <span class="cm">=&gt;</span>
<span class="s">[</span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@rdate</span><span class="cm">,</span> <span class="i">@starttime</span> <span class="s">)</span><span class="cm">,</span> <span class="s">{</span> <span class="w">value</span> <span class="cm">=&gt;</span> <span class="q">&#39;DATE-TIME&#39;</span> <span class="s">}</span> <span class="s">]</span><span class="cm">,</span>
<span class="s">)</span><span class="sc">;</span>
<span class="s">}</span>
<span class="c">#</span>
<span class="c"># Add the event into the calendar</span>
<span class="c">#</span>
<span class="i">$calendar</span><span class="i">-&gt;add_entry</span><span class="s">(</span><span class="i">$vevent</span><span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Print the result</span>
<span class="c">#</span>
<span class="k">print</span> <span class="i">$calendar</span><span class="i">-&gt;as_string</span><span class="sc">;</span>
<span class="k">exit</span><span class="sc">;</span>
<span class="c">#=== FUNCTION ================================================================</span>
<span class="c"># NAME: make_date</span>
<span class="c"># PURPOSE: Make the event date for recurrence</span>
<span class="c"># PARAMETERS: $refdate</span>
<span class="c"># An arrayref to the reference date array (usually</span>
<span class="c"># today&#39;s date)</span>
<span class="c"># $dow Day of week for the event date (1-7, 1=Monday)</span>
<span class="c"># $n The nth day of the week in the given month required</span>
<span class="c"># for the event date</span>
<span class="c"># $offset Number of days to offset the computed date</span>
<span class="c"># RETURNS: The resulting date as a list for Date::Calc</span>
<span class="c"># DESCRIPTION: We want to compute a simple date with an offset, such as</span>
<span class="c"># &quot;the Saturday before the first Monday of the month&quot;. We do</span>
<span class="c"># this my computing a pre-offset date (first Monday of month)</span>
<span class="c"># then apply the offset (Saturday before).</span>
<span class="c"># THROWS: No exceptions</span>
<span class="c"># COMMENTS: TODO Needs more testing to be considered truly universal</span>
<span class="c"># SEE ALSO:</span>
<span class="c">#===============================================================================</span>
<a name="make_date-"></a><span class="k">sub </span><span class="m">make_date</span> <span class="s">{</span>
<span class="k">my</span> <span class="s">(</span> <span class="i">$refdate</span><span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span><span class="cm">,</span> <span class="i">$offset</span> <span class="s">)</span> = <span class="i">@_</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Compute the required date: the nth day of week in this year and month</span>
<span class="c">#</span>
<span class="k">my</span> <span class="i">@date</span> = <span class="i">Nth_Weekday_of_Month_Year</span><span class="s">(</span> <span class="i">@$refdate</span>[ <span class="n">0</span><span class="cm">,</span> <span class="n">1</span> ]<span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span> <span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># If the computed date is before the base date advance a month</span>
<span class="c">#</span>
<span class="k">if</span> <span class="s">(</span> <span class="i">Day_of_Year</span><span class="s">(</span><span class="i">@date</span><span class="s">)</span> &lt;= <span class="i">Day_of_Year</span><span class="s">(</span><span class="i">@$refdate</span><span class="s">)</span> <span class="s">)</span> <span class="s">{</span>
<span class="c">#</span>
<span class="c"># Add a month and recompute</span>
<span class="c">#</span>
<span class="i">@date</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@date</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">0</span> <span class="s">)</span><span class="sc">;</span>
<span class="i">@date</span> = <span class="i">Nth_Weekday_of_Month_Year</span><span class="s">(</span> <span class="i">@date</span>[ <span class="n">0</span><span class="cm">,</span> <span class="n">1</span> ]<span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span> <span class="s">)</span><span class="sc">;</span>
<span class="s">}</span>
<span class="c">#</span>
<span class="c"># Apply the day offset</span>
<span class="c">#</span>
<span class="i">@date</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@date</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="i">$offset</span> <span class="s">)</span><span class="sc">;</span>
<span class="c">#</span>
<span class="c"># Return a list</span>
<span class="c">#</span>
<span class="k">return</span> <span class="s">(</span><span class="i">@date</span><span class="s">)</span><span class="sc">;</span>
<span class="s">}</span>
<span class="c"># vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker</span>
<a name="EOF-"></a></pre>
</body>
</html>