Skip to content
Permalink
Browse files

fix(dateFilter, input): fix Date parsing in IE/Edge when timezone off…

…set contains `:`

When `Date.parse`-ing a date string, IE and Edge don't recognize the timezone offset in the format
`+HH:mm` (but only without the `:`). According to [the spec][1], the timezone offset should
contain `:`. The [ISO 8601 Standard][2] allows both forms (with and without `:`).
Although the `Date` implementation in JavaScript does not 100% follow the ISO 8601 Standard (it's
just _based on it_), all other browsers seem to recognize both forms as well.

[1]: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
[2]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC

Fixes #13880

Closes #13887
  • Loading branch information
gkalpak committed Jan 29, 2016
1 parent df6e731 commit 571afd6558786d7b99e2aebd307b4a94c9f2bb87
Showing with 94 additions and 64 deletions.
  1. +6 −2 src/Angular.js
  2. +1 −1 src/ng/filter/filters.js
  3. +70 −45 test/ng/directive/inputSpec.js
  4. +17 −16 test/ng/filter/filtersSpec.js
@@ -1205,7 +1205,10 @@ function fromJson(json) {
}


var ALL_COLONS = /:/g;
function timezoneToOffset(timezone, fallback) {
// IE/Edge do not "understand" colon (`:`) in timezone
timezone = timezone.replace(ALL_COLONS, '');
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
@@ -1220,8 +1223,9 @@ function addDateMinutes(date, minutes) {

function convertTimezoneToLocal(date, timezone, reverse) {
reverse = reverse ? -1 : 1;
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
var dateTimezoneOffset = date.getTimezoneOffset();
var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
}


@@ -607,7 +607,7 @@ function dateFilter($locale) {

var dateTimezoneOffset = date.getTimezoneOffset();
if (timezone) {
dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
date = convertTimezoneToLocal(date, timezone, true);
}
forEach(parts, function(value) {
@@ -628,17 +628,22 @@ describe('input', function() {
});


it('should use any timezone if specified in the options', function() {
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
var ngModelOptions = "{timezone: '" + tz + "'}";
var inputElm = helper.compileInput(
'<input type="month" ng-model="value" ng-model-options="' + ngModelOptions + '" />');

helper.changeInputValueTo('2013-07');
expect(+$rootScope.value).toBe(Date.UTC(2013, 5, 30, 19, 0, 0));
helper.changeInputValueTo('2013-07');
expect(+$rootScope.value).toBe(Date.UTC(2013, 5, 30, 19, 0, 0));

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2014, 5, 30, 19, 0, 0));
});
expect(inputElm.val()).toBe('2014-07');
});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2014, 5, 30, 19, 0, 0));
});
expect(inputElm.val()).toBe('2014-07');
}
);


it('should label parse errors as `month`', function() {
@@ -865,17 +870,22 @@ describe('input', function() {
});


it('should use any timezone if specified in the options', function() {
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
var ngModelOptions = "{timezone: '" + tz + "'}";
var inputElm = helper.compileInput(
'<input type="week" ng-model="value" ng-model-options="' + ngModelOptions + '" />');

helper.changeInputValueTo('2013-W03');
expect(+$rootScope.value).toBe(Date.UTC(2013, 0, 16, 19, 0, 0));
helper.changeInputValueTo('2013-W03');
expect(+$rootScope.value).toBe(Date.UTC(2013, 0, 16, 19, 0, 0));

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2014, 0, 16, 19, 0, 0));
});
expect(inputElm.val()).toBe('2014-W03');
});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2014, 0, 16, 19, 0, 0));
});
expect(inputElm.val()).toBe('2014-W03');
}
);


it('should label parse errors as `week`', function() {
@@ -1066,17 +1076,22 @@ describe('input', function() {
});


it('should use any timezone if specified in the options', function() {
var inputElm = helper.compileInput('<input type="datetime-local" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
var ngModelOptions = "{timezone: '" + tz + "'}";
var inputElm = helper.compileInput(
'<input type="datetime-local" ng-model="value" ng-model-options="' + ngModelOptions + '" />');

helper.changeInputValueTo('2000-01-01T06:02');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));
helper.changeInputValueTo('2000-01-01T06:02');
expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0));

$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');
});
$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');
}
);


it('should fallback to default timezone in case an unknown timezone was passed', function() {
@@ -1390,17 +1405,22 @@ describe('input', function() {
});


it('should use any timezone if specified in the options', function() {
var inputElm = helper.compileInput('<input type="time" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
var ngModelOptions = "{timezone: '" + tz + "'}";
var inputElm = helper.compileInput(
'<input type="time" ng-model="value" ng-model-options="' + ngModelOptions + '" />');

helper.changeInputValueTo('23:02:00');
expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 18, 2, 0));
helper.changeInputValueTo('23:02:00');
expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 18, 2, 0));

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(1971, 0, 1, 18, 2, 0));
});
expect(inputElm.val()).toBe('23:02:00.000');
});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(1971, 0, 1, 18, 2, 0));
});
expect(inputElm.val()).toBe('23:02:00.000');
}
);


it('should allow to specify the milliseconds', function() {
@@ -1697,17 +1717,22 @@ describe('input', function() {
});


it('should use any timezone if specified in the options', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');
they('should use any timezone if specified in the options (format: $prop)',
{'+HHmm': '+0500', '+HH:mm': '+05:00'},
function(tz) {
var ngModelOptions = "{timezone: '" + tz + "'}";
var inputElm = helper.compileInput(
'<input type="date" ng-model="value" ng-model-options="' + ngModelOptions + '" />');

helper.changeInputValueTo('2000-01-01');
expect(+$rootScope.value).toBe(Date.UTC(1999, 11, 31, 19, 0, 0));
helper.changeInputValueTo('2000-01-01');
expect(+$rootScope.value).toBe(Date.UTC(1999, 11, 31, 19, 0, 0));

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2000, 11, 31, 19, 0, 0));
});
expect(inputElm.val()).toBe('2001-01-01');
});
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2000, 11, 31, 19, 0, 0));
});
expect(inputElm.val()).toBe('2001-01-01');
}
);


it('should label parse errors as `date`', function() {
@@ -1,7 +1,6 @@
'use strict';

describe('filters', function() {

var filter;

beforeEach(inject(function($filter) {
@@ -165,13 +164,12 @@ describe('filters', function() {
}));
});


describe('number', function() {
var number;

beforeEach(inject(function($rootScope) {
beforeEach(function() {
number = filter('number');
}));
});


it('should do basic filter', function() {
@@ -270,17 +268,16 @@ describe('filters', function() {
});

describe('date', function() {

var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
var secondWeek = new angular.mock.TzDate(+5, '2013-01-11T12:00:00.000Z'); //Friday Jan 11, 2012
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
var secondWeek = new angular.mock.TzDate(+5, '2013-01-11T12:00:00.000Z'); //Friday Jan 11, 2013
var date;

beforeEach(inject(function($filter) {
date = $filter('date');
}));
beforeEach(function() {
date = filter('date');
});

it('should ignore falsy inputs', function() {
expect(date(null)).toBeNull();
@@ -456,7 +453,6 @@ describe('filters', function() {
expect(date(morning, 'yy/xxx')).toEqual('10/xxx');
});


it('should support various iso8061 date strings with timezone as input', function() {
var format = 'yyyy-MM-dd ss';

@@ -479,7 +475,6 @@ describe('filters', function() {
expect(date('2003-09-10T13Z', format)).toEqual('2003-09-' + localDay + ' 00');
});


it('should parse iso8061 date strings without timezone as local time', function() {
var format = 'yyyy-MM-dd HH-mm-ss';

@@ -514,7 +509,13 @@ describe('filters', function() {
});

it('should support conversion to any timezone', function() {
expect(date(new Date(Date.UTC(2003, 8, 10, 3, 2, 4)), 'yyyy-MM-dd HH-mm-ssZ', 'GMT+0500')).toEqual('2003-09-10 08-02-04+0500');
var dateObj = new Date(Date.UTC(2003, 8, 10, 3, 2, 4));
var format = 'yyyy-MM-dd HH-mm-ssZ';

expect(date(dateObj, format, '+0500')).toEqual('2003-09-10 08-02-04+0500');
expect(date(dateObj, format, '+05:00')).toEqual('2003-09-10 08-02-04+0500');
expect(date(dateObj, format, 'GMT+0500')).toEqual('2003-09-10 08-02-04+0500');
expect(date(dateObj, format, 'GMT+05:00')).toEqual('2003-09-10 08-02-04+0500');
});

it('should fallback to default timezone in case an unknown timezone was passed', function() {

0 comments on commit 571afd6

Please sign in to comment.
You can’t perform that action at this time.