diff --git a/lib/kolab_sync_timezone_converter.php b/lib/kolab_sync_timezone_converter.php
index 01c2a11..91fcdf0 100644
--- a/lib/kolab_sync_timezone_converter.php
+++ b/lib/kolab_sync_timezone_converter.php
@@ -1,656 +1,649 @@
 <?php
 
 /*
  +--------------------------------------------------------------------------+
  | Kolab Sync (ActiveSync for Kolab)                                        |
  |                                                                          |
  | Copyright (C) 2011-2017, Kolab Systems AG <contact@kolabsys.com>         |
  | Copyright (C) 2008-2012, Metaways Infosystems GmbH                       |
  |                                                                          |
  | This program is free software: you can redistribute it and/or modify     |
  | it under the terms of the GNU Affero General Public License as published |
  | by the Free Software Foundation, either version 3 of the License, or     |
  | (at your option) any later version.                                      |
  |                                                                          |
  | This program is distributed in the hope that it will be useful,          |
  | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
  | GNU Affero General Public License for more details.                      |
  |                                                                          |
  | You should have received a copy of the GNU Affero General Public License |
  | along with this program. If not, see <http://www.gnu.org/licenses/>      |
  +--------------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak@kolabsys.com>                      |
  | Author: Jonas Fischer <j.fischer@metaways.de>                            |
  +--------------------------------------------------------------------------+
 */
 
 /**
  * Activesync timezone converter
  */
 class kolab_sync_timezone_converter
 {
     /**
      * holds the instance of the singleton
      *
      * @var ?kolab_sync_timezone_converter
      */
     private static $_instance;
 
     protected $_startDate = [];
 
-    /**
-     * If set then the timezone guessing results will be cached.
-     * This is strongly recommended for performance reasons.
-     *
-     * @var rcube_cache
-     */
-    protected $cache = null;
-
     /**
      * array of offsets known by ActiceSync clients, but unknown by php
+     *
      * @var array
      */
     protected $_knownTimezones = [
         '0AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' => [
             'Pacific/Kwajalein' => 'MHT',
         ],
     ];
 
 
     protected $_legacyTimezones = [
         // This is an outdated timezone that outlook keeps sending because of an outdate timezone database on windows
         'Lv///0kAcgBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkABAADABcAOwA7AOcDAAAAAEkAcgBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAwAEAAAAAAAAAAAAxP///w==' => [
             'Asia/Tehran' => '+0330',
         ],
     ];
 
     /**
      * don't use the constructor. Use the singleton.
      *
      * @param $_logger
      */
     private function __construct()
     {
     }
 
     /**
      * don't clone. Use the singleton.
      */
     private function __clone()
     {
     }
 
     /**
      * the singleton pattern
      *
      * @return kolab_sync_timezone_converter
      */
     public static function getInstance()
     {
         if (self::$_instance === null) {
             self::$_instance = new kolab_sync_timezone_converter();
         }
 
         return self::$_instance;
     }
 
 
     /**
      * Returns a timezone with an offset matching the time difference
      * of $dt from $referenceDt.
      *
      * If set and matching the offset, kolab_format::$timezone is preferred.
      *
      * @param DateTime $dt          The date time value for which we
      *                              calculate the offset.
      * @param DateTime $referenceDt The reference value, for instance in UTC.
      *
      * @return DateTimeZone|null
      */
     public function getOffsetTimezone($dt, $referenceDt)
     {
         $interval = $referenceDt->diff($dt);
         $tz = new DateTimeZone($interval->format('%R%H%I')); //e.g. +0200
         $utcOffset = $tz->getOffset($dt);
 
         //Prefer the configured timezone if it matches the offset.
         if (kolab_format::$timezone) {
             if (kolab_format::$timezone->getOffset($dt) == $utcOffset) {
                 return kolab_format::$timezone;
             }
         }
 
         //Look for any timezone with a matching offset.
         foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) {
             $timezone = new DateTimeZone($timezoneIdentifier);
             if ($timezone->getOffset($dt) == $utcOffset) {
                 return $timezone;
             }
         }
         return null;
     }
 
 
     /**
      * Returns a list of timezones that match to the {@param $_offsets}
      *
      * If {@see $_expectedTimezone} is set then the method will terminate as soon
      * as the expected timezone has matched and the expected timezone will be the
      * first entry to the returned array.
      *
      * @param string|array $_offsets
      *
      * @return array
      */
     public function getListOfTimezones($_offsets)
     {
         if (is_string($_offsets) && isset($this->_knownTimezones[$_offsets])) {
             $timezones = $this->_knownTimezones[$_offsets];
         } elseif (is_string($_offsets) && isset($this->_legacyTimezones[$_offsets])) {
             $timezones = $this->_legacyTimezones[$_offsets];
         } else {
             if (is_string($_offsets)) {
                 // unpack timezone info to array
                 $_offsets = $this->_unpackTimezoneInfo($_offsets);
             }
 
             if (!$this->_validateOffsets($_offsets)) {
                 return [];
             }
             $this->_setDefaultStartDateIfEmpty($_offsets);
 
             $timezones = [];
             foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) {
                 $timezone = new DateTimeZone($timezoneIdentifier);
                 if (false !== ($matchingTransition = $this->_checkTimezone($timezone, $_offsets))) {
                     $timezones[$timezoneIdentifier] = $matchingTransition['abbr'];
                 }
             }
         }
 
         return $timezones;
     }
 
     /**
      * Returns PHP timezone that matches to the {@param $_offsets}
      *
      * If {@see $_expectedTimezone} is set then the method will return this timezone if it matches.
      *
      * @param string|array $_offsets          Activesync timezone definition
      * @param string       $_expectedTimezone Expected timezone name
      *
      * @return string Expected timezone name
      */
     public function getTimezone($_offsets, $_expectedTimezone = null)
     {
         $timezones = $this->getListOfTimezones($_offsets);
 
         if ($_expectedTimezone && isset($timezones[$_expectedTimezone])) {
             return $_expectedTimezone;
         } else {
             return key($timezones);
         }
     }
 
     /**
      * Return packed string for given {@param $_timezone}
      *
      * @param string     $_timezone  Timezone identifier
      * @param string|int $_startDate Start date
      *
      * @return string Packed timezone offsets
      */
     public function encodeTimezone($_timezone, $_startDate = null)
     {
         foreach ($this->_knownTimezones as $packedString => $knownTimezone) {
             if (array_key_exists($_timezone, $knownTimezone)) {
                 return $packedString;
             }
         }
 
         $offsets = $this->getOffsetsForTimezone($_timezone, $_startDate);
 
         return $this->_packTimezoneInfo($offsets);
     }
 
 
     /**
      * Returns an encoded timezone representation from $date
      *
      * @param DateTime $date The date with the timezone to encode
      *
      * @return string|null Timezone name
      */
     public static function encodeTimezoneFromDate($date)
     {
         if ($date instanceof DateTime) {
             $timezone = $date->getTimezone();
 
             if (($tz_name = $timezone->getName()) != 'UTC') {
                 $tzc = self::getInstance();
                 if ($tz_name = $tzc->encodeTimezone($tz_name, $date->format('Y-m-d'))) {
                     return $tz_name;
                 }
             }
         }
 
         return null;
     }
 
     /**
      * Get offsets for given timezone
      *
      * @param string     $_timezone  Timezone identifier
      * @param string|int $_startDate Start date
      *
      * @return array|null Timezone offsets
      */
     public function getOffsetsForTimezone($_timezone, $_startDate = null)
     {
         $this->_setStartDate($_startDate);
 
         $offsets = $this->_getOffsetsTemplate();
 
         try {
             $timezone = new DateTimeZone($_timezone);
         } catch (Exception $e) {
             return null;
         }
 
         [$standardTransition, $daylightTransition] = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate['year']);
 
         if ($standardTransition) {
             $offsets['bias'] = $standardTransition['offset'] / 60 * -1;
             if ($daylightTransition) {
                 $offsets = $this->_generateOffsetsForTransition($offsets, $standardTransition, 'standard', $timezone);
                 $offsets = $this->_generateOffsetsForTransition($offsets, $daylightTransition, 'daylight', $timezone);
 
                 //@todo how do we get the standardBias (is usually 0)?
                 //$offsets['standardBias'] = ...
 
                 $offsets['daylightBias'] = ($daylightTransition['offset'] - $standardTransition['offset']) / 60 * -1;
                 $offsets['standardHour'] -= $offsets['daylightBias'] / 60;
                 $offsets['daylightHour'] += $offsets['daylightBias'] / 60;
             }
         }
 
         return $offsets;
     }
 
     /**
      * Get offsets for timezone transition
      *
      * @param array        $_offsets    Timezone offsets
      * @param array        $_transition Timezone transition information
      * @param string       $_type       Transition type: 'standard' or 'daylight'
      * @param DateTimeZone $_timezone   Timezone of the transition
      *
      * @return array
      */
     protected function _generateOffsetsForTransition(array $_offsets, array $_transition, $_type, $_timezone)
     {
         $transitionDate = new DateTime($_transition['time'], $_timezone);
 
         if ($_transition['offset']) {
             $transitionDate->modify($_transition['offset'] . ' seconds');
         }
 
         $_offsets[$_type . 'Month']     = (int) $transitionDate->format('n');
         $_offsets[$_type . 'DayOfWeek'] = (int) $transitionDate->format('w');
         $_offsets[$_type . 'Minute']    = (int) $transitionDate->format('i');
         $_offsets[$_type . 'Hour']      = (int) $transitionDate->format('G');
 
         for ($i = 5; $i > 0; $i--) {
             if ($this->_isNthOcurrenceOfWeekdayInMonth($transitionDate, $i)) {
                 $_offsets[$_type . 'Week'] = $i;
                 break;
             };
         }
 
         return $_offsets;
     }
 
     /**
      * Test if the weekday of the given {@param $_timestamp} is the {@param $_occurence}th occurence of this weekday within its month.
      *
      * @param DateTime $_datetime
      * @param int $_occurence [1 to 5, where 5 indicates the final occurrence during the month if that day of the week does not occur 5 times]
      *
      * @return bool
      */
     protected function _isNthOcurrenceOfWeekdayInMonth($_datetime, $_occurence)
     {
         if ($_occurence <= 1) {
             return true;
         }
 
         $orig = $_datetime->format('n');
 
         if ($_occurence == 5) {
             $modified = clone($_datetime);
             $modified->modify('1 week');
             $mod = $modified->format('n');
 
             // modified date is a next month
             return $mod > $orig || ($mod == 1 && $orig == 12);
         }
 
         $modified = clone($_datetime);
         $modified->modify(sprintf('-%d weeks', $_occurence - 1));
         $mod = $modified->format('n');
 
         if ($mod != $orig) {
             return false;
         }
 
         $modified = clone($_datetime);
         $modified->modify(sprintf('-%d weeks', $_occurence));
         $mod = $modified->format('n');
 
         // modified month is earlier than original
         return $mod < $orig || ($mod == 12 && $orig == 1);
     }
 
     /**
      * Check if the given {@param $_standardTransition} and {@param $_daylightTransition}
      * match to the object property {@see $_offsets}
      *
      * @param array        $_standardTransition
      * @param array        $_daylightTransition
      * @param array        $_offsets
      * @param DateTimeZone $tz
      *
      * @return bool
      */
     protected function _checkTransition($_standardTransition, $_daylightTransition, $_offsets, $tz)
     {
         if (empty($_standardTransition) || empty($_offsets)) {
             return false;
         }
 
         $standardOffset = ($_offsets['bias'] + $_offsets['standardBias']) * 60 * -1;
 
         // check each condition in a single if statement and break the chain when one condition is not met - for performance reasons
         if ($standardOffset == $_standardTransition['offset']) {
 
             if (empty($_offsets['daylightMonth']) && (empty($_daylightTransition) || empty($_daylightTransition['isdst']))) {
                 // No DST
                 return true;
             }
 
             $daylightOffset = ($_offsets['bias'] + $_offsets['daylightBias']) * 60 * -1;
 
             // the milestone is sending a positive value for daylightBias while it should send a negative value
             $daylightOffsetMilestone = ($_offsets['bias'] + ($_offsets['daylightBias'] * -1)) * 60 * -1;
 
             if (
                 !empty($_daylightTransition)
                 && ($daylightOffset == $_daylightTransition['offset'] || $daylightOffsetMilestone == $_daylightTransition['offset'])
             ) {
                 // date-time input here contains UTC timezone specifier (+0000),
                 // we have to convert the date to the requested timezone afterwards.
                 $standardDate = new DateTime($_standardTransition['time']);
                 $daylightDate = new DateTime($_daylightTransition['time']);
                 $standardDate->setTimezone($tz);
                 $daylightDate->setTimezone($tz);
 
                 if ($standardDate->format('n') == $_offsets['standardMonth'] &&
                     $daylightDate->format('n') == $_offsets['daylightMonth'] &&
                     $standardDate->format('w') == $_offsets['standardDayOfWeek'] &&
                     $daylightDate->format('w') == $_offsets['daylightDayOfWeek']
                 ) {
                     return $this->_isNthOcurrenceOfWeekdayInMonth($daylightDate, $_offsets['daylightWeek']) &&
                            $this->_isNthOcurrenceOfWeekdayInMonth($standardDate, $_offsets['standardWeek']);
                 }
             }
         }
 
         return false;
     }
 
     /**
      * decode timezone info from activesync
      *
      * @param string $_packedTimezoneInfo the packed timezone info
      * @return array
      */
     protected function _unpackTimezoneInfo($_packedTimezoneInfo)
     {
         $timezoneUnpackString = 'lbias/a64standardName/vstandardYear/vstandardMonth/vstandardDayOfWeek/vstandardWeek/vstandardHour/vstandardMinute/vstandardSecond/vstandardMilliseconds/lstandardBias'
             . '/a64daylightName/vdaylightYear/vdaylightMonth/vdaylightDayOfWeek/vdaylightWeek/vdaylightHour/vdaylightMinute/vdaylightSecond/vdaylightMilliseconds/ldaylightBias';
 
         $timezoneInfo = unpack($timezoneUnpackString, base64_decode($_packedTimezoneInfo));
 
         if ($timezoneInfo['standardHour'] == 23 && $timezoneInfo['standardMilliseconds'] == 999
             && $timezoneInfo['standardMinute'] == 59 && $timezoneInfo['standardSecond'] == 59
         ) {
             $timezoneInfo['standardHour'] = 24;
             $timezoneInfo['standardMinute'] = 0;
             $timezoneInfo['standardSecond'] = 0;
             $timezoneInfo['standardMilliseconds'] = 0;
         }
 
         return $timezoneInfo;
     }
 
     /**
      * Encode timezone info to activesync
      *
      * @param array $_timezoneInfo
      *
      * @return string|null
      */
     protected function _packTimezoneInfo($_timezoneInfo)
     {
         if (!is_array($_timezoneInfo)) {
             return null;
         }
 
         // According to e.g. https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime,
         // 24 is not allowed in the Hour field, and consequently Outlook can't deal with it.
         // This is the same workaround that Outlook applies.
         if ($_timezoneInfo['standardHour'] == 24) {
             $_timezoneInfo['standardHour'] = 23;
             $_timezoneInfo['standardMinute'] = 59;
             $_timezoneInfo['standardSecond'] = 59;
             $_timezoneInfo['standardMilliseconds'] = 999;
         }
 
         $packed = pack(
             "la64vvvvvvvvla64vvvvvvvvl",
             $_timezoneInfo['bias'],
             $_timezoneInfo['standardName'],
             $_timezoneInfo['standardYear'],
             $_timezoneInfo['standardMonth'],
             $_timezoneInfo['standardDayOfWeek'],
             $_timezoneInfo['standardWeek'],
             $_timezoneInfo['standardHour'],
             $_timezoneInfo['standardMinute'],
             $_timezoneInfo['standardSecond'],
             $_timezoneInfo['standardMilliseconds'],
             $_timezoneInfo['standardBias'],
             $_timezoneInfo['daylightName'],
             $_timezoneInfo['daylightYear'],
             $_timezoneInfo['daylightMonth'],
             $_timezoneInfo['daylightDayOfWeek'],
             $_timezoneInfo['daylightWeek'],
             $_timezoneInfo['daylightHour'],
             $_timezoneInfo['daylightMinute'],
             $_timezoneInfo['daylightSecond'],
             $_timezoneInfo['daylightMilliseconds'],
             $_timezoneInfo['daylightBias']
         );
 
         return base64_encode($packed);
     }
 
     /**
      * Returns complete offsets array with all fields empty
      *
      * Used e.g. when reverse-generating ActiveSync Timezone Offset Information
      * based on a given Timezone, {@see getOffsetsForTimezone}
      *
      * @return array
      */
     protected function _getOffsetsTemplate()
     {
         return [
             'bias'              => 0,
             'standardName'      => '',
             'standardYear'      => 0,
             'standardMonth'     => 0,
             'standardDayOfWeek' => 0,
             'standardWeek'      => 0,
             'standardHour'      => 0,
             'standardMinute'    => 0,
             'standardSecond'    => 0,
             'standardMilliseconds' => 0,
             'standardBias'      => 0,
             'daylightName'      => '',
             'daylightYear'      => 0,
             'daylightMonth'     => 0,
             'daylightDayOfWeek' => 0,
             'daylightWeek'      => 0,
             'daylightHour'      => 0,
             'daylightMinute'    => 0,
             'daylightSecond'    => 0,
             'daylightMilliseconds' => 0,
             'daylightBias'      => 0,
         ];
     }
 
     /**
      * Validate and set offsets
      *
      * @param array $value
      *
      * @return bool Validation result
      */
     protected function _validateOffsets($value)
     {
         // validate $value
         if ((!empty($value['standardMonth']) || !empty($value['standardWeek']) || !empty($value['daylightMonth']) || !empty($value['daylightWeek'])) &&
             (empty($value['standardMonth']) || empty($value['standardWeek']) || empty($value['daylightMonth']) || empty($value['daylightWeek']))
         ) {
             // It is not possible not set standard offsets without setting daylight offsets and vice versa
             return false;
         }
 
         return true;
     }
 
     /**
      * Parse and set object property {@see $_startDate}
      *
      * @param mixed $_startDate
      * @return void
      */
     protected function _setStartDate($_startDate)
     {
         if (empty($_startDate)) {
             $this->_setDefaultStartDateIfEmpty();
             return;
         }
 
         $startDateParsed = [];
 
         if (is_string($_startDate)) {
             $startDateParsed['string'] = $_startDate;
             $startDateParsed['ts']     = strtotime($_startDate);
         } elseif (is_int($_startDate)) {
             $startDateParsed['ts']     = $_startDate;
             $startDateParsed['string'] = date('Y-m-d', $_startDate);
         } else {
             $this->_setDefaultStartDateIfEmpty();
             return;
         }
 
         $startDateParsed['object'] = new DateTime($startDateParsed['string']);
 
         $startDateParsed = array_merge($startDateParsed, getdate($startDateParsed['ts']));
 
         $this->_startDate = $startDateParsed;
     }
 
     /**
      * Set default value for object property {@see $_startdate} if it is not set yet.
      * Tries to guess the correct startDate depending on object property {@see $_offsets} and
      * falls back to current date.
      *
      * @param array $_offsets [offsets may be avaluated for a given start year]
      * @return void
      */
     protected function _setDefaultStartDateIfEmpty($_offsets = null)
     {
         if (!empty($this->_startDate)) {
             return;
         }
 
         if (!empty($_offsets['standardYear'])) {
             $this->_setStartDate($_offsets['standardYear'] . '-01-01');
         } else {
             $this->_setStartDate(time());
         }
     }
 
     /**
      * Check if the given {@param $_timezone} matches the {@see $_offsets}
      * and also evaluate the daylight saving time transitions for this timezone if necessary.
      *
      * @param DateTimeZone $timezone
      * @param array        $offsets
      *
      * @return array|bool
      */
     protected function _checkTimezone(DateTimeZone $timezone, $offsets)
     {
         [$standardTransition, $daylightTransition] = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate['year']);
 
         if ($this->_checkTransition($standardTransition, $daylightTransition, $offsets, $timezone)) {
             return $standardTransition;
         }
 
         return false;
     }
 
     /**
      * Returns the standard and daylight transitions for the given {@param $_timezone}
      * and {@param $_year}.
      *
      * @param DateTimeZone $_timezone
      * @param int          $_year
      *
      * @return array
      */
     protected function _getTransitionsForTimezoneAndYear(DateTimeZone $_timezone, $_year)
     {
         $standardTransition = null;
         $daylightTransition = null;
 
         $start       = mktime(0, 0, 0, 12, 1, $_year - 1);
         $end         = mktime(24, 0, 0, 12, 31, $_year);
         $transitions = $_timezone->getTransitions($start, $end);
 
         if ($transitions === false) {
             return [null, null];
         }
 
         foreach ($transitions as $index => $transition) {
             if (date('Y', $transition['ts']) == $_year) {
                 if (isset($transitions[$index + 1]) && date('Y', $transitions[$index]['ts']) == date('Y', $transitions[$index + 1]['ts'])) {
                     $daylightTransition = $transition['isdst'] ? $transition : $transitions[$index + 1];
                     $standardTransition = $transition['isdst'] ? $transitions[$index + 1] : $transition;
                 } else {
                     $daylightTransition = $transition['isdst'] ? $transition : null;
                     $standardTransition = $transition['isdst'] ? null : $transition;
                 }
                 break;
             } elseif ($index == count($transitions) - 1) {
                 $standardTransition = $transition;
             }
         }
 
         return [$standardTransition, $daylightTransition];
     }
 }