Skip to content

Update upstream #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
"addDateMinutes": false,
"convertTimezoneToLocal": false,
"timezoneToOffset": false,
"startingTag": false,
Expand Down
1 change: 1 addition & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
fromJson,
convertTimezoneToLocal,
timezoneToOffset,
addDateMinutes,
startingTag,
tryDecodeURIComponent,
parseKeyValue,
Expand Down
33 changes: 25 additions & 8 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1468,20 +1468,17 @@ 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
// contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone) {
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
}
return parsedDate;
return parseDateAndConvertTimeZoneToLocal(value, previousDate);
}
ctrl.$$parserName = type;
return undefined;
Expand All @@ -1493,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 '';
}
});
Expand Down Expand Up @@ -1531,7 +1531,24 @@ function createDateInputType(type, regexp, parseDate, format) {
}

function parseObservedDateValue(val) {
return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val;
}

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);
}
return parsedDate;
}
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/ng/directive/ngModelOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
211 changes: 211 additions & 0 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -866,6 +881,23 @@ describe('input', function() {

expect($rootScope.form.alias.$error.max).toBeFalsy();
});

it('should validate when timezone is provided.', function() {
inputElm = helper.compileInput('<input type="month" ng-model="value" name="alias" ' +
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
$rootScope.maxVal = '2013-01';
$rootScope.value = new Date(Date.UTC(2013, 0, 1, 0, 0, 0));
$rootScope.$digest();

expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();

$rootScope.value = '';
helper.changeInputValueTo('2013-01');
expect(inputElm).toBeValid();
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
});
});

Expand Down Expand Up @@ -987,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) {
Expand Down Expand Up @@ -1099,6 +1155,25 @@ describe('input', function() {

expect($rootScope.form.alias.$error.max).toBeFalsy();
});

it('should validate when timezone is provided.', function() {
inputElm = helper.compileInput('<input type="week" ng-model="value" name="alias" ' +
'max="{{ maxVal }}" ng-model-options="{timezone: \'-2400\', allowInvalid: true}"/>');
// The calendar week comparison date is January 17. Setting the timezone to -2400
// makes the January 18 date value valid.
$rootScope.maxVal = '2013-W03';
$rootScope.value = new Date(Date.UTC(2013, 0, 18));
$rootScope.$digest();

expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();

$rootScope.value = '';
helper.changeInputValueTo('2013-W03');
expect(inputElm).toBeValid();
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
});
});

Expand Down Expand Up @@ -1193,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) {
Expand Down Expand Up @@ -1368,6 +1462,23 @@ describe('input', function() {

expect($rootScope.form.alias.$error.max).toBeFalsy();
});

it('should validate when timezone is provided.', function() {
inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" name="alias" ' +
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
$rootScope.maxVal = '2013-01-01T00:00:00';
$rootScope.value = new Date(Date.UTC(2013, 0, 1, 0, 0, 0));
$rootScope.$digest();

expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();

$rootScope.value = '';
helper.changeInputValueTo('2013-01-01T00:00:00');
expect(inputElm).toBeValid();
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
});


Expand Down Expand Up @@ -1538,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) {
Expand Down Expand Up @@ -1686,6 +1816,23 @@ describe('input', function() {

expect($rootScope.form.alias.$error.max).toBeFalsy();
});

it('should validate when timezone is provided.', function() {
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" ' +
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
$rootScope.maxVal = '22:30:00';
$rootScope.value = new Date(Date.UTC(1970, 0, 1, 22, 30, 0));
$rootScope.$digest();

expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();

$rootScope.value = '';
helper.changeInputValueTo('22:30:00');
expect(inputElm).toBeValid();
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
});


Expand Down Expand Up @@ -1850,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) {
Expand Down Expand Up @@ -1935,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() {
Expand Down Expand Up @@ -2031,6 +2224,24 @@ describe('input', function() {

expect($rootScope.form.alias.$error.max).toBeFalsy();
});

it('should validate when timezone is provided.', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" name="alias" ' +
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');

$rootScope.maxVal = '2013-12-01';
$rootScope.value = new Date(Date.UTC(2013, 11, 1, 0, 0, 0));
$rootScope.$digest();

expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();

$rootScope.value = '';
helper.changeInputValueTo('2013-12-01');
expect(inputElm).toBeValid();
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
});


Expand Down