@@ -17,12 +17,15 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
17
17
yearRange : 20 ,
18
18
minDate : null ,
19
19
maxDate : null ,
20
- shortcutPropagation : false
20
+ shortcutPropagation : false ,
21
+ ngModelOptions : { }
21
22
} )
22
23
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' , 'uibDateUtils' ,
25
+ function ( $scope , $attrs , $parse , $interpolate , $log , dateFilter , datepickerConfig , $datepickerSuppressError , dateUtils ) {
24
26
var self = this ,
25
- ngModelCtrl = { $setViewValue : angular . noop } ; // nullModelCtrl;
27
+ ngModelCtrl = { $setViewValue : angular . noop } , // nullModelCtrl;
28
+ ngModelOptions = { } ;
26
29
27
30
// Modes chain
28
31
this . modes = [ 'day' , 'month' , 'year' ] ;
@@ -41,11 +44,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
41
44
angular . forEach ( [ 'minDate' , 'maxDate' ] , function ( key ) {
42
45
if ( $attrs [ key ] ) {
43
46
$scope . $parent . $watch ( $attrs [ key ] , function ( value ) {
44
- self [ key ] = value ? new Date ( value ) : null ;
47
+ self [ key ] = value ? dateUtils . fromTimezone ( new Date ( value ) , ngModelOptions . timezone ) : null ;
45
48
self . refreshView ( ) ;
46
49
} ) ;
47
50
} else {
48
- self [ key ] = datepickerConfig [ key ] ? new Date ( datepickerConfig [ key ] ) : null ;
51
+ self [ key ] = datepickerConfig [ key ] ? dateUtils . fromTimezone ( new Date ( datepickerConfig [ key ] ) , ngModelOptions . timezone ) : null ;
49
52
}
50
53
} ) ;
51
54
@@ -70,7 +73,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
70
73
this . activeDate = $scope . $parent . $eval ( $attrs . initDate ) || new Date ( ) ;
71
74
$scope . $parent . $watch ( $attrs . initDate , function ( initDate ) {
72
75
if ( initDate && ( ngModelCtrl . $isEmpty ( ngModelCtrl . $modelValue ) || ngModelCtrl . $invalid ) ) {
73
- self . activeDate = initDate ;
76
+ self . activeDate = dateUtils . fromTimezone ( initDate , ngModelOptions . timezone ) ;
74
77
self . refreshView ( ) ;
75
78
}
76
79
} ) ;
@@ -94,8 +97,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
94
97
return false ;
95
98
} ;
96
99
97
- this . init = function ( ngModelCtrl_ ) {
100
+ this . init = function ( ngModelCtrl_ , ngModelOptions_ ) {
98
101
ngModelCtrl = ngModelCtrl_ ;
102
+ ngModelOptions = ngModelOptions_ && ngModelOptions_ . $options || datepickerConfig . ngModelOptions ;
99
103
100
104
ngModelCtrl . $render = function ( ) {
101
105
self . render ( ) ;
@@ -108,7 +112,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
108
112
isValid = ! isNaN ( date ) ;
109
113
110
114
if ( isValid ) {
111
- this . activeDate = date ;
115
+ this . activeDate = dateUtils . fromTimezone ( date , ngModelOptions . timezone ) ;
112
116
} else if ( ! $datepickerSuppressError ) {
113
117
$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.' ) ;
114
118
}
@@ -121,13 +125,15 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
121
125
this . _refreshView ( ) ;
122
126
123
127
var date = ngModelCtrl . $viewValue ? new Date ( ngModelCtrl . $viewValue ) : null ;
128
+ date = dateUtils . fromTimezone ( date , ngModelOptions . timezone ) ;
124
129
ngModelCtrl . $setValidity ( 'dateDisabled' , ! date ||
125
130
this . element && ! this . isDisabled ( date ) ) ;
126
131
}
127
132
} ;
128
133
129
134
this . createDateObject = function ( date , format ) {
130
135
var model = ngModelCtrl . $viewValue ? new Date ( ngModelCtrl . $viewValue ) : null ;
136
+ model = dateUtils . fromTimezone ( model , ngModelOptions . timezone ) ;
131
137
return {
132
138
date : date ,
133
139
label : dateFilter ( date , format ) ,
@@ -161,7 +167,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
161
167
$scope . select = function ( date ) {
162
168
if ( $scope . datepickerMode === self . minMode ) {
163
169
var dt = ngModelCtrl . $viewValue ? new Date ( ngModelCtrl . $viewValue ) : new Date ( 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ;
170
+ dt = dateUtils . fromTimezone ( dt , ngModelOptions . timezone ) ;
164
171
dt . setFullYear ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) ) ;
172
+ dt = dateUtils . toTimezone ( dt , ngModelOptions . timezone ) ;
165
173
ngModelCtrl . $setViewValue ( dt ) ;
166
174
ngModelCtrl . $render ( ) ;
167
175
} else {
@@ -461,13 +469,16 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
461
469
customClass : '&' ,
462
470
shortcutPropagation : '&?'
463
471
} ,
464
- require : [ 'uibDatepicker' , '^ngModel' ] ,
472
+ require : [ 'uibDatepicker' , '^ngModel' , '^?ngModelOptions' ] ,
465
473
controller : 'UibDatepickerController' ,
466
474
controllerAs : 'datepicker' ,
467
475
link : function ( scope , element , attrs , ctrls ) {
468
- var datepickerCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
476
+ var datepickerCtrl = ctrls [ 0 ] ,
477
+ ngModelCtrl = ctrls [ 1 ] ,
478
+ ngModelOptions = ctrls [ 2 ] ;
479
+
469
480
470
- datepickerCtrl . init ( ngModelCtrl ) ;
481
+ datepickerCtrl . init ( ngModelCtrl , ngModelOptions ) ;
471
482
}
472
483
} ;
473
484
} )
@@ -543,19 +554,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
543
554
altInputFormats : [ ]
544
555
} )
545
556
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 ) {
557
+ . controller ( 'UibDatepickerPopupController' , [ '$scope' , '$element' , '$attrs' , '$compile' , '$parse' , '$document' , '$rootScope' , '$uibPosition' , 'dateFilter' , 'uibDateParser' , 'uibDatepickerPopupConfig' , '$timeout' , 'uibDateUtils' , 'uibDatepickerConfig' ,
558
+ function ( scope , element , attrs , $compile , $parse , $document , $rootScope , $position , dateFilter , dateParser , datepickerPopupConfig , $timeout , dateUtils , datepickerConfig ) {
548
559
var self = this ;
549
560
var cache = { } ,
550
561
isHtml5DateInput = false ;
551
562
var dateFormat , closeOnDateSelection , appendToBody , onOpenFocus ,
552
563
datepickerPopupTemplateUrl , datepickerTemplateUrl , popupEl , datepickerEl ,
553
- ngModel , $popup , altInputFormats ;
564
+ ngModel , ngModelOptions , $popup , altInputFormats ;
554
565
555
566
scope . watchData = { } ;
556
567
557
- this . init = function ( _ngModel_ ) {
568
+ this . init = function ( _ngModel_ , _ngModelOptions_ ) {
558
569
ngModel = _ngModel_ ;
570
+ ngModelOptions = _ngModelOptions_ && _ngModelOptions_ . $options || datepickerConfig . ngModelOptions ;
559
571
closeOnDateSelection = angular . isDefined ( attrs . closeOnDateSelection ) ? scope . $parent . $eval ( attrs . closeOnDateSelection ) : datepickerPopupConfig . closeOnDateSelection ;
560
572
appendToBody = angular . isDefined ( attrs . datepickerAppendToBody ) ? scope . $parent . $eval ( attrs . datepickerAppendToBody ) : datepickerPopupConfig . appendToBody ;
561
573
onOpenFocus = angular . isDefined ( attrs . onOpenFocus ) ? scope . $parent . $eval ( attrs . onOpenFocus ) : datepickerPopupConfig . onOpenFocus ;
@@ -605,6 +617,9 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
605
617
datepickerEl = angular . element ( popupEl . children ( ) [ 0 ] ) ;
606
618
datepickerEl . attr ( 'template-url' , datepickerTemplateUrl ) ;
607
619
620
+ scope . ngModelOptions = { $options : angular . extend ( { timezone : null } , ngModelOptions ) } ;
621
+ datepickerEl . attr ( { 'ng-model-options' : 'ngModelOptions' } ) ;
622
+
608
623
if ( isHtml5DateInput ) {
609
624
if ( attrs . type === 'month' ) {
610
625
datepickerEl . attr ( 'datepicker-mode' , '"month"' ) ;
@@ -615,7 +630,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
615
630
if ( attrs . datepickerOptions ) {
616
631
var options = scope . $parent . $eval ( attrs . datepickerOptions ) ;
617
632
if ( options && options . initDate ) {
618
- scope . initDate = options . initDate ;
633
+ scope . initDate = dateUtils . fromTimezone ( options . initDate , ngModelOptions . timezone ) ;
619
634
datepickerEl . attr ( 'init-date' , 'initDate' ) ;
620
635
delete options . initDate ;
621
636
}
@@ -628,9 +643,12 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
628
643
if ( attrs [ key ] ) {
629
644
var getAttribute = $parse ( attrs [ key ] ) ;
630
645
scope . $parent . $watch ( getAttribute , function ( value ) {
631
- scope . watchData [ key ] = value ;
632
646
if ( key === 'minDate' || key === 'maxDate' ) {
633
- cache [ key ] = new Date ( value ) ;
647
+ cache [ key ] = dateUtils . fromTimezone ( new Date ( value ) , ngModelOptions . timezone ) ;
648
+ }
649
+ scope . watchData [ key ] = cache [ key ] || value ;
650
+ if ( key === 'initDate' ) {
651
+ scope . watchData [ key ] = dateUtils . fromTimezone ( new Date ( value ) , ngModelOptions . timezone ) ;
634
652
}
635
653
} ) ;
636
654
datepickerEl . attr ( cameltoDash ( key ) , 'watchData.' + key ) ;
@@ -667,8 +685,13 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
667
685
ngModel . $validators . date = validator ;
668
686
ngModel . $parsers . unshift ( parseDate ) ;
669
687
ngModel . $formatters . push ( function ( value ) {
670
- scope . date = value ;
671
- return ngModel . $isEmpty ( value ) ? value : dateFilter ( value , dateFormat ) ;
688
+ if ( ngModel . $isEmpty ( value ) ) {
689
+ scope . date = value ;
690
+ return value ;
691
+ }
692
+ var dt = dateUtils . fromTimezone ( value , ngModelOptions . timezone ) ;
693
+ scope . date = dt ;
694
+ return dateFilter ( dt , dateFormat ) ;
672
695
} ) ;
673
696
} else {
674
697
ngModel . $formatters . push ( function ( value ) {
@@ -825,7 +848,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
825
848
return undefined ;
826
849
}
827
850
828
- return date ;
851
+ return dateUtils . toTimezone ( date , ngModelOptions . timezone ) ;
829
852
}
830
853
831
854
return undefined ;
@@ -903,7 +926,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
903
926
904
927
. directive ( 'uibDatepickerPopup' , function ( ) {
905
928
return {
906
- require : [ 'ngModel' , 'uibDatepickerPopup ' ] ,
929
+ require : [ 'uibDatepickerPopup' , ' ngModel', '^?ngModelOptions ' ] ,
907
930
controller : 'UibDatepickerPopupController' ,
908
931
scope : {
909
932
isOpen : '=?' ,
@@ -914,10 +937,11 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
914
937
customClass : '&'
915
938
} ,
916
939
link : function ( scope , element , attrs , ctrls ) {
917
- var ngModel = ctrls [ 0 ] ,
918
- ctrl = ctrls [ 1 ] ;
940
+ var ctrl = ctrls [ 0 ] ,
941
+ ngModel = ctrls [ 1 ] ,
942
+ ngModelOptions = ctrls [ 2 ] ;
919
943
920
- ctrl . init ( ngModel ) ;
944
+ ctrl . init ( ngModel , ngModelOptions ) ;
921
945
}
922
946
} ;
923
947
} )
@@ -930,4 +954,40 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
930
954
return attrs . templateUrl || 'uib/template/datepicker/popup.html' ;
931
955
}
932
956
} ;
957
+ } )
958
+
959
+ . service ( 'uibDateUtils' , function ( ) {
960
+ return {
961
+ toTimezone : toTimezone ,
962
+ fromTimezone : fromTimezone ,
963
+ timezoneToOffset : timezoneToOffset ,
964
+ addDateMinutes : addDateMinutes ,
965
+ convertTimezoneToLocal : convertTimezoneToLocal
966
+ } ;
967
+
968
+ function toTimezone ( date , timezone ) {
969
+ return timezone ? convertTimezoneToLocal ( date , timezone ) : date ;
970
+ }
971
+
972
+ function fromTimezone ( date , timezone ) {
973
+ return timezone ? convertTimezoneToLocal ( date , timezone , true ) : date ;
974
+ }
975
+
976
+ //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
977
+ function timezoneToOffset ( timezone , fallback ) {
978
+ var requestedTimezoneOffset = Date . parse ( 'Jan 01, 1970 00:00:00 ' + timezone ) / 60000 ;
979
+ return isNaN ( requestedTimezoneOffset ) ? fallback : requestedTimezoneOffset ;
980
+ }
981
+
982
+ function addDateMinutes ( date , minutes ) {
983
+ date = new Date ( date . getTime ( ) ) ;
984
+ date . setMinutes ( date . getMinutes ( ) + minutes ) ;
985
+ return date ;
986
+ }
987
+
988
+ function convertTimezoneToLocal ( date , timezone , reverse ) {
989
+ reverse = reverse ? - 1 : 1 ;
990
+ var timezoneOffset = timezoneToOffset ( timezone , date . getTimezoneOffset ( ) ) ;
991
+ return addDateMinutes ( date , reverse * ( timezoneOffset - date . getTimezoneOffset ( ) ) ) ;
992
+ }
933
993
} ) ;
0 commit comments