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

feat(ngModel): support conversion to timezone other than UTC #11005

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
"fromTimezoneToLocal": false,
"fromLocalToTimezone": false,
"timezoneToOffset": false,
"startingTag": false,
"tryDecodeURIComponent": false,
"parseKeyValue": false,
Expand Down
28 changes: 28 additions & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
toJsonReplacer: true,
toJson: true,
fromJson: true,
fromTimezoneToLocal: true,
fromLocalToTimezone: true,
timezoneToOffset: true,
startingTag: true,
tryDecodeURIComponent: true,
parseKeyValue: true,
Expand Down Expand Up @@ -1068,6 +1071,31 @@ function fromJson(json) {
}


function timezoneToOffset(timezone, fallback) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}


function addDateMinutes(date, minutes) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + minutes);
return date;
}


function fromTimezoneToLocal(date, timezone) {
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, timezoneOffset - date.getTimezoneOffset());
}


function fromLocalToTimezone(date, timezone) {
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, date.getTimezoneOffset() - timezoneOffset);
}


/**
* @returns {string} Returns the string representation of the element.
*/
Expand Down
9 changes: 4 additions & 5 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1156,8 +1156,8 @@ function createDateInputType(type, regexp, parseDate, format) {
// parser/formatter in the processing chain so that the model
// contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
if (timezone) {
parsedDate = fromTimezoneToLocal(parsedDate, timezone);
}
return parsedDate;
}
Expand All @@ -1170,9 +1170,8 @@ function createDateInputType(type, regexp, parseDate, format) {
}
if (isValidDate(value)) {
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
if (previousDate && timezone) {
previousDate = fromLocalToTimezone(previousDate, timezone);
}
return $filter('date')(value, format, timezone);
} else {
Expand Down
6 changes: 4 additions & 2 deletions src/ng/directive/ngModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1097,8 +1097,10 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
`ngModel` as getters/setters.
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
* `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
* otherwise the default timezone of the browser will be used.
* `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
* continental US time zone abbreviations, but for general use, use a time zone offset, for
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
* If not specified, the timezone of the browser will be used.
*
* @example

Expand Down
8 changes: 2 additions & 6 deletions src/ng/filter/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,8 @@ function dateFilter($locale) {

var dateTimezoneOffset = date.getTimezoneOffset();
if (timezone) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
if (!isNaN(requestedTimezoneOffset)) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + dateTimezoneOffset - requestedTimezoneOffset);
dateTimezoneOffset = requestedTimezoneOffset;
}
dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
date = fromLocalToTimezone(date, timezone);
}
forEach(parts, function(value) {
fn = DATE_FORMATS[value];
Expand Down
87 changes: 87 additions & 0 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,19 @@ 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\'}" />');

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


it('should label parse errors as `month`', function() {
var inputElm = helper.compileInput('<input type="month" ng-model="val" name="alias" />', {
valid: false,
Expand All @@ -636,6 +649,17 @@ describe('input', function() {
expect(inputElm.val()).toBe('2013-12');
});

it('should only change the month of a bound date in any timezone', function() {
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'+0500\'}" />');

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2013, 6, 31, 20, 0, 0));
});
helper.changeInputValueTo('2013-09');
expect(+$rootScope.value).toBe(Date.UTC(2013, 7, 31, 20, 0, 0));
expect(inputElm.val()).toBe('2013-09');
});

describe('min', function() {
var inputElm;
beforeEach(function() {
Expand Down Expand Up @@ -814,6 +838,19 @@ 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\'}" />');

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


it('should label parse errors as `week`', function() {
var inputElm = helper.compileInput('<input type="week" ng-model="val" name="alias" />', {
valid: false,
Expand Down Expand Up @@ -990,6 +1027,30 @@ 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\'}" />');

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


it('should fallback to default timezone in case an unknown timezone was passed', function() {
var inputElm = helper.compileInput(
'<input type="datetime-local" ng-model="value1" ng-model-options="{timezone: \'WTF\'}" />' +
'<input type="datetime-local" ng-model="value2" />');

helper.changeGivenInputTo(inputElm.eq(0), '2000-01-01T06:02');
helper.changeGivenInputTo(inputElm.eq(1), '2000-01-01T06:02');
expect($rootScope.value1).toEqual($rootScope.value2);
});


it('should allow to specify the milliseconds', function() {
var inputElm = helper.compileInput('<input type="datetime-local" ng-model="value"" />');

Expand Down Expand Up @@ -1278,6 +1339,19 @@ 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\'}" />');

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


it('should allow to specify the milliseconds', function() {
var inputElm = helper.compileInput('<input type="time" ng-model="value"" />');

Expand Down Expand Up @@ -1559,6 +1633,19 @@ 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\'}" />');

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


it('should label parse errors as `date`', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="val" name="alias" />', {
valid: false,
Expand Down
4 changes: 2 additions & 2 deletions test/ng/filter/filtersSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ describe('filters', function() {
});

it('should fallback to default timezone in case an unknown timezone was passed', function() {
var value = new angular.mock.TzDate(-2, '2003-09-10T01:02:04.000Z');
expect(date(value, 'yyyy-MM-dd HH-mm-ssZ', 'WTF')).toEqual('2003-09-10 03-02-04+0200');
var value = new Date(2003, 8, 10, 3, 2, 4);
expect(date(value, 'yyyy-MM-dd HH-mm-ssZ', 'WTF')).toEqual(date(value, 'yyyy-MM-dd HH-mm-ssZ'));
});
});
});