Blog

Spaß mit ICal-Dateien und Zeitzonen

20.07.2022 // Renée Bäcker

Wir nutzen das Ticketsystem Znuny zur Kommunikation mit externen Personen wie zum Beispiel Interessenten und Kunden. Leider werden in Znuny ICal-Dateianhänge nicht als Termin erkannt und dementsprechend auch nicht angezeigt.

Aus diesem Grund haben wir eine neue Erweiterung für das System geschrieben, die uns alle Termine eines Tickets in einem extra Bereich anzeigen soll:

70b41190-69fd-4ece-a343-1efcc254788d.png

Um das zu erreichen, werden alle eingehenden Mails durch einen sogenannten Postmaster-Filter auf einen Dateianhang mit dem MIME-Type text/calendar geprüft. Ist ein solcher Anhang vorhanden, werden die folgenden Informationen aus dem Anhang gezogen:

  • Datum
  • Betreff

Dazu nehmen wir das Modul Data::ICal:

    # alle Anhänge eines Tickets ermitteln
    my $Attachments = $Param{GetParam}->{Attachment};

    # ersten Anhang des Typs "text/calendar" ermitteln
    my ($IcalAttachment) = grep {
        $_->{MimeType} eq 'text/calendar';
    } @{ $Attachments || [] };

    return 1 if !$IcalAttachment;

    # Objekt mit Daten aus Anhang instanziieren
    my $Ical = Data::ICal->new(
        data => $IcalAttachment->{Content},
    );
 
    # Daten aus letztem Event extrahieren
    for my $Entry ( @{ $Ical->entries || [] } ) {
        next if !$Entry->isa('Data::ICal::Entry::Event');

        my $Description = $Entry->property('SUMMARY')->[0]->value;
        my $Start       = $Entry->property('DTSTART')->[0];
        my $End         = $Entry->property('DTEND')->[0];
    }

Wenn wir uns das Datum näher anschauen und uns mit Data::Printer das Objekt ausgeben lassen, sehen wir eine Angabe zur Zeitzone. Je nachdem wie die Zeitzone angegeben ist, weiß ich nicht direkt den Zeitunterschied zu UTC, hier ein kleines Beispiel:

Data::ICal::Property  {
    Parents       Class::Accessor
    public methods (8) : as_string, decoded_value, encode, key, new, parameters, value, vcal10
    private methods (5) : _fold, _parameters, _parameters_as_string, _quoted_parameter_values, _value_as_string
    internals: {
        _parameters   {
            TZID   "W. Europe Standard Time"
        },
        key           "dtstart",
        value         "20210601T080000",
        vcal10        0
    }
}

Das Datum möchte ich aber in UTC speichern, weil alle anderen Zeitangaben in der Datenbank ebenfalls in UTC gespeichert sind und damit das Datum je nach Zeitzone auch richtig angezeigt wird.

Ich muss also zunächst aus der Zeitzonenangabe den Zeitunterschied/Offset des Termins herausfinden. Anschließend muss ich das ICal-Datumsformat mit dem richtigen Offset parsen, um zum Schluss das richtige Datumsformat in UTC speichern zu können.

Es gibt eine Vielzahl von Perl-Modulen, die sich mit dem Thema Datum befassen. Das einzige Perl-Modul jedoch, das ich für alle möglichen Varianten von Zeitzonen gefunden habe, ist Date::Manip beziehungsweise Date::Manip::TZ. Dieses nutze ich nun, um die Zeitzonenvariante Kontinent/Stadt (beispielsweise Europe/Berlin) zu bekommen.

# "richtiger" Name der Zeitzone
my $TimeZone;
{
    # statt festem Wert dann $Start->parameters->{TZID}
    local $ENV{TZ} = "W. Europe Standard Time";
    $TimeZone = Date::Manip::TZ->new;
}

print $TimeZone->zone . "\n"; # Europe/Berlin

Mit dieser Variante können mehrere Module umgehen, unter anderem DateTime.

Zum Parsen des Datums nehme ich die das Modul Date::ICal:

my $Date = Date::ICal->new( ical => $Start->value );

Und für die Generierung des richtigen Formats nehme ich DateTime.

my $DateTime = DateTime->new(
    year      => $Date->year,
    month     => $Date->month,
    day       => $Date->day,
    hour      => $Date->hour,
    minute    => $Date->minute,
    second    => $Date->second,
    time_zone => $TimeZone->zone,
);

# Das ist dann automatisch UTC
my $Formatted = $DateTime->ymd . ' ' . $DateTime->hms;

So bekomme ich aus einem beliebigen Datum in ICal-Dateien mit einer beliebigen Zeitzonenangabe ein Datum, das in der Zeitzone UTC angegeben ist – es ist damit in dem Format angegeben, das für den Datenbankeintrag notwendig ist.


Permalink: