Vlad
Vlad

Reputation: 978

Perl Data::ICal print event time as T000000Z instead of omitting it

I'm trying to generate an ICal feed using Data:ICal but some events are printed without a time. I've read that the time is not required if it's 000000 but Google Calendar does not handle those events without a time properly.

Here is an example script and output. I need the output to to be in the UTC timezone.

#!/usr/bin/perl -w
use strict;
use Date::ICal;
use Data::ICal;
use Data::ICal::Entry::Event;
use DateTime;
use Data::Dumper;

sub get_utc_offset($) {
    my ($orig_tz_str) = @_;

    # Using a set winter date to avoid problems with daylight savings time
    my $utc_compare_datetime = DateTime->new(
        year      => 2012,
        month     => 1,
        day       => 1,
        hour      => 1,
        minute    => 1,
        time_zone => 'UTC'
    );

    my $tz             = DateTime::TimeZone->new(name => $orig_tz_str);
    my $utc_offset     = $tz->offset_for_datetime($utc_compare_datetime);
    my $utc_offset_str = DateTime::TimeZone->offset_as_string($utc_offset);

    return $utc_offset_str;
}

sub add_ical_event($$$$$$) {
    my ($calendar, $start, $end, $summary, $description, $timezone) = @_;
    my $offset   = get_utc_offset($timezone);
    $description = 'none' if (!$description);

    my $event = Data::ICal::Entry::Event->new();
    $event->add_properties(
        summary     => $summary,
        description => $description,
        dtstart     => Date::ICal->new( ical  => $start, offset => $offset )->ical,
        dtend       => Date::ICal->new( ical  => $end,   offset => $offset )->ical,
        dtstamp     => Date::ICal->new( epoch => time                      )->ical
    );
    $calendar->add_entry($event);
}


# Tests
# ----------------------------------------------------------------------------

my $timezone = 'America/New_York';

my $calendar = Data::ICal->new();
$calendar->add_properties(
    method         => "PUBLISH",
    prodid         => "-//Test Cal//NONSGML Calendar//EN",
    'X-WR-CALNAME' => 'Test Cal'
);

my (%events) = (
    1 => {
        summary     => 'Test Shift Tool - Testing Shift',
        description => '',
        start       => '20130828T160000',
        end         => '20130828T190000',
        timezone    => $timezone
    },
    2 => {
        summary     => 'New Member Meeting',
        description => '',
        start       => '20130722T190000',
        end         => '20130722T210000',
        timezone    => $timezone
    },
    3 => {
        summary     => 'public',
        description => '',
        start       => '20130630T130000',
        end         => '20130630T140000',
        timezone    => $timezone
    }
);
foreach my $key (sort keys %events) {
    my $e = $events{$key};
    add_ical_event(
        $calendar,
        $e->{start},
        $e->{end},
        $e->{summary},
        $e->{description},
        $e->{timezone}
    );
}
print $calendar->as_string;

Notice that some events have start or end dates without a time. When I manually add T000000Z, those events are imported properly to Google Calendar. Any suggestions on how to force all events to have a time?

BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
PRODID:-//Digital Cheetah//NONSGML Calendar//EN
X-WR-CALNAME:Digital Cheetah
BEGIN:VEVENT
DESCRIPTION:none
DTEND:20130829Z
DTSTAMP:20130823T214317Z
DTSTART:20130828T210000Z
SUMMARY:Test Shift Tool - Testing Shift
END:VEVENT
BEGIN:VEVENT
DESCRIPTION:none
DTEND:20130723T020000Z
DTSTAMP:20130823T214317Z
DTSTART:20130723Z
SUMMARY:New Member Meeting
END:VEVENT
BEGIN:VEVENT
DESCRIPTION:none
DTEND:20130630T190000Z
DTSTAMP:20130823T214317Z
DTSTART:20130630T180000Z
SUMMARY:public
END:VEVENT
END:VCALENDAR

Upvotes: 1

Views: 1426

Answers (1)

rutter
rutter

Reputation: 11452

I've read that the time is not required if it's 000000

That's not what the RFC says. Let's refer to the following sections:

  • 4.3.4 Date
  • 4.3.5 Date-Time
  • 4.8.7.2 Date/Time

I'll quote the relevant format specifications, here:

date               = date-value

date-value         = date-fullyear date-month date-mday
date-fullyear      = 4DIGIT

date-time  = date "T" time ;As specified in the date and time
                             ;value definitions

dtstamp    = "DTSTAMP" stmparam ":" date-time CRLF

When your output includes a DTSTAMP, the ICal specification expects a date-time following it.

Which brings us to Date::ICal and its ical method. Does it return an iCal date or a date-time? As it turns out, it tries to guess which format you want by checking whether your timestamp has a time of 000000. See for yourself at line 286 of ICal.pm.

It could be that we expect Data::ICal::Entry to handle this scenario. I could be missing validation code on that end, but at the moment I'm not seeing anything that's obviously relevant. It looks like it accepts the property values without checking them.

Depending on your perspective, this sounds like a bug or a limitation of the libraries.

So... how should you fix this? Ideally, one of these libraries should probably check for and handle this scenario. In the meantime, though, you need to get back on your feet:

Quick and dirty fix: if your time is zero, bump it by one second; ical will now return a valid but slightly inaccurate date-time string.

A little better: check the return value from ical; if it's a date, reformat it as a date-time.

Test this before using it, but maybe something like this:

dtstart => $ical =~ s/(\d{8})Z/$1T000000Z/r;

Upvotes: 1

Related Questions