diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 4e77397878dd..24f32a616535 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -11,6 +11,11 @@
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
+var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
+var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/;
+var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
+var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
+var TIME_REGEXP = /^(\d\d):(\d\d)$/;
var inputType = {
@@ -89,7 +94,337 @@ var inputType = {
*/
'text': textInputType,
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.date
+ *
+ * @description
+ * HTML5 or text input with date validation and transformation. In browsers that do not yet support
+ * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601
+ * date format (yyyy-MM-dd), for example: `2009-01-06`. Will also accept a valid ISO date or Date object
+ * as model input, but will always output a Date object to the model.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('2013-10-22');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('2015-01-01');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+
+ */
+ 'date': dateInputType,
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.dateTimeLocal
+ *
+ * @description
+ * HTML5 or text input with datetime validation and transformation. In browsers that do not yet support
+ * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601
+ * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. Will also accept a valid ISO
+ * datetime string or Date object as model input, but will always output a Date object to the model.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('2010-12-28T14:57');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('2015-01-01T23:59');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+
+ */
+ 'datetime-local': dateTimeLocalInputType,
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.time
+ *
+ * @description
+ * HTML5 or text input with time validation and transformation. In browsers that do not yet support
+ * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601
+ * local time format (HH:mm), for example: `14:57`. Will also accept a valid ISO
+ * time string or Date object as model input, but will always output a Date object to the model of January 1, 1900, or
+ * local date `new Date(0, 0, 1, HH, mm)`.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('14:57');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('23:59');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+
+ */
+ 'time': timeInputType,
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.week
+ *
+ * @description
+ * HTML5 or text input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
+ * the HTML5 week input, a text element will be used. The text must be entered in a valid ISO-8601
+ * week format (yyyy-W##), for example: `2013-W02`. Will also accept a valid ISO
+ * week string or Date object as model input, but will always output a Date object to the model.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('2013-W01');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('2015-W01');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+
+ */
+ 'week': weekInputType,
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.month
+ *
+ * @description
+ * HTML5 or text input with month validation and transformation. In browsers that do not yet support
+ * the HTML5 month input, a text element will be used. The text must be entered in a valid ISO-8601
+ * month format (yyyy-MM), for example: `2009-01`. Will also accept a valid ISO month or Date object
+ * as model input, but will always output a Date object to the model.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+
+
+
+
+
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('2013-10');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ inp ut('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('2015-01');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+
+ */
+ 'month': monthInputType,
+
/**
* @ngdoc inputType
* @name ng.directive:input.number
@@ -530,6 +865,389 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
}
+function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ if(ctrl.$isEmpty(value)) {
+ ctrl.$setValidity('week', true);
+ return value;
+ }
+
+ if(WEEK_REGEXP.test(value)) {
+ ctrl.$setValidity('week', true);
+ return new Date(getTime(value).time);
+ }
+
+ ctrl.$setValidity('week', false);
+ return undefined;
+ });
+
+ ctrl.$formatters.push(function(value) {
+ if(isDate(value)) {
+ return $filter('date')(value, 'yyyy-Www');
+ }
+ return ctrl.$isEmpty(value) ? '' : ''+value;
+ });
+
+ if(attr.min) {
+ var minValidator = function(value) {
+ var valTime = getTime(value),
+ minTime = getTime(attr.min);
+
+ var valid = ctrl.$isEmpty(value) ||
+ valTime.time >= minTime.time;
+
+ ctrl.$setValidity('min', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if(attr.max) {
+ var maxValidator = function(value) {
+ var valTime = getTime(value),
+ maxTime = getTime(attr.max);
+
+ var valid = ctrl.$isEmpty(value) ||
+ valTime.time <= maxTime.time;
+
+ ctrl.$setValidity('max', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ function getFirstThursday(year) {
+ var d = 1, date;
+ while(true) {
+ date = new Date(year, 0, d++);
+ if(date.getDay() === 4) {
+ return date;
+ }
+ }
+ }
+
+ function getThisThursday(date) {
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + (4 - date.getDay()));
+ }
+
+ var MILLISECONDS_PER_WEEK = 6.048e8;
+
+ function getWeek(date) {
+ var firstThurs = getFirstThursday(date.getFullYear()),
+ thisThurs = getThisThursday(date),
+ diff = +thisThurs - +firstThurs;
+
+ return 1 + Math.round(diff / MILLISECONDS_PER_WEEK);
+ }
+
+ function getTime(isoWeek) {
+ if(isDate(isoWeek)) {
+ return {
+ year: isoWeek.getFullYear(),
+ week: getWeek(isoWeek),
+ time: +isoWeek
+ };
+ }
+
+ if(isString(isoWeek)) {
+ WEEK_REGEXP.lastIndex = 0;
+ var parts = WEEK_REGEXP.exec(isoWeek);
+ if(parts) {
+ var year = +parts[1],
+ week = +parts[2],
+ firstThurs = getFirstThursday(year),
+ addDays = (week - 1) * 7;
+
+ return {
+ time: +new Date(year, 0, firstThurs.getDate() + addDays),
+ week: week,
+ year: year
+ };
+ }
+ }
+
+ return NaN;
+ }
+}
+
+function timeInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ if(ctrl.$isEmpty(value)) {
+ ctrl.$setValidity('time', true);
+ return value;
+ }
+
+ if(TIME_REGEXP.test(value)) {
+ ctrl.$setValidity('time', true);
+ return new Date(getTime(value));
+ }
+
+ ctrl.$setValidity('time', false);
+ return undefined;
+ });
+
+ ctrl.$formatters.push(function(value) {
+ if(isDate(value)) {
+ return $filter('date')(value, 'HH:mm');
+ }
+ return ctrl.$isEmpty(value) ? '' : '' + value;
+ });
+
+ if(attr.min) {
+ var minValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) >= getTime(attr.min));
+ ctrl.$setValidity('min', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if(attr.max) {
+ var maxValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) <= getTime(attr.max));
+ ctrl.$setValidity('max', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ function getTime(iso) {
+ if(isDate(iso)) {
+ return +iso;
+ }
+
+ if(isString(iso)) {
+ TIME_REGEXP.lastIndex = 0;
+ var parts = TIME_REGEXP.exec(iso),
+ HH = +parts[1],
+ mm = +parts[1];
+
+ return +new Date(0, 0, 1, HH, mm);
+ }
+
+ return NaN;
+ }
+}
+
+function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ if(ctrl.$isEmpty(value)) {
+ ctrl.$setValidity('datetimelocal', true);
+ return value;
+ }
+
+ if(DATETIMELOCAL_REGEXP.test(value)) {
+ ctrl.$setValidity('datetimelocal', true);
+ return new Date(getTime(value));
+ }
+
+ ctrl.$setValidity('datetimelocal', false);
+ return undefined;
+ });
+
+ ctrl.$formatters.push(function(value) {
+ if(isDate(value)) {
+ return $filter('date')(value, 'yyyy-MM-ddTHH:mm');
+ }
+ return ctrl.$isEmpty(value) ? '' : '' + value;
+ });
+
+ if(attr.min) {
+ var minValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) >= getTime(attr.min));
+ ctrl.$setValidity('min', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if(attr.max) {
+ var maxValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) <= getTime(attr.max));
+ ctrl.$setValidity('max', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ function getTime(iso) {
+ if(isDate(iso)) {
+ return +iso;
+ }
+
+ if(isString(iso)) {
+ DATETIMELOCAL_REGEXP.lastIndex = 0;
+ var parts = DATETIMELOCAL_REGEXP.exec(iso),
+ yyyy = +parts[1],
+ MM = +parts[2] - 1,
+ dd = +parts[3],
+ HH = +parts[4],
+ mm = +parts[5];
+
+ return +new Date(yyyy, MM, dd, HH, mm);
+ }
+
+ return NaN;
+ }
+}
+
+function monthInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ if(ctrl.$isEmpty(value)) {
+ ctrl.$setValidity('month', true);
+ return value;
+ }
+
+ if(MONTH_REGEXP.test(value)) {
+ ctrl.$setValidity('month', true);
+ return new Date(getTime(value));
+ }
+
+ ctrl.$setValidity('month', false);
+ return undefined;
+ });
+
+ ctrl.$formatters.push(function(value) {
+ if(isDate(value)) {
+ return $filter('month')(value, 'yyyy-MM');
+ }
+ return ctrl.$isEmpty(value) ? '' : '' + value;
+ });
+
+ if(attr.min) {
+ var minValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) >= getTime(attr.min));
+ ctrl.$setValidity('min', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if(attr.max) {
+ var maxValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) <= getTime(attr.max));
+ ctrl.$setValidity('max', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ function getTime(iso) {
+ if(isDate(iso)) {
+ return +iso;
+ }
+
+ if(isString(iso)) {
+ DATE_REGEXP.lastIndex = 0;
+ var parts = DATE_REGEXP.exec(iso),
+ yyyy = +parts[1],
+ mm = +parts[2] - 1,
+ time = new Date(yyyy, mm, 1);
+ return +time;
+ }
+
+ return NaN;
+ }
+}
+
+function dateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ if(ctrl.$isEmpty(value)) {
+ ctrl.$setValidity('date', true);
+ return value;
+ }
+
+ if(DATE_REGEXP.test(value)) {
+ ctrl.$setValidity('date', true);
+ return new Date(getTime(value));
+ }
+
+ ctrl.$setValidity('date', false);
+ return undefined;
+ });
+
+ ctrl.$formatters.push(function(value) {
+ if(isDate(value)) {
+ return $filter('date')(value, 'yyyy-MM-dd');
+ }
+ return ctrl.$isEmpty(value) ? '' : '' + value;
+ });
+
+ if(attr.min) {
+ var minValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) >= getTime(attr.min));
+ ctrl.$setValidity('min', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if(attr.max) {
+ var maxValidator = function(value) {
+ var valid = ctrl.$isEmpty(value) ||
+ (getTime(value) <= getTime(attr.max));
+ ctrl.$setValidity('max', valid);
+ return valid ? value : undefined;
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ function getTime(iso) {
+ if(isDate(iso)) {
+ return +iso;
+ }
+
+ if(isString(iso)) {
+ DATE_REGEXP.lastIndex = 0;
+ var parts = DATE_REGEXP.exec(iso),
+ yyyy = +parts[1],
+ mm = +parts[2] - 1,
+ dd = +parts[3],
+ time = new Date(yyyy, mm, dd);
+ return +time;
+ }
+
+ return NaN;
+ }
+}
+
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
@@ -772,14 +1490,14 @@ function checkboxInputType(scope, element, attr, ctrl) {
*/
-var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
+var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attr, ctrl) {
if (ctrl) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
- $browser);
+ $browser, $filter);
}
}
};
diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js
index c92ed384792e..93773210dcaf 100644
--- a/src/ng/filter/filters.js
+++ b/src/ng/filter/filters.js
@@ -228,6 +228,35 @@ function timeZoneGetter(date) {
return paddedZone;
}
+function getFirstThursday(year) {
+ var d = 1,
+ date = new Date(year, 0, d);
+ while(date.getDay() !== 4) {
+ d++;
+ date = new Date(year, 0, d);
+ }
+ return date;
+}
+
+function getThursdayThisWeek(date) {
+ var day = date.getDay(),
+ d = date.getDate();
+
+ return new Date(date.getFullYear(), date.getMonth(), d + (4 - day));
+}
+
+function weekGetter(size) {
+ return function(date) {
+ var firstThurs = getFirstThursday(date.getFullYear()),
+ thisThurs = getThursdayThisWeek(date);
+
+ var diff = +thisThurs - +firstThurs,
+ result = 1 + Math.round(diff / 6.048e8);
+
+ return padNumber(result, size);
+ };
+}
+
function ampmGetter(date, formats) {
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
}
@@ -256,10 +285,12 @@ var DATE_FORMATS = {
EEEE: dateStrGetter('Day'),
EEE: dateStrGetter('Day', true),
a: ampmGetter,
- Z: timeZoneGetter
+ Z: timeZoneGetter,
+ ww: weekGetter(2),
+ w: weekGetter(1)
};
-var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
NUMBER_STRING = /^\-?\d+$/;
/**
@@ -294,6 +325,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
* * `'a'`: am/pm marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
+ * * `'ww'`: ISO-8601 week of year (00-53)
+ * * `'w'`: ISO-8601 week of year (0-53)
*
* `format` string can also be one of the following predefined
* {@link guide/i18n localizable formats}:
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 26abceae1cfe..3cb72f3ff80d 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -751,6 +751,649 @@ describe('input', function() {
// INPUT TYPES
+ describe('month', function (){
+ it('should set the view if the model is valid ISO8601 month', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.january = '2013-01';
+ });
+
+ expect(inputElm.val()).toBe('2013-01');
+ });
+
+ it('should set the view if the model is a valid Date object', function (){
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.march = new Date(2013, 2, 1);
+ });
+
+ expect(inputElm.val()).toBe('2013-03');
+ });
+
+ it('should set the model undefined if the input is an invalid month string', function () {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.value = new Date(2013, 0, 1);
+ });
+
+
+ expect(inputElm.val()).toBe('2013-01');
+
+ try {
+ //set to text for browsers with datetime-local validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('stuff');
+ expect(inputElm.val()).toBe('stuff');
+ expect(scope.value).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2012-12');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2013-07');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2013, 6, 1));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function(){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2012-03');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2012, 2, 1));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2013-05');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeUndefined();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+ });
+ });
+
+ describe('week', function (){
+ it('should set the view if the model is valid ISO8601 week', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.secondWeek = '2013-W02';
+ });
+
+ expect(inputElm.val()).toBe('2013-W02');
+ });
+
+ it('should set the view if the model is a valid Date object', function (){
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.secondWeek = new Date(2013, 0, 11);
+ });
+
+ expect(inputElm.val()).toBe('2013-W02');
+ });
+
+ it('should set the model undefined if the input is an invalid week string', function () {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.value = new Date(2013, 0, 11);
+ });
+
+
+ expect(inputElm.val()).toBe('2013-W02');
+
+ try {
+ //set to text for browsers with datetime-local validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('stuff');
+ expect(inputElm.val()).toBe('stuff');
+ expect(scope.value).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2012-W12');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2013-W03');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2013, 0, 17));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function(){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2012-W01');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2012, 0, 5));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2013-W03');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeUndefined();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+ });
+ });
+
+ describe('datetime-local', function () {
+ it('should set the view if the model is valid ISO8601 local datetime', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.lunchtime = '2013-12-16T11:30';
+ });
+
+ expect(inputElm.val()).toBe('2013-12-16T11:30');
+ });
+
+ it('should set the view if the model if a valid Date object.', function(){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.tenSecondsToNextYear = new Date(2013, 11, 31, 23, 59);
+ });
+
+ expect(inputElm.val()).toBe('2013-12-31T23:59');
+ });
+
+ it('should set the model undefined if the view is invalid', function (){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.breakMe = new Date(2009, 0, 6, 16, 25);
+ });
+
+ expect(inputElm.val()).toBe('2009-01-06T16:25');
+
+ try {
+ //set to text for browsers with datetime-local validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('stuff');
+ expect(inputElm.val()).toBe('stuff');
+ expect(scope.breakMe).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('1999-12-31T01:02');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2000-01-01T23:02');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2019-12-31T01:02');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+
+ it('should validate', function() {
+ changeInputValueTo('2000-01-01T01:02');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+ });
+
+ it('should validate even if max value changes on-the-fly', function(done) {
+ scope.max = '2013-01-01T01:02';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2014-01-01T12:34');
+ expect(inputElm).toBeInvalid();
+
+ scope.max = '2001-01-01T01:02';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+
+ it('should validate even if min value changes on-the-fly', function(done) {
+ scope.min = '2013-01-01T01:02';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2010-01-01T12:34');
+ expect(inputElm).toBeInvalid();
+
+ scope.min = '2014-01-01T01:02';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+ });
+
+
+ describe('time', function () {
+ it('should set the view if the model is valid ISO8601 time', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.lunchtime = '11:30';
+ });
+
+ expect(inputElm.val()).toBe('11:30');
+ });
+
+ it('should set the view if the model if a valid Date object.', function(){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.threeFortyOnePm = new Date(0, 0, 1, 15, 41);
+ });
+
+ expect(inputElm.val()).toBe('15:41');
+ });
+
+ it('should set the model undefined if the view is invalid', function (){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.breakMe = new Date(0, 0, 1, 16, 25);
+ });
+
+ expect(inputElm.val()).toBe('16:25');
+
+ try {
+ //set to text for browsers with time validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('stuff');
+ expect(inputElm.val()).toBe('stuff');
+ expect(scope.breakMe).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('01:02');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('23:02');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(0, 0, 1, 23, 2));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('23:00');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+
+ it('should validate', function() {
+ changeInputValueTo('05:30');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(0, 0, 1, 5, 30));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+ });
+
+ it('should validate even if max value changes on-the-fly', function(done) {
+ scope.max = '21:02';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('22:34');
+ expect(inputElm).toBeInvalid();
+
+ scope.max = '12:34';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+
+ it('should validate even if min value changes on-the-fly', function(done) {
+ scope.min = '08:45';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('06:15');
+ expect(inputElm).toBeInvalid();
+
+ scope.min = '13:50';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+ });
+
+
+ describe('datetime-local', function () {
+ it('should set the view if the model is valid ISO8601 local datetime', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.lunchtime = '2013-12-16T11:30';
+ });
+
+ expect(inputElm.val()).toBe('2013-12-16T11:30');
+ });
+
+ it('should set the view if the model if a valid Date object.', function(){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.tenSecondsToNextYear = new Date(2013, 11, 31, 23, 59);
+ });
+
+ expect(inputElm.val()).toBe('2013-12-31T23:59');
+ });
+
+ it('should set the model undefined if the view is invalid', function (){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.breakMe = new Date(2009, 0, 6, 16, 25);
+ });
+
+ expect(inputElm.val()).toBe('2009-01-06T16:25');
+
+ try {
+ //set to text for browsers with datetime-local validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('stuff');
+ expect(inputElm.val()).toBe('stuff');
+ expect(scope.breakMe).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('1999-12-31T01:02');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2000-01-01T23:02');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2019-12-31T01:02');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+
+ it('should validate', function() {
+ changeInputValueTo('2000-01-01T01:02');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+ });
+
+ it('should validate even if max value changes on-the-fly', function(done) {
+ scope.max = '2013-01-01T01:02';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2014-01-01T12:34');
+ expect(inputElm).toBeInvalid();
+
+ scope.max = '2001-01-01T01:02';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+
+ it('should validate even if min value changes on-the-fly', function(done) {
+ scope.min = '2013-01-01T01:02';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2010-01-01T12:34');
+ expect(inputElm).toBeInvalid();
+
+ scope.min = '2014-01-01T01:02';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+ });
+
+ describe('date', function () {
+ it('should set the view if the model is valid ISO8601 date', function() {
+ compileInput('');
+
+ scope.$apply(function(){
+ scope.birthday = '1977-10-22';
+ });
+
+ expect(inputElm.val()).toBe('1977-10-22');
+ });
+
+ it('should set the view if the model if a valid Date object.', function(){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.christmas = new Date(2013, 11, 25);
+ });
+
+ expect(inputElm.val()).toBe('2013-12-25');
+ });
+
+ it('should set the model undefined if the view is invalid', function (){
+ compileInput('');
+
+ scope.$apply(function (){
+ scope.arrMatey = new Date(2014, 8, 14);
+ });
+
+ expect(inputElm.val()).toBe('2014-09-14');
+
+ try {
+ //set to text for browsers with date validation.
+ inputElm[0].setAttribute('type', 'text');
+ } catch(e) {
+ //for IE8
+ }
+
+ changeInputValueTo('1-2-3');
+ expect(inputElm.val()).toBe('1-2-3');
+ expect(scope.arrMatey).toBeUndefined();
+ expect(inputElm).toBeInvalid();
+ });
+
+ describe('min', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('1999-12-31');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.min).toBeTruthy();
+ });
+
+ it('should validate', function (){
+ changeInputValueTo('2000-01-01');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1));
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+ });
+
+ describe('max', function (){
+ beforeEach(function (){
+ compileInput('');
+ scope.$digest();
+ });
+
+ it('should invalidate', function (){
+ changeInputValueTo('2019-12-31');
+ expect(inputElm).toBeInvalid();
+ expect(scope.value).toBeFalsy();
+ expect(scope.form.alias.$error.max).toBeTruthy();
+ });
+
+ it('should validate', function() {
+ changeInputValueTo('2000-01-01');
+ expect(inputElm).toBeValid();
+ expect(+scope.value).toBe(+new Date(2000, 0, 1));
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+ });
+
+ it('should validate even if max value changes on-the-fly', function(done) {
+ scope.max = '2013-01-01';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2014-01-01');
+ expect(inputElm).toBeInvalid();
+
+ scope.max = '2001-01-01';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+
+ it('should validate even if min value changes on-the-fly', function(done) {
+ scope.min = '2013-01-01';
+ compileInput('');
+ scope.$digest();
+
+ changeInputValueTo('2010-01-01');
+ expect(inputElm).toBeInvalid();
+
+ scope.min = '2014-01-01';
+ scope.$digest(function () {
+ expect(inputElm).toBeValid();
+ done();
+ });
+ });
+ });
describe('number', function() {
diff --git a/test/ng/filter/filtersSpec.js b/test/ng/filter/filtersSpec.js
index 2d648b652984..da19c41adc4f 100644
--- a/test/ng/filter/filtersSpec.js
+++ b/test/ng/filter/filtersSpec.js
@@ -192,7 +192,7 @@ describe('filters', function() {
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
-
+ var secondWeek = new angular.mock.TzDate(+5, '2013-01-11T12:00:00.000Z'); //Friday Jan 11, 2012
var date;
beforeEach(inject(function($filter) {
@@ -215,6 +215,12 @@ describe('filters', function() {
});
it('should accept various format strings', function() {
+ expect(date(secondWeek, 'yyyy-Ww')).
+ toEqual('2013-W2');
+
+ expect(date(secondWeek, 'yyyy-Www')).
+ toEqual('2013-W02');
+
expect(date(morning, "yy-MM-dd HH:mm:ss")).
toEqual('10-09-03 07:05:08');