<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>IMAP &#8211; Johnny Morano&#039;s Tech Articles</title>
	<atom:link href="https://jmorano.moretrix.com/tag/imap/feed/" rel="self" type="application/rss+xml" />
	<link>https://jmorano.moretrix.com</link>
	<description>Ramblings of an old-fashioned space cowboy</description>
	<lastBuildDate>Sat, 09 Apr 2022 07:18:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>https://jmorano.moretrix.com/wp-content/uploads/2022/04/cropped-jmorano_emblem-32x32.png</url>
	<title>IMAP &#8211; Johnny Morano&#039;s Tech Articles</title>
	<link>https://jmorano.moretrix.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Perl: Archive E-Mails in an IMAP Folder</title>
		<link>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/</link>
					<comments>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 05 Nov 2015 09:33:47 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[IMAP]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1199</guid>

					<description><![CDATA[IMAP folders are really because you can have your e-mails synchronized on multiple devices, without losing e-mails across&#8230;]]></description>
										<content:encoded><![CDATA[
<p>IMAP folders are really because you can have your e-mails synchronized on multiple devices, without losing e-mails across your devices when retrieving your new e-mails. IMAP folders actually also aren&#8217;t that cool because e-mails are usually never deleted or even archived. Having millions of e-mails can make some e-mail readers on certain devices really slow.</p>



<p>The below script is an example how to clean and archive e-mails. The script was written in Perl and tested on a Courier IMAP server. Remember the Perl devise: there are million ways to write Perl scripts.</p>



<p>The following modules were used:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use Net::IMAP::Simple::SSL;
use Email::Simple;
use Getopt::Long qw/:config bundling/;
use DateTime;
use YAML qw/LoadFile/;
use Log::Log4perl;
use Pod::Usage;
use Data::Dumper;</pre>



<p>The configuration file is written in YAML and the logging of the script is handled by Log4Perl.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">log4perl.logger.shihai= DEBUG, shihaiLogfile
log4perl.logger.hostlistdb= DEBUG, shihaiLogfile

log4perl.appender.shihaiLogfile          = Log::Log4perl::Appender::File
log4perl.appender.shihaiLogfile.filename = /var/tmp/shihai.log
log4perl.appender.shihaiLogfile.layout   = Log::Log4perl::Layout::PatternLayout
log4perl.appender.shihaiLogfile.layout.ConversionPattern = %d %p [%x][%r millis][%c][%F{1}:%L][%M] %m%n</pre>



<p>The configuration file contains the login credentials and the threshold values:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">imap:
    host: 'mail.example.com'
    user: 'bob@example.com'
    pass: 'myultrasecretpassword'

threshold:
    archive:
        years: 3
    delete:
        years: 8
</pre>



<p>The threshold values are in fact the same parameters that can be used in the DateTime method &#8216;subtract()&#8217;;</p>



<p>The main loop of the program will:</p>



<ul class="wp-block-list"><li>Connect to the IMAP server</li><li>Retrieve all IMAP folders and loop through them</li><li>Loop through all messages in each mailbox</li></ul>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Connect to the IMAP server
my $imap = connect_imap( $config-&amp;gt;{imap} );

# get all mailboxes and loop through them
my @mailboxes = get_mailboxes($imap);
foreach my $mailbox_name (@mailboxes){

    # Skip all Archive boxes 
    next if $mailbox_name =~ /Archive/;

    # select the mailbox and get the number of messages
    my $mb = $imap-&amp;gt;select($mailbox_name);
    unless(defined $mb){
        $logger-&amp;gt;error("Mailbox [$mailbox_name] doesn't exist: ", $imap-&amp;gt;errstr());
        next;
    }

    $logger-&amp;gt;info("Scanning $mailbox_name");

    # loop through the messages
    foreach my $i (1 .. $mb){
        my ($from, $subject, $date, $year) = get_mail_header($imap, $i);

        if(defined $date){
            if($date &amp;lt; $delete_date ){
                delete_mail($imap, $i);
            }
            elsif($date &amp;lt; $archive_date){
                $logger-&amp;gt;info("Archiving [$i][$from][$subject][$date]");
                my $archive_box = get_archive_box($imap, $mailbox_name);
                move_mail($imap, $i, $archive_box)
            }
        }
    }

    $imap-&amp;gt;expunge_mailbox($mailbox_name);
}

$imap-&amp;gt;quit;
</pre>



<p>Let&#8217;s go through the different subroutines called in the mainloop.</p>



<p><strong>connect_imap()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub connect_imap {
    my ($cfg) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my $imap = Net::IMAP::Simple::SSL-&amp;gt;new($cfg-&amp;gt;{host})
        or $logger-&amp;gt;logdie("Unable to connect to IMAP server: $Net::IMAP::Simple::errstr");
    $logger-&amp;gt;info("Connected to IMAP host $cfg-&amp;gt;{host}");

    unless( $imap-&amp;gt;login($cfg-&amp;gt;{user}, $cfg-&amp;gt;{pass}) ){
        $logger-&amp;gt;logdie("Login failed: ", $imap-&amp;gt;errstr);

    }
    $logger-&amp;gt;info("Logged to IMAP host $cfg-&amp;gt;{host} as user '$cfg-&amp;gt;{user}'");

    return $imap;
}
</pre>



<p>The parameters expected in the &#8216;$cfg&#8217; hash are:</p>



<ul class="wp-block-list"><li>host</li><li>user</li><li>pass</li></ul>



<p><strong>get_mailboxes()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_mailboxes {
    my ($imap) = @_;
    my @mailboxes = $imap-&amp;gt;mailboxes;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    return @mailboxes;
}
</pre>



<p>Ok, I admit, I shouldn&#8217;t have created an extra subroutine for it&#8230; but i was kind of in a flow!</p>



<p><strong>get_mail_header()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_mail_header {
    my ($imap, $i) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my $header = $imap-&amp;gt;top($i);
    unless( $header ){
        $logger-&amp;gt;error("No header found for message $i in ", $imap-&amp;gt;current_box);
    }
    
    my $email = Email::Simple-&amp;gt;new(join '', @{ $header });
    unless( $email ){
        $logger-&amp;gt;error("No Email::Simple object, skipping...");
        return
    }

    my ($subject) = $email-&amp;gt;header('Subject');
    my ($date)    = $email-&amp;gt;header('Date');
    my ($from)    = $email-&amp;gt;header('From');

    # $logger-&amp;gt;debug("Got e-mail [$from] [$subject] [$date]");

    unless(defined $date){
        $logger-&amp;gt;error("No date found: ", $email-&amp;gt;header_obj-&amp;gt;as_string);
        delete_mail($imap, $i);
        return;
    }
    
    my($junk, $day, $month, $year) = ( $date =~ m/(...,\s+)?([0-9]{1,2})\s+(...)\s+(\d{4})/ );

    my $date_obj;
    if(defined $year &amp;amp;&amp;amp; defined $month &amp;amp;&amp;amp; defined $day){
        $date_obj = DateTime-&amp;gt;new(
            year  =&amp;gt; $year,
            month =&amp;gt; $months{$month},
            day   =&amp;gt; $day,
        );
    }    

    return ($from, $subject, $date_obj, $year, $month, $day);
}
</pre>



<p>This subroutine takes the &#8216;$imap&#8217; object and the message number as input parameters. It will then retrieve the mail header and convert it to an &#8216;Email::Simple&#8217; object. I&#8217;ve chosen this module so I can easily extract header fields.<br />If no &#8216;Date:&#8217; field was found in the e-mail, then the tool will just delete the email. I don&#8217;t like e-mails with wrong or missing headers <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (they&#8217;re usually spam anyway).</p>



<p><strong>delete_mail()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub delete_mail {
    my($imap, $i) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    if( $imap-&amp;gt;delete($i) ){
        $logger-&amp;gt;info("Deleted message number $i from ", $imap-&amp;gt;current_box);
    }
}
</pre>



<p>Pretty straightforward.</p>



<p><strong>get_archive_box()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_archive_box{
    my($imap, $mailbox_name) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my ($archive_box) = ($mailbox_name);
    $archive_box =~ s/INBOX/INBOX.Archive/;

    if( not grep /^$archive_box$/, @mailboxes) {
        create_mailbox($imap, $archive_box);
        subscribe($imap, $archive_box);
    }

    return $archive_box;       
}
</pre>



<p>This subroutine will assemble the archive mailbox name. It will then check if the mailbox already exists and otherwise create it and subscribe to it.</p>



<p><strong>create_mailbox()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub create_mailbox {
    my($imap, $mb) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    $imap-&amp;gt;create_mailbox($mb) or $logger-&amp;gt;logdie("Mailbox creation '$mb' failed: ", $imap-&amp;gt;errstr());
    $logger-&amp;gt;info("Created mailbox $mb");
}
</pre>



<p>It will basically just create the mailbox and log about it.</p>



<p><strong>subscribe()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub subscribe {
    my($imap, $box) =@_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    $imap-&amp;gt;folder_subscribe($box);
    $logger-&amp;gt;info("Subscribed to mailbox $box");
}
</pre>



<p><strong>move_mail()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub move_mail {
    my($imap, $i, $new_box) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    if( $imap-&amp;gt;copy($i, $new_box) ){
        $logger-&amp;gt;info("Copied message number [$i] from ", $imap-&amp;gt;current_box ," to [$new_box]");
        delete_mail($imap, $i);
    }
}
</pre>



<p>Moving an e-mail consists of copying it first to the new mailbox and then afterwards removing it from the old mailbox.</p>



<p>And that&#8217;s basically it!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>A Timesheet to Google Calendar Importer (in Perl)</title>
		<link>https://jmorano.moretrix.com/2010/12/a-timesheet-to-google-calendar-importer-in-perl/</link>
					<comments>https://jmorano.moretrix.com/2010/12/a-timesheet-to-google-calendar-importer-in-perl/#respond</comments>
		
		<dc:creator><![CDATA[insaniac]]></dc:creator>
		<pubDate>Thu, 23 Dec 2010 15:31:41 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[Google Calendar]]></category>
		<category><![CDATA[IMAP]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=455</guid>

					<description><![CDATA[Somebody once told me that a good engineer / scientist is extremely lazy. He HAS to be extremely&#8230;]]></description>
										<content:encoded><![CDATA[<p>Somebody once told me that a good engineer / scientist is extremely lazy. He HAS to be extremely otherwise he isn&#8217;t good. And I like being lazy (read: I love to automate repetitive tasks).</p>
<p>Working as a contractor, we have to fill in timesheets which have to be approved and so. Now, I also like to view in my agenda when I&#8217;ve worked and how long. This information is important at the end of the month when creating an invoice (which state the number of days I&#8217;ve worked).<br />
<span id="more-455"></span><br />
The project management tool at my current job, sends out emails when a certain period has been approved by my manager. The email body looks somewhat like this:</p>
<pre>
Name:        Morano, Johnny
Status:      Approved
Week Ending: 25-Dec-2010
Manager:     Name, Manager
                                 HOURS                                 
=======================================================================
Ln|Work Request   |Act |  Sun|  Mon|  Tue|  Wed|  Thu|  Fri|  Sat|Total
-----------------------------------------------------------------------
1 |xxxxxx         |COD | 0.00| 8.00| 8.00| 8.00| 8.00| 0.00| 0.00|32.00
-----------------------------------------------------------------------
TOTAL                  | 0.00| 8.00| 8.00| 8.00| 8.00| 0.00| 0.00|32.00
=======================================================================

                                DETAILS                                
=======================================================================
Ln|Work Request Name        |Activity       |Site  |Asset    |SubAsset 
-----------------------------------------------------------------------
1 |xxxxxxxxxx               |Code & UnitTest|XXXXXX|XXXXX    |N/A      
=======================================================================

                            TIMESHEET NOTES                            
=======================================================================
 
=======================================================================
</pre>
<p>These emails all get sorted in my Courier IMAP email server into a specific mailbox, e.g. &#8216;INBOX.Moretrix.Timesheets&#8217;.</p>
<p>My agenda is currently held at Google Calendar, which is an excellent tool for creating private and public agenda&#8217;s.</p>
<p>The programming language I&#8217;ve used is, of course, Perl. In Perl, there are easy to use modules like <code>Net::IMAP::Simple</code> and <code>Net::Google::Calendar</code> which are going to help us building this automated process.</p>
<p>We will need to gather some information, before we start writing our code. The following is required:<br />
&#8211; IMAP user login account<br />
&#8211; IMAP password<br />
&#8211; IMAP mailbox<br />
&#8211; GMail user account<br />
&#8211; GMail password<br />
&#8211; Google Calendar name (you need to create a separate calendar in Google Calendar, I&#8217;ve called mine &#8216;Business&#8217;</p>
<p>The header of script shows which modules to use:</p>
<pre class="brush:perl">
#!/usr/bin/perl
use strict; use warnings;

use Net::IMAP::Simple::SSL;
use Email::Simple;
use DateTime;
use Net::Google::Calendar;

my %months = (
    Jan => 1, May => 5, Sep => 9,
    Feb => 2, Jun => 6, Oct => 10,
    Mar => 3, Jul => 7, Nov => 11,
    Apr => 4, Aug => 8, Dec => 12,
);

my($imap_host, $imap_user, $imap_pass, $scan_box);
my ($gmail_username, $gmail_password, $gmail_calendar);
$imap_user      ||= 'IMAP username';
$imap_pass      ||= 'IMAP password';
$imap_host      ||= 'IMAP host';
$scan_box       ||= 'INBOX.Moretrix.Timesheets';
$gmail_username ||= 'GMail user@gmail.com';
$gmail_password ||= 'GMail password';
$gmail_calendar ||= 'Business';
</pre>
<p>This part is pretty basic and requires no explanation.</p>
<p>The following part will try to connect to the IMAP server and initialize a connection to the Google Calendar.</p>
<pre class="brush:perl">
my $imap = Net::IMAP::Simple::SSL->new($imap_host)
        or die "Unable to connect to IMAP server: $Net::IMAP::Simple::errstr n";
die( "Login failed: " . $imap->errstr . "n" ) unless $imap->login($imap_user, $imap_pass);

my $number_of_messages = $imap->select($scan_box);
if( not defined $number_of_messages ){
    error "failed to get number of messages for $scan_box: " . $imap->errstr();
    next
}

#
# Log in on Google Calendars
#
my $gcal = Net::Google::Calendar->new();
$gcal->login($gmail_username, $gmail_password);
info "Logged in to Google Calendars";

#
# Search my 'Business' calendar
#
for my $c ($gcal->get_calendars) {
    debug "Calendar title: ". $c->title;
    debug "Calendar ID   : ". $c->id;
    if($c->title eq $gmail_calendar){
        $gcal->set_calendar($c);
        debug "Found calendar $gmail_calendar...";
        debug $c->title." has ".scalar($gcal->get_events)." events";
        last;
    }
}
</pre>
<p>Still no rocket science, we use the modules like they are described in their perldoc pages, and finally we search for the calendar we want to edit in Google Calendars.</p>
<p>So at this point we have an IMAP connection and a connection to Google Calendars. Now we will loop through all the messages in the configured IMAP folder and start parsing the messages.<br />
We will only need the &#8216;TOTAL&#8217; lines with all the working hours and the line stating &#8216;Week Ending&#8217;. The latter is used to calcute the dates of the working hours.<br />
Since this IMAP folder also contains other emails than the timesheet emails, we&#8217;ll refine our search procedure by taking only those emails that match a certain subject.</p>
<pre class="brush:perl">
#
# Loop through the emails
#
foreach my $message ( 1 .. $number_of_messages ){
    my $header = $imap->top($message);
    (warning( "No header found: ", $imap->errstr() ), next) unless $header;

    my $email = Email::Simple->new(join '', @{ $header });
    (warning( "No Email::Simple object", $imap->errstr() ), next) unless $email;

    next unless $email->header('Subject') =~ /Approved Business Timesheet/;

    info sprintf("n[%03d] %-29s | %-56s", $message, $email->header('Date'), $email->header('Subject'));
</pre>
<p>At this point, we know that the mail message number (which is in <code>$message</code>) is an email that we need to parse.<br />
Now we will grab the information required from the body of the email and start the calculation of the dates.</p>
<pre class="brush:perl">
    # Grab data from email body
    my $body = $imap->get( $message ) or die $imap->errstr;
    $body = "$body";
    my($week_ending) = ($body =~ /Week Ending:([^n]*)/m);
    my($totals)      = ($body =~ /TOTAL([^n]*)/m);
    $week_ending              =~ s/s+//g;
    $totals                   =~ s/s+//g;

    # Create DateTime object based on the 'Week Ending' string in the body of the email
    my($day,$month,$year) = split /-/, $week_ending;
    my $date_obj  = DateTime->new(year => $year, month => $months{$month}, day => $day);
    $date_obj->set(hour => 0);
    $date_obj->set(minute => 0);
    $date_obj->set(second => 0);
    $date_obj->set_time_zone('Europe/Brussels');
    # subtract 5 days since Week Ending shows Saturday, but we start on Monday
    $date_obj->subtract(days => 5);
</pre>
<p>In the next part, we cleanup the <code>@totals</code> array, since we don&#8217;t need the days Sunday and Saturday, nor do we need the grand total displayed in the email.</p>
<pre class="brush:perl">
    #
    # Cleanup the @totals
    #
    my (@totals) = split /|/, $totals;
    # last value is grand total, we dont need that
    pop(@totals);
    # last value now is saturday, we dont need that
    pop(@totals);
    # first value is empty due to the leading | in $totals
    shift(@totals);
    # first value is sunday, we dont need that
    shift(@totals);
</pre>
<p>The comments in the code explain enough.<br />
Now that we have our totals (which are the values per day), we can loop through the days and create the appropriate calendar items:</p>
<pre class="brush:perl">
    # loop through the days of the week
    foreach my $t (@totals){

        # skip days with 0 working hours
        next unless $t > 0;

        # These values are to narrow down the search for Google Calendars events
        my $day_start  = DateTime->from_object(object => $date_obj);
        my $day_end    = DateTime->from_object(object => $date_obj);
        $day_end->add(days => 1);

        # Start and end time of the event we want to create/update
        my $start_date = DateTime->from_object(object => $date_obj);
        my $end_date   = DateTime->from_object(object => $date_obj);
        $start_date->add(hours => 9);
        $end_date->add(hours => 9 + $t);

        # Print some logging
        info 'From: '.$start_date->dmy. ' '.$start_date->hms. " | ".
             'Until: '.$end_date->dmy. ' '.$end_date->hms. "  => $t hours";

        # Create the author contact person
        my $author = Net::Google::Calendar::Person->new();
        $author->name('Johnny Morano');
        $author->email('johnny.morano@gmail.com');

        # Create Google Calendar entry object
        my $entry = Net::Google::Calendar::Entry->new();
        $entry->title('Approved Business Timesheet');
        $entry->content(
            "Worked $t hours. ".
            'From: ' .$start_date->dmy. ' '.$start_date->hms. " | ".
            'Until: '.$end_date->dmy  . ' '.$end_date->hms . 
            " Approved in email from " . $email->header('Date')
        );
        $entry->location('Leuven, Belgium');
        $entry->when($start_date, $end_date);
        $entry->author($author);

        my @cal_events = $gcal->get_events('start-min' => $day_start, 'start-max' => $day_end);
        my $found   = 0;
        foreach my $e (@cal_events) {
            if($e->title =~ /Approved Business Timesheet/){
                my $changes = 0;
                ($e->title($entry->title()), $changes++)            if $e->title    ne $entry->title;
                ($e->content($entry->content()), $changes++)        if $e->content  ne $entry->content;
                ($e->location($entry->location()), $changes++)      if $e->location ne $entry->location;
                ($e->when($entry->when()), $changes++)              if $e->when     ne $entry->when;
                ($e->author($entry->author()), $changes++)          if $e->author   ne $entry->author;

                if($changes){
                    $gcal->update_entry($e) if $changes;
                    info " --> UPDATED CALENDAR ENTRY with $changes changes";
                    $found++; last;
                }
            }
        }
        if(not $found){
            $gcal->add_entry($entry);
            info " --> ADDED CALENDAR ENTRY";
        }

        # go to next day
        $date_obj->add(days => 1);
    }

}
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2010/12/a-timesheet-to-google-calendar-importer-in-perl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
