From 6622ae7153c016fa650e517961948f273256af5c Mon Sep 17 00:00:00 2001 From: nathangray Date: Thu, 1 Jun 2017 11:20:38 -0600 Subject: [PATCH] Calendar - Ok, working for Edmonton & Berlin... --- calendar/inc/class.calendar_bo.inc.php | 4 +- calendar/inc/class.calendar_boupdate.inc.php | 5 +- calendar/inc/class.calendar_rrule.inc.php | 8 +- calendar/test/TimezoneTest.php | 269 +++++++++++++++++++ 4 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 calendar/test/TimezoneTest.php diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index b31925e537..d377d40ae7 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -1121,7 +1121,9 @@ class calendar_bo // insert at least the event itself, if it's behind the horizont $event['recur_enddate'] = $this->date2ts($end) < $this->date2ts($event['end']) ? $event['end'] : $end; } - $event['recur_enddate'] = new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); + $event['recur_enddate'] = is_a($event['recur_enddate'],'DateTime') ? + $event['recur_enddate'] : + new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); // unset exceptions, as we need to add them as recurrence too, but marked as exception unset($event['recur_exception']); // loop over all recurrences and insert them, if they are after $start diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index 79792dcae8..83e8b3c433 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -1184,7 +1184,7 @@ class calendar_boupdate extends calendar_bo if ($event['recur_type'] != MCAL_RECUR_NONE && $event['recur_enddate']) { $event['recur_enddate'] = new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); - $event['recur_enddate']->setTime(23,59,59); + //$event['recur_enddate']->setTime(23,59,59); $rrule = calendar_rrule::event2rrule($event, true, Api\DateTime::$server_timezone->getName()); $rrule->rewind(); $enddate = $rrule->current(); @@ -1196,7 +1196,7 @@ class calendar_boupdate extends calendar_bo while ($rrule->valid() && ($enddate = $occurrence)); $enddate->modify(($event['end'] - $event['start']).' second'); //$enddate->setTimezone(); - //$event['recur_enddate'] = $enddate->format('ts'); + $event['recur_enddate'] = $enddate->format('ts'); //error_log(__METHOD__."($event[title]) start=".Api\DateTime::to($event['start'],'string').', end='.Api\DateTime::to($event['end'],'string').', range_end='.Api\DateTime::to($event['recur_enddate'],'string')); } @@ -1230,6 +1230,7 @@ class calendar_boupdate extends calendar_bo ); $time->modify(($event['end'] - $event['start']).' seconds'); //$event['recur_enddate'] = $save_event['recur_enddate'] = Api\DateTime::to($time, 'ts'); + $event['recur_enddate'] = $save_event['recur_enddate'] = $time; } $timestamps = array('modified','created'); // all-day events are handled in server time diff --git a/calendar/inc/class.calendar_rrule.inc.php b/calendar/inc/class.calendar_rrule.inc.php index 8f8124a89d..35b42080bf 100644 --- a/calendar/inc/class.calendar_rrule.inc.php +++ b/calendar/inc/class.calendar_rrule.inc.php @@ -760,7 +760,13 @@ class calendar_rrule implements Iterator if ($event['recur_enddate']) { $enddate = is_a($event['recur_enddate'],'DateTime') ? $event['recur_enddate'] : new Api\DateTime($event['recur_enddate'],$timestamp_tz); - // $enddate->setTime(23,59,59); + $end = is_a($event['end'],'DateTime') ? $event['end'] : new Api\DateTime($event['end'],$timestamp_tz); + $end->setTimezone($enddate->getTimezone()); + $enddate->setTime($end->format('H'),$end->format('i'),59); + if($event['whole_day']) + { + $enddate->setTime(23,59,59); + } $enddate->setTimezone(self::$tz_cache[$to_tz]); } if (is_array($event['recur_exception'])) diff --git a/calendar/test/TimezoneTest.php b/calendar/test/TimezoneTest.php new file mode 100644 index 0000000000..56e227ffe2 --- /dev/null +++ b/calendar/test/TimezoneTest.php @@ -0,0 +1,269 @@ +bo = new \calendar_boupdate(); + + //$this->mockTracking($this->bo, 'calendar_tracking'); + + $this->recur_end = new Api\DateTime(mktime(0,0,0,date('m'), date('d') + static::RECUR_DAYS, date('Y'))); + echo "End date: " . $this->recur_end; + } + + public function tearDown() + { + //$this->bo->delete($this->cal_id); + $this->bo = null; + + // need to call preferences constructor and read_repository, to set user timezone again + $GLOBALS['egw']->preferences->__construct($GLOBALS['egw_info']['user']['account_id']); + $GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository(false); // no session prefs! + + // Re-load date/time preferences + Api\DateTime::init(); + } + + /** + * Test one combination of event / client / server timezone on a daily recurring + * event to make sure it has the correct number of days, and its timezone + * stays as set. + * + * @param type $timezones + * + * @dataProvider eventProvider + */ + public function testTimezones($timezones) + { + + echo $this->tzString($timezones)."\n"; + + $this->setTimezones($timezones); + + $event = $this->makeEvent($timezones); + + + // Save the event + $this->cal_id = $this->bo->save($event); + + // Check + $this->checkEvent($timezones, $this->cal_id); + } + + /** + * Test one combination of event / client / server timezone on a daily recurring + * all day event to make sure it has the correct number of days, and its timezone + * stays as set. + * + * @param type $timezones + * + * @dataProvider eventProvider + */ + public function testTimezonesAllDay($timezones) + { + echo $this->tzString($timezones)."\n"; + + $this->setTimezones($timezones); + + $event = $this->makeEvent($timezones, true); + + + // Save the event + $this->cal_id = $this->bo->save($event); + + // Check + $this->checkEvent($timezones, $this->cal_id); + } + + protected function checkEvent($timezones, $cal_id) + { + // Load the event + $loaded = $this->bo->read($cal_id); + + $message = $this->makeMessage($timezones, $loaded); + + // Check that the start date is the same (user time) + $this->assertEquals( + Api\DateTime::to(\mktime($loaded['whole_day'] ? 0 : static::START_TIME, 0, 0, date('m'), date('d')+1, date('Y')), Api\DateTime::DATABASE), + Api\DateTime::to($loaded['start'], Api\DateTime::DATABASE), + 'Start date'. $message + ); + + // Check that the end date is the same (user time) + $this->assertEquals( + Api\DateTime::to( + $loaded['whole_day'] ? \mktime(0, 0, 0, date('m'), date('d')+2, date('Y'))-1 : + \mktime(static::END_TIME, 0, 0, date('m'), date('d')+1, date('Y') + ), Api\DateTime::DATABASE), + Api\DateTime::to($loaded['end'], Api\DateTime::DATABASE), + 'End date'. $message + ); + + // Check event recurring timezone is unchanged + $this->assertEquals($timezones['event'], $loaded['tzid'], 'Timezone' . $message); + + // Check recurring end date is unchanged (user time) + $loaded_end = new Api\DateTime($loaded['recur_enddate']); + $this->assertEquals($this->recur_end->format('Ymd'), $loaded_end->format('Ymd'), 'Recur end date' . $message); + + // Recurrences + $so = new \calendar_so(); + $recurrences = $so->get_recurrences($cal_id); + $this->assertEquals(static::RECUR_DAYS, count($recurrences)-1, 'Recurrence count' . $message); + } + + /** + * Provide an event for checking, along with a list of timezones + */ + public function eventProvider() + { + $tests = array(); + $tz_combos = $this->makeCombos(); + + foreach($tz_combos as $timezones) + { + $tests[] = Array($timezones); + } + + return $tests; + } + + /** + * Make a map of all the different client / server / event combinations + * that we'll use. + */ + protected function makeCombos() + { + // Timezone list + $tz_list = Array( + // 'Pacific/Tahiti', // -10 + 'America/Edmonton', // -8 + 'Europe/Berlin', // +2 + // 'Pacific/Auckland', // +12 + // 'UTC' + ); + $tz_combos = Array(); + + // Pick some timezones to use - every combination from the list + $client_index = $server_index = $event_index = 0; + do { + $tz_combos[] = array( + 'client' => $tz_list[$client_index], + 'server' => $tz_list[$server_index], + 'event' => $tz_list[$event_index] + ); + $client_index++; + if($client_index > count($tz_list)-1) + { + $server_index++; + $client_index = 0; + } + if($server_index > count($tz_list)-1) + { + $event_index++; + $server_index = 0; + } + } while ($event_index < count($tz_list)); + + /* one specific test + $tz_combos = array(array( + 'client' => 'America/Edmonton', + 'server' => 'Europe/Berlin', + 'event' => 'America/Edmonton' + )); + // */ + return $tz_combos; + } + + /** + * Make the array of event information + * + * @param Array $timezones + * @param boolean $whole_day + * @return Array Event array, unsaved. + */ + protected function makeEvent($timezones, $whole_day = false) + { + $event = array( + 'title' => ($whole_day ? 'Whole day ' : '')."Test for " . $this->tzString($timezones), + 'des' => ($whole_day ? 'Whole day ' : '').'Test for test ' . $this->getName() . ' ' . $this->tzString($timezones), + 'start' => \mktime(static::START_TIME, 0, 0, date('m'), date('d')+1, date('Y')), + 'end' => \mktime(static::END_TIME, 0, 0, date('m'), date('d')+1, date('Y')), + 'tzid' => $timezones['event'], + 'recur_type' => 1, // MCAL_RECUR_DAILY + 'recur_enddate' => $this->recur_end->format('ts'), + 'whole_day' => $whole_day, + 'participants' => array( + $GLOBALS['egw_info']['user']['account_id'] => 'A' + ) + ); + return $event; + } + + protected function makeMessage($timezones, $event) + { + return ' ' . Api\DateTime::to($event['recur_enddate'], Api\DateTime::DATABASE) . ' '. + ($event['whole_day'] ? '(whole day) ' : '') . $this->tzString($timezones); + } + + /** + * Set the current client & server timezones as given + * + * @param Array $timezones + */ + protected function setTimezones($timezones) + { + // Set the client preference & server preference + $GLOBALS['egw_info']['server']['server_timezone'] = $timezones['server']; + $GLOBALS['egw_info']['user']['preferences']['common']['tz'] = $timezones['client']; + + // Load date/time preferences into egw_time + Api\DateTime::init(); + } + + /** + * Make a nice string for the timezone combination we're using + * + * @param Array $timezones + */ + protected function tzString($timezones) + { + return "[Event: {$timezones['event']} Client: {$timezones['client']} Server: {$timezones['server']}]"; + } +}