fix(input): allow overriding timezone for date input types

This commit also fixes a bug where part of the Date object
was re-used even after the input was emptied.

Fixes #16181
Closes #13382
Closes #16336
This commit is contained in:
Martin Staffa
2018-04-06 16:40:14 +02:00
committed by GitHub
parent b7bb797e5c
commit 3a2aea78ad
5 changed files with 142 additions and 2 deletions
+1
View File
@@ -79,6 +79,7 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
"addDateMinutes": false,
"convertTimezoneToLocal": false,
"timezoneToOffset": false,
"startingTag": false,
+1
View File
@@ -75,6 +75,7 @@
fromJson,
convertTimezoneToLocal,
timezoneToOffset,
addDateMinutes,
startingTag,
tryDecodeURIComponent,
parseKeyValue,
+15 -2
View File
@@ -1468,11 +1468,12 @@ function createDateInputType(type, regexp, parseDate, format) {
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
badInputChecker(scope, element, attr, ctrl, type);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var timezone = ctrl && ctrl.$options.getOption('timezone');
var previousDate;
var previousTimezone;
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (regexp.test(value)) {
// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
@@ -1489,12 +1490,15 @@ function createDateInputType(type, regexp, parseDate, format) {
}
if (isValidDate(value)) {
previousDate = value;
if (previousDate && timezone) {
var timezone = ctrl.$options.getOption('timezone');
if (timezone) {
previousTimezone = timezone;
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
}
return $filter('date')(value, format, timezone);
} else {
previousDate = null;
previousTimezone = null;
return '';
}
});
@@ -1531,7 +1535,16 @@ function createDateInputType(type, regexp, parseDate, format) {
}
function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
var timezone = ctrl.$options.getOption('timezone');
if (previousTimezone && previousTimezone !== timezone) {
// If the timezone has changed, adjust the previousDate to the default timezone
// so that the new date is converted with the correct timezone offset
previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone));
}
var parsedDate = parseDate(value, previousDate);
if (!isNaN(parsedDate) && timezone) {
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
}
+2
View File
@@ -462,6 +462,8 @@ defaultModelOptions = new ModelOptions({
* continental US time zone abbreviations, but for general use, use a time zone offset, for
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
* If not specified, the timezone of the browser will be used.
* Note that changing the timezone will have no effect on the current date, and is only applied after
* the next input / model change.
*
*/
var ngModelOptionsDirective = function() {
+123
View File
@@ -718,6 +718,21 @@ describe('input', function() {
});
it('should be possible to override the timezone', function() {
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
helper.changeInputValueTo('2013-07');
expect(+$rootScope.value).toBe(Date.UTC(2013, 6, 1));
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2013, 6, 1));
});
expect(inputElm.val()).toBe('2013-06');
});
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
@@ -1004,6 +1019,30 @@ describe('input', function() {
});
it('should be possible to override the timezone', function() {
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
// January 19 2013 is a Saturday
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
});
expect(inputElm.val()).toBe('2013-W03');
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+2400'});
// To check that the timezone overwrite works, apply an offset of +24 hours.
// Since January 19 is a Saturday, +24 will turn the formatted Date into January 20 - Sunday -
// which is in calendar week 4 instead of 3.
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
});
// Verifying that the displayed week is week 4 confirms that overriding the timezone worked
expect(inputElm.val()).toBe('2013-W04');
});
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
@@ -1229,6 +1268,25 @@ describe('input', function() {
});
it('should be possible to override the timezone', function() {
var inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
helper.changeInputValueTo('2000-01-01T01:02');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+0500'});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2001, 0, 1, 1, 2, 0));
});
expect(inputElm.val()).toBe('2001-01-01T06:02:00.000');
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
helper.changeInputValueTo('2000-01-01T01:02');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
});
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
@@ -1591,6 +1649,25 @@ describe('input', function() {
});
it('should be possible to override the timezone', function() {
var inputElm = helper.compileInput('<input type="time" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
helper.changeInputValueTo('23:02:00');
expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 23, 2, 0));
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(1971, 0, 1, 23, 2, 0));
});
expect(inputElm.val()).toBe('18:02:00.000');
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
helper.changeInputValueTo('23:02:00');
// The year is still set from the previous date
expect(+$rootScope.value).toBe(Date.UTC(1971, 0, 1, 23, 2, 0));
});
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
@@ -1920,6 +1997,24 @@ describe('input', function() {
});
it('should be possible to override the timezone', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
helper.changeInputValueTo('2000-01-01');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1));
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2001, 0, 1));
});
expect(inputElm.val()).toBe('2000-12-31');
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
helper.changeInputValueTo('2000-01-01');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
});
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
@@ -2005,6 +2100,34 @@ describe('input', function() {
dealoc(formElm);
});
it('should not reuse the hours part of a previous date object after changing the timezone', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
helper.changeInputValueTo('2000-01-01');
// The Date parser sets the hours part of the Date to 0 (00:00) (UTC)
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
// Change the timezone offset so that the display date is a day earlier
// This does not change the model, but our implementation
// internally caches a Date object with this offset
// and re-uses it if part of the Date changes.
// See https://github.com/angular/angular.js/commit/1a1ef62903c8fdf4ceb81277d966a8eff67f0a96
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2000, 0, 1, 0));
});
expect(inputElm.val()).toBe('1999-12-31');
// At this point, the cached Date has its hours set to to 19 (00:00 - 05:00 = 19:00)
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
// When changing the timezone back to UTC, the hours part of the Date should be set to
// the default 0 (UTC) and not use the modified value of the cached Date object.
helper.changeInputValueTo('2000-01-01');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
});
describe('min', function() {
it('should invalidate', function() {