diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js
index 3cbbfe9809..36f93cc319 100644
--- a/src/datepicker/datepicker.js
+++ b/src/datepicker/datepicker.js
@@ -206,7 +206,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
datepickerMode: '=?',
dateDisabled: '&',
customClass: '&',
- shortcutPropagation: '&?'
+ shortcutPropagation: '&?',
+ minDate: '@',
+ maxDate: '@'
},
require: ['datepicker', '?^ngModel'],
controller: 'DatepickerController',
@@ -577,6 +579,62 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
}
+ function compareDates(date1, date2) {
+ if (isNaN(date1) || isNaN(date2)) {
+ return undefined;
+ }
+ else {
+ return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()) );
+ }
+ }
+
+ function getDateLimitToCheck(limitName) {
+ var watchDate = scope.watchData[limitName];
+ if (!watchDate) {
+ return null;
+ } else {
+ return new Date(watchDate);
+ }
+ }
+
+ function isDateLimitMet(limitName, dateToCheck, viewValue) {
+ var parsedDate = dateParser.parse(viewValue, dateFormat);
+ var dateCompare = compareDates(parsedDate, dateToCheck);
+ if (limitName == 'minDate') {
+ return !dateCompare || dateCompare > 0;
+ } else if (limitName == 'maxDate') {
+ return !dateCompare || dateCompare < 0;
+ }
+ }
+
+ function dateLimitParseFormat(limitName, viewValue, isFormatOnly) {
+ var dateLimit = getDateLimitToCheck(limitName);
+ if (dateLimit) {
+ var isMet = isDateLimitMet(limitName, dateLimit, viewValue);
+ ngModel.$setValidity(limitName, isMet);
+ return (isFormatOnly || isMet) ? viewValue : undefined;
+ } else {
+ return viewValue;
+ }
+ }
+
+ function minLimitParse(viewValue) {
+ return dateLimitParseFormat('minDate', viewValue, false);
+ }
+
+ function minLimitFormat(viewValue) {
+ return dateLimitParseFormat('minDate', viewValue, true);
+ }
+
+ function maxLimitParse(viewValue) {
+ return dateLimitParseFormat('maxDate', viewValue, false);
+ }
+
+ function maxLimitFormat(viewValue) {
+ return dateLimitParseFormat('maxDate', viewValue, true);
+ }
+
+
function parseDate(viewValue) {
if (angular.isNumber(viewValue)) {
// presumably timestamp to date object
@@ -616,14 +674,34 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
}
}
+ function parsing(viewValue) {
+ var result = parseDate(viewValue);
+ if (result) {
+ var minMet = minLimitParse(viewValue);
+ var maxMet = maxLimitParse(viewValue);
+ result = (minMet && maxMet) ? result : undefined;
+ }
+ return result;
+ }
+
if (!isHtml5DateInput) {
// Internal API to maintain the correct ng-invalid-[key] class
ngModel.$$parserName = 'date';
ngModel.$validators.date = validator;
- ngModel.$parsers.unshift(parseDate);
+ ngModel.$parsers.unshift(parsing);
ngModel.$formatters.push(function (value) {
scope.date = value;
- return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
+ if (ngModel.$isEmpty(value)) {
+ return value;
+ }
+ else {
+ value = minLimitFormat(value);
+ if (value) {
+ value = maxLimitFormat(value);
+ }
+ value = maxLimitFormat(value);
+ return dateFilter(value, dateFormat);
+ }
});
}
else {
diff --git a/src/datepicker/docs/demo.html b/src/datepicker/docs/demo.html
index 7ea3b98fd2..ba0c0c41e3 100644
--- a/src/datepicker/docs/demo.html
+++ b/src/datepicker/docs/demo.html
@@ -31,7 +31,7 @@
Popup
\ No newline at end of file
+
diff --git a/src/datepicker/docs/demo.js b/src/datepicker/docs/demo.js
index 1412d392c4..268b7f102f 100644
--- a/src/datepicker/docs/demo.js
+++ b/src/datepicker/docs/demo.js
@@ -1,11 +1,11 @@
angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($scope) {
$scope.today = function() {
- $scope.dt = new Date();
+ $scope.pickerDate.dt = new Date();
};
$scope.today();
$scope.clear = function () {
- $scope.dt = null;
+ $scope.pickerDate.dt = null;
};
// Disable weekend selection
diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js
index 95a121347c..62db5dbd2b 100644
--- a/src/datepicker/test/datepicker.spec.js
+++ b/src/datepicker/test/datepicker.spec.js
@@ -1199,8 +1199,12 @@ describe('datepicker directive', function () {
$document = _$document_;
$sniffer = _$sniffer_;
$rootScope.isopen = true;
+ $rootScope.format='yyyy-MM-dd';
$rootScope.date = new Date('September 30, 2010 15:30:00');
- var wrapElement = $compile('')($rootScope);
+ $rootScope.minimumDate = new Date('January 1, 1970');
+ $rootScope.maximumDate = new Date('January 1, 2020');
+ $rootScope.dateDisabledHandler = jasmine.createSpy('dateDisabledHandler');
+ var wrapElement = $compile('
')($rootScope);
$rootScope.$digest();
assignElements(wrapElement);
}));
@@ -1238,8 +1242,46 @@ describe('datepicker directive', function () {
$rootScope.date = new Date('January 10, 1983 10:00:00');
$rootScope.$digest();
expect(inputEl.val()).toBe('1983-01-10');
+ expect(inputEl).not.toHaveClass('ng-invalid');
+ expect(inputEl).not.toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ });
+
+ it('updates the input correctly when model changes to a date prior to minimum date', function() {
+ $rootScope.date = new Date('January 10, 1963 10:00:00');
+ $rootScope.$digest();
+ expect(inputEl.val()).toBe('1963-01-10');
+ expect(inputEl).toHaveClass('ng-invalid');
+ expect(inputEl).toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ });
+
+ it('input is not marked with invalid minimum date when minimum is not a valid date', function() {
+ expect(inputEl).not.toHaveClass('ng-invalid-min-date');
+ $rootScope.minimumDate = 'pizza';
+ $rootScope.$digest();
+ expect(inputEl).not.toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-date');
});
+ it('updates the input correctly when model changes to a date past the maximum date', function() {
+ $rootScope.date = new Date('January 10, 2023 10:00:00');
+ $rootScope.$digest();
+ expect(inputEl.val()).toBe('2023-01-10');
+ expect(inputEl).toHaveClass('ng-invalid');
+ expect(inputEl).toHaveClass('ng-invalid-max-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-date');
+ });
+
+ it('input is not marked with invalid maximum date when maximum is not a valid date', function() {
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ $rootScope.maximumDate = new Date('pizza');
+ $rootScope.$digest();
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ });
+
+
it('closes the dropdown when a day is clicked', function() {
expect(dropdownEl.css('display')).not.toBe('none');
@@ -1290,6 +1332,40 @@ describe('datepicker directive', function () {
expect(inputEl).not.toHaveClass('ng-invalid');
expect(inputEl).not.toHaveClass('ng-invalid-date');
});
+ it('sets `ng-invalid` for date prior to minimum date', function() {
+ changeInputValueTo(inputEl, '1960-12-01');
+
+ expect(inputEl).toHaveClass('ng-invalid');
+ expect(inputEl).toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ expect(inputEl).toHaveClass('ng-invalid-date');
+ expect($rootScope.date).toBeUndefined();
+ expect(inputEl.val()).toBe('1960-12-01');
+ });
+
+ it('sets `ng-invalid` for date (european format) prior to minimum date', function() {
+ $rootScope.format='dd.MM.yyyy';
+ $rootScope.$digest();
+ changeInputValueTo(inputEl, '20.12.1960');
+
+ expect(inputEl).toHaveClass('ng-invalid');
+ expect(inputEl).toHaveClass('ng-invalid-min-date');
+ expect(inputEl).not.toHaveClass('ng-invalid-max-date');
+ expect(inputEl).toHaveClass('ng-invalid-date');
+ expect($rootScope.date).toBeUndefined();
+ expect(inputEl.val()).toBe('20.12.1960');
+ });
+
+ it('sets `ng-invalid` for date past the maximum date', function() {
+ changeInputValueTo(inputEl, '2050-12-01');
+
+ expect(inputEl).toHaveClass('ng-invalid');
+ expect(inputEl).not.toHaveClass('ng-invalid-min-date');
+ expect(inputEl).toHaveClass('ng-invalid-max-date');
+ expect(inputEl).toHaveClass('ng-invalid-date');
+ expect($rootScope.date).toBeUndefined();
+ expect(inputEl.val()).toBe('2050-12-01');
+ });
describe('focus', function () {
beforeEach(function() {