u01jmg3 / ics-parser

Parser for iCalendar Events • PHP 8+, 7 (≥ 7.4), 5 (≥ 5.6)
MIT License
450 stars 144 forks source link

iCalDateWithTimeZone calculation always starts at UTC #197

Closed tyzoid closed 5 years ago

tyzoid commented 5 years ago

Description of the Issue:

When setting the calendar timezone, conversions from the event time to the calendar time occur from UTC, completely ignoring the calendar default timezone. This not only yields a counterintuitive result, but also an incorrect one.

For example, a DTSTART of TZID=America/New_York:20181204T180000 will get converted to a dtstart_tz of 20181204T130000 when the calendar default is set to Europe/Copenhagen. Instead of being converted to UTC or Europe/Copenhagen (+5 or +6hrs after America/New_York at time of writing), the dtstart_tz gets converted to something that's -5 hours from America/New_York.

It should be added, that the unix timestamp parsed as element 2 of dtstart_array is correct.

Steps to Reproduce:

Example iCalendar (https://dl.tyzoid.com/test.ics):

BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:America/New_York
TZURL:http://tzurl.org/zoneinfo/America/New_York
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20181213T205417
DTSTAMP:20181213T205417
LAST-MODIFIED:20181213T205417
UID:49ZKIH630HTZV3YK8NJZY
SUMMARY:CSGO
CLASS:PUBLIC
STATUS:CONFIRMED
RRULE:FREQ=WEEKLY
DESCRIPTION:Hosted by Avery
DTSTART;TZID=America/New_York:20181204T180000
DTEND;TZID=America/New_York:20181204T190000
END:VEVENT
END:VCALENDAR

Example Code:

<?php
date_default_timezone_set('Europe/Copenhagen');

$ical = new ICal(
    "https://dl.tyzoid.com/test.ics",
    ['defaultTimeZone' => 'Europe/Copenhagen']
);

var_dump($ical->events()[0]);

This yields the following (snippet)

  ["dtstart_array"]=>
  array(4) {
    [0]=>
    array(1) {
      ["TZID"]=>
      string(16) "America/New_York"
    }
    [1]=>
    string(15) "20181204T180000"
    [2]=>
    int(1543964400)
    [3]=>
    string(37) "TZID=America/New_York:20181204T180000"
  }
  ["dtend_array"]=>
  array(4) {
    [0]=>
    array(1) {
      ["TZID"]=>
      string(16) "America/New_York"
    }
    [1]=>
    string(15) "20181204T190000"
    [2]=>
    int(1543968000)
    [3]=>
    string(37) "TZID=America/New_York:20181204T190000"
  }
  ["dtstart_tz"]=>
  string(15) "20181204T130000"
  ["dtend_tz"]=>
  string(15) "20181204T140000"

Code Exploration

It seems like this is down to the following code, inside iCalDateWithTimeZone:

        if ($key === 'DURATION') {
            $duration = end($dateArray);
            $dateTime = $this->parseDuration($event['DTSTART'], $duration, null);
        } else {
            $dateTime = new \DateTime($dateArray[1], new \DateTimeZone(self::TIME_ZONE_UTC));
            $dateTime->setTimezone(new \DateTimeZone($this->calendarTimeZone()));
        }
        // Force time zone
        if (isset($dateArray[0]['TZID'])) {
            if ($this->isValidIanaTimeZoneId($dateArray[0]['TZID'])) {
                $dateTime->setTimezone(new \DateTimeZone($dateArray[0]['TZID']));
            } elseif ($this->isValidCldrTimeZoneId($dateArray[0]['TZID'])) {
                $dateTime->setTimezone(new \DateTimeZone($this->isValidCldrTimeZoneId($dateArray[0]['TZID'], true)));
            } else {
                $dateTime->setTimezone(new \DateTimeZone($this->defaultTimeZone));
            }
        }

This parses the value of $dateArray[1], which in this case, is 20181204T190000 for DTSTART, as UTC. It then sets the timezone with the calendar time zone, and finally with the "forced" timezone information found in $dateArray[0], which in this case is America/New_York.

The error, as far as I can tell, is that instead of parsing the time as America/New_York to start, it parses as UTC, then tries to set it to the event timezone of America/New_York.

I'm not familiar enough with this library to know if changing the behaviour in this function will produce side effects, so I've simply documented my findings here.

u01jmg3 commented 5 years ago

Many thanks for the thorough description. Shall check this out in due course but time zone issues are always tricky to solve.