mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[timezones] Prevent timepicker day from changing when modifying
hours/minutes. Closes #5370. When a moment date with a timezone is passed to a bootstrap timepicker the timezone is converted to browser time. This can cause the timepicker to show a day that does not match with an input field used to modify the date string. On input this will offset the timezone to prevent the day from changing, and remove the offfset before outputting.
This commit is contained in:
parent
65d68a8734
commit
f9ee4e3ab0
4 changed files with 109 additions and 10 deletions
52
src/ui/public/timepicker/__tests__/offset_timezone.js
Normal file
52
src/ui/public/timepicker/__tests__/offset_timezone.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
var angular = require('angular');
|
||||
var expect = require('expect.js');
|
||||
var ngMock = require('ngMock');
|
||||
var moment = require('moment-timezone');
|
||||
var sinon = require('sinon');
|
||||
var $ = require('jquery');
|
||||
|
||||
require('ui/timepicker/offset_timezone');
|
||||
|
||||
describe('Offset timezone', function () {
|
||||
let $compile;
|
||||
let $rootScope;
|
||||
let element;
|
||||
let getTimezoneOffset;
|
||||
|
||||
const html = '<div offset-timezone ng-model="value"/>{{value}}</div>';
|
||||
const timezoneOffset = 60;
|
||||
const mockDate = '2015-11-23T15:01:18-01:00';
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
getTimezoneOffset = sinon.stub(Date.prototype, 'getTimezoneOffset', function () {
|
||||
return timezoneOffset;
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(function () {
|
||||
element = $compile(html)($rootScope);
|
||||
});
|
||||
|
||||
it('should offset the timezone so the day is not changed when converting from moment to the Date constructor', function () {
|
||||
var mockDate = $rootScope.value = moment(mockDate).tz('UTC');
|
||||
$rootScope.$digest();
|
||||
var offsetDate = moment(element.controller('ngModel').$modelValue);
|
||||
|
||||
expect(offsetDate.diff(mockDate, 'minutes')).to.be(timezoneOffset);
|
||||
});
|
||||
|
||||
it('should keep the date the same when reading from the DOM', function () {
|
||||
var mockDate = $rootScope.value = moment(mockDate).tz('UTC');
|
||||
$rootScope.$digest();
|
||||
var domDate = moment($(element).text().replace(/"/g, ''));
|
||||
|
||||
expect(domDate.diff(mockDate, 'minutes')).to.be(0);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
getTimezoneOffset.restore();
|
||||
});
|
||||
});
|
46
src/ui/public/timepicker/offset_timezone.js
Normal file
46
src/ui/public/timepicker/offset_timezone.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var angular = require('angular');
|
||||
var moment = require('moment');
|
||||
|
||||
require('ui/modules')
|
||||
.get('kibana')
|
||||
.directive('offsetTimezone', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function (scope, $el, attrs, ngModelCntrl) {
|
||||
ngModelCntrl.$formatters.unshift(toDatePicker);
|
||||
ngModelCntrl.$parsers.unshift(fromDatePicker);
|
||||
|
||||
// State for whether the last change was internal or external
|
||||
// Internal changes(i.e selecting a new day multiple times) should not
|
||||
// continue to offset the date.
|
||||
var offsetDate = false;
|
||||
|
||||
function fromDatePicker(value) {
|
||||
if (!value) return;
|
||||
var date = moment(value);
|
||||
|
||||
if (offsetDate) {
|
||||
var offset = value.getTimezoneOffset() + date.utcOffset();
|
||||
offsetDate = false;
|
||||
date.minutes(date.minutes() - offset);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
function toDatePicker(value) {
|
||||
if (!value) return;
|
||||
|
||||
var date = new Date(value.format('YYYY-MM-DDTHH:mm:ss.SSSZ'));
|
||||
var offset = date.getTimezoneOffset() + value.utcOffset();
|
||||
date.setMinutes(date.getMinutes() + offset);
|
||||
offsetDate = true;
|
||||
|
||||
ngModelCntrl.$modelValue = date;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -124,7 +124,11 @@
|
|||
<input type="text" required class="form-control" input-datetime="{{format}}" ng-model="absolute.from">
|
||||
</div>
|
||||
<div>
|
||||
<datepicker ng-model="absolute.from" max-date="absolute.to" show-weeks="false"></datepicker>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="absolute.from"
|
||||
max-date="absolute.to"
|
||||
show-weeks="false"></datepicker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -137,7 +141,11 @@
|
|||
<input type="text" required class="form-control" input-datetime="{{format}}" ng-model="absolute.to">
|
||||
</div>
|
||||
<div>
|
||||
<datepicker ng-model="absolute.to" min-date="absolute.from" show-weeks="false"></datepicker>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="absolute.to"
|
||||
min-date="absolute.from"
|
||||
show-weeks="false"></datepicker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ define(function (require) {
|
|||
require('ui/timepicker/quick_ranges');
|
||||
require('ui/timepicker/refresh_intervals');
|
||||
require('ui/timepicker/time_units');
|
||||
require('ui/timepicker/offset_timezone');
|
||||
|
||||
module.directive('kbnTimepicker', function (quickRanges, timeUnits, refreshIntervals) {
|
||||
return {
|
||||
|
@ -60,14 +61,6 @@ define(function (require) {
|
|||
{text: 'Years ago', value: 'y'},
|
||||
];
|
||||
|
||||
$scope.$watch('absolute.from', function (date) {
|
||||
if (_.isDate(date)) $scope.absolute.from = moment(date);
|
||||
});
|
||||
|
||||
$scope.$watch('absolute.to', function (date) {
|
||||
if (_.isDate(date)) $scope.absolute.to = moment(date);
|
||||
});
|
||||
|
||||
$scope.setMode = function (thisMode) {
|
||||
switch (thisMode) {
|
||||
case 'quick':
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue