Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit a1c26a6

Browse files
committed
feat(datepicker): ng-model-options: timezone
for #4837
1 parent 1e24ff7 commit a1c26a6

File tree

4 files changed

+274
-19
lines changed

4 files changed

+274
-19
lines changed

src/dateparser/dateparser.js

+32
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,36 @@ angular.module('ui.bootstrap.dateparser', [])
330330
function toInt(str) {
331331
return parseInt(str, 10);
332332
}
333+
334+
this.toTimezone = toTimezone;
335+
this.fromTimezone = fromTimezone;
336+
this.timezoneToOffset = timezoneToOffset;
337+
this.addDateMinutes = addDateMinutes;
338+
this.convertTimezoneToLocal = convertTimezoneToLocal;
339+
340+
function toTimezone(date, timezone) {
341+
return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
342+
}
343+
344+
function fromTimezone(date, timezone) {
345+
return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
346+
}
347+
348+
//https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
349+
function timezoneToOffset(timezone, fallback) {
350+
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
351+
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
352+
}
353+
354+
function addDateMinutes(date, minutes) {
355+
date = new Date(date.getTime());
356+
date.setMinutes(date.getMinutes() + minutes);
357+
return date;
358+
}
359+
360+
function convertTimezoneToLocal(date, timezone, reverse) {
361+
reverse = reverse ? -1 : 1;
362+
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
363+
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
364+
}
333365
}]);

src/datepicker/datepicker.js

+39-19
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1717
yearRange: 20,
1818
minDate: null,
1919
maxDate: null,
20-
shortcutPropagation: false
20+
shortcutPropagation: false,
21+
ngModelOptions: {}
2122
})
2223

23-
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) {
24+
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
25+
function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
2426
var self = this,
25-
ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
27+
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
28+
ngModelOptions = {};
2629

2730
// Modes chain
2831
this.modes = ['day', 'month', 'year'];
@@ -41,11 +44,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
4144
angular.forEach(['minDate', 'maxDate'], function(key) {
4245
if ($attrs[key]) {
4346
$scope.$parent.$watch($attrs[key], function(value) {
44-
self[key] = value ? new Date(value) : null;
47+
self[key] = value ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : null;
4548
self.refreshView();
4649
});
4750
} else {
48-
self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
51+
self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
4952
}
5053
});
5154

@@ -67,10 +70,10 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
6770
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
6871

6972
if (angular.isDefined($attrs.initDate)) {
70-
this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
73+
this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
7174
$scope.$parent.$watch($attrs.initDate, function(initDate) {
7275
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
73-
self.activeDate = initDate;
76+
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
7477
self.refreshView();
7578
}
7679
});
@@ -96,6 +99,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
9699

97100
this.init = function(ngModelCtrl_) {
98101
ngModelCtrl = ngModelCtrl_;
102+
ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
99103

100104
ngModelCtrl.$render = function() {
101105
self.render();
@@ -108,7 +112,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
108112
isValid = !isNaN(date);
109113

110114
if (isValid) {
111-
this.activeDate = date;
115+
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
112116
} else if (!$datepickerSuppressError) {
113117
$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.');
114118
}
@@ -121,13 +125,15 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
121125
this._refreshView();
122126

123127
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
128+
date = dateParser.fromTimezone(date, ngModelOptions.timezone);
124129
ngModelCtrl.$setValidity('dateDisabled', !date ||
125130
this.element && !this.isDisabled(date));
126131
}
127132
};
128133

129134
this.createDateObject = function(date, format) {
130135
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
136+
model = dateParser.fromTimezone(model, ngModelOptions.timezone);
131137
return {
132138
date: date,
133139
label: dateFilter(date, format),
@@ -160,8 +166,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
160166

161167
$scope.select = function(date) {
162168
if ($scope.datepickerMode === self.minMode) {
163-
var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
169+
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
164170
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
171+
dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
165172
ngModelCtrl.$setViewValue(dt);
166173
ngModelCtrl.$render();
167174
} else {
@@ -543,19 +550,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
543550
altInputFormats: []
544551
})
545552

546-
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout',
547-
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
553+
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
554+
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
548555
var self = this;
549556
var cache = {},
550557
isHtml5DateInput = false;
551558
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
552559
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
553-
ngModel, $popup, altInputFormats;
560+
ngModel, ngModelOptions, $popup, altInputFormats;
554561

555562
scope.watchData = {};
556563

557564
this.init = function(_ngModel_) {
558565
ngModel = _ngModel_;
566+
ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
559567
closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
560568
appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
561569
onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
@@ -568,6 +576,8 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
568576
if (datepickerPopupConfig.html5Types[attrs.type]) {
569577
dateFormat = datepickerPopupConfig.html5Types[attrs.type];
570578
isHtml5DateInput = true;
579+
ngModelOptions.timezoneHtml5 = ngModelOptions.timezone;
580+
ngModelOptions.timezone = null;
571581
} else {
572582
dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
573583
attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
@@ -595,8 +605,11 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
595605

596606
// popup element used to display calendar
597607
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
608+
scope.ngModelOptions = angular.copy(ngModelOptions);
609+
scope.ngModelOptions.timezone = null;
598610
popupEl.attr({
599611
'ng-model': 'date',
612+
'ng-model-options': 'ngModelOptions',
600613
'ng-change': 'dateSelection(date)',
601614
'template-url': datepickerPopupTemplateUrl
602615
});
@@ -615,7 +628,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
615628
if (attrs.datepickerOptions) {
616629
var options = scope.$parent.$eval(attrs.datepickerOptions);
617630
if (options && options.initDate) {
618-
scope.initDate = options.initDate;
631+
scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone || ngModelOptions.timezoneHtml5);
619632
datepickerEl.attr('init-date', 'initDate');
620633
delete options.initDate;
621634
}
@@ -628,9 +641,12 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
628641
if (attrs[key]) {
629642
var getAttribute = $parse(attrs[key]);
630643
scope.$parent.$watch(getAttribute, function(value) {
631-
scope.watchData[key] = value;
632644
if (key === 'minDate' || key === 'maxDate') {
633-
cache[key] = new Date(value);
645+
cache[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
646+
}
647+
scope.watchData[key] = cache[key] || value;
648+
if (key === 'initDate') {
649+
scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone || ngModelOptions.timezoneHtml5);
634650
}
635651
});
636652
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
@@ -667,12 +683,16 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
667683
ngModel.$validators.date = validator;
668684
ngModel.$parsers.unshift(parseDate);
669685
ngModel.$formatters.push(function(value) {
670-
scope.date = value;
671-
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
686+
if (ngModel.$isEmpty(value)) {
687+
scope.date = value;
688+
return value;
689+
}
690+
scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
691+
return dateFilter(scope.date, dateFormat);
672692
});
673693
} else {
674694
ngModel.$formatters.push(function(value) {
675-
scope.date = value;
695+
scope.date = dateParser.fromTimezone(value, ngModelOptions.timezoneHtml5);
676696
return value;
677697
});
678698
}
@@ -827,7 +847,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
827847
if (angular.isString(viewValue)) {
828848
var date = parseDateString(viewValue);
829849
if (!isNaN(date)) {
830-
return date;
850+
return dateParser.toTimezone(date, ngModelOptions.timezone);
831851
}
832852
}
833853

src/datepicker/docs/readme.md

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ The datepicker has 3 modes:
100100
allowInvalid support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions).
101101

102102

103+
* `ng-model-options`
104+
_(Default: {})_ -
105+
Timezone support. [More on ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions).
106+
103107
### uib-datepicker-popup settings ###
104108

105109
Options for the uib-datepicker must be passed as JSON using the `datepicker-options` attribute. This list is only for popup settings.

0 commit comments

Comments
 (0)