[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:
Jonathan Budzenski 2015-11-12 16:14:46 -06:00
parent 65d68a8734
commit f9ee4e3ab0
4 changed files with 109 additions and 10 deletions

View 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();
});
});

View 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;
}
}
};
});
});

View file

@ -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>

View file

@ -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':