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

Commit

Permalink
fix(dateFilter, input): fix Date parsing in IE/Edge when timezone off…
Browse files Browse the repository at this point in the history
…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 db1180f commit 622c421
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 64 deletions.
8 changes: 6 additions & 2 deletions src/Angular.js
Expand Up @@ -1215,7 +1215,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;
}
Expand All @@ -1230,8 +1233,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));
}


Expand Down
2 changes: 1 addition & 1 deletion src/ng/filter/filters.js
Expand Up @@ -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) {
Expand Down
115 changes: 70 additions & 45 deletions test/ng/directive/inputSpec.js
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
33 changes: 17 additions & 16 deletions test/ng/filter/filtersSpec.js
@@ -1,7 +1,6 @@
'use strict';

describe('filters', function() {

var filter;

beforeEach(inject(function($filter) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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';

Expand All @@ -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';

Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 622c421

Please sign in to comment.