Skip to content

Commit 88b54cb

Browse files
committed
validate that datepicker input parses
1 parent a72104c commit 88b54cb

File tree

6 files changed

+45
-22
lines changed

6 files changed

+45
-22
lines changed

src/demo-app/datepicker/datepicker-demo.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ <h2>Result</h2>
4040
placeholder="Pick a date"
4141
(dateInput)="onDateInput($event)"
4242
(dateChange)="onDateChange($event)">
43+
<md-error *ngIf="resultPickerModel.hasError('mdDatepickerParse')">Not a valid date!</md-error>
4344
<md-error *ngIf="resultPickerModel.hasError('mdDatepickerMin')">Too early!</md-error>
4445
<md-error *ngIf="resultPickerModel.hasError('mdDatepickerMax')">Too late!</md-error>
4546
<md-error *ngIf="resultPickerModel.hasError('mdDatepickerFilter')">Date unavailable!</md-error>

src/lib/core/datetime/date-adapter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ export abstract class DateAdapter<D> {
107107
* @param value The value to parse.
108108
* @param parseFormat The expected format of the value being parsed
109109
* (type is implementation-dependent).
110-
* @returns The parsed date, or null if date could not be parsed.
110+
* @returns The parsed date.
111111
*/
112112
abstract parse(value: any, parseFormat: any): D | null;
113113

114114
/**
115115
* Formats a date as a string.
116-
* @param date The value to parse.
116+
* @param date The value to format.
117117
* @param displayFormat The format to use to display the date as a string.
118-
* @returns The parsed date, or null if date could not be parsed.
118+
* @returns The formatted date string.
119119
*/
120120
abstract format(date: D, displayFormat: any): string;
121121

@@ -157,11 +157,11 @@ export abstract class DateAdapter<D> {
157157
abstract getISODateString(date: D): string;
158158

159159
/**
160-
* Checks whether the given value is a date object of type `D`.
160+
* Checks whether the given value is a valid date object.
161161
* @param value The value to check.
162-
* @returns Whether the value is a date object of type `D`.
162+
* @returns Whether the value is a valid date object.
163163
*/
164-
abstract isDateObject(value: any): boolean;
164+
abstract isValidDate(value: any): boolean;
165165

166166
/**
167167
* Sets the locale used for all dates.

src/lib/core/datetime/native-date-adapter.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,16 @@ describe('NativeDateAdapter', () => {
305305
}
306306
});
307307

308-
it('should count a Date as a date object', () => {
309-
expect(adapter.isDateObject(new Date())).toBe(true);
308+
it('should count a Date as a valid date object', () => {
309+
expect(adapter.isValidDate(new Date())).toBe(true);
310310
});
311311

312-
it('should not count a string as a date object', () => {
313-
expect(adapter.isDateObject('1/1/2017')).toBe(false);
312+
it('should not count a string as a valid date object', () => {
313+
expect(adapter.isValidDate('1/1/2017')).toBe(false);
314+
});
315+
316+
it('should not count InvalidDate as a valid date object', () => {
317+
expect(adapter.isValidDate(new Date(NaN))).toBe(false);
314318
});
315319
});
316320

src/lib/core/datetime/native-date-adapter.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,16 @@ export class NativeDateAdapter extends DateAdapter<Date> {
157157
parse(value: any): Date | null {
158158
// We have no way using the native JS Date to set the parse format or locale, so we ignore these
159159
// parameters.
160-
let timestamp = typeof value == 'number' ? value : Date.parse(value);
161-
return isNaN(timestamp) ? null : new Date(timestamp);
160+
if (typeof value == 'number') {
161+
return new Date(value);
162+
}
163+
return value ? new Date(Date.parse(value)) : null;
162164
}
163165

164166
format(date: Date, displayFormat: Object): string {
167+
if (!this.isValidDate(date)) {
168+
return 'INVALID DATE';
169+
}
165170
if (SUPPORTS_INTL_API) {
166171
if (this.useUtcForDisplay) {
167172
date = new Date(Date.UTC(
@@ -207,8 +212,14 @@ export class NativeDateAdapter extends DateAdapter<Date> {
207212
].join('-');
208213
}
209214

210-
isDateObject(value: any) {
211-
return value instanceof Date;
215+
isValidDate(value: any) {
216+
if (value == null) {
217+
return true;
218+
}
219+
if (value instanceof Date) {
220+
return !isNaN(value.getTime());
221+
}
222+
return false;
212223
}
213224

214225
/** Creates a date but allows the month and date to overflow. */

src/lib/datepicker/datepicker-input.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,6 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
116116
this._dateFormats.parse.dateInput);
117117
}
118118
set value(value: D | null) {
119-
if (value != null && !this._dateAdapter.isDateObject(value)) {
120-
throw Error('Datepicker: value not recognized as a date object by DateAdapter.');
121-
}
122119
let oldDate = this.value;
123120
this._renderer.setProperty(this._elementRef.nativeElement, 'value',
124121
value ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : '');
@@ -170,16 +167,22 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
170167

171168
private _datepickerSubscription: Subscription;
172169

170+
/** The form control validator for whether the input parses. */
171+
private _parseValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
172+
return (!control.value || this._dateAdapter.isValidDate(control.value)) ?
173+
null : {'mdDatepickerParse': true};
174+
}
175+
173176
/** The form control validator for the min date. */
174177
private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
175-
return (!this.min || !control.value ||
178+
return (!this.min || !this._dateAdapter.isValidDate(this.min) || !control.value ||
176179
this._dateAdapter.compareDate(this.min, control.value) <= 0) ?
177180
null : {'mdDatepickerMin': {'min': this.min, 'actual': control.value}};
178181
}
179182

180183
/** The form control validator for the max date. */
181184
private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
182-
return (!this.max || !control.value ||
185+
return (!this.max || !this._dateAdapter.isValidDate(this.max) || !control.value ||
183186
this._dateAdapter.compareDate(this.max, control.value) >= 0) ?
184187
null : {'mdDatepickerMax': {'max': this.max, 'actual': control.value}};
185188
}
@@ -192,7 +195,8 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
192195

193196
/** The combined form control validator for this input. */
194197
private _validator: ValidatorFn | null =
195-
Validators.compose([this._minValidator, this._maxValidator, this._filterValidator]);
198+
Validators.compose(
199+
[this._parseValidator, this._minValidator, this._maxValidator, this._filterValidator]);
196200

197201
constructor(
198202
private _elementRef: ElementRef,

src/lib/datepicker/datepicker.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ export class MdDatepicker<D> implements OnDestroy {
127127
get startAt(): D {
128128
// If an explicit startAt is set we start there, otherwise we start at whatever the currently
129129
// selected value is.
130-
return this._startAt || (this._datepickerInput ? this._datepickerInput.value : null);
130+
return this._startAt ||
131+
(this._datepickerInput && this._dateAdapter.isValidDate(this._datepickerInput.value) ?
132+
this._datepickerInput.value : null);
131133
}
132134
set startAt(date: D) { this._startAt = date; }
133135
private _startAt: D;
@@ -237,7 +239,8 @@ export class MdDatepicker<D> implements OnDestroy {
237239
}
238240
this._datepickerInput = input;
239241
this._inputSubscription =
240-
this._datepickerInput._valueChange.subscribe((value: D) => this._selected = value);
242+
this._datepickerInput._valueChange.subscribe((value: D) =>
243+
this._selected = this._dateAdapter.isValidDate(value) ? value : null);
241244
}
242245

243246
/** Open the calendar. */

0 commit comments

Comments
 (0)