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

Commit 023fab7

Browse files
committed
feat(input): allow to define the timezone for parsing dates
Angular used to always use the browser timezone when parsing `input[date]`, `input[time]`, … The timezone can now be changed to `UTC` via `ngModelOptions`. Closes #8447.
1 parent 132454c commit 023fab7

File tree

2 files changed

+91
-7
lines changed

2 files changed

+91
-7
lines changed

src/ng/directive/input.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ var inputType = {
113113
* modern browsers do not yet support this input type, it is important to provide cues to users on the
114114
* expected input format via a placeholder or label. The model must always be a Date object.
115115
*
116+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
117+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
118+
*
116119
* @param {string} ngModel Assignable angular expression to data-bind to.
117120
* @param {string=} name Property name of the form under which the control is published.
118121
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
@@ -198,6 +201,9 @@ var inputType = {
198201
* the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
199202
* local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. The model must be a Date object.
200203
*
204+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
205+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
206+
*
201207
* @param {string} ngModel Assignable angular expression to data-bind to.
202208
* @param {string=} name Property name of the form under which the control is published.
203209
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
@@ -284,6 +290,9 @@ var inputType = {
284290
* local time format (HH:mm), for example: `14:57`. Model must be a Date object. This binding will always output a
285291
* Date object to the model of January 1, 1900, or local date `new Date(0, 0, 1, HH, mm)`.
286292
*
293+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
294+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
295+
*
287296
* @param {string} ngModel Assignable angular expression to data-bind to.
288297
* @param {string=} name Property name of the form under which the control is published.
289298
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
@@ -369,6 +378,9 @@ var inputType = {
369378
* the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
370379
* week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object.
371380
*
381+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
382+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
383+
*
372384
* @param {string} ngModel Assignable angular expression to data-bind to.
373385
* @param {string=} name Property name of the form under which the control is published.
374386
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
@@ -453,6 +465,9 @@ var inputType = {
453465
* month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is
454466
* not set to the first of the month, the first of that model's month is assumed.
455467
*
468+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
469+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
470+
*
456471
* @param {string} ngModel Assignable angular expression to data-bind to.
457472
* @param {string=} name Property name of the form under which the control is published.
458473
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be
@@ -1021,6 +1036,7 @@ function weekParser(isoWeek) {
10211036
week = +parts[2],
10221037
firstThurs = getFirstThursdayOfYear(year),
10231038
addDays = (week - 1) * 7;
1039+
10241040
return new Date(year, 0, firstThurs.getDate() + addDays);
10251041
}
10261042
}
@@ -1042,7 +1058,7 @@ function createDateParser(regexp, mapping) {
10421058

10431059
if(parts) {
10441060
parts.shift();
1045-
map = { yyyy: 0, MM: 1, dd: 1, HH: 0, mm: 0 };
1061+
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0 };
10461062

10471063
forEach(parts, function(part, index) {
10481064
if(index < mapping.length) {
@@ -1061,6 +1077,7 @@ function createDateParser(regexp, mapping) {
10611077
function createDateInputType(type, regexp, parseDate, format) {
10621078
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
10631079
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
1080+
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
10641081

10651082
ctrl.$parsers.push(function(value) {
10661083
if(ctrl.$isEmpty(value)) {
@@ -1070,7 +1087,11 @@ function createDateInputType(type, regexp, parseDate, format) {
10701087

10711088
if(regexp.test(value)) {
10721089
ctrl.$setValidity(type, true);
1073-
return parseDate(value);
1090+
var parsedDate = parseDate(value);
1091+
if (timezone === 'UTC') {
1092+
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
1093+
}
1094+
return parsedDate;
10741095
}
10751096

10761097
ctrl.$setValidity(type, false);
@@ -1079,7 +1100,7 @@ function createDateInputType(type, regexp, parseDate, format) {
10791100

10801101
ctrl.$formatters.push(function(value) {
10811102
if(isDate(value)) {
1082-
return $filter('date')(value, format);
1103+
return $filter('date')(value, format, timezone);
10831104
}
10841105
return '';
10851106
});
@@ -2604,6 +2625,9 @@ var ngValueDirective = function() {
26042625
* `ngModelOptions="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
26052626
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
26062627
`ngModel` as getters/setters.
2628+
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
2629+
* `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
2630+
* otherwise the default timezone of the browser will be used.
26072631
*
26082632
* @example
26092633

test/ng/directive/inputSpec.js

+64-4
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,18 @@ describe('input', function() {
16271627
expect(inputElm).toBeValid();
16281628
});
16291629

1630+
it('should use UTC if specified in the options', function() {
1631+
compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1632+
1633+
changeInputValueTo('2013-07');
1634+
expect(+scope.value).toBe(Date.UTC(2013, 6, 1));
1635+
1636+
scope.$apply(function() {
1637+
scope.value = new Date(Date.UTC(2014, 6, 1));
1638+
});
1639+
expect(inputElm.val()).toBe('2014-07');
1640+
});
1641+
16301642

16311643
describe('min', function (){
16321644
beforeEach(function (){
@@ -1746,6 +1758,18 @@ describe('input', function() {
17461758
expect(inputElm).toBeValid();
17471759
});
17481760

1761+
it('should use UTC if specified in the options', function() {
1762+
compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1763+
1764+
changeInputValueTo('2013-W03');
1765+
expect(+scope.value).toBe(Date.UTC(2013, 0, 17));
1766+
1767+
scope.$apply(function() {
1768+
scope.value = new Date(Date.UTC(2014, 0, 17));
1769+
});
1770+
expect(inputElm.val()).toBe('2014-W03');
1771+
});
1772+
17491773
describe('min', function (){
17501774
beforeEach(function (){
17511775
compileInput('<input type="week" ng-model="value" name="alias" min="2013-W01" />');
@@ -1863,6 +1887,18 @@ describe('input', function() {
18631887
expect(inputElm).toBeValid();
18641888
});
18651889

1890+
it('should use UTC if specified in the options', function() {
1891+
compileInput('<input type="datetime-local" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
1892+
1893+
changeInputValueTo('2000-01-01T01:02');
1894+
expect(+scope.value).toBe(Date.UTC(2000, 0, 1, 1, 2));
1895+
1896+
scope.$apply(function() {
1897+
scope.value = new Date(Date.UTC(2001, 0, 1, 1, 2));
1898+
});
1899+
expect(inputElm.val()).toBe('2001-01-01T01:02');
1900+
});
1901+
18661902
describe('min', function (){
18671903
beforeEach(function (){
18681904
compileInput('<input type="datetime-local" ng-model="value" name="alias" min="2000-01-01T12:30" />');
@@ -1947,7 +1983,7 @@ describe('input', function() {
19471983
compileInput('<input type="time" ng-model="threeFortyOnePm"/>');
19481984

19491985
scope.$apply(function (){
1950-
scope.threeFortyOnePm = new Date(0, 0, 1, 15, 41);
1986+
scope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41);
19511987
});
19521988

19531989
expect(inputElm.val()).toBe('15:41');
@@ -1957,7 +1993,7 @@ describe('input', function() {
19571993
compileInput('<input type="time" ng-model="breakMe"/>');
19581994

19591995
scope.$apply(function (){
1960-
scope.breakMe = new Date(0, 0, 1, 16, 25);
1996+
scope.breakMe = new Date(1970, 0, 1, 16, 25);
19611997
});
19621998

19631999
expect(inputElm.val()).toBe('16:25');
@@ -2008,6 +2044,18 @@ describe('input', function() {
20082044
expect(inputElm).toBeValid();
20092045
});
20102046

2047+
it('should use UTC if specified in the options', function() {
2048+
compileInput('<input type="time" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2049+
2050+
changeInputValueTo('23:02');
2051+
expect(+scope.value).toBe(Date.UTC(1970, 0, 1, 23, 2));
2052+
2053+
scope.$apply(function() {
2054+
scope.value = new Date(Date.UTC(1971, 0, 1, 23, 2));
2055+
});
2056+
expect(inputElm.val()).toBe('23:02');
2057+
});
2058+
20112059
describe('min', function (){
20122060
beforeEach(function (){
20132061
compileInput('<input type="time" ng-model="value" name="alias" min="09:30" />');
@@ -2023,7 +2071,7 @@ describe('input', function() {
20232071
it('should validate', function (){
20242072
changeInputValueTo('23:02');
20252073
expect(inputElm).toBeValid();
2026-
expect(+scope.value).toBe(+new Date(0, 0, 1, 23, 2));
2074+
expect(+scope.value).toBe(+new Date(1970, 0, 1, 23, 2));
20272075
expect(scope.form.alias.$error.min).toBeFalsy();
20282076
});
20292077
});
@@ -2043,7 +2091,7 @@ describe('input', function() {
20432091
it('should validate', function() {
20442092
changeInputValueTo('05:30');
20452093
expect(inputElm).toBeValid();
2046-
expect(+scope.value).toBe(+new Date(0, 0, 1, 5, 30));
2094+
expect(+scope.value).toBe(+new Date(1970, 0, 1, 5, 30));
20472095
expect(scope.form.alias.$error.max).toBeFalsy();
20482096
});
20492097
});
@@ -2153,6 +2201,18 @@ describe('input', function() {
21532201
expect(inputElm).toBeValid();
21542202
});
21552203

2204+
it('should use UTC if specified in the options', function() {
2205+
compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
2206+
2207+
changeInputValueTo('2000-01-01');
2208+
expect(+scope.value).toBe(Date.UTC(2000, 0, 1));
2209+
2210+
scope.$apply(function() {
2211+
scope.value = new Date(Date.UTC(2001, 0, 1));
2212+
});
2213+
expect(inputElm.val()).toBe('2001-01-01');
2214+
});
2215+
21562216
describe('min', function (){
21572217
beforeEach(function (){
21582218
compileInput('<input type="date" ng-model="value" name="alias" min="2000-01-01" />');

0 commit comments

Comments
 (0)