├── README.md ├── okreminders.ics ├── remmy.pl └── testreminders /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | remmy.pl - A script to convert reminder type files into iCalendar format 4 | 5 | # SYNOPSIS 6 | 7 | remmy.pl < reminderfile > icsfile 8 | 9 | # DESCRIPTION 10 | 11 | This single Perl script convert reminders as used by Dianne Skoll's powerful command line program **remind** to the iCalendar format (**.ics**). iCalendar is a widely accepted standard for calendar entries and can be read by popular application like Microsoft's Outlook, Apple's iCal or Google Calendar. *remmy.pl* parses *remind*s input file and tries to adopt many of the sophisticated rules for reminders as implemented in *remind*. 12 | 13 | *remmy.pl* solely relies on Perl, an installation of *remind* is not necessary. 14 | 15 | **Note** that this script is neither a replacement nor an improvement of *remind*, in fact, it handles only a very limited subset of *remind* specification for parsing reminder-type input files. 16 | 17 | While *remmy* is build with reference to RFC2445, full compliance cannot be ensured. 18 | 19 | # OPTIONS 20 | 21 | not in use 22 | 23 | # DEPENDENCIES 24 | 25 | - Perl UUID::Tiny 26 | - Perl DateTime 27 | 28 | # COMPATIBILITIES AND LIMITATIONS 29 | 30 | The man page of *remind* lists several examples using the REM command. 31 | Currently, **remmy** supports examples 1 to 18 as given in subsection 32 | INTERPRETATION OF DATE SPECIFICATIONS. The output of **remmy** has been 33 | successfully tested with MS Outlook 2003 and Apple iCal 4.0. 34 | 35 | **remmy** supports the local time. It is planned to switch to UTC 36 | calendar specification in a future version. 37 | 38 | The rule "weekday and day" present is limited in a way that there are no 39 | "jumps" to the following month if the next weekday is in the next month. 40 | This leads to the functionality that the specified day clusters the rule 41 | into the 4 weeks of a month, meaning the rule will always be the 1st, 42 | 2nd, 3rd or 4th weekday of every month. 43 | 44 | # TESTS AND BUGS 45 | 46 | Inspired from *remind*'s man page, some test cases have been defined in 47 | the file `testreminders`. The output from *remmy* is in 48 | `okreminders.ics`. Each calender event should work as specified and 49 | expected. It was not tested with all calender application. 50 | 51 | A quick test for the output: 52 | 53 | diff -I ORGANIZER -I UID -I DTSTAMP okreminders.ics <(cat testreminders|./remmy.pl) 54 | 55 | Please report bugs to the author or improve the code by yourself and 56 | share the changes. You are encouraged to test **remmy**s output with 57 | your preferred calendar application. 58 | 59 | # HOMEPAGE 60 | 61 | [https://github.com/smartmic/remmy.git](https://github.com/smartmic/remmy.git) 62 | 63 | # LICENSE 64 | 65 | Copyright (c) 2012 by Martin Michel 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining a 68 | copy of this software and associated documentation files (the 69 | "Software"), to deal in the Software without restriction, including 70 | without limitation the rights to use, copy, modify, merge, publish, 71 | distribute, sublicense, and/or sell copies of the Software, and to 72 | permit persons to whom the Software is furnished to do so, subject to 73 | the following conditions: 74 | 75 | The above copyright notice and this permission notice shall be included 76 | in all copies or substantial portions of the Software. 77 | 78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 79 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 81 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 82 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 83 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 84 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | 86 | # REFERENCES 87 | 88 | The *remind* manual. 89 | 90 | The *RFC2445* specification. 91 | 92 | Mark Atwood has written a similar programm which uses the output of the 93 | *remind -s* command. See [rem2ics](https://metacpan.org/pod/rem2ics). 94 | 95 | # SEE ALSO 96 | 97 | [remind](https://dianne.skoll.ca/projects/remind/), [rem2ics](https://metacpan.org/pod/rem2ics) 98 | -------------------------------------------------------------------------------- /okreminders.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//acme//NONSGML remmy v0.1 4 | METHOD:PUBLISH 5 | 6 | BEGIN:VEVENT 7 | ORGANIZER:MAILTO:martin@crunchbang 8 | UID:1235B374-1CF7-11E9-B641-E8307DF01BFF 9 | LOCATION:see description 10 | CLASS:PUBLIC 11 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=1 12 | DTSTART:20190101 13 | DTEND:20190101 14 | SUMMARY: First of every month 15 | DESCRIPTION: First of every month 16 | DTSTAMP:20190120T220509 17 | END:VEVENT 18 | 19 | BEGIN:VEVENT 20 | ORGANIZER:MAILTO:martin@crunchbang 21 | UID:12366EF2-1CF7-11E9-B641-D285718F8057 22 | LOCATION:see description 23 | CLASS:PUBLIC 24 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=31 25 | DTSTART:20190131 26 | DTEND:20190131 27 | SUMMARY: 31th of every month with 31 days 28 | DESCRIPTION: 31th of every month with 31 days 29 | DTSTAMP:20190120T220509 30 | END:VEVENT 31 | 32 | BEGIN:VEVENT 33 | ORGANIZER:MAILTO:martin@crunchbang 34 | UID:12370FC4-1CF7-11E9-B641-8CE9A36CD8EC 35 | LOCATION:see description 36 | CLASS:PUBLIC 37 | RRULE:FREQ=DAILY;INTERVAL=1;BYMONTH=2 38 | DTSTART:20190201 39 | DTEND:20190201 40 | SUMMARY: Every day in February 41 | DESCRIPTION: Every day in February 42 | DTSTAMP:20190120T220509 43 | END:VEVENT 44 | 45 | BEGIN:VEVENT 46 | ORGANIZER:MAILTO:martin@crunchbang 47 | UID:1237AE85-1CF7-11E9-B641-B17007E05BB6 48 | LOCATION:see description 49 | CLASS:PUBLIC 50 | RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTHDAY=29;BYMONTH=2 51 | DTSTART:20200229 52 | DTEND:20200229 53 | SUMMARY: Every 29th of February 54 | DESCRIPTION: Every 29th of February 55 | DTSTAMP:20190120T220509 56 | END:VEVENT 57 | 58 | BEGIN:VEVENT 59 | ORGANIZER:MAILTO:martin@crunchbang 60 | UID:12385F2A-1CF7-11E9-B641-83ACE55036DB 61 | LOCATION:see description 62 | CLASS:PUBLIC 63 | RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTHDAY=6;BYMONTH=1 64 | DTSTART:20190106 65 | DTEND:20190106 66 | SUMMARY: Every 6th of January 67 | DESCRIPTION: Every 6th of January 68 | DTSTAMP:20190120T220509 69 | END:VEVENT 70 | 71 | BEGIN:VEVENT 72 | ORGANIZER:MAILTO:martin@crunchbang 73 | UID:12392E13-1CF7-11E9-B641-CADB5E3EF7ED 74 | LOCATION:see description 75 | CLASS:PUBLIC 76 | RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20191231 77 | DTSTART:20190101 78 | DTEND:20190101 79 | SUMMARY: Every day in 2019 80 | DESCRIPTION: Every day in 2019 81 | DTSTAMP:20190120T220509 82 | END:VEVENT 83 | 84 | BEGIN:VEVENT 85 | ORGANIZER:MAILTO:martin@crunchbang 86 | UID:123A02D9-1CF7-11E9-B641-8ABE34EDD009 87 | LOCATION:see description 88 | CLASS:PUBLIC 89 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=1;UNTIL=20191231 90 | DTSTART:20190101 91 | DTEND:20190101 92 | SUMMARY: 1st of every month in 2019 93 | DESCRIPTION: 1st of every month in 2019 94 | DTSTAMP:20190120T220509 95 | END:VEVENT 96 | 97 | BEGIN:VEVENT 98 | ORGANIZER:MAILTO:martin@crunchbang 99 | UID:123ACCAF-1CF7-11E9-B641-A6BA32A2D721 100 | LOCATION:see description 101 | CLASS:PUBLIC 102 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=23;UNTIL=20191231 103 | DTSTART:20190123 104 | DTEND:20190123 105 | SUMMARY: 23rd of every month in 2019 106 | DESCRIPTION: 23rd of every month in 2019 107 | DTSTAMP:20190120T220509 108 | END:VEVENT 109 | 110 | BEGIN:VEVENT 111 | ORGANIZER:MAILTO:martin@crunchbang 112 | UID:123B954F-1CF7-11E9-B641-AEF8A719A354 113 | LOCATION:see description 114 | CLASS:PUBLIC 115 | RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20200229 116 | DTSTART:20200201 117 | DTEND:20200201 118 | SUMMARY: Every day in February 2020 119 | DESCRIPTION: Every day in February 2020 120 | DTSTAMP:20190120T220509 121 | END:VEVENT 122 | 123 | BEGIN:VEVENT 124 | ORGANIZER:MAILTO:martin@crunchbang 125 | UID:123C61B0-1CF7-11E9-B641-FF26AC33D65A 126 | LOCATION:see description 127 | CLASS:PUBLIC 128 | RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20190930 129 | DTSTART:20190901 130 | DTEND:20190901 131 | SUMMARY: Every day in September 2019 132 | DESCRIPTION: Every day in September 2019 133 | DTSTAMP:20190120T220509 134 | END:VEVENT 135 | 136 | BEGIN:VEVENT 137 | ORGANIZER:MAILTO:martin@crunchbang 138 | UID:123D341C-1CF7-11E9-B641-9CEFDAE6E29C 139 | LOCATION:see description 140 | CLASS:PUBLIC 141 | DTSTART:20200703 142 | DTEND:20200703 143 | SUMMARY: 3rd July 2020 144 | DESCRIPTION: 3rd July 2020 145 | DTSTAMP:20190120T220509 146 | END:VEVENT 147 | 148 | BEGIN:VEVENT 149 | ORGANIZER:MAILTO:martin@crunchbang 150 | UID:123DFB6A-1CF7-11E9-B641-B021F67ADF46 151 | LOCATION:see description 152 | CLASS:PUBLIC 153 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=SA 154 | DTSTART:20190119 155 | DTEND:20190119 156 | SUMMARY: Every Saturday 157 | DESCRIPTION: Every Saturday 158 | DTSTAMP:20190120T220510 159 | END:VEVENT 160 | 161 | BEGIN:VEVENT 162 | ORGANIZER:MAILTO:martin@crunchbang 163 | UID:123F57F8-1CF7-11E9-B641-ED4968749BE7 164 | LOCATION:see description 165 | CLASS:PUBLIC 166 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE 167 | DTSTART:20190114 168 | DTEND:20190114 169 | SUMMARY: Every Monday, Tuesday, Wednesday 170 | DESCRIPTION: Every Monday, Tuesday, Wednesday 171 | DTSTAMP:20190120T220510 172 | END:VEVENT 173 | 174 | BEGIN:VEVENT 175 | ORGANIZER:MAILTO:martin@crunchbang 176 | UID:1240B2EA-1CF7-11E9-B641-F75EFAABB715 177 | LOCATION:see description 178 | CLASS:PUBLIC 179 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=+3WE,+3TH 180 | DTSTART:20190115 181 | DTEND:20190115 182 | SUMMARY: First Wednesday and Thursday after the 15th of every Month 183 | DESCRIPTION: First Wednesday and Thursday after the 15th of every Month 184 | DTSTAMP:20190120T220510 185 | END:VEVENT 186 | 187 | BEGIN:VEVENT 188 | ORGANIZER:MAILTO:martin@crunchbang 189 | UID:12418030-1CF7-11E9-B641-D14694ACA2F9 190 | LOCATION:see description 191 | CLASS:PUBLIC 192 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO;BYMONTH=3 193 | DTSTART:20190304 194 | DTEND:20190304 195 | SUMMARY: Every Monday in March 196 | DESCRIPTION: Every Monday in March 197 | DTSTAMP:20190120T220510 198 | END:VEVENT 199 | 200 | BEGIN:VEVENT 201 | ORGANIZER:MAILTO:martin@crunchbang 202 | UID:1242A9D9-1CF7-11E9-B641-84B882931DE9 203 | LOCATION:see description 204 | CLASS:PUBLIC 205 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=6 206 | DTSTART:20190603 207 | DTEND:20190603 208 | SUMMARY: Every Monday, Wednesday and Friday in June 209 | DESCRIPTION: Every Monday, Wednesday and Friday in June 210 | DTSTAMP:20190120T220510 211 | END:VEVENT 212 | 213 | BEGIN:VEVENT 214 | ORGANIZER:MAILTO:martin@crunchbang 215 | UID:1243C36A-1CF7-11E9-B641-FCAD42DD94DF 216 | LOCATION:see description 217 | CLASS:PUBLIC 218 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=SU,TU;BYMONTH=8;UNTIL=20181231 219 | DTSTART:20180107 220 | DTEND:20180107 221 | SUMMARY: Every Tuesday, Sunday in August 2018 222 | DESCRIPTION: Every Tuesday, Sunday in August 2018 223 | DTSTAMP:20190120T220510 224 | END:VEVENT 225 | 226 | BEGIN:VEVENT 227 | ORGANIZER:MAILTO:martin@crunchbang 228 | UID:124516DD-1CF7-11E9-B641-E41314529429 229 | LOCATION:see description 230 | CLASS:PUBLIC 231 | RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=SU,SA;UNTIL=20211231 232 | DTSTART:20210110 233 | DTEND:20210110 234 | SUMMARY: Every Saturday and Sunday in 2018 235 | DESCRIPTION: Every Saturday and Sunday in 2018 236 | DTSTAMP:20190120T220510 237 | END:VEVENT 238 | 239 | BEGIN:VEVENT 240 | ORGANIZER:MAILTO:martin@crunchbang 241 | UID:12464AC2-1CF7-11E9-B641-AB98C0D7FB1F 242 | LOCATION:see description 243 | CLASS:PUBLIC 244 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=+3MO;UNTIL=20201231 245 | DTSTART:20200115 246 | DTEND:20200115 247 | SUMMARY: 1st Monday after 15th of every month in 2020 248 | DESCRIPTION: 1st Monday after 15th of every month in 2020 249 | DTSTAMP:20190120T220510 250 | END:VEVENT 251 | 252 | BEGIN:VEVENT 253 | ORGANIZER:MAILTO:martin@crunchbang 254 | UID:124741DE-1CF7-11E9-B641-FDDED8439414 255 | LOCATION:see description 256 | CLASS:PUBLIC 257 | RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=-1 258 | DTSTART:20190101T200000 259 | DTEND:20190101T200000 260 | SUMMARY: Last day of every month at 8 pm 261 | DESCRIPTION: Last day of every month at 8 pm 262 | DTSTAMP:20190120T220510 263 | END:VEVENT 264 | 265 | END:VCALENDAR 266 | -------------------------------------------------------------------------------- /remmy.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | =head1 NAME 4 | 5 | remmy.pl - A script to convert reminder type files into iCalendar format 6 | 7 | =head1 SYNOPSIS 8 | 9 | remmy.pl < reminderfile > icsfile 10 | 11 | =head1 DESCRIPTION 12 | 13 | This single Perl script convert reminders as used by Dianne Skoll's powerful command line program B to the iCalendar format (B<.ics>). iCalendar is a widely accepted standard for calendar entries and can be read by popular application like Microsoft's Outlook, Apple's iCal or Google Calendar. I parses Is input file and tries to adopt many of the sophisticated rules for reminders as implemented in I. 14 | 15 | While B is build upon the specification of the RFC2445, full compliance cannot be ensured. 16 | 17 | =head1 OPTIONS 18 | 19 | not in use 20 | 21 | =head1 DEPENDENCIES 22 | 23 | Perl UUID::Tiny E<10> E<8>Perl DateTime 24 | 25 | 26 | =head1 COMPATIBILITIES AND LIMITATIONS 27 | 28 | The man page of I lists several examples using the REM command. Currently, B supports examples 1 to 18 as given in subsection INTERPRETATION OF DATE SPECIFICATIONS. The output of B has been successfully tested with MS Outlook 2003 and Apple iCal 4.0. 29 | 30 | B supports the local time. It is planned to switch to UTC calendar specification in a future version. 31 | 32 | The rule "weekday and day" present is limited in a way that there are no "jumps" to the following month if the next weekday is in the next month. This leads to the functionality that the specified day clusters the rule into the 4 weeks of a month, meaning the rule will always be the 1st, 2nd, 3rd or 4th weekday of every month. 33 | 34 | =head1 BUGS 35 | 36 | Please report bugs to the author or improve the code by yourself and share the 37 | changes. You are encouraged to test Bs output with your preferred calendar application. 38 | 39 | =head1 HOMEPAGE 40 | 41 | L 42 | 43 | =head1 LICENSE 44 | 45 | Copyright (c) 2012 by Martin Michel 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 52 | 53 | =head1 REFERENCES 54 | 55 | The I manual. 56 | 57 | The I specification. 58 | 59 | Mark Atwood has written a similar programm which uses the output of the I command. See L. 60 | 61 | =head1 SEE ALSO 62 | 63 | L, L 64 | 65 | =cut 66 | 67 | use strict; 68 | use warnings; 69 | use UUID::Tiny ':std'; 70 | use DateTime; 71 | use POSIX; 72 | 73 | my $corpname = "acme"; 74 | my $scriptname = "remmy"; 75 | my $scriptversion = 0.1; 76 | 77 | my @userinput = ; 78 | my $lineno=1; 79 | 80 | &create_start; 81 | 82 | foreach my $line (@userinput) { 83 | # Checking the input for valid Reminders 84 | unless (($line =~ /^REM\s.+\sMSG/) 85 | || ($line =~ /^REM\s.+\s[AT|UNTIL|DURATION]\s.+\sMSG/) 86 | || ($line =~ /^REM\s.+\sAT\s.+\s[UNTIL|DURATION]\s.+\sMSG/)) 87 | { 88 | #print "\nNo valid reminder.\n"; 89 | } 90 | # --- END Checking the input for valid Reminders 91 | 92 | # Extracting the information 93 | else { 94 | &static_hdr; 95 | my @values = split(/REM|AT|DURATION|UNTIL|MSG/, $userinput[$lineno-1]); 96 | my $fields = scalar (@values); 97 | 98 | # Field 1 after "REM" will always be parsed for date information... 99 | my ($y,@mo,$dy,$delta); # month variable can be variable also 100 | my @rfcdays = qw(SU MO TU WE TH FR SA); 101 | my ($sec,$min,$hour,$today,$month,$yr19,@rest) = localtime(time); 102 | my $day; 103 | #my $utc_off = (int `date +%z`)/100; 104 | my @weekdays = qw(Sun Mon Tue Wed Thu Fri Sat); 105 | my $dint = 0; 106 | my $addweek; 107 | my $backflag = ""; 108 | 109 | foreach my $val ($values[1] =~ m/ (\d+)/g) { 110 | if ($val ~~ [1..31]) { 111 | $dy = $val; 112 | if ($values[1] =~ m/$val\s\+(\d+)/) { 113 | $delta = $1; 114 | print "BEGIN:VALARM\n"; 115 | $delta = (int $1)*1440; 116 | printf qq{TRIGGER:-PT%dM\n},$delta; 117 | print "ACTION:DISPLAY\n" 118 | . "DESCRIPTION:Reminder\n" 119 | . "END:VALARM\n"; 120 | } 121 | if ($values[1] =~ m/$val\s-(\d+)/) { 122 | $backflag = "-"; 123 | } 124 | } 125 | if ($val ~~ [1990..2075]) { 126 | $y = $val; 127 | my $yrule_1 = "BYDAY=" . join(',',@rfcdays); 128 | #print "$yrule_1\n"; 129 | } 130 | } 131 | 132 | 133 | my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); 134 | my $mint = 0; 135 | my @morule_1 = "BYMONTH="; 136 | foreach my $month (@months) { 137 | $mint++; 138 | if ($values[1] =~ /$month/i) { 139 | push (@mo,$mint); 140 | push(@morule_1,"$mo[0],"); 141 | #last; 142 | } 143 | } 144 | 145 | #chop(@morule_1); 146 | my @wrule_1 = "BYDAY="; 147 | #my $today = (localtime(time ))[6]; 148 | my ($fdflag,$nyflag,$numwk,$dy_org); 149 | my @wkpos; 150 | foreach my $day (@weekdays) { 151 | if ($values[1] =~ /$day/i) { 152 | if (($dint - $today) < 0) {$addweek = 7;} 153 | else {$addweek = 0;} 154 | 155 | my $daydiff = $dint - $today + $addweek; 156 | my @nextday = localtime(time + (86400 * $daydiff)); 157 | if (!defined $fdflag && !defined $dy && !defined $mo[0]) { 158 | if (!defined $y) { 159 | $y = $nextday[5]; 160 | $y = $y+1900; 161 | $nyflag = 1; 162 | } 163 | ($dy,$mo[0]) = @nextday[3..4]; 164 | $mo[0]++; 165 | $fdflag = 1; 166 | } 167 | #last; 168 | if (defined $dy && !defined $fdflag) { 169 | #$numwk = ($dy,4)[$dy > 4]; 170 | if ($dy <= 7) { $numwk = 1 }; 171 | if ($dy > 7 and $dy <= 14) { $numwk = 2; } 172 | if ($dy > 14 and $dy <= 21) { $numwk = 3; } 173 | if ($dy > 21) { $numwk = 4; } 174 | push(@wrule_1,"+$numwk"); 175 | } 176 | push (@wkpos, $dint); 177 | push(@wrule_1,"$rfcdays[$dint],"); 178 | } 179 | $dint++; 180 | } 181 | 182 | if (!defined $dy && !defined $addweek) { 183 | my $dyrule = "RRULE:FREQ=DAILY;INTERVAL=1;"; 184 | if (!defined $nyflag && defined $y) { 185 | my $lastmonth; 186 | if (!defined $mo[0]) {$mo[0]=1;$lastmonth=12;} else {$lastmonth = $mo[0];} 187 | $dyrule = $dyrule.sprintf("UNTIL=%04d%02d%02d", 188 | $y,$lastmonth,&monthdays($y,$lastmonth)); 189 | } 190 | elsif (defined $mo[0]) { 191 | $dyrule = $dyrule.join("",@morule_1); 192 | chop($dyrule); 193 | } 194 | print $dyrule."\n"; 195 | } 196 | 197 | 198 | if (!defined $mo[0] && defined $dy) { 199 | my $morule = "RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=$backflag$dy;"; 200 | if (defined $addweek) { 201 | my @a = split(/BYMONTHDAY/,$morule); 202 | $morule = $a[0].join("",@wrule_1); 203 | } 204 | if (!defined $nyflag && defined $y) { 205 | chop($morule); 206 | $morule = $morule.";UNTIL=$y"."1231;"; 207 | } 208 | chop($morule); 209 | print $morule."\n"; 210 | } 211 | elsif (defined $dy && !defined $y && !defined $addweek) { 212 | my $yrule = "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTHDAY=$dy;"; 213 | $yrule = $yrule.join("",@morule_1); 214 | chop($yrule); 215 | print $yrule."\n"; 216 | } 217 | 218 | my $wkrule; 219 | if (defined $addweek && defined $mo[0]) { 220 | $wkrule = "RRULE:FREQ=WEEKLY;WKST=SU;"; 221 | $wkrule = $wkrule.join("",@wrule_1); 222 | if (defined $mo[0] && !defined $fdflag) { 223 | chop($wkrule); 224 | $wkrule = $wkrule.";".join("",@morule_1); 225 | } 226 | if (!defined $nyflag && defined $y) { 227 | chop($wkrule); 228 | $wkrule = $wkrule.";UNTIL=$y"."1231;"; 229 | $mo[0] = 1; 230 | } 231 | chop($wkrule); 232 | print $wkrule."\n"; 233 | } 234 | 235 | unless (defined $dy) {$dy=1;} 236 | 237 | unless (defined $y) { 238 | $y = $yr19+1900; 239 | if (defined $mo[0] && $mo[0]<$month+1){$y++;} 240 | if (defined $mo[0] && $mo[0]==2 && $dy==29) { 241 | while (&leapyear ($y) == 365) {$y++;} 242 | } 243 | } 244 | 245 | unless (defined $mo[0]) { 246 | $mo[0] = $month+1; 247 | while (&monthdays($y,$mo[0]) < $dy) {$mo[0]++;} 248 | } 249 | 250 | if (defined $wkrule) { 251 | 252 | # DateTime uses different counting than RFC/remmy for Sunday: 253 | if ($wkpos[0] == 0) {$wkpos[0] = 7;} 254 | 255 | ($y,$mo[0],$dy) = &finddate($y,$mo[0],$dy,$wkpos[0]); 256 | } 257 | 258 | # --- END Field 1 after "REM" will always be parsed for date information... 259 | 260 | 261 | my $dtstart = sprintf("DTSTART:%04d%02d%02d",$y,$mo[0],$dy); 262 | my $dtend = sprintf("DTEND:%04d%02d%02d",$y,$mo[0],$dy); 263 | 264 | # Field after AT is scanned for time specifications... 265 | my $h = 0;my $m = 0;my $s = 0; 266 | 267 | unless ($userinput[$lineno-1] =~ /AT/) {} 268 | else { 269 | my @mytime = ($line =~ /AT\s+(\d+):(\d+)\s+?\+?(\d+)?/); 270 | $h = $mytime[0]; 271 | $m = $mytime[1]; 272 | $s = 0; 273 | if (defined $mytime[2]) { 274 | print "BEGIN:VALARM\n"; 275 | printf qq{TRIGGER:-PT%dM\n},$mytime[2]; 276 | print "ACTION:DISPLAY\n" 277 | . "DESCRIPTION:Reminder\n" 278 | . "END:VALARM\n"; 279 | 280 | } 281 | $dtstart = $dtstart.sprintf("T%02d%02d%02d",$h,$m,$s); 282 | $dtend = $dtend.sprintf("T%02d%02d%02d",$h,$m,$s); 283 | } 284 | 285 | # --- END Field after AT is scanned for time specifications... 286 | 287 | print $dtstart."\n"; 288 | print $dtend."\n"; 289 | #printf qq{DTSTART:%04d%02d%02dT%02d%02d%02d\n}, $y,$mo[0],$dy,$h,$m,$s; 290 | # Note that year and month has been corrected in the expression above... 291 | #printf qq{DTEND:%04d%02d%02dT%02d%02d%02d\n}, $y,$mo[0],$dy,$h,$m,$s; 292 | 293 | # Last field after MSG is scanned for messages 294 | 295 | unless ($line =~ /MSG/) {print "SUMMARY:\n";} 296 | else { 297 | if ($values[-1] =~ /%"(.+?)%"/) {print"SUMMARY:$1\n";} 298 | } 299 | 300 | my $content = $values[-1]; 301 | $content =~ s/%.//g; 302 | print "SUMMARY:$content"; 303 | print "DESCRIPTION:$content"; 304 | # --- END Last field after MSG is scanned for messages 305 | 306 | # Timestamp for entry 307 | printf qq{DTSTAMP:%04d%02d%02dT%02d%02d%02d\n}, $yr19+=1900,$month+=1,$today,$hour,$min,$sec; 308 | # --- END Timestamp for entry 309 | 310 | print "END:VEVENT\n"; 311 | 312 | } 313 | # --- END Extracting the information 314 | 315 | $lineno++; 316 | } 317 | 318 | &create_end; 319 | 320 | # --- START Functions 321 | # 322 | sub create_start { 323 | print "BEGIN:VCALENDAR\n" 324 | . "VERSION:2.0\n" 325 | . "PRODID:-//$corpname//NONSGML $scriptname v$scriptversion\n" 326 | . "METHOD:PUBLISH\n"; 327 | } 328 | 329 | sub create_end { 330 | print "\nEND:VCALENDAR\n"; 331 | } 332 | 333 | sub static_hdr { 334 | print "\nBEGIN:VEVENT\n"; 335 | 336 | # Organizer entry 337 | my $idname = `id -nu`; 338 | my $hostname = `uname -n`; 339 | my $address = join('@',$idname,$hostname); 340 | $address =~ s/\n//g; 341 | print "ORGANIZER:MAILTO:$address\n"; 342 | # --- END Organizer entry 343 | 344 | # UUID Generation 345 | my $uuid = uc(create_uuid_as_string(UUID_V1)); 346 | #$uuid =~ s/\n//g; 347 | print "UID:$uuid\n"; 348 | # --- END UUID Generation 349 | 350 | print "LOCATION:see description\n" 351 | . "CLASS:PUBLIC\n"; 352 | } 353 | 354 | sub leapyear { 355 | my $tage; 356 | if ( $_[0] % 400 == 0 || ( $_[0] % 4 == 0 && $_[0] % 100 != 0 ) ) {$tage=366;} 357 | else {$tage=365;} 358 | return $tage; 359 | } 360 | 361 | sub monthdays { 362 | my ($tage,$mtage); 363 | $tage = &leapyear($_[0]); 364 | if ( $_[1] == 2 ) { 365 | if ( $tage == 365 ) {$mtage=28;} else {$mtage=29;} 366 | } 367 | elsif ( $_[1] == 4 || $_[1] == 6 || $_[1] == 9 || $_[1] == 11 ) {$mtage=30;} 368 | else {$mtage=31;} 369 | return $mtage; 370 | } 371 | 372 | sub finddate { 373 | my $dt = DateTime->new( 374 | year => $_[0], 375 | month => $_[1], 376 | day => $_[2], 377 | ); 378 | my $dur = DateTime::Duration->new( 379 | days => 1, 380 | ); 381 | 382 | while ($dt->day_of_week != $_[3]) { 383 | $dt = $dt + $dur; 384 | } 385 | return ($dt->year, $dt->month, $dt->day); 386 | } 387 | 388 | 389 | # --- END Functions 390 | -------------------------------------------------------------------------------- /testreminders: -------------------------------------------------------------------------------- 1 | REM 1 MSG First of every month 2 | REM 31 MSG 31th of every month with 31 days 3 | REM Feb MSG Every day in February 4 | REM 29 Feb MSG Every 29th of February 5 | REM Jan 6 MSG Every 6th of January 6 | REM 2019 MSG Every day in 2019 7 | REM 1 2019 MSG 1st of every month in 2019 8 | REM 2019 23 MSG 23rd of every month in 2019 9 | REM Feb 2020 MSG Every day in February 2020 10 | REM Sep 2019 MSG Every day in September 2019 11 | REM 2020 Jul 3 MSG 3rd July 2020 12 | REM Sat MSG Every Saturday 13 | REM Mon Tue Wed MSG Every Monday, Tuesday, Wednesday 14 | REM Wed Thu 15 MSG First Wednesday and Thursday after the 15th of every Month 15 | REM Mon March MSG Every Monday in March 16 | REM Mon Wed Fri Jun MSG Every Monday, Wednesday and Friday in June 17 | REM Tue Sun Aug 2018 MSG Every Tuesday, Sunday in August 2018 18 | REM Sat Sun 2021 MSG Every Saturday and Sunday in 2018 19 | REM Mon 15 2020 MSG 1st Monday after 15th of every month in 2020 20 | REM 1 -1 AT 20:00 MSG Last day of every month at 8 pm 21 | --------------------------------------------------------------------------------