diff --git a/src/.jshintrc b/src/.jshintrc
index d0561483c9ad..3ee5f1c440ca 100644
--- a/src/.jshintrc
+++ b/src/.jshintrc
@@ -70,6 +70,9 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
+ "fromTimezoneToLocal": false,
+ "fromLocalToTimezone": false,
+ "timezoneToOffset": false,
"startingTag": false,
"tryDecodeURIComponent": false,
"parseKeyValue": false,
diff --git a/src/Angular.js b/src/Angular.js
index 2a12f4c8e538..f9468b6d9b5f 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -65,6 +65,9 @@
toJsonReplacer: true,
toJson: true,
fromJson: true,
+ fromTimezoneToLocal: true,
+ fromLocalToTimezone: true,
+ timezoneToOffset: true,
startingTag: true,
tryDecodeURIComponent: true,
parseKeyValue: true,
@@ -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.
*/
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index eb58f295a7eb..37a8c4c5a110 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -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;
}
@@ -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 {
diff --git a/src/ng/directive/ngModel.js b/src/ng/directive/ngModel.js
index 2aa897624e55..6098cd08a829 100644
--- a/src/ng/directive/ngModel.js
+++ b/src/ng/directive/ngModel.js
@@ -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
- * ``, ``, ... . Right now, the only supported value is `'UTC'`,
- * otherwise the default timezone of the browser will be used.
+ * ``, ``, ... . 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
diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js
index 657d29ed7a91..8fd1a5ae44ae 100644
--- a/src/ng/filter/filters.js
+++ b/src/ng/filter/filters.js
@@ -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];
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index ad98d52abdf9..f9640cbf325f 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -613,6 +613,19 @@ describe('input', function() {
});
+ it('should use any timezone if specified in the options', function() {
+ var inputElm = helper.compileInput('');
+
+ 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('', {
valid: false,
@@ -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('');
+
+ $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() {
@@ -814,6 +838,19 @@ describe('input', function() {
});
+ it('should use any timezone if specified in the options', function() {
+ var inputElm = helper.compileInput('');
+
+ 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('', {
valid: false,
@@ -990,6 +1027,30 @@ describe('input', function() {
});
+ it('should use any timezone if specified in the options', function() {
+ var inputElm = helper.compileInput('');
+
+ 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(
+ '' +
+ '');
+
+ 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('');
@@ -1278,6 +1339,19 @@ describe('input', function() {
});
+ it('should use any timezone if specified in the options', function() {
+ var inputElm = helper.compileInput('');
+
+ 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('');
@@ -1559,6 +1633,19 @@ describe('input', function() {
});
+ it('should use any timezone if specified in the options', function() {
+ var inputElm = helper.compileInput('');
+
+ 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('', {
valid: false,
diff --git a/test/ng/filter/filtersSpec.js b/test/ng/filter/filtersSpec.js
index bd101b432882..0da8ff0af385 100644
--- a/test/ng/filter/filtersSpec.js
+++ b/test/ng/filter/filtersSpec.js
@@ -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'));
});
});
});