mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-27 07:50:57 +01:00
Detect timezone discrepancies and prompt users to reconcile them
Summary: Ref T3025. This adds a check for different client/server timezone offsets and gives users an option to fix them or ignore them. Test Plan: - Fiddled with timezone in Settings and System Preferences. - Got appropriate prompts and behavior after simulating various trips to and from exotic locales. In particular, this slightly tricky case seems to work correctly: - Travel to NY. - Ignore discrepancy (you're only there for a couple hours for an important meeting, and returning to SF on a later flight). - Return to SF for a few days. - Travel back to NY. - You should be prompted again, since you left the timezone after you ignored the discrepancy. {F1654528} {F1654529} {F1654530} Reviewers: chad Reviewed By: chad Maniphest Tasks: T3025 Differential Revision: https://secure.phabricator.com/D15961
This commit is contained in:
parent
e902fc0e2a
commit
a91004ef1b
9 changed files with 205 additions and 1 deletions
|
@ -81,6 +81,7 @@ return array(
|
|||
'javelin-behavior-scrollbar',
|
||||
'javelin-behavior-durable-column',
|
||||
'conpherence-thread-manager',
|
||||
'javelin-behavior-detect-timezone',
|
||||
),
|
||||
'core.pkg.css' => array(
|
||||
'phabricator-core-css',
|
||||
|
|
|
@ -3355,6 +3355,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php',
|
||||
'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php',
|
||||
'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php',
|
||||
'PhabricatorSettingsTimezoneController' => 'applications/settings/controller/PhabricatorSettingsTimezoneController.php',
|
||||
'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
|
||||
'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php',
|
||||
'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
|
||||
|
@ -8065,6 +8066,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSettingsMainController' => 'PhabricatorController',
|
||||
'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
||||
'PhabricatorSettingsPanel' => 'Phobject',
|
||||
'PhabricatorSettingsTimezoneController' => 'PhabricatorController',
|
||||
'PhabricatorSetupCheck' => 'Phobject',
|
||||
'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorSetupIssue' => 'Phobject',
|
||||
|
|
|
@ -755,6 +755,17 @@ final class PhabricatorUser
|
|||
return new DateTimeZone($this->getTimezoneIdentifier());
|
||||
}
|
||||
|
||||
public function getTimeZoneOffset() {
|
||||
$timezone = $this->getTimeZone();
|
||||
$now = new DateTime('@'.PhabricatorTime::getNow());
|
||||
$offset = $timezone->getOffset($now);
|
||||
|
||||
// Javascript offsets are in minutes and have the opposite sign.
|
||||
$offset = -(int)($offset / 60);
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
public function formatShortDateTime($when, $now = null) {
|
||||
if ($now === null) {
|
||||
$now = PhabricatorTime::getNow();
|
||||
|
|
|
@ -32,6 +32,8 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication {
|
|||
'(?:(?P<id>\d+)/)?(?:panel/(?P<key>[^/]+)/)?'
|
||||
=> 'PhabricatorSettingsMainController',
|
||||
'adjust/' => 'PhabricatorSettingsAdjustController',
|
||||
'timezone/(?P<offset>[^/]+)/'
|
||||
=> 'PhabricatorSettingsTimezoneController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSettingsTimezoneController
|
||||
extends PhabricatorController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$client_offset = $request->getURIData('offset');
|
||||
$client_offset = (int)$client_offset;
|
||||
|
||||
$timezones = DateTimeZone::listIdentifiers();
|
||||
$now = new DateTime('@'.PhabricatorTime::getNow());
|
||||
|
||||
$options = array(
|
||||
'ignore' => pht('Ignore Conflict'),
|
||||
);
|
||||
|
||||
foreach ($timezones as $identifier) {
|
||||
$zone = new DateTimeZone($identifier);
|
||||
$offset = -($zone->getOffset($now) / 60);
|
||||
if ($offset == $client_offset) {
|
||||
$options[$identifier] = $identifier;
|
||||
}
|
||||
}
|
||||
|
||||
$settings_help = pht(
|
||||
'You can change your date and time preferences in Settings.');
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$timezone = $request->getStr('timezone');
|
||||
|
||||
$pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET;
|
||||
|
||||
$preferences = $viewer->loadPreferences();
|
||||
|
||||
if ($timezone == 'ignore') {
|
||||
$preferences
|
||||
->setPreference($pref_ignore, $client_offset)
|
||||
->save();
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Conflict Ignored'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'The conflict between your browser and profile timezone '.
|
||||
'settings will be ignored.'))
|
||||
->appendParagraph($settings_help)
|
||||
->addCancelButton('/', pht('Done'));
|
||||
}
|
||||
|
||||
if (isset($options[$timezone])) {
|
||||
$preferences
|
||||
->setPreference($pref_ignore, null)
|
||||
->save();
|
||||
|
||||
$viewer
|
||||
->setTimezoneIdentifier($timezone)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
$server_offset = $viewer->getTimeZoneOffset();
|
||||
|
||||
if ($client_offset == $server_offset) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Timezone Calibrated'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Your browser timezone and profile timezone are now '.
|
||||
'in agreement (%s).',
|
||||
$this->formatOffset($client_offset)))
|
||||
->appendParagraph($settings_help)
|
||||
->addCancelButton('/', pht('Done'));
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setName('timezone')
|
||||
->setLabel(pht('Timezone'))
|
||||
->setOptions($options));
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Adjust Timezone'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Your browser timezone (%s) differs from your profile timezone '.
|
||||
'(%s). You can ignore this conflict or adjust your profile setting '.
|
||||
'to match your client.',
|
||||
$this->formatOffset($client_offset),
|
||||
$this->formatOffset($server_offset)))
|
||||
->appendForm($form)
|
||||
->addCancelButton(pht('Cancel'))
|
||||
->addSubmitButton(pht('Submit'));
|
||||
}
|
||||
|
||||
private function formatOffset($offset) {
|
||||
$offset = $offset / 60;
|
||||
|
||||
if ($offset >= 0) {
|
||||
return pht('GMT-%d', $offset);
|
||||
} else {
|
||||
return pht('GMT+%d', -$offset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
|
|||
$pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT;
|
||||
$pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT;
|
||||
$pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
|
||||
$pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET;
|
||||
$preferences = $user->loadPreferences();
|
||||
|
||||
$errors = array();
|
||||
|
@ -41,7 +42,8 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
|
|||
$request->getStr($pref_date))
|
||||
->setPreference(
|
||||
$pref_week_start,
|
||||
$request->getStr($pref_week_start));
|
||||
$request->getStr($pref_week_start))
|
||||
->setPreference($pref_ignore, null);
|
||||
|
||||
if (!$errors) {
|
||||
$preferences->save();
|
||||
|
|
|
@ -43,6 +43,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
|
||||
const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed';
|
||||
const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites';
|
||||
const PREFERENCE_IGNORE_OFFSET = 'time.offset.ignore';
|
||||
|
||||
// These are in an unusual order for historic reasons.
|
||||
const MAILTAG_PREFERENCE_NOTIFY = 0;
|
||||
|
|
|
@ -223,6 +223,30 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
|
|||
}
|
||||
|
||||
if ($user) {
|
||||
if ($user->isLoggedIn()) {
|
||||
$offset = $user->getTimeZoneOffset();
|
||||
|
||||
$preferences = $user->loadPreferences();
|
||||
$ignore_key = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET;
|
||||
|
||||
$ignore = $preferences->getPreference($ignore_key);
|
||||
if (!strlen($ignore)) {
|
||||
$ignore = null;
|
||||
}
|
||||
|
||||
Javelin::initBehavior(
|
||||
'detect-timezone',
|
||||
array(
|
||||
'offset' => $offset,
|
||||
'uri' => '/settings/timezone/',
|
||||
'message' => pht(
|
||||
'Your browser timezone setting differs from the timezone '.
|
||||
'setting in your profile.'),
|
||||
'ignoreKey' => $ignore_key,
|
||||
'ignore' => $ignore,
|
||||
));
|
||||
}
|
||||
|
||||
$default_img_uri =
|
||||
celerity_get_resource_uri(
|
||||
'rsrc/image/icon/fatcow/document_black.png');
|
||||
|
|
53
webroot/rsrc/js/core/behavior-detect-timezone.js
Normal file
53
webroot/rsrc/js/core/behavior-detect-timezone.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @provides javelin-behavior-detect-timezone
|
||||
* @requires javelin-behavior
|
||||
* javelin-uri
|
||||
* phabricator-notification
|
||||
*/
|
||||
|
||||
JX.behavior('detect-timezone', function(config) {
|
||||
|
||||
var offset = new Date().getTimezoneOffset();
|
||||
var ignore = config.ignore;
|
||||
|
||||
if (ignore !== null) {
|
||||
// If we're ignoring a client offset and it's the current offset, just
|
||||
// bail. This means the user has chosen to ignore the clock difference
|
||||
// between the current client setting and their server setting.
|
||||
if (offset == ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're ignoring a client offset but the current offset is different,
|
||||
// wipe the offset. If you go from SF to NY, ignore the difference, return
|
||||
// to SF, then travel back to NY a few months later, we want to prompt you
|
||||
// again. This code will clear the ignored setting upon your return to SF.
|
||||
new JX.Request('/settings/adjust/', JX.bag)
|
||||
.setData({key: config.ignoreKey, value: ''})
|
||||
.send();
|
||||
|
||||
ignore = null;
|
||||
}
|
||||
|
||||
// If the client and server clocks are in sync, we're all set.
|
||||
if (offset == config.offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new JX.Notification()
|
||||
.alterClassName('jx-notification-alert', true)
|
||||
.setContent(config.message)
|
||||
.setDuration(0);
|
||||
|
||||
notification.listen('activate', function() {
|
||||
JX.Stratcom.context().kill();
|
||||
notification.hide();
|
||||
|
||||
var uri = config.uri + offset + '/';
|
||||
|
||||
new JX.Workflow(uri)
|
||||
.start();
|
||||
});
|
||||
|
||||
notification.show();
|
||||
});
|
Loading…
Reference in a new issue