Skip to content

Commit da74b5d

Browse files
Merge pull request #74 from angular/master
Update upstream
2 parents 0d08754 + 3a2aea7 commit da74b5d

File tree

5 files changed

+240
-8
lines changed

5 files changed

+240
-8
lines changed

src/.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"toJsonReplacer": false,
8080
"toJson": false,
8181
"fromJson": false,
82+
"addDateMinutes": false,
8283
"convertTimezoneToLocal": false,
8384
"timezoneToOffset": false,
8485
"startingTag": false,

src/Angular.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
fromJson,
7676
convertTimezoneToLocal,
7777
timezoneToOffset,
78+
addDateMinutes,
7879
startingTag,
7980
tryDecodeURIComponent,
8081
parseKeyValue,

src/ng/directive/input.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,20 +1468,17 @@ function createDateInputType(type, regexp, parseDate, format) {
14681468
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
14691469
badInputChecker(scope, element, attr, ctrl, type);
14701470
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
1471-
var timezone = ctrl && ctrl.$options.getOption('timezone');
14721471
var previousDate;
1472+
var previousTimezone;
14731473

14741474
ctrl.$parsers.push(function(value) {
14751475
if (ctrl.$isEmpty(value)) return null;
1476+
14761477
if (regexp.test(value)) {
14771478
// Note: We cannot read ctrl.$modelValue, as there might be a different
14781479
// parser/formatter in the processing chain so that the model
14791480
// contains some different data format!
1480-
var parsedDate = parseDate(value, previousDate);
1481-
if (timezone) {
1482-
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
1483-
}
1484-
return parsedDate;
1481+
return parseDateAndConvertTimeZoneToLocal(value, previousDate);
14851482
}
14861483
ctrl.$$parserName = type;
14871484
return undefined;
@@ -1493,12 +1490,15 @@ function createDateInputType(type, regexp, parseDate, format) {
14931490
}
14941491
if (isValidDate(value)) {
14951492
previousDate = value;
1496-
if (previousDate && timezone) {
1493+
var timezone = ctrl.$options.getOption('timezone');
1494+
if (timezone) {
1495+
previousTimezone = timezone;
14971496
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
14981497
}
14991498
return $filter('date')(value, format, timezone);
15001499
} else {
15011500
previousDate = null;
1501+
previousTimezone = null;
15021502
return '';
15031503
}
15041504
});
@@ -1531,7 +1531,24 @@ function createDateInputType(type, regexp, parseDate, format) {
15311531
}
15321532

15331533
function parseObservedDateValue(val) {
1534-
return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
1534+
return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val;
1535+
}
1536+
1537+
function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
1538+
var timezone = ctrl.$options.getOption('timezone');
1539+
1540+
if (previousTimezone && previousTimezone !== timezone) {
1541+
// If the timezone has changed, adjust the previousDate to the default timezone
1542+
// so that the new date is converted with the correct timezone offset
1543+
previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone));
1544+
}
1545+
1546+
var parsedDate = parseDate(value, previousDate);
1547+
1548+
if (!isNaN(parsedDate) && timezone) {
1549+
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
1550+
}
1551+
return parsedDate;
15351552
}
15361553
};
15371554
}

src/ng/directive/ngModelOptions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ defaultModelOptions = new ModelOptions({
462462
* continental US time zone abbreviations, but for general use, use a time zone offset, for
463463
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
464464
* If not specified, the timezone of the browser will be used.
465+
* Note that changing the timezone will have no effect on the current date, and is only applied after
466+
* the next input / model change.
465467
*
466468
*/
467469
var ngModelOptionsDirective = function() {

test/ng/directive/inputSpec.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,21 @@ describe('input', function() {
718718
});
719719

720720

721+
it('should be possible to override the timezone', function() {
722+
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
723+
724+
helper.changeInputValueTo('2013-07');
725+
expect(+$rootScope.value).toBe(Date.UTC(2013, 6, 1));
726+
727+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
728+
729+
$rootScope.$apply(function() {
730+
$rootScope.value = new Date(Date.UTC(2013, 6, 1));
731+
});
732+
expect(inputElm.val()).toBe('2013-06');
733+
});
734+
735+
721736
they('should use any timezone if specified in the options (format: $prop)',
722737
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
723738
function(tz) {
@@ -866,6 +881,23 @@ describe('input', function() {
866881

867882
expect($rootScope.form.alias.$error.max).toBeFalsy();
868883
});
884+
885+
it('should validate when timezone is provided.', function() {
886+
inputElm = helper.compileInput('<input type="month" ng-model="value" name="alias" ' +
887+
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
888+
$rootScope.maxVal = '2013-01';
889+
$rootScope.value = new Date(Date.UTC(2013, 0, 1, 0, 0, 0));
890+
$rootScope.$digest();
891+
892+
expect($rootScope.form.alias.$error.max).toBeFalsy();
893+
expect($rootScope.form.alias.$valid).toBeTruthy();
894+
895+
$rootScope.value = '';
896+
helper.changeInputValueTo('2013-01');
897+
expect(inputElm).toBeValid();
898+
expect($rootScope.form.alias.$error.max).toBeFalsy();
899+
expect($rootScope.form.alias.$valid).toBeTruthy();
900+
});
869901
});
870902
});
871903

@@ -987,6 +1019,30 @@ describe('input', function() {
9871019
});
9881020

9891021

1022+
it('should be possible to override the timezone', function() {
1023+
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1024+
1025+
// January 19 2013 is a Saturday
1026+
$rootScope.$apply(function() {
1027+
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
1028+
});
1029+
1030+
expect(inputElm.val()).toBe('2013-W03');
1031+
1032+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+2400'});
1033+
1034+
// To check that the timezone overwrite works, apply an offset of +24 hours.
1035+
// Since January 19 is a Saturday, +24 will turn the formatted Date into January 20 - Sunday -
1036+
// which is in calendar week 4 instead of 3.
1037+
$rootScope.$apply(function() {
1038+
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
1039+
});
1040+
1041+
// Verifying that the displayed week is week 4 confirms that overriding the timezone worked
1042+
expect(inputElm.val()).toBe('2013-W04');
1043+
});
1044+
1045+
9901046
they('should use any timezone if specified in the options (format: $prop)',
9911047
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
9921048
function(tz) {
@@ -1099,6 +1155,25 @@ describe('input', function() {
10991155

11001156
expect($rootScope.form.alias.$error.max).toBeFalsy();
11011157
});
1158+
1159+
it('should validate when timezone is provided.', function() {
1160+
inputElm = helper.compileInput('<input type="week" ng-model="value" name="alias" ' +
1161+
'max="{{ maxVal }}" ng-model-options="{timezone: \'-2400\', allowInvalid: true}"/>');
1162+
// The calendar week comparison date is January 17. Setting the timezone to -2400
1163+
// makes the January 18 date value valid.
1164+
$rootScope.maxVal = '2013-W03';
1165+
$rootScope.value = new Date(Date.UTC(2013, 0, 18));
1166+
$rootScope.$digest();
1167+
1168+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1169+
expect($rootScope.form.alias.$valid).toBeTruthy();
1170+
1171+
$rootScope.value = '';
1172+
helper.changeInputValueTo('2013-W03');
1173+
expect(inputElm).toBeValid();
1174+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1175+
expect($rootScope.form.alias.$valid).toBeTruthy();
1176+
});
11021177
});
11031178
});
11041179

@@ -1193,6 +1268,25 @@ describe('input', function() {
11931268
});
11941269

11951270

1271+
it('should be possible to override the timezone', function() {
1272+
var inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1273+
1274+
helper.changeInputValueTo('2000-01-01T01:02');
1275+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
1276+
1277+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+0500'});
1278+
$rootScope.$apply(function() {
1279+
$rootScope.value = new Date(Date.UTC(2001, 0, 1, 1, 2, 0));
1280+
});
1281+
expect(inputElm.val()).toBe('2001-01-01T06:02:00.000');
1282+
1283+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
1284+
1285+
helper.changeInputValueTo('2000-01-01T01:02');
1286+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
1287+
});
1288+
1289+
11961290
they('should use any timezone if specified in the options (format: $prop)',
11971291
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
11981292
function(tz) {
@@ -1368,6 +1462,23 @@ describe('input', function() {
13681462

13691463
expect($rootScope.form.alias.$error.max).toBeFalsy();
13701464
});
1465+
1466+
it('should validate when timezone is provided.', function() {
1467+
inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" name="alias" ' +
1468+
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
1469+
$rootScope.maxVal = '2013-01-01T00:00:00';
1470+
$rootScope.value = new Date(Date.UTC(2013, 0, 1, 0, 0, 0));
1471+
$rootScope.$digest();
1472+
1473+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1474+
expect($rootScope.form.alias.$valid).toBeTruthy();
1475+
1476+
$rootScope.value = '';
1477+
helper.changeInputValueTo('2013-01-01T00:00:00');
1478+
expect(inputElm).toBeValid();
1479+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1480+
expect($rootScope.form.alias.$valid).toBeTruthy();
1481+
});
13711482
});
13721483

13731484

@@ -1538,6 +1649,25 @@ describe('input', function() {
15381649
});
15391650

15401651

1652+
it('should be possible to override the timezone', function() {
1653+
var inputElm = helper.compileInput('<input type="time" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1654+
1655+
helper.changeInputValueTo('23:02:00');
1656+
expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 23, 2, 0));
1657+
1658+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
1659+
$rootScope.$apply(function() {
1660+
$rootScope.value = new Date(Date.UTC(1971, 0, 1, 23, 2, 0));
1661+
});
1662+
expect(inputElm.val()).toBe('18:02:00.000');
1663+
1664+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
1665+
helper.changeInputValueTo('23:02:00');
1666+
// The year is still set from the previous date
1667+
expect(+$rootScope.value).toBe(Date.UTC(1971, 0, 1, 23, 2, 0));
1668+
});
1669+
1670+
15411671
they('should use any timezone if specified in the options (format: $prop)',
15421672
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
15431673
function(tz) {
@@ -1686,6 +1816,23 @@ describe('input', function() {
16861816

16871817
expect($rootScope.form.alias.$error.max).toBeFalsy();
16881818
});
1819+
1820+
it('should validate when timezone is provided.', function() {
1821+
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" ' +
1822+
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
1823+
$rootScope.maxVal = '22:30:00';
1824+
$rootScope.value = new Date(Date.UTC(1970, 0, 1, 22, 30, 0));
1825+
$rootScope.$digest();
1826+
1827+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1828+
expect($rootScope.form.alias.$valid).toBeTruthy();
1829+
1830+
$rootScope.value = '';
1831+
helper.changeInputValueTo('22:30:00');
1832+
expect(inputElm).toBeValid();
1833+
expect($rootScope.form.alias.$error.max).toBeFalsy();
1834+
expect($rootScope.form.alias.$valid).toBeTruthy();
1835+
});
16891836
});
16901837

16911838

@@ -1850,6 +1997,24 @@ describe('input', function() {
18501997
});
18511998

18521999

2000+
it('should be possible to override the timezone', function() {
2001+
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2002+
2003+
helper.changeInputValueTo('2000-01-01');
2004+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1));
2005+
2006+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
2007+
$rootScope.$apply(function() {
2008+
$rootScope.value = new Date(Date.UTC(2001, 0, 1));
2009+
});
2010+
expect(inputElm.val()).toBe('2000-12-31');
2011+
2012+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
2013+
helper.changeInputValueTo('2000-01-01');
2014+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
2015+
});
2016+
2017+
18532018
they('should use any timezone if specified in the options (format: $prop)',
18542019
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
18552020
function(tz) {
@@ -1935,6 +2100,34 @@ describe('input', function() {
19352100
dealoc(formElm);
19362101
});
19372102

2103+
it('should not reuse the hours part of a previous date object after changing the timezone', function() {
2104+
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2105+
2106+
helper.changeInputValueTo('2000-01-01');
2107+
// The Date parser sets the hours part of the Date to 0 (00:00) (UTC)
2108+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
2109+
2110+
// Change the timezone offset so that the display date is a day earlier
2111+
// This does not change the model, but our implementation
2112+
// internally caches a Date object with this offset
2113+
// and re-uses it if part of the Date changes.
2114+
// See https://github.com/angular/angular.js/commit/1a1ef62903c8fdf4ceb81277d966a8eff67f0a96
2115+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
2116+
$rootScope.$apply(function() {
2117+
$rootScope.value = new Date(Date.UTC(2000, 0, 1, 0));
2118+
});
2119+
expect(inputElm.val()).toBe('1999-12-31');
2120+
2121+
// At this point, the cached Date has its hours set to to 19 (00:00 - 05:00 = 19:00)
2122+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
2123+
2124+
// When changing the timezone back to UTC, the hours part of the Date should be set to
2125+
// the default 0 (UTC) and not use the modified value of the cached Date object.
2126+
helper.changeInputValueTo('2000-01-01');
2127+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
2128+
});
2129+
2130+
19382131
describe('min', function() {
19392132

19402133
it('should invalidate', function() {
@@ -2031,6 +2224,24 @@ describe('input', function() {
20312224

20322225
expect($rootScope.form.alias.$error.max).toBeFalsy();
20332226
});
2227+
2228+
it('should validate when timezone is provided.', function() {
2229+
var inputElm = helper.compileInput('<input type="date" ng-model="value" name="alias" ' +
2230+
'max="{{ maxVal }}" ng-model-options="{timezone: \'UTC\', allowInvalid: true}"/>');
2231+
2232+
$rootScope.maxVal = '2013-12-01';
2233+
$rootScope.value = new Date(Date.UTC(2013, 11, 1, 0, 0, 0));
2234+
$rootScope.$digest();
2235+
2236+
expect($rootScope.form.alias.$error.max).toBeFalsy();
2237+
expect($rootScope.form.alias.$valid).toBeTruthy();
2238+
2239+
$rootScope.value = '';
2240+
helper.changeInputValueTo('2013-12-01');
2241+
expect(inputElm).toBeValid();
2242+
expect($rootScope.form.alias.$error.max).toBeFalsy();
2243+
expect($rootScope.form.alias.$valid).toBeTruthy();
2244+
});
20342245
});
20352246

20362247

0 commit comments

Comments
 (0)