diff --git a/lib/private/datetimezone.php b/lib/private/datetimezone.php index e4a0af4fc6..727ce321db 100644 --- a/lib/private/datetimezone.php +++ b/lib/private/datetimezone.php @@ -44,19 +44,66 @@ class DateTimeZone implements IDateTimeZone { $timeZone = $this->config->getUserValue($this->session->get('user_id'), 'core', 'timezone', null); if ($timeZone === null) { if ($this->session->exists('timezone')) { - $offsetHours = $this->session->get('timezone'); - // Note: the timeZone name is the inverse to the offset, - // so a positive offset means negative timeZone - // and the other way around. - if ($offsetHours > 0) { - return new \DateTimeZone('Etc/GMT-' . $offsetHours); - } else { - return new \DateTimeZone('Etc/GMT+' . abs($offsetHours)); - } - } else { - return new \DateTimeZone('UTC'); + return $this->guessTimeZoneFromOffset($this->session->get('timezone')); } + $timeZone = $this->getDefaultTimeZone(); } - return new \DateTimeZone($timeZone); + + try { + return new \DateTimeZone($timeZone); + } catch (\Exception $e) { + \OCP\Util::writeLog('datetimezone', 'Failed to created DateTimeZone "' . $timeZone . "'", \OCP\Util::DEBUG); + return new \DateTimeZone($this->getDefaultTimeZone()); + } + } + + /** + * Guess the DateTimeZone for a given offset + * + * We first try to find a Etc/GMT* timezone, if that does not exist, + * we try to find it manually, before falling back to UTC. + * + * @param mixed $offset + * @return \DateTimeZone + */ + protected function guessTimeZoneFromOffset($offset) { + try { + // Note: the timeZone name is the inverse to the offset, + // so a positive offset means negative timeZone + // and the other way around. + if ($offset > 0) { + $timeZone = 'Etc/GMT-' . $offset; + } else { + $timeZone = 'Etc/GMT+' . abs($offset); + } + + return new \DateTimeZone($timeZone); + } catch (\Exception $e) { + // If the offset has no Etc/GMT* timezone, + // we try to guess one timezone that has the same offset + foreach (\DateTimeZone::listIdentifiers() as $timeZone) { + $dtz = new \DateTimeZone($timeZone); + $dtOffset = $dtz->getOffset(new \DateTime()); + if ($dtOffset == 3600 * $offset) { + return $dtz; + } + } + + // No timezone found, fallback to UTC + \OCP\Util::writeLog('datetimezone', 'Failed to find DateTimeZone for offset "' . $offset . "'", \OCP\Util::DEBUG); + return new \DateTimeZone($this->getDefaultTimeZone()); + } + } + + /** + * Get the default timezone of the server + * + * Falls back to UTC if it is not yet set. + * + * @return string + */ + protected function getDefaultTimeZone() { + $serverTimeZone = date_default_timezone_get(); + return $serverTimeZone ?: 'UTC'; } } diff --git a/tests/lib/util.php b/tests/lib/util.php index 6870b21807..a852eee8ad 100644 --- a/tests/lib/util.php +++ b/tests/lib/util.php @@ -52,16 +52,31 @@ class Test_Util extends \Test\TestCase { OC_Util::formatDate(1350129205, false, 'Mordor/Barad-dûr'); } - function testFormatDateWithTZFromSession() { + public function formatDateWithTZFromSessionData() + { + return array( + array(3, 'October 13, 2012 at 2:53:25 PM GMT+3'), + array(15, 'October 13, 2012 at 11:53:25 AM GMT+0'), + array(-13, 'October 13, 2012 at 11:53:25 AM GMT+0'), + array(3.5, 'October 13, 2012 at 3:23:25 PM GMT+3:30'), + array(9.5, 'October 13, 2012 at 9:23:25 PM GMT+9:30'), + array(-4.5, 'October 13, 2012 at 7:23:25 AM GMT-4:30'), + array(15.5, 'October 13, 2012 at 11:53:25 AM GMT+0'), + ); + } + + /** + * @dataProvider formatDateWithTZFromSessionData + */ + function testFormatDateWithTZFromSession($offset, $expected) { date_default_timezone_set("UTC"); $oldDateTimeFormatter = \OC::$server->query('DateTimeFormatter'); - \OC::$server->getSession()->set('timezone', 3); + \OC::$server->getSession()->set('timezone', $offset); $newDateTimeFormatter = new \OC\DateTimeFormatter(\OC::$server->getDateTimeZone()->getTimeZone(), new \OC_L10N('lib', 'en')); $this->setDateFormatter($newDateTimeFormatter); $result = OC_Util::formatDate(1350129205, false); - $expected = 'October 13, 2012 at 2:53:25 PM GMT+3'; $this->assertEquals($expected, $result); $this->setDateFormatter($oldDateTimeFormatter);