public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false)
    {
        // First we will just see if the tzid is a support timezone identifier.
        //
        // The only exception is if the timezone starts with (. This is to
        // handle cases where certain microsoft products generate timezone
        // identifiers that for instance look like:
        //
        // (GMT+01.00) Sarajevo/Warsaw/Zagreb
        //
        // Since PHP 5.5.10, the first bit will be used as the timezone and
        // this method will return just GMT+01:00. This is wrong, because it
        // doesn't take DST into account.
        if ('(' !== $tzid[0]) {
            // PHP has a bug that logs PHP warnings even it shouldn't:
            // https://bugs.php.net/bug.php?id=67881
            //
            // That's why we're checking if we'll be able to successfull instantiate
            // \DateTimeZone() before doing so. Otherwise we could simply instantiate
            // and catch the exception.
            $tzIdentifiers = \DateTimeZone::listIdentifiers();

            try {
                if (
                    (in_array($tzid, $tzIdentifiers)) ||
                    (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
                    (in_array($tzid, self::getIdentifiersBC()))
                ) {
                    return new \DateTimeZone($tzid);
                }
            } catch (\Exception $e) {
            }
        }

        self::loadTzMaps();

        // Next, we check if the tzid is somewhere in our tzid map.
        if (isset(self::$map[$tzid])) {
            return new \DateTimeZone(self::$map[$tzid]);
        }

        // Some Microsoft products prefix the offset first, so let's strip that off
        // and see if it is our tzid map.  We don't want to check for this first just
        // in case there are overrides in our tzid map.
        if (preg_match('/^\((UTC|GMT)(\+|\-)[\d]{2}\:[\d]{2}\) (.*)/', $tzid, $matches)) {
            $tzidAlternate = $matches[3];
            if (isset(self::$map[$tzidAlternate])) {
                return new \DateTimeZone(self::$map[$tzidAlternate]);
            }
        }

        // Maybe the author was hyper-lazy and just included an offset. We
        // support it, but we aren't happy about it.
        if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
            // Note that the path in the source will never be taken from PHP 5.5.10
            // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
            // already gets returned early in this function. Once we drop support
            // for versions under PHP 5.5.10, this bit can be taken out of the
            // source.
            // @codeCoverageIgnoreStart
            return new \DateTimeZone('Etc/GMT'.$matches[1].ltrim(substr($matches[2], 0, 2), '0'));
            // @codeCoverageIgnoreEnd
        }

        if ($vcalendar) {
            // If that didn't work, we will scan VTIMEZONE objects
            foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) {
                if ((string) $vtimezone->TZID === $tzid) {
                    // Some clients add 'X-LIC-LOCATION' with the olson name.
                    if (isset($vtimezone->{'X-LIC-LOCATION'})) {
                        $lic = (string) $vtimezone->{'X-LIC-LOCATION'};

                        // Libical generators may specify strings like
                        // "SystemV/EST5EDT". For those we must remove the
                        // SystemV part.
                        if ('SystemV/' === substr($lic, 0, 8)) {
                            $lic = substr($lic, 8);
                        }

                        return self::getTimeZone($lic, null, $failIfUncertain);
                    }
                    // Microsoft may add a magic number, which we also have an
                    // answer for.
                    if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
                        $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();

                        // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
                        if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) {
                            return new \DateTimeZone('Europe/Sarajevo');
                        }

                        if (isset(self::$microsoftExchangeMap[$cdoId])) {
                            return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
                        }
                    }
                }
            }
        }

        if ($failIfUncertain) {
            throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid);
        }

        // If we got all the way here, we default to UTC.
        return new \DateTimeZone(date_default_timezone_get());
    }