From 92aca0a583db2d3b8d2b7a3d970be82b956b3125 Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Fri, 27 Nov 2015 23:06:00 -0500 Subject: [PATCH 1/5] feat(datepicker): ng-model-options: timezone for #4837 --- src/dateparser/dateparser.js | 32 ++++ src/datepicker/datepicker.js | 57 ++++--- src/datepicker/docs/readme.md | 4 + src/datepicker/test/datepicker.spec.js | 199 +++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 18 deletions(-) diff --git a/src/dateparser/dateparser.js b/src/dateparser/dateparser.js index b60b7865d0..fd6670bca8 100644 --- a/src/dateparser/dateparser.js +++ b/src/dateparser/dateparser.js @@ -330,4 +330,36 @@ angular.module('ui.bootstrap.dateparser', []) function toInt(str) { return parseInt(str, 10); } + + this.toTimezone = toTimezone; + this.fromTimezone = fromTimezone; + this.timezoneToOffset = timezoneToOffset; + this.addDateMinutes = addDateMinutes; + this.convertTimezoneToLocal = convertTimezoneToLocal; + + function toTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone) : date; + } + + function fromTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; + } + + //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207 + 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 convertTimezoneToLocal(date, timezone, reverse) { + reverse = reverse ? -1 : 1; + var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); + return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); + } }]); diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 2435e26793..53e918a6b2 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -18,12 +18,15 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst yearColumns: 5, minDate: null, maxDate: null, - shortcutPropagation: false + shortcutPropagation: false, + ngModelOptions: {} }) -.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) { +.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser', + function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) { var self = this, - ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; + ngModelOptions = {}; // Modes chain this.modes = ['day', 'month', 'year']; @@ -42,11 +45,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst angular.forEach(['minDate', 'maxDate'], function(key) { if ($attrs[key]) { $scope.$parent.$watch($attrs[key], function(value) { - self[key] = value ? angular.isDate(value) ? new Date(value) : new Date(dateFilter(value, 'medium')) : null; + self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null; self.refreshView(); }); } else { - self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null; } }); @@ -68,10 +71,10 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); if (angular.isDefined($attrs.initDate)) { - this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); + this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date(); $scope.$parent.$watch($attrs.initDate, function(initDate) { if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { - self.activeDate = initDate; + self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); self.refreshView(); } }); @@ -97,6 +100,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; + ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions; ngModelCtrl.$render = function() { self.render(); @@ -109,7 +113,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst isValid = !isNaN(date); if (isValid) { - this.activeDate = date; + this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone); } else if (!$datepickerSuppressError) { $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } @@ -122,6 +126,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst this._refreshView(); var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + date = dateParser.fromTimezone(date, ngModelOptions.timezone); ngModelCtrl.$setValidity('dateDisabled', !date || this.element && !this.isDisabled(date)); } @@ -129,6 +134,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst this.createDateObject = function(date, format) { var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + model = dateParser.fromTimezone(model, ngModelOptions.timezone); return { date: date, label: dateFilter(date, format), @@ -161,8 +167,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst $scope.select = function(date) { if ($scope.datepickerMode === self.minMode) { - var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); + var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0); dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + dt = dateParser.toTimezone(dt, ngModelOptions.timezone); ngModelCtrl.$setViewValue(dt); ngModelCtrl.$render(); } else { @@ -546,19 +553,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst altInputFormats: [] }) -.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', -function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) { +.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', +function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) { var self = this; var cache = {}, isHtml5DateInput = false; var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, - ngModel, $popup, altInputFormats; + ngModel, ngModelOptions, $popup, altInputFormats; scope.watchData = {}; this.init = function(_ngModel_) { ngModel = _ngModel_; + ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions; closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; @@ -571,6 +579,8 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (datepickerPopupConfig.html5Types[attrs.type]) { dateFormat = datepickerPopupConfig.html5Types[attrs.type]; isHtml5DateInput = true; + ngModelOptions.timezoneHtml5 = ngModelOptions.timezone; + ngModelOptions.timezone = null; } else { dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; attrs.$observe('uibDatepickerPopup', function(value, oldValue) { @@ -598,8 +608,11 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi // popup element used to display calendar popupEl = angular.element('
'); + scope.ngModelOptions = angular.copy(ngModelOptions); + scope.ngModelOptions.timezone = null; popupEl.attr({ 'ng-model': 'date', + 'ng-model-options': 'ngModelOptions', 'ng-change': 'dateSelection(date)', 'template-url': datepickerPopupTemplateUrl }); @@ -618,7 +631,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (attrs.datepickerOptions) { var options = scope.$parent.$eval(attrs.datepickerOptions); if (options && options.initDate) { - scope.initDate = options.initDate; + scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone || ngModelOptions.timezoneHtml5); datepickerEl.attr('init-date', 'initDate'); delete options.initDate; } @@ -632,7 +645,11 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi var getAttribute = $parse(attrs[key]); scope.$parent.$watch(getAttribute, function(value) { if (key === 'minDate' || key === 'maxDate') { - cache[key] = angular.isDate(value) ? new Date(value) : new Date(dateFilter(value, 'medium')); + cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')); + } + scope.watchData[key] = cache[key] || value; + if (key === 'initDate') { + scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone || ngModelOptions.timezoneHtml5); } scope.watchData[key] = cache[key] || value; }); @@ -670,12 +687,16 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi ngModel.$validators.date = validator; ngModel.$parsers.unshift(parseDate); ngModel.$formatters.push(function(value) { - scope.date = value; - return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat); + if (ngModel.$isEmpty(value)) { + scope.date = value; + return value; + } + scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); + return dateFilter(scope.date, dateFormat); }); } else { ngModel.$formatters.push(function(value) { - scope.date = value; + scope.date = dateParser.fromTimezone(value, ngModelOptions.timezoneHtml5); return value; }); } @@ -830,7 +851,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (angular.isString(viewValue)) { var date = parseDateString(viewValue); if (!isNaN(date)) { - return date; + return dateParser.toTimezone(date, ngModelOptions.timezone); } } diff --git a/src/datepicker/docs/readme.md b/src/datepicker/docs/readme.md index 55e2c69762..91deb8587b 100644 --- a/src/datepicker/docs/readme.md +++ b/src/datepicker/docs/readme.md @@ -104,6 +104,10 @@ The datepicker has 3 modes: allowInvalid support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions). +* `ng-model-options` + _(Default: {})_ - + Timezone support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions). + ### uib-datepicker-popup settings ### Options for the uib-datepicker must be passed as JSON using the `datepicker-options` attribute. This list is only for popup settings. diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index 0f295f2ced..4cb2843e0f 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -110,6 +110,14 @@ describe('datepicker', function() { }); } + function getSelectedElement(index) { + var buttons = getAllOptionsEl(); + var el = $.grep(buttons, function(button, idx) { + return angular.element(button).hasClass('btn-info'); + })[0]; + return angular.element(el); + } + function triggerKeyDown(element, key, ctrl) { var keyCodes = { 'enter': 13, @@ -1309,6 +1317,70 @@ describe('datepicker', function() { }); }); + describe('datepickerConfig.ngModelOptions', function() { + describe('timezone', function() { + var originalConfig = {}; + beforeEach(inject(function(uibDatepickerConfig) { + angular.extend(originalConfig, uibDatepickerConfig); + uibDatepickerConfig.ngModelOptions = { timezone: '+060' }; + $rootScope.date = new Date('2005-11-07T01:00:00.000Z'); + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + afterEach(inject(function(uibDatepickerConfig) { + // return it to the original state + angular.extend(uibDatepickerConfig, originalConfig); + })); + + it('sets date to appropriate date', function() { + expectSelectedElement(8); + }); + + it('updates the input when a day is clicked', function() { + clickOption(9); + expect($rootScope.date).toEqual(new Date('2005-11-08T01:00:00.000Z')); + }); + + it('init date', function() { + $rootScope.initDate = new Date('2006-01-01T01:00:00.000Z'); + $rootScope.date = null; + element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(getTitle()).toEqual('January 2006'); + }); + + it('min date', function() { + $rootScope.minDate = new Date('2010-10-01T01:00:00.000Z'); + element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(getSelectedElement().prop('disabled')).toBe(true); + }); + }); + }); + + describe('uib-datepicker ng-model-options', function() { + describe('timezone', function() { + beforeEach(inject(function() { + $rootScope.date = new Date('2005-11-07T01:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+060'}; + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('sets date to appropriate date', function() { + expectSelectedElement(8); + }); + + it('updates the input when a day is clicked', function() { + clickOption(9); + expect($rootScope.date).toEqual(new Date('2005-11-08T01:00:00.000Z')); + }); + }); + }); + describe('setting datepickerPopupConfig', function() { var originalConfig = {}; beforeEach(inject(function(uibDatepickerPopupConfig) { @@ -2541,6 +2613,133 @@ describe('datepicker', function() { }); }); + describe('uibDatepickerConfig.ngModelOptions', function() { + describe('timezone', function() { + var inputEl, dropdownEl, $document, $sniffer, $timeout; + + function assignElements(wrapElement) { + inputEl = wrapElement.find('input'); + dropdownEl = wrapElement.find('ul'); + element = dropdownEl.find('table'); + } + + beforeEach(inject(function(uibDatepickerConfig) { + uibDatepickerConfig.ngModelOptions = { timezone: '+060' }; + $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); + $rootScope.isopen = true; + var wrapper = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapper); + })); + + afterEach(inject(function (uibDatepickerConfig) { + // return it to the original state + uibDatepickerConfig.ngModelOptions = {}; + })); + + it('interprets the date appropriately', function() { + expect(inputEl.val()).toBe('09/30/2010'); + }); + + it('updates the input when a day is clicked', function() { + clickOption(17); + expect(inputEl.val()).toBe('09/15/2010'); + expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + }); + + it('shows the correct title', function() { + expect(getTitle()).toBe('September 2010'); + }); + + it('init date', function() { + $rootScope.initDate = new Date('2006-01-01T01:00:00.000Z'); + $rootScope.date = null; + var wrapper = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapper); + + expect(getTitle()).toBe('January 2006'); + }); + + it('min date', function() { + $rootScope.minDate = new Date('2010-10-01T01:00:00.000Z'); + var wrapper = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapper); + + expect(getSelectedElement().prop('disabled')).toBe(true); + }); + }); + }); + + describe('ng-model-options', function() { + describe('timezone', function() { + var inputEl, dropdownEl, $document, $sniffer, $timeout; + + function assignElements(wrapElement) { + inputEl = wrapElement.find('input'); + dropdownEl = wrapElement.find('ul'); + element = dropdownEl.find('table'); + } + + beforeEach(function() { + $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+060' }; + $rootScope.isopen = true; + var wrapper = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapper); + }); + + it('interprets the date appropriately', function() { + expect(inputEl.val()).toBe('09/30/2010'); + }); + + it('has `selected` only the correct day', function() { + expectSelectedElement(32); + }); + + it('updates the input when a day is clicked', function() { + clickOption(17); + expect(inputEl.val()).toBe('09/15/2010'); + expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + }); + }); + + describe('timezone HTML5', function() { + var inputEl, dropdownEl, $document, $sniffer, $timeout; + + function assignElements(wrapElement) { + inputEl = wrapElement.find('input'); + dropdownEl = wrapElement.find('ul'); + element = dropdownEl.find('table'); + } + + beforeEach(function() { + $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+060' }; + $rootScope.isopen = true; + var wrapper = $compile('
')($rootScope); + $rootScope.$digest(); + assignElements(wrapper); + }); + + it('interprets the date appropriately', function() { + expect(inputEl.val()).toBe('2010-09-30'); + }); + + it('has `selected` only the correct day', function() { + expectSelectedElement(32); + }); + + it('updates the input when a day is clicked', function() { + clickOption(17); + expect(inputEl.val()).toBe('2010-09-15'); + expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + }); + }); + }); + describe('with empty initial state', function() { beforeEach(inject(function() { $rootScope.date = null; From 30079ec42eee9db327b27f52b0c5b19f2674dea4 Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Mon, 14 Dec 2015 21:32:57 -0500 Subject: [PATCH 2/5] feat(datepicker): ng-model-options: timezone fixes --- src/datepicker/docs/readme.md | 11 +++---- src/datepicker/test/datepicker.spec.js | 44 +++++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/datepicker/docs/readme.md b/src/datepicker/docs/readme.md index 91deb8587b..a880dc59e9 100644 --- a/src/datepicker/docs/readme.md +++ b/src/datepicker/docs/readme.md @@ -100,13 +100,10 @@ The datepicker has 3 modes: Number of columns displayed in year selection. * `ng-model-options` - _(Default: {})_ - - allowInvalid support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions). - - -* `ng-model-options` - _(Default: {})_ - - Timezone support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions). + _(Default: `{}`)_ - + Supported properties: + * allowInvalid + * timezone ### uib-datepicker-popup settings ### diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index 4cb2843e0f..456fa60040 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1317,13 +1317,13 @@ describe('datepicker', function() { }); }); - describe('datepickerConfig.ngModelOptions', function() { + describe('datepickerConfig ngModelOptions', function() { describe('timezone', function() { var originalConfig = {}; beforeEach(inject(function(uibDatepickerConfig) { angular.extend(originalConfig, uibDatepickerConfig); - uibDatepickerConfig.ngModelOptions = { timezone: '+060' }; - $rootScope.date = new Date('2005-11-07T01:00:00.000Z'); + uibDatepickerConfig.ngModelOptions = { timezone: '+600' }; + $rootScope.date = new Date('2005-11-07T10:00:00.000Z'); element = $compile('')($rootScope); $rootScope.$digest(); })); @@ -1339,11 +1339,11 @@ describe('datepicker', function() { it('updates the input when a day is clicked', function() { clickOption(9); - expect($rootScope.date).toEqual(new Date('2005-11-08T01:00:00.000Z')); + expect($rootScope.date).toEqual(new Date('2005-11-08T10:00:00.000Z')); }); it('init date', function() { - $rootScope.initDate = new Date('2006-01-01T01:00:00.000Z'); + $rootScope.initDate = new Date('2006-01-01T10:00:00.000Z'); $rootScope.date = null; element = $compile('')($rootScope); $rootScope.$digest(); @@ -1352,7 +1352,7 @@ describe('datepicker', function() { }); it('min date', function() { - $rootScope.minDate = new Date('2010-10-01T01:00:00.000Z'); + $rootScope.minDate = new Date('2010-10-01T10:00:00.000Z'); element = $compile('')($rootScope); $rootScope.$digest(); @@ -1364,8 +1364,8 @@ describe('datepicker', function() { describe('uib-datepicker ng-model-options', function() { describe('timezone', function() { beforeEach(inject(function() { - $rootScope.date = new Date('2005-11-07T01:00:00.000Z'); - $rootScope.ngModelOptions = { timezone: '+060'}; + $rootScope.date = new Date('2005-11-07T10:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+600'}; element = $compile('')($rootScope); $rootScope.$digest(); })); @@ -1376,7 +1376,7 @@ describe('datepicker', function() { it('updates the input when a day is clicked', function() { clickOption(9); - expect($rootScope.date).toEqual(new Date('2005-11-08T01:00:00.000Z')); + expect($rootScope.date).toEqual(new Date('2005-11-08T10:00:00.000Z')); }); }); }); @@ -2613,7 +2613,7 @@ describe('datepicker', function() { }); }); - describe('uibDatepickerConfig.ngModelOptions', function() { + describe('uibDatepickerConfig ngModelOptions', function() { describe('timezone', function() { var inputEl, dropdownEl, $document, $sniffer, $timeout; @@ -2624,8 +2624,8 @@ describe('datepicker', function() { } beforeEach(inject(function(uibDatepickerConfig) { - uibDatepickerConfig.ngModelOptions = { timezone: '+060' }; - $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); + uibDatepickerConfig.ngModelOptions = { timezone: '+600' }; + $rootScope.date = new Date('2010-09-30T10:00:00.000Z'); $rootScope.isopen = true; var wrapper = $compile('
')($rootScope); $rootScope.$digest(); @@ -2644,7 +2644,7 @@ describe('datepicker', function() { it('updates the input when a day is clicked', function() { clickOption(17); expect(inputEl.val()).toBe('09/15/2010'); - expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z')); }); it('shows the correct title', function() { @@ -2652,7 +2652,7 @@ describe('datepicker', function() { }); it('init date', function() { - $rootScope.initDate = new Date('2006-01-01T01:00:00.000Z'); + $rootScope.initDate = new Date('2006-01-01T10:00:00.000Z'); $rootScope.date = null; var wrapper = $compile('
')($rootScope); $rootScope.$digest(); @@ -2662,7 +2662,7 @@ describe('datepicker', function() { }); it('min date', function() { - $rootScope.minDate = new Date('2010-10-01T01:00:00.000Z'); + $rootScope.minDate = new Date('2010-10-01T10:00:00.000Z'); var wrapper = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapper); @@ -2683,8 +2683,8 @@ describe('datepicker', function() { } beforeEach(function() { - $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); - $rootScope.ngModelOptions = { timezone: '+060' }; + $rootScope.date = new Date('2010-09-30T10:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+600' }; $rootScope.isopen = true; var wrapper = $compile('
')($rootScope); $rootScope.$digest(); @@ -2702,11 +2702,11 @@ describe('datepicker', function() { it('updates the input when a day is clicked', function() { clickOption(17); expect(inputEl.val()).toBe('09/15/2010'); - expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z')); }); }); - describe('timezone HTML5', function() { + describe('timezone HTML5 date input', function() { var inputEl, dropdownEl, $document, $sniffer, $timeout; function assignElements(wrapElement) { @@ -2716,8 +2716,8 @@ describe('datepicker', function() { } beforeEach(function() { - $rootScope.date = new Date('2010-09-30T01:00:00.000Z'); - $rootScope.ngModelOptions = { timezone: '+060' }; + $rootScope.date = new Date('2010-09-30T10:00:00.000Z'); + $rootScope.ngModelOptions = { timezone: '+600' }; $rootScope.isopen = true; var wrapper = $compile('
')($rootScope); $rootScope.$digest(); @@ -2735,7 +2735,7 @@ describe('datepicker', function() { it('updates the input when a day is clicked', function() { clickOption(17); expect(inputEl.val()).toBe('2010-09-15'); - expect($rootScope.date).toEqual(new Date('2010-09-15T01:00:00.000Z')); + expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z')); }); }); }); From 873873ef4f867159162505055346078baeae36a7 Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Tue, 15 Dec 2015 22:49:41 -0500 Subject: [PATCH 3/5] feat(datepicker): ng-model-options: timezone: nix timezoneHtml5 --- src/datepicker/datepicker.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 53e918a6b2..d85e1c6496 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -579,8 +579,6 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (datepickerPopupConfig.html5Types[attrs.type]) { dateFormat = datepickerPopupConfig.html5Types[attrs.type]; isHtml5DateInput = true; - ngModelOptions.timezoneHtml5 = ngModelOptions.timezone; - ngModelOptions.timezone = null; } else { dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; attrs.$observe('uibDatepickerPopup', function(value, oldValue) { @@ -631,7 +629,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (attrs.datepickerOptions) { var options = scope.$parent.$eval(attrs.datepickerOptions); if (options && options.initDate) { - scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone || ngModelOptions.timezoneHtml5); + scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone); datepickerEl.attr('init-date', 'initDate'); delete options.initDate; } @@ -649,7 +647,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi } scope.watchData[key] = cache[key] || value; if (key === 'initDate') { - scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone || ngModelOptions.timezoneHtml5); + scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); } scope.watchData[key] = cache[key] || value; }); @@ -696,7 +694,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi }); } else { ngModel.$formatters.push(function(value) { - scope.date = dateParser.fromTimezone(value, ngModelOptions.timezoneHtml5); + scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); return value; }); } From 42b91173b7396b0a3e99a14dfd013d9ad9bbc856 Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Tue, 15 Dec 2015 23:38:43 -0500 Subject: [PATCH 4/5] feat(datepicker): ng-model-options: timezone: fix initDate --- src/datepicker/datepicker.js | 1 - src/datepicker/test/datepicker.spec.js | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index d85e1c6496..f1256e6ab2 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -649,7 +649,6 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (key === 'initDate') { scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); } - scope.watchData[key] = cache[key] || value; }); datepickerEl.attr(cameltoDash(key), 'watchData.' + key); diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index 456fa60040..57b423ac84 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1343,7 +1343,7 @@ describe('datepicker', function() { }); it('init date', function() { - $rootScope.initDate = new Date('2006-01-01T10:00:00.000Z'); + $rootScope.initDate = new Date('2006-01-01T00:00:00.000Z'); $rootScope.date = null; element = $compile('')($rootScope); $rootScope.$digest(); @@ -1352,7 +1352,7 @@ describe('datepicker', function() { }); it('min date', function() { - $rootScope.minDate = new Date('2010-10-01T10:00:00.000Z'); + $rootScope.minDate = new Date('2010-10-01T00:00:00.000Z'); element = $compile('')($rootScope); $rootScope.$digest(); @@ -1906,19 +1906,19 @@ describe('datepicker', function() { expect(inputEl.val()).toBe('10-January-1983'); }); }); - + describe('custom format with time', function() { beforeEach(inject(function() { var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); - + it('updates the model correctly when the input value changes', function() { $rootScope.date = new Date(2015, 10, 24, 10, 0); $rootScope.$digest(); expect(inputEl.val()).toBe('Nov-24-2015 10:00 AM'); - + inputEl.val('Nov-24-2015 11:00 AM').trigger('input'); $rootScope.$digest(); expect($rootScope.date).toEqual(new Date(2015, 10, 24, 11, 0)); @@ -2652,7 +2652,7 @@ describe('datepicker', function() { }); it('init date', function() { - $rootScope.initDate = new Date('2006-01-01T10:00:00.000Z'); + $rootScope.initDate = new Date('2006-01-01T00:00:00.000Z'); $rootScope.date = null; var wrapper = $compile('
')($rootScope); $rootScope.$digest(); @@ -2662,7 +2662,7 @@ describe('datepicker', function() { }); it('min date', function() { - $rootScope.minDate = new Date('2010-10-01T10:00:00.000Z'); + $rootScope.minDate = new Date('2010-10-01T00:00:00.000Z'); var wrapper = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapper); From 32071bbcecdf9a7392fc3d0c64fb3d9bacee114e Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Wed, 16 Dec 2015 01:02:37 -0500 Subject: [PATCH 5/5] feat(datepicker): ng-model-options: timezone: dateParse spec --- src/dateparser/test/dateparser.spec.js | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/dateparser/test/dateparser.spec.js b/src/dateparser/test/dateparser.spec.js index 2b518fd7ad..3e94025895 100644 --- a/src/dateparser/test/dateparser.spec.js +++ b/src/dateparser/test/dateparser.spec.js @@ -387,4 +387,116 @@ describe('date parser', function() { expect(dateParser.init).toHaveBeenCalled(); })); + + + describe('timezone functions', function() { + describe('toTimezone', function() { + it('adjusts date: PST - EST', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.toTimezone(date, 'PST'); + var toEastDate = dateParser.toTimezone(date, 'EST'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3); + }); + + it('adjusts date: GMT-0500 - GMT+0500', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.toTimezone(date, 'GMT-0500'); + var toEastDate = dateParser.toTimezone(date, 'GMT+0500'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10); + }); + + it('adjusts date: -600 - +600', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.toTimezone(date, '-600'); + var toEastDate = dateParser.toTimezone(date, '+600'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12); + }); + + it('tolerates null date', function() { + var date = null; + var toNullDate = dateParser.toTimezone(date, '-600'); + expect(toNullDate).toEqual(date); + }); + + it('tolerates null timezone', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toNullTimezoneDate = dateParser.toTimezone(date, null); + expect(toNullTimezoneDate).toEqual(date); + }); + }); + + describe('fromTimezone', function() { + it('adjusts date: PST - EST', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var fromWestDate = dateParser.fromTimezone(date, 'PST'); + var fromEastDate = dateParser.fromTimezone(date, 'EST'); + expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -3); + }); + + it('adjusts date: GMT-0500 - GMT+0500', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var fromWestDate = dateParser.fromTimezone(date, 'GMT-0500'); + var fromEastDate = dateParser.fromTimezone(date, 'GMT+0500'); + expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -10); + }); + + it('adjusts date: -600 - +600', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var fromWestDate = dateParser.fromTimezone(date, '-600'); + var fromEastDate = dateParser.fromTimezone(date, '+600'); + expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -12); + }); + + it('tolerates null date', function() { + var date = null; + var toNullDate = dateParser.fromTimezone(date, '-600'); + expect(toNullDate).toEqual(date); + }); + + it('tolerates null timezone', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toNullTimezoneDate = dateParser.fromTimezone(date, null); + expect(toNullTimezoneDate).toEqual(date); + }); + }); + + describe('timezoneToOffset', function() { + it('calculates minutes off from current timezone', function() { + var offsetMinutesUtc = dateParser.timezoneToOffset('UTC'); + var offsetMinutesUtcPlus1 = dateParser.timezoneToOffset('GMT+0100'); + expect(offsetMinutesUtc - offsetMinutesUtcPlus1).toEqual(60); + }); + }); + + describe('addDateMinutes', function() { + it('adds minutes to a date', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var oneHourMore = dateParser.addDateMinutes(date, 60); + expect(oneHourMore).toEqual(new Date('2008-01-01T01:00:00.000Z')); + }); + }); + + describe('convertTimezoneToLocal', function() { + it('adjusts date: PST - EST', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.convertTimezoneToLocal(date, 'PST'); + var toEastDate = dateParser.convertTimezoneToLocal(date, 'EST'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3); + }); + + it('adjusts date: GMT-0500 - GMT+0500', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.convertTimezoneToLocal(date, 'GMT-0500'); + var toEastDate = dateParser.convertTimezoneToLocal(date, 'GMT+0500'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10); + }); + + it('adjusts date: -600 - +600', function() { + var date = new Date('2008-01-01T00:00:00.000Z'); + var toWestDate = dateParser.convertTimezoneToLocal(date, '-600'); + var toEastDate = dateParser.convertTimezoneToLocal(date, '+600'); + expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12); + }); + }); + }); });