diff --git a/src/.eslintrc.json b/src/.eslintrc.json
index e053a4dfb972..6cfa489bf0d0 100644
--- a/src/.eslintrc.json
+++ b/src/.eslintrc.json
@@ -79,6 +79,7 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
+ "addDateMinutes": false,
"convertTimezoneToLocal": false,
"timezoneToOffset": false,
"startingTag": false,
diff --git a/src/Angular.js b/src/Angular.js
index 7eda3a028b6b..38c564f62df5 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -75,6 +75,7 @@
fromJson,
convertTimezoneToLocal,
timezoneToOffset,
+ addDateMinutes,
startingTag,
tryDecodeURIComponent,
parseKeyValue,
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 5d9bf66fa468..93119d305d42 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -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;
@@ -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 '';
}
});
@@ -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;
}
};
}
diff --git a/src/ng/directive/ngModelOptions.js b/src/ng/directive/ngModelOptions.js
index 02a328ff3748..62408d2ea84a 100644
--- a/src/ng/directive/ngModelOptions.js
+++ b/src/ng/directive/ngModelOptions.js
@@ -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() {
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 8c86b44ceada..658ce6adf955 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -718,6 +718,21 @@ describe('input', function() {
});
+ it('should be possible to override the timezone', function() {
+ var inputElm = helper.compileInput('');
+
+ 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) {
@@ -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('');
+ $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();
+ });
});
});
@@ -987,6 +1019,30 @@ describe('input', function() {
});
+ it('should be possible to override the timezone', function() {
+ var inputElm = helper.compileInput('');
+
+ // 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) {
@@ -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('');
+ // 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();
+ });
});
});
@@ -1193,6 +1268,25 @@ describe('input', function() {
});
+ it('should be possible to override the timezone', function() {
+ var inputElm = helper.compileInput('');
+
+ 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) {
@@ -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('');
+ $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();
+ });
});
@@ -1538,6 +1649,25 @@ describe('input', function() {
});
+ it('should be possible to override the timezone', function() {
+ var inputElm = helper.compileInput('');
+
+ 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) {
@@ -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('');
+ $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();
+ });
});
@@ -1850,6 +1997,24 @@ describe('input', function() {
});
+ it('should be possible to override the timezone', function() {
+ var inputElm = helper.compileInput('');
+
+ 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) {
@@ -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('');
+
+ 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() {
@@ -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('');
+
+ $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();
+ });
});