#!/usr/bin/perl -w ### Outlook/Appointment.pm v0.3.1 ### ### Copyright 2002 Leigh L. Klotz Jr. ### 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: ### ### The above copyright notice and this permission notice shall be ### included in all copies or substantial portions of the Software. ### ### 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. package Outlook::Appointment; use Mail::Internet; use Mail::Address; use Date::Parse; use Date::Format; # Remove these subjects strings on messages # If you choose to use subject-based filtering (i.e., all messages go to you, but ones with a certain subject # get added as appointments), you will need to add the prefix you use to this regular expression. For example, if you want all messages starting with # Subject "APPT: " and Subject "APPT: Fwd: " to be handled as appointments, # change it to $subjectprefix="(APPT: )|(APPT: F[wW][dD]?: )" $subjectprefix = "(\\[?F[wW][dD]?: )"; sub new($) { my $type = shift; my %params = @_; my $self = {}; $self->{'error'} = ""; bless $self, $type; return $self; } sub parse($$) { my $self = shift; my $mail = shift; my $subject = $mail->get('Subject'); chomp($subject) if ($subject); my @lines = @{$mail->body}; # Remove an outer layer of brackets on subjects, # changing "[Fwd: Meeting]" -> "Fwd: Meeting" $subject =~ s/^\[([^\]]+)\]$/$1/; # Remove the various forwarding prefixes $subject =~ s/^$subjectprefix//; # Check for "Update: " and "Canceled: " which are special prefixes $self->{'isUpdate'} = 0; $self->{'isCanceled'} = 0; if ($subject =~ s/^Updated: //) { $self->{'isUpdate'} = 1; } elsif ($subject =~ s/^Canceled: //) { $self->{'isCanceled'} = 1; } my $title = $subject; my $month_start; my $day_start; my $year_start; my $hour_start; my $min_start; my $hour_dur; my $min_dur; my $notes= ""; my $icon_id = 0; my $found = 0; my $ignore; my $warnings = ""; $self->{'From'} = $mail->get("From"); chomp($self->{'From'}); for my $line (@lines) { # *~*~*~*~*~*~*~*~*~* seems to terminate the appointment text, if present. last if ($line =~ /^(\*~)+$/); # ((Where)|(Location): .*$ if ($line =~ m/^[> \t]*(Where|Location):[ \t]*(.*)/) { $notes .= $2; next; } # When: Wednesday, October 23, 2002 4:30 PM-5:00 PM (GMT-08:00) Pacific # or the VCALENDAR hack: # DESCRIPTION:When: Wednesday\, October 23\, 2002 4:30 PM-5:00 PM (GMT-08:00) Pacific # TODO: This doesn't handle appointments that cross days. if ( ($line =~ m/^[> \t]*When:[ \t]*([^-]+)-([^(-]+) \(GMT([+-])([0-9]+):([0-9]+)\)/) || ($line =~ m/^DESCRIPTION:When: ([^-]+)-([^(-]+) \(GMT([+-])([0-9]+):([0-9]+)\)/)) { my $start = $1; my $end = $2; my $tz = "$3$4$5"; # e.g. "-0500", which is what Date::Parse wants my $sec; $start =~ s/\\,/,/g; # remove commas in case of VCALENDAR version $end =~ s/\\,/,/g; # remove commas in case of VCALENDAR version print STDERR "Parsing $start\n"; # What a crock -- Date::Parse::strptime returns the timezone as a separate value in seconds. # What I want is for it to parse a date/time with a timezone in it and return it to me in the current time zone. # Fortunately, Date::Parse::str2time works to convert to local time in seconds, so I convert to that to get local time # and then use Date::Format::time2str to convert back to a string, and then use Date::Parse::strptime to parse that string! # Is there a better way to do this? ($sec,$min_start,$hour_start,$day_start,$month_start,$year_start)=strptime(time2str("%c", str2time($start))); print STDERR "Parsed as $month_start/$day_start/$year_start $hour_start:$min_start\n"; if (defined($year_start) && defined($month_start) && defined($day_start) && defined($hour_start) && defined($min_start)) { $month_start++; # 0-based # crock of a parser returns 2002=102, 2003=03... if ($year_start < 1900) { if ($year_start < 100) { $year_start += 2000; } else { $year_start += 1900; } } print STDERR "start: ", $hour_start,":", $min_start, " ",$year_start,"/",$month_start,"/",$day_start, "\n"; ($sec,$min_end,$hour_end)=strptime("$month_start/$day_start/$year_start $end"); if ($min_end ne "" && $hour_end ne "") { print STDERR "end: ", $hour_end,":",$min_end, " ", $tz, "\n"; $hour_dur = $hour_end - $hour_start; $min_dur = $min_end - $min_start; if ($min_dur < 0) { $min_dur += 60; $hour_dur -= 1; } elsif ($min_dur >= 60) { $min_dur -= 60; $hour_dur += 1; } } else { $min_dur = $hour_dur = 0; } $found=1; next; } else { $warnings .= "Line $start did not parse\n"; } next; } } if (! $found) { $self->{'error'} = "Appropriate 'When:' line not found"; # report warnings only if not found $self->{'error'} .= "\n" . $warnings if ($warnings); return $self; } $self->{'title'} = $title; $self->{'month_start'} = $month_start; $self->{'day_start'} = $day_start; $self->{'year_start'} = $year_start; $self->{'hour_start'} = $hour_start; $self->{'min_start'} = $min_start; $self->{'hour_dur'} = $hour_dur; $self->{'min_dur'} = $min_dur; $self->{'notes'} = $notes; $self->{'icon_id'} = $icon_id; return $self; } sub as_short_string($) { my $self = shift; return sprintf( "appointment %s %02d/%02d/%d %02d:%02d (%02d:%02d)", $self->{'From'}, $self->{'month_start'} , $self->{'day_start'} , $self->{'year_start'} , $self->{'hour_start'} , $self->{'min_start'} , $self->{'hour_dur'} , $self->{'min_dur'}); } sub error($) { my $self = shift; return $self->{'error'}; } sub isCanceled($) { my $self = shift; return $self->{'isCanceled'}; } sub isUpdate($) { my $self = shift; return $self->{'isUpdate'}; } # Used for local database lookup to map existing appointments to other data sub uniqueKey($) { my $self = shift; return sprintf("%02d/%02d/%d|%02d:%02d|(%02d:%02d)|%s|%s", # do not include from in unique key because of forwarding $self->{'month_start'}, $self->{'day_start'}, $self->{'year_start'}, $self->{'hour_start'}, $self->{'min_start'}, $self->{'hour_dur'}, $self->{'min_dur'}, $self->{'title'}, $self->{'notes'}); } 1;