Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 4355dee

Browse files
committed
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
1 parent 2f0ac69 commit 4355dee

File tree

5 files changed

+142
-2
lines changed

5 files changed

+142
-2
lines changed

src/.eslintrc.json

+1
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

+1
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

+15-2
Original file line numberDiff line numberDiff line change
@@ -1464,12 +1464,13 @@ function createDateInputType(type, regexp, parseDate, format) {
14641464
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
14651465
badInputChecker(scope, element, attr, ctrl);
14661466
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
1467-
var timezone = ctrl && ctrl.$options.getOption('timezone');
14681467
var previousDate;
1468+
var previousTimezone;
14691469

14701470
ctrl.$$parserName = type;
14711471
ctrl.$parsers.push(function(value) {
14721472
if (ctrl.$isEmpty(value)) return null;
1473+
14731474
if (regexp.test(value)) {
14741475
// Note: We cannot read ctrl.$modelValue, as there might be a different
14751476
// parser/formatter in the processing chain so that the model
@@ -1485,12 +1486,15 @@ function createDateInputType(type, regexp, parseDate, format) {
14851486
}
14861487
if (isValidDate(value)) {
14871488
previousDate = value;
1488-
if (previousDate && timezone) {
1489+
var timezone = ctrl.$options.getOption('timezone');
1490+
if (timezone) {
1491+
previousTimezone = timezone;
14891492
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
14901493
}
14911494
return $filter('date')(value, format, timezone);
14921495
} else {
14931496
previousDate = null;
1497+
previousTimezone = null;
14941498
return '';
14951499
}
14961500
});
@@ -1527,7 +1531,16 @@ function createDateInputType(type, regexp, parseDate, format) {
15271531
}
15281532

15291533
function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
1534+
var timezone = ctrl.$options.getOption('timezone');
1535+
1536+
if (previousTimezone && previousTimezone !== timezone) {
1537+
// If the timezone has changed, adjust the previousDate to the default timezone
1538+
// so that the new date is converted with the correct timezone offset
1539+
previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone));
1540+
}
1541+
15301542
var parsedDate = parseDate(value, previousDate);
1543+
15311544
if (!isNaN(parsedDate) && timezone) {
15321545
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
15331546
}

src/ng/directive/ngModelOptions.js

+2
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ defaultModelOptions = new ModelOptions({
356356
* continental US time zone abbreviations, but for general use, use a time zone offset, for
357357
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
358358
* If not specified, the timezone of the browser will be used.
359+
* Note that changing the timezone will have no effect on the current date, and is only applied after
360+
* the next input / model change.
359361
*
360362
*/
361363
var ngModelOptionsDirective = function() {

test/ng/directive/inputSpec.js

+123
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,21 @@ describe('input', function() {
688688
});
689689

690690

691+
it('should be possible to override the timezone', function() {
692+
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
693+
694+
helper.changeInputValueTo('2013-07');
695+
expect(+$rootScope.value).toBe(Date.UTC(2013, 6, 1));
696+
697+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
698+
699+
$rootScope.$apply(function() {
700+
$rootScope.value = new Date(Date.UTC(2013, 6, 1));
701+
});
702+
expect(inputElm.val()).toBe('2013-06');
703+
});
704+
705+
691706
they('should use any timezone if specified in the options (format: $prop)',
692707
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
693708
function(tz) {
@@ -974,6 +989,30 @@ describe('input', function() {
974989
});
975990

976991

992+
it('should be possible to override the timezone', function() {
993+
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
994+
995+
// January 19 2013 is a Saturday
996+
$rootScope.$apply(function() {
997+
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
998+
});
999+
1000+
expect(inputElm.val()).toBe('2013-W03');
1001+
1002+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+2400'});
1003+
1004+
// To check that the timezone overwrite works, apply an offset of +24 hours.
1005+
// Since January 19 is a Saturday, +24 will turn the formatted Date into January 20 - Sunday -
1006+
// which is in calendar week 4 instead of 3.
1007+
$rootScope.$apply(function() {
1008+
$rootScope.value = new Date(Date.UTC(2013, 0, 19));
1009+
});
1010+
1011+
// Verifying that the displayed week is week 4 confirms that overriding the timezone worked
1012+
expect(inputElm.val()).toBe('2013-W04');
1013+
});
1014+
1015+
9771016
they('should use any timezone if specified in the options (format: $prop)',
9781017
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
9791018
function(tz) {
@@ -1199,6 +1238,25 @@ describe('input', function() {
11991238
});
12001239

12011240

1241+
it('should be possible to override the timezone', function() {
1242+
var inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1243+
1244+
helper.changeInputValueTo('2000-01-01T01:02');
1245+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
1246+
1247+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '+0500'});
1248+
$rootScope.$apply(function() {
1249+
$rootScope.value = new Date(Date.UTC(2001, 0, 1, 1, 2, 0));
1250+
});
1251+
expect(inputElm.val()).toBe('2001-01-01T06:02:00.000');
1252+
1253+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
1254+
1255+
helper.changeInputValueTo('2000-01-01T01:02');
1256+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
1257+
});
1258+
1259+
12021260
they('should use any timezone if specified in the options (format: $prop)',
12031261
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
12041262
function(tz) {
@@ -1561,6 +1619,25 @@ describe('input', function() {
15611619
});
15621620

15631621

1622+
it('should be possible to override the timezone', function() {
1623+
var inputElm = helper.compileInput('<input type="time" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1624+
1625+
helper.changeInputValueTo('23:02:00');
1626+
expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 23, 2, 0));
1627+
1628+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
1629+
$rootScope.$apply(function() {
1630+
$rootScope.value = new Date(Date.UTC(1971, 0, 1, 23, 2, 0));
1631+
});
1632+
expect(inputElm.val()).toBe('18:02:00.000');
1633+
1634+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
1635+
helper.changeInputValueTo('23:02:00');
1636+
// The year is still set from the previous date
1637+
expect(+$rootScope.value).toBe(Date.UTC(1971, 0, 1, 23, 2, 0));
1638+
});
1639+
1640+
15641641
they('should use any timezone if specified in the options (format: $prop)',
15651642
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
15661643
function(tz) {
@@ -1890,6 +1967,24 @@ describe('input', function() {
18901967
});
18911968

18921969

1970+
it('should be possible to override the timezone', function() {
1971+
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1972+
1973+
helper.changeInputValueTo('2000-01-01');
1974+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1));
1975+
1976+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
1977+
$rootScope.$apply(function() {
1978+
$rootScope.value = new Date(Date.UTC(2001, 0, 1));
1979+
});
1980+
expect(inputElm.val()).toBe('2000-12-31');
1981+
1982+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
1983+
helper.changeInputValueTo('2000-01-01');
1984+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
1985+
});
1986+
1987+
18931988
they('should use any timezone if specified in the options (format: $prop)',
18941989
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
18951990
function(tz) {
@@ -1975,6 +2070,34 @@ describe('input', function() {
19752070
dealoc(formElm);
19762071
});
19772072

2073+
it('should not reuse the hours part of a previous date object after changing the timezone', function() {
2074+
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2075+
2076+
helper.changeInputValueTo('2000-01-01');
2077+
// The Date parser sets the hours part of the Date to 0 (00:00) (UTC)
2078+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
2079+
2080+
// Change the timezone offset so that the display date is a day earlier
2081+
// This does not change the model, but our implementation
2082+
// internally caches a Date object with this offset
2083+
// and re-uses it if part of the Date changes.
2084+
// See https://github.com/angular/angular.js/commit/1a1ef62903c8fdf4ceb81277d966a8eff67f0a96
2085+
inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});
2086+
$rootScope.$apply(function() {
2087+
$rootScope.value = new Date(Date.UTC(2000, 0, 1, 0));
2088+
});
2089+
expect(inputElm.val()).toBe('1999-12-31');
2090+
2091+
// At this point, the cached Date has its hours set to to 19 (00:00 - 05:00 = 19:00)
2092+
inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
2093+
2094+
// When changing the timezone back to UTC, the hours part of the Date should be set to
2095+
// the default 0 (UTC) and not use the modified value of the cached Date object.
2096+
helper.changeInputValueTo('2000-01-01');
2097+
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 0));
2098+
});
2099+
2100+
19782101
describe('min', function() {
19792102

19802103
it('should invalidate', function() {

0 commit comments

Comments
 (0)