diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f845fc8ed6..f28359d79f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1481,6 +1481,9 @@ phutil_register_library_map(array( 'PhabricatorTestDataGenerator' => 'applications/lipsum/generator/PhabricatorTestDataGenerator.php', 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', + 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', + 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', + 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', 'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineCursor.php', 'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineDAO.php', 'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/PhabricatorTimelineEvent.php', @@ -3028,7 +3031,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants', 'PhabricatorObjectItemListExample' => 'PhabricatorUIExample', - 'PhabricatorObjectItemListView' => 'AphrontView', + 'PhabricatorObjectItemListView' => 'AphrontTagView', 'PhabricatorObjectItemView' => 'AphrontTagView', 'PhabricatorObjectListView' => 'AphrontView', 'PhabricatorObjectMailReceiver' => 'PhabricatorMailReceiver', @@ -3291,6 +3294,7 @@ phutil_register_library_map(array( 'PhabricatorTestCase' => 'ArcanistPhutilTestCase', 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', + 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php index c31642c3bb..ae45f72035 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php @@ -232,13 +232,7 @@ final class PhabricatorChatLogChannelLogController ); } else if ($at_date) { - $timezone = new DateTimeZone($user->getTimezoneIdentifier()); - try { - $date = new DateTime($at_date, $timezone); - $timestamp = $date->format('U'); - } catch (Exception $e) { - $timestamp = null; - } + $timestamp = PhabricatorTime::parseLocalTime($at_date, $user); if ($timestamp) { $context_logs = $query diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index 4072412fd6..8e06625165 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -50,27 +50,25 @@ final class PhabricatorCountdownEditController $e_text = null; if (!strlen($title)) { $e_text = pht('Required'); - $errors[] = pht('You must give it a name.'); + $errors[] = pht('You must give the countdown a name.'); } - // If the user types something like "5 PM", convert it to a timestamp - // using their local time, not the server time. - $timezone = new DateTimeZone($user->getTimezoneIdentifier()); - - try { - $date = new DateTime($epoch, $timezone); - $timestamp = $date->format('U'); - } catch (Exception $e) { - $errors[] = pht('You entered an incorrect date. You can enter date'. - ' like \'2011-06-26 13:33:37\' to create an event at'. - ' 13:33:37 on the 26th of June 2011.'); - $timestamp = null; + if (strlen($epoch)) { + $timestamp = PhabricatorTime::parseLocalTime($epoch, $user); + if (!$timestamp) { + $errors[] = pht( + 'You entered an incorrect date. You can enter date '. + 'like \'2011-06-26 13:33:37\' to create an event at '. + '13:33:37 on the 26th of June 2011.'); + } + } else { + $e_epoch = pht('Required'); + $errors[] = pht('You must specify the end date for a countdown.'); } - $countdown->setTitle($title); - $countdown->setEpoch($timestamp); - if (!count($errors)) { + $countdown->setTitle($title); + $countdown->setEpoch($timestamp); $countdown->setAuthorPHID($user->getPHID()); $countdown->save(); return id(new AphrontRedirectResponse()) @@ -84,9 +82,7 @@ final class PhabricatorCountdownEditController } if ($countdown->getEpoch()) { - $display_epoch = phabricator_datetime( - $countdown->getEpoch(), - $user); + $display_epoch = phabricator_datetime($countdown->getEpoch(), $user); } else { $display_epoch = $request->getStr('epoch'); } diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index ff5f6ba1f3..ea94b69008 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -709,11 +709,8 @@ final class ManiphestReportController extends ManiphestController { // Do locale-aware parsing so that the user's timezone is assumed for // time windows like "3 PM", rather than assuming the server timezone. - $timezone = new DateTimeZone($user->getTimezoneIdentifier()); - try { - $date = new DateTime($window_str, $timezone); - $window_epoch = $date->format('U'); - } catch (Exception $e) { + $window_epoch = PhabricatorTime::parseLocalTime($window_str, $user); + if (!$window_epoch) { $error = 'Invalid'; $window_epoch = time() - (60 * 60 * 24 * 7); } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index b1c8adc32a..ed69cb4fa1 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -207,18 +207,7 @@ abstract class PhabricatorApplicationSearchEngine { return null; } - $viewer = $this->requireViewer(); - - $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); - - try { - $date = new DateTime($date_time, $timezone); - $timestamp = $date->format('U'); - } catch (Exception $e) { - $timestamp = null; - } - - return $timestamp; + return PhabricatorTime::parseLocalTime($date_time, $this->requireViewer()); } diff --git a/src/infrastructure/time/PhabricatorTime.php b/src/infrastructure/time/PhabricatorTime.php new file mode 100644 index 0000000000..ec5680331d --- /dev/null +++ b/src/infrastructure/time/PhabricatorTime.php @@ -0,0 +1,61 @@ + $epoch, + 'timezone' => $timezone, + ); + + return new PhabricatorTimeGuard(last_key(self::$stack)); + } + + public static function popTime($key) { + if ($key !== last_key(self::$stack)) { + throw new Exception("PhabricatorTime::popTime with bad key."); + } + array_pop(self::$stack); + + if (empty(self::$stack)) { + date_default_timezone_set(self::$originalZone); + } else { + $frame = end(self::$stack); + date_default_timezone_set($frame['timezone']); + } + } + + public static function getNow() { + if (self::$stack) { + $frame = end(self::$stack); + return $frame['epoch']; + } + return time(); + } + + public static function parseLocalTime($time, PhabricatorUser $user) { + $old_zone = date_default_timezone_get(); + + date_default_timezone_set($user->getTimezoneIdentifier()); + $timestamp = (int)strtotime($time, PhabricatorTime::getNow()); + if ($timestamp <= 0) { + $timestamp = null; + } + date_default_timezone_set($old_zone); + + return $timestamp; + } + +} diff --git a/src/infrastructure/time/PhabricatorTimeGuard.php b/src/infrastructure/time/PhabricatorTimeGuard.php new file mode 100644 index 0000000000..2add3db309 --- /dev/null +++ b/src/infrastructure/time/PhabricatorTimeGuard.php @@ -0,0 +1,15 @@ +frameKey = $frame_key; + } + + public function __destruct() { + PhabricatorTime::popTime($this->frameKey); + } + +} diff --git a/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php b/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php new file mode 100644 index 0000000000..54f10d875c --- /dev/null +++ b/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php @@ -0,0 +1,82 @@ +assertEqual( + true, + (PhabricatorTime::getNow() === $t)); + + unset($time); + + $this->assertEqual( + false, + (PhabricatorTime::getNow() === $t)); + } + + public function testParseLocalTime() { + $u = new PhabricatorUser(); + $u->setTimezoneIdentifier('UTC'); + + $v = new PhabricatorUser(); + $v->setTimezoneIdentifier('America/Los_Angeles'); + + $t = 1370202281; // 2013-06-02 12:44:41 -0700 + $time = PhabricatorTime::pushTime($t, 'America/Los_Angeles'); + + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('now', $u)); + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('now', $v)); + + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 12:44:41 -0700', $u)); + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 12:44:41 -0700', $v)); + + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 12:44:41 PDT', $u)); + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 12:44:41 PDT', $v)); + + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 19:44:41', $u)); + $this->assertEqual( + $t, + PhabricatorTime::parseLocalTime('2013-06-02 12:44:41', $v)); + + $this->assertEqual( + $t + 3600, + PhabricatorTime::parseLocalTime('+1 hour', $u)); + $this->assertEqual( + $t + 3600, + PhabricatorTime::parseLocalTime('+1 hour', $v)); + + unset($time); + + $t = 1370239200; // 2013-06-02 23:00:00 -0700 + $time = PhabricatorTime::pushTime($t, 'America/Los_Angeles'); + + // For the UTC user, midnight was 6 hours ago because it's early in the + // morning for htem. For the PDT user, midnight was 23 hours ago. + $this->assertEqual( + $t + (-6 * 3600) + 60, + PhabricatorTime::parseLocalTime('12:01:00 AM', $u)); + $this->assertEqual( + $t + (-23 * 3600) + 60, + PhabricatorTime::parseLocalTime('12:01:00 AM', $v)); + + unset($time); + } + +}