diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index 04a0ba3247..4fa3a98491 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -910,6 +910,9 @@ class calendar_bo $start = $this->date2ts($event['start'],true); if ($event['whole_day']) { + $start = new Api\DateTime($event['start'], Api\DateTime::$server_timezone); + $start->setTime(0,0,0); + $start = $start->format('ts'); $time = $this->so->startOfDay(new Api\DateTime($event['end'], Api\DateTime::$user_timezone)); $time->setTime(23, 59, 59); $end = $this->date2ts($time,true); @@ -1122,10 +1125,16 @@ class calendar_bo // 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 - $rrule = calendar_rrule::event2rrule($event, true); // true = we operate in usertime, like the rest of calendar_bo + $rrule = calendar_rrule::event2rrule($event, !$event['whole_day'], Api\DateTime::$user_timezone->getName()); // true = we operate in usertime, like the rest of calendar_bo foreach($rrule as $time) { $time->setUser(); // $time is in timezone of event, convert it to usertime used here + if($event['whole_day']) + { + // All day events are processed in server timezone + $time->setServer(); + $time->setTime(0,0,0); + } if (($ts = $this->date2ts($time)) < $start-$event_length) { //echo "

".$time." --> ignored as $ts < $start-$event_length

\n"; diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index b4350068a3..49d4fc1d7d 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -1236,6 +1236,13 @@ class calendar_boupdate extends calendar_bo if (!empty($event['end'])) { $time = new Api\DateTime($event['end'], Api\DateTime::$user_timezone); + + // Check to see if switching timezones changes the date, we'll need to adjust for that + $end_event_timezone = clone $time; + $time->setServer(); + $delta = (int)$end_event_timezone->format('z') - (int)$time->format('z'); + $time->add("$delta days"); + $time->setTime(23, 59, 59); $event['end'] = Api\DateTime::to($time, 'ts'); $save_event['end'] = $time; @@ -1247,13 +1254,18 @@ class calendar_boupdate extends calendar_bo } if (!empty($event['recur_enddate'])) { - // all-day events are handled in server time - $time = $this->so->startOfDay( - new Api\DateTime($event['recur_enddate'], Api\DateTime::$server_timezone), - Api\DateTime::$server_timezone->getName() - ); - $time->modify(($event['end'] - $event['start']).' seconds'); - //$event['recur_enddate'] = $save_event['recur_enddate'] = Api\DateTime::to($time, 'ts'); + // all-day events are handled in server time, but here (BO) it's in user time + $time = new Api\DateTime($event['recur_enddate'], Api\DateTime::$user_timezone); + $time->setTime(23, 59, 59); + // Check to see if switching timezones changes the date, we'll need to adjust for that + $enddate_event_timezone = clone $time; + $time->setServer(); + $delta = (int)$enddate_event_timezone->format('z') - (int)$time->format('z'); + $time->add("$delta days"); + + //$time->setServer(); + $time->setTime(23, 59, 59); + $event['recur_enddate'] = $save_event['recur_enddate'] = $time; } $timestamps = array('modified','created'); diff --git a/calendar/inc/class.calendar_rrule.inc.php b/calendar/inc/class.calendar_rrule.inc.php index 60920f5ae5..08f744d580 100644 --- a/calendar/inc/class.calendar_rrule.inc.php +++ b/calendar/inc/class.calendar_rrule.inc.php @@ -756,7 +756,7 @@ class calendar_rrule implements Iterator self::rrule2tz($event, $time, $to_tz); $time->setTimezone(self::$tz_cache[$to_tz]); - + if ($event['recur_enddate']) { $enddate = is_a($event['recur_enddate'],'DateTime') ? clone $event['recur_enddate'] : new Api\DateTime($event['recur_enddate'],$timestamp_tz); diff --git a/calendar/test/TimezoneTest.php b/calendar/test/TimezoneTest.php index 5d845e01fa..5643fe0c50 100644 --- a/calendar/test/TimezoneTest.php +++ b/calendar/test/TimezoneTest.php @@ -21,9 +21,6 @@ class TimezoneTest extends \EGroupware\Api\AppTest { protected $bo; - // TODO: Do this at different times, at least 12 hours apart - const START_TIME = 9; - const END_TIME = 10; const RECUR_DAYS = 5; protected $recur_end; @@ -45,7 +42,6 @@ class TimezoneTest extends \EGroupware\Api\AppTest { //$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->format('Y-m-d') . '(Event time)'; } public function tearDown() @@ -66,25 +62,22 @@ class TimezoneTest extends \EGroupware\Api\AppTest { * event to make sure it has the correct number of days, and its timezone * stays as set. * - * @param type $timezones + * @param Array $timezones Timezone settings for event, client & server + * @param Array $times Start & end hours * * @dataProvider eventProvider */ - public function testTimezones($timezones) + public function testTimezones($timezones, $times) { - - echo $this->tzString($timezones)."\n"; - $this->setTimezones($timezones); - $event = $this->makeEvent($timezones); - + $event = $this->makeEvent($timezones, $times); // Save the event $this->cal_id = $this->bo->save($event); // Check - $this->checkEvent($timezones, $this->cal_id); + $this->checkEvent($timezones, $this->cal_id, $times); } /** @@ -92,38 +85,45 @@ class TimezoneTest extends \EGroupware\Api\AppTest { * all day event to make sure it has the correct number of days, and its timezone * stays as set. * - * @param type $timezones + * @param Array $timezones Timezone settings for event, client & server + * @param Array $times Start & end hours * * @dataProvider eventProvider */ - public function notestTimezonesAllDay($timezones) + public function testTimezonesAllDay($timezones, $times) { - echo $this->tzString($timezones)."\n"; - $this->setTimezones($timezones); - $event = $this->makeEvent($timezones, true); - + $event = $this->makeEvent($timezones, $times, true); // Save the event $this->cal_id = $this->bo->save($event); // Check - $this->checkEvent($timezones, $this->cal_id); + $this->checkEvent($timezones, $this->cal_id, $times); } - protected function checkEvent($timezones, $cal_id) + /** + * Load the event and check that it matches expectations + * + * @param Array $timezones List of timezones (event, client, server) + * @param int $cal_id + * @param Array $times start and end times (just hours) + */ + protected function checkEvent($timezones, $cal_id, $times) { // Load the event - // BO does caching, need array to avoid it + // BO does caching, pass ID as array to avoid it $loaded = $this->bo->read(Array($cal_id)); $loaded = $loaded[$cal_id]; $message = $this->makeMessage($timezones, $loaded); + $start_time = \mktime($loaded['whole_day'] ? 0 : $times['start'], 0, 0, date('m'), date('d')+1, date('Y')); + // 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($start_time, Api\DateTime::DATABASE), Api\DateTime::to($loaded['start'], Api\DateTime::DATABASE), 'Start date'. $message ); @@ -132,7 +132,7 @@ class TimezoneTest extends \EGroupware\Api\AppTest { $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') + \mktime($times['end'], 0, 0, date('m'), date('d')+1, date('Y') ), Api\DateTime::DATABASE), Api\DateTime::to($loaded['end'], Api\DateTime::DATABASE), 'End date'. $message @@ -148,7 +148,16 @@ class TimezoneTest extends \EGroupware\Api\AppTest { // Recurrences $so = new \calendar_so(); $recurrences = $so->get_recurrences($cal_id); - $this->assertEquals(static::RECUR_DAYS, count($recurrences)-1, 'Recurrence count' . $message); + unset($recurrences[0]); + $this->assertEquals(static::RECUR_DAYS, count($recurrences), 'Recurrence count' . $message); + foreach($recurrences as $recur_start_time => $participant) + { + $this->assertEquals( + Api\DateTime::to($start_time, 'H:i:s'), + $loaded['whole_day'] ? '00:00:00' : Api\DateTime::to(Api\DateTime::server2user($recur_start_time), 'H:i:s'), + 'Recurrence start time' . $message + ); + } } /** @@ -157,11 +166,22 @@ class TimezoneTest extends \EGroupware\Api\AppTest { public function eventProvider() { $tests = array(); - $tz_combos = $this->makeCombos(); + $tz_combos = $this->makeTZCombos(); + // Start times to test, 1 chosen to cross days + $times = array(1, 9); + foreach($tz_combos as $timezones) { - $tests[] = Array($timezones); + foreach($times as $start_time) + { + $tests[] = Array($timezones, + Array( + 'start' => $start_time, + 'end' => $start_time + 1 + ) + ); + } } return $tests; @@ -171,15 +191,16 @@ class TimezoneTest extends \EGroupware\Api\AppTest { * Make a map of all the different client / server / event combinations * that we'll use. */ - protected function makeCombos() + protected function makeTZCombos() { // Timezone list $tz_list = Array( 'Pacific/Tahiti', // -10 - 'America/Edmonton', // -8 'Europe/Berlin', // +2 - 'Pacific/Auckland', // +12 - 'UTC' + // The first 2 are usually sufficient + //'America/Edmonton', // -8 + //'Pacific/Auckland', // +12 + //'UTC' ); $tz_combos = Array(); @@ -218,16 +239,16 @@ class TimezoneTest extends \EGroupware\Api\AppTest { * Make the array of event information * * @param Array $timezones - * @param boolean $whole_day * @return Array Event array, unsaved. + * @param boolean $whole_day */ - protected function makeEvent($timezones, $whole_day = false) + protected function makeEvent($timezones, $times, $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')), + 'start' => \mktime($whole_day ? 0 : $times['start'], 0, 0, date('m'), date('d')+1, date('Y')), + 'end' => $whole_day ? \mktime(23, 59, 59, date('m'), date('d')+1, date('Y')) : \mktime($times['end'], 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'), @@ -241,7 +262,7 @@ class TimezoneTest extends \EGroupware\Api\AppTest { protected function makeMessage($timezones, $event) { - return ' ' . Api\DateTime::to($event['recur_enddate'], Api\DateTime::DATABASE) . ' '. + return ' ' . ($event['id'] ? '[#'.$event['id'] .'] ' : '') . Api\DateTime::to($event['recur_enddate'], Api\DateTime::DATABASE) . ' '. ($event['whole_day'] ? '(whole day) ' : '') . $this->tzString($timezones); }