mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(datepicker): allow ISO 8601 strings as inputs (#7091)
This commit is contained in:
@@ -164,12 +164,18 @@ export abstract class DateAdapter<D> {
|
||||
abstract addCalendarDays(date: D, days: number): D;
|
||||
|
||||
/**
|
||||
* Gets the RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339) for the given
|
||||
* date.
|
||||
* Gets the RFC 3339 compatible string (https://tools.ietf.org/html/rfc3339) for the given date.
|
||||
* @param date The date to get the ISO date string for.
|
||||
* @returns The ISO date string date string.
|
||||
*/
|
||||
abstract getISODateString(date: D): string;
|
||||
abstract toIso8601(date: D): string;
|
||||
|
||||
/**
|
||||
* Creates a date from an RFC 3339 compatible string (https://tools.ietf.org/html/rfc3339).
|
||||
* @param iso8601String The ISO date string to create a date from
|
||||
* @returns The date created from the ISO date string.
|
||||
*/
|
||||
abstract fromIso8601(iso8601String: string): D | null;
|
||||
|
||||
/**
|
||||
* Checks whether the given object is considered a date instance by this DateAdapter.
|
||||
|
||||
@@ -331,6 +331,14 @@ describe('NativeDateAdapter', () => {
|
||||
let d = '1/1/2017';
|
||||
expect(adapter.isDateInstance(d)).toBe(false);
|
||||
});
|
||||
|
||||
it('should create dates from valid ISO strings', () => {
|
||||
expect(adapter.fromIso8601('1985-04-12T23:20:50.52Z')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1996-12-19T16:39:57-08:00')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1937-01-01T12:00:27.87+00:20')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1990-13-31T23:59:00Z')).toBeNull();
|
||||
expect(adapter.fromIso8601('1/1/2017')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,15 @@ const DEFAULT_DAY_OF_WEEK_NAMES = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches strings that have the form of a valid RFC 3339 string
|
||||
* (https://tools.ietf.org/html/rfc3339). Note that the string may not actually be a valid date
|
||||
* because the regex will match strings an with out of bounds month, date, etc.
|
||||
*/
|
||||
const ISO_8601_REGEX =
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))$/;
|
||||
|
||||
|
||||
/** Creates an array and fills it with values. */
|
||||
function range<T>(length: number, valueFunction: (index: number) => T): T[] {
|
||||
const valuesArray = Array(length);
|
||||
@@ -202,7 +211,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
|
||||
this.getYear(date), this.getMonth(date), this.getDate(date) + days);
|
||||
}
|
||||
|
||||
getISODateString(date: Date): string {
|
||||
toIso8601(date: Date): string {
|
||||
return [
|
||||
date.getUTCFullYear(),
|
||||
this._2digit(date.getUTCMonth() + 1),
|
||||
@@ -210,6 +219,18 @@ export class NativeDateAdapter extends DateAdapter<Date> {
|
||||
].join('-');
|
||||
}
|
||||
|
||||
fromIso8601(iso8601String: string): Date | null {
|
||||
// The `Date` constructor accepts formats other than ISO 8601, so we need to make sure the
|
||||
// string is the right format first.
|
||||
if (ISO_8601_REGEX.test(iso8601String)) {
|
||||
let d = new Date(iso8601String);
|
||||
if (this.isValid(d)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
isDateInstance(obj: any) {
|
||||
return obj instanceof Date;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from '@angular/material/core';
|
||||
import {first} from 'rxjs/operator/first';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {coerceDateProperty} from './coerce-date-property';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
import {MdDatepickerIntl} from './datepicker-intl';
|
||||
|
||||
@@ -64,19 +65,31 @@ export class MdCalendar<D> implements AfterContentInit, OnDestroy {
|
||||
private _intlChanges: Subscription;
|
||||
|
||||
/** A date representing the period (month or year) to start the calendar in. */
|
||||
@Input() startAt: D;
|
||||
@Input()
|
||||
get startAt(): D | null { return this._startAt; }
|
||||
set startAt(value: D | null) { this._startAt = coerceDateProperty(this._dateAdapter, value); }
|
||||
private _startAt: D | null;
|
||||
|
||||
/** Whether the calendar should be started in month or year view. */
|
||||
@Input() startView: 'month' | 'year' = 'month';
|
||||
|
||||
/** The currently selected date. */
|
||||
@Input() selected: D | null;
|
||||
@Input()
|
||||
get selected(): D | null { return this._selected; }
|
||||
set selected(value: D | null) { this._selected = coerceDateProperty(this._dateAdapter, value); }
|
||||
private _selected: D | null;
|
||||
|
||||
/** The minimum selectable date. */
|
||||
@Input() minDate: D | null;
|
||||
@Input()
|
||||
get minDate(): D | null { return this._minDate; }
|
||||
set minDate(value: D | null) { this._minDate = coerceDateProperty(this._dateAdapter, value); }
|
||||
private _minDate: D | null;
|
||||
|
||||
/** The maximum selectable date. */
|
||||
@Input() maxDate: D | null;
|
||||
@Input()
|
||||
get maxDate(): D | null { return this._maxDate; }
|
||||
set maxDate(value: D | null) { this._maxDate = coerceDateProperty(this._dateAdapter, value); }
|
||||
private _maxDate: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {async, inject, TestBed} from '@angular/core/testing';
|
||||
import {DateAdapter, JAN, MdNativeDateModule} from '@angular/material/core';
|
||||
import {coerceDateProperty} from './index';
|
||||
|
||||
|
||||
describe('coerceDateProperty', () => {
|
||||
let adapter: DateAdapter<Date>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MdNativeDateModule],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(inject([DateAdapter], (dateAdapter: DateAdapter<Date>) => {
|
||||
adapter = dateAdapter;
|
||||
}));
|
||||
|
||||
it('should pass through existing date', () => {
|
||||
const d = new Date(2017, JAN, 1);
|
||||
expect(coerceDateProperty(adapter, d)).toBe(d);
|
||||
});
|
||||
|
||||
it('should pass through invalid date', () => {
|
||||
const d = new Date(NaN);
|
||||
expect(coerceDateProperty(adapter, d)).toBe(d);
|
||||
});
|
||||
|
||||
it('should pass through null and undefined', () => {
|
||||
expect(coerceDateProperty(adapter, null)).toBeNull();
|
||||
expect(coerceDateProperty(adapter, undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should coerce empty string to null', () => {
|
||||
expect(coerceDateProperty(adapter, '')).toBe(null);
|
||||
});
|
||||
|
||||
it('should coerce ISO 8601 string to date', () => {
|
||||
let isoString = '2017-01-01T00:00:00Z';
|
||||
expect(coerceDateProperty(adapter, isoString)).toEqual(new Date(isoString));
|
||||
});
|
||||
|
||||
it('should throw when given a number', () => {
|
||||
expect(() => coerceDateProperty(adapter, 5)).toThrow();
|
||||
expect(() => coerceDateProperty(adapter, 0)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when given a string with incorrect format', () => {
|
||||
expect(() => coerceDateProperty(adapter, '1/1/2017')).toThrow();
|
||||
expect(() => coerceDateProperty(adapter, 'hello')).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DateAdapter} from '@angular/material/core';
|
||||
|
||||
|
||||
/**
|
||||
* Function that attempts to coerce a value to a date using a DateAdapter. Date instances, null,
|
||||
* and undefined will be passed through. Empty strings will be coerced to null. Valid ISO 8601
|
||||
* strings (https://www.ietf.org/rfc/rfc3339.txt) will be coerced to dates. All other values will
|
||||
* result in an error being thrown.
|
||||
* @param adapter The date adapter to use for coercion
|
||||
* @param value The value to coerce.
|
||||
* @return A date object coerced from the value.
|
||||
* @throws Throws when the value cannot be coerced.
|
||||
*/
|
||||
export function coerceDateProperty<D>(adapter: DateAdapter<D>, value: any): D | null {
|
||||
if (typeof value === 'string') {
|
||||
if (value == '') {
|
||||
value = null;
|
||||
} else {
|
||||
value = adapter.fromIso8601(value) || value;
|
||||
}
|
||||
}
|
||||
if (value == null || adapter.isDateInstance(value)) {
|
||||
return value;
|
||||
}
|
||||
throw Error(`Datepicker: Value must be either a date object recognized by the DateAdapter or ` +
|
||||
`an ISO 8601 string. Instead got: ${value}`);
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core';
|
||||
import {MdFormField} from '@angular/material/form-field';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {coerceDateProperty} from './coerce-date-property';
|
||||
import {MdDatepicker} from './datepicker';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
@@ -74,8 +75,8 @@ export class MdDatepickerInputEvent<D> {
|
||||
host: {
|
||||
'[attr.aria-haspopup]': 'true',
|
||||
'[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
|
||||
'[attr.min]': 'min ? _dateAdapter.getISODateString(min) : null',
|
||||
'[attr.max]': 'max ? _dateAdapter.getISODateString(max) : null',
|
||||
'[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',
|
||||
'[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',
|
||||
'[disabled]': 'disabled',
|
||||
'(input)': '_onInput($event.target.value)',
|
||||
'(change)': '_onChange()',
|
||||
@@ -122,9 +123,7 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
|
||||
return this._value;
|
||||
}
|
||||
set value(value: D | null) {
|
||||
if (value != null && !this._dateAdapter.isDateInstance(value)) {
|
||||
throw Error('Datepicker: value not recognized as a date object by DateAdapter.');
|
||||
}
|
||||
value = coerceDateProperty(this._dateAdapter, value);
|
||||
this._lastValueValid = !value || this._dateAdapter.isValid(value);
|
||||
value = this._getValidDateOrNull(value);
|
||||
|
||||
@@ -142,7 +141,7 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
|
||||
@Input()
|
||||
get min(): D | null { return this._min; }
|
||||
set min(value: D | null) {
|
||||
this._min = value;
|
||||
this._min = coerceDateProperty(this._dateAdapter, value);
|
||||
this._validatorOnChange();
|
||||
}
|
||||
private _min: D | null;
|
||||
@@ -151,7 +150,7 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
|
||||
@Input()
|
||||
get max(): D | null { return this._max; }
|
||||
set max(value: D | null) {
|
||||
this._max = value;
|
||||
this._max = coerceDateProperty(this._dateAdapter, value);
|
||||
this._validatorOnChange();
|
||||
}
|
||||
private _max: D | null;
|
||||
@@ -199,21 +198,24 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
|
||||
|
||||
/** The form control validator for the min date. */
|
||||
private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
|
||||
return (!this.min || !control.value ||
|
||||
this._dateAdapter.compareDate(this.min, control.value) <= 0) ?
|
||||
null : {'mdDatepickerMin': {'min': this.min, 'actual': control.value}};
|
||||
const controlValue = coerceDateProperty(this._dateAdapter, control.value);
|
||||
return (!this.min || !controlValue ||
|
||||
this._dateAdapter.compareDate(this.min, controlValue) <= 0) ?
|
||||
null : {'mdDatepickerMin': {'min': this.min, 'actual': controlValue}};
|
||||
}
|
||||
|
||||
/** The form control validator for the max date. */
|
||||
private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
|
||||
return (!this.max || !control.value ||
|
||||
this._dateAdapter.compareDate(this.max, control.value) >= 0) ?
|
||||
null : {'mdDatepickerMax': {'max': this.max, 'actual': control.value}};
|
||||
const controlValue = coerceDateProperty(this._dateAdapter, control.value);
|
||||
return (!this.max || !controlValue ||
|
||||
this._dateAdapter.compareDate(this.max, controlValue) >= 0) ?
|
||||
null : {'mdDatepickerMax': {'max': this.max, 'actual': controlValue}};
|
||||
}
|
||||
|
||||
/** The form control validator for the date filter. */
|
||||
private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
|
||||
return !this._dateFilter || !control.value || this._dateFilter(control.value) ?
|
||||
const controlValue = coerceDateProperty(this._dateAdapter, control.value);
|
||||
return !this._dateFilter || !controlValue || this._dateFilter(controlValue) ?
|
||||
null : {'mdDatepickerFilter': true};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {
|
||||
DEC,
|
||||
JAN,
|
||||
JUL,
|
||||
JUN,
|
||||
MAT_DATE_LOCALE,
|
||||
MdNativeDateModule,
|
||||
NativeDateModule,
|
||||
@@ -47,6 +49,7 @@ describe('MdDatepicker', () => {
|
||||
DatepickerWithChangeAndInputEvents,
|
||||
DatepickerWithFilterAndValidation,
|
||||
DatepickerWithFormControl,
|
||||
DatepickerWithISOStrings,
|
||||
DatepickerWithMinAndMaxValidation,
|
||||
DatepickerWithNgModel,
|
||||
DatepickerWithStartAt,
|
||||
@@ -270,8 +273,9 @@ describe('MdDatepicker', () => {
|
||||
it('should throw when given wrong data type', () => {
|
||||
testComponent.date = '1/1/2017' as any;
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(/Datepicker: value not recognized as a date object by DateAdapter\./);
|
||||
expect(() => fixture.detectChanges()).toThrowError(
|
||||
'Datepicker: Value must be either a date object recognized by the DateAdapter or an ' +
|
||||
'ISO 8601 string. Instead got: 1/1/2017');
|
||||
|
||||
testComponent.date = null;
|
||||
});
|
||||
@@ -865,6 +869,32 @@ describe('MdDatepicker', () => {
|
||||
expect(testComponent.onDateInput).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with ISO 8601 strings as input', () => {
|
||||
let fixture: ComponentFixture<DatepickerWithISOStrings>;
|
||||
let testComponent: DatepickerWithISOStrings;
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(DatepickerWithISOStrings);
|
||||
testComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
afterEach(async(() => {
|
||||
testComponent.datepicker.close();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should coerce ISO strings', async(() => {
|
||||
expect(() => fixture.detectChanges()).not.toThrow();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.datepicker.startAt).toEqual(new Date(2017, JUL, 1));
|
||||
expect(testComponent.datepickerInput.value).toEqual(new Date(2017, JUN, 1));
|
||||
expect(testComponent.datepickerInput.min).toEqual(new Date(2017, JAN, 1));
|
||||
expect(testComponent.datepickerInput.max).toEqual(new Date(2017, DEC, 31));
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with missing DateAdapter and MD_DATE_FORMATS', () => {
|
||||
@@ -1179,3 +1209,18 @@ class DatepickerWithi18n {
|
||||
@ViewChild('d') datepicker: MdDatepicker<Date>;
|
||||
@ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput<Date>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<input [mdDatepicker]="d" [(ngModel)]="value" [min]="min" [max]="max">
|
||||
<md-datepicker #d [startAt]="startAt"></md-datepicker>
|
||||
`
|
||||
})
|
||||
class DatepickerWithISOStrings {
|
||||
value = new Date(2017, JUN, 1).toISOString();
|
||||
min = new Date(2017, JAN, 1).toISOString();
|
||||
max = new Date (2017, DEC, 31).toISOString();
|
||||
startAt = new Date(2017, JUL, 1).toISOString();
|
||||
@ViewChild('d') datepicker: MdDatepicker<Date>;
|
||||
@ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput<Date>;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {DOCUMENT} from '@angular/platform-browser';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {MdCalendar} from './calendar';
|
||||
import {coerceDateProperty} from './coerce-date-property';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
import {MdDatepickerInput} from './datepicker-input';
|
||||
|
||||
@@ -131,7 +132,7 @@ export class MdDatepicker<D> implements OnDestroy {
|
||||
// selected value is.
|
||||
return this._startAt || (this._datepickerInput ? this._datepickerInput.value : null);
|
||||
}
|
||||
set startAt(date: D | null) { this._startAt = date; }
|
||||
set startAt(date: D | null) { this._startAt = coerceDateProperty(this._dateAdapter, date); }
|
||||
private _startAt: D | null;
|
||||
|
||||
/** The view that the calendar should start in. */
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core';
|
||||
import {MdCalendarCell} from './calendar-body';
|
||||
import {coerceDateProperty} from './coerce-date-property';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
@@ -45,7 +46,7 @@ export class MdMonthView<D> implements AfterContentInit {
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate = value || this._dateAdapter.today();
|
||||
this._activeDate = coerceDateProperty(this._dateAdapter, value) || this._dateAdapter.today();
|
||||
if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {
|
||||
this._init();
|
||||
}
|
||||
@@ -54,12 +55,12 @@ export class MdMonthView<D> implements AfterContentInit {
|
||||
|
||||
/** The currently selected date. */
|
||||
@Input()
|
||||
get selected(): D { return this._selected; }
|
||||
set selected(value: D) {
|
||||
this._selected = value;
|
||||
this._selectedDate = this._getDateInCurrentMonth(this.selected);
|
||||
get selected(): D | null { return this._selected; }
|
||||
set selected(value: D | null) {
|
||||
this._selected = coerceDateProperty(this._dateAdapter, value);
|
||||
this._selectedDate = this._getDateInCurrentMonth(this._selected);
|
||||
}
|
||||
private _selected: D;
|
||||
private _selected: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
@@ -172,13 +173,13 @@ export class MdMonthView<D> implements AfterContentInit {
|
||||
* Gets the date in this month that the given Date falls on.
|
||||
* Returns null if the given Date is in another month.
|
||||
*/
|
||||
private _getDateInCurrentMonth(date: D): number | null {
|
||||
return this._hasSameMonthAndYear(date, this.activeDate) ?
|
||||
private _getDateInCurrentMonth(date: D | null): number | null {
|
||||
return date && this._hasSameMonthAndYear(date, this.activeDate) ?
|
||||
this._dateAdapter.getDate(date) : null;
|
||||
}
|
||||
|
||||
/** Checks whether the 2 dates are non-null and fall within the same month of the same year. */
|
||||
private _hasSameMonthAndYear(d1: D, d2: D): boolean {
|
||||
private _hasSameMonthAndYear(d1: D | null, d2: D | null): boolean {
|
||||
return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) &&
|
||||
this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export * from './datepicker-module';
|
||||
export * from './calendar';
|
||||
export * from './calendar-body';
|
||||
export * from './coerce-date-property';
|
||||
export * from './datepicker';
|
||||
export * from './datepicker-input';
|
||||
export * from './datepicker-intl';
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core';
|
||||
import {MdCalendarCell} from './calendar-body';
|
||||
import {coerceDateProperty} from './coerce-date-property';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
@@ -40,7 +41,7 @@ export class MdYearView<D> implements AfterContentInit {
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate = value || this._dateAdapter.today();
|
||||
this._activeDate = coerceDateProperty(this._dateAdapter, value) || this._dateAdapter.today();
|
||||
if (this._dateAdapter.getYear(oldActiveDate) != this._dateAdapter.getYear(this._activeDate)) {
|
||||
this._init();
|
||||
}
|
||||
@@ -49,12 +50,12 @@ export class MdYearView<D> implements AfterContentInit {
|
||||
|
||||
/** The currently selected date. */
|
||||
@Input()
|
||||
get selected(): D { return this._selected; }
|
||||
set selected(value: D) {
|
||||
this._selected = value;
|
||||
this._selectedMonth = this._getMonthInCurrentYear(this.selected);
|
||||
get selected(): D | null { return this._selected; }
|
||||
set selected(value: D | null) {
|
||||
this._selected = coerceDateProperty(this._dateAdapter, value);
|
||||
this._selectedMonth = this._getMonthInCurrentYear(this._selected);
|
||||
}
|
||||
private _selected: D;
|
||||
private _selected: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
@@ -118,7 +119,7 @@ export class MdYearView<D> implements AfterContentInit {
|
||||
* Gets the month in this year that the given Date falls on.
|
||||
* Returns null if the given Date is in another year.
|
||||
*/
|
||||
private _getMonthInCurrentYear(date: D) {
|
||||
private _getMonthInCurrentYear(date: D | null) {
|
||||
return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ?
|
||||
this._dateAdapter.getMonth(date) : null;
|
||||
}
|
||||
|
||||
@@ -309,6 +309,14 @@ describe('MomentDateAdapter', () => {
|
||||
expect(adapter.isDateInstance(d)).toBe(false);
|
||||
});
|
||||
|
||||
it('should create dates from valid ISO strings', () => {
|
||||
expect(adapter.fromIso8601('1985-04-12T23:20:50.52Z')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1996-12-19T16:39:57-08:00')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1937-01-01T12:00:27.87+00:20')).not.toBeNull();
|
||||
expect(adapter.fromIso8601('1990-13-31T23:59:00Z')).toBeNull();
|
||||
expect(adapter.fromIso8601('1/1/2017')).toBeNull();
|
||||
});
|
||||
|
||||
it('setLocale should not modify global moment locale', () => {
|
||||
expect(moment.locale()).toBe('en');
|
||||
adapter.setLocale('ja-JP');
|
||||
@@ -342,7 +350,7 @@ describe('MomentDateAdapter', () => {
|
||||
adapter.addCalendarDays(date, 1);
|
||||
adapter.addCalendarMonths(date, 1);
|
||||
adapter.addCalendarYears(date, 1);
|
||||
adapter.getISODateString(date);
|
||||
adapter.toIso8601(date);
|
||||
adapter.isDateInstance(date);
|
||||
adapter.isValid(date);
|
||||
expect(date.locale()).toBe('en');
|
||||
|
||||
@@ -170,10 +170,15 @@ export class MomentDateAdapter extends DateAdapter<Moment> {
|
||||
return this.clone(date).add({days});
|
||||
}
|
||||
|
||||
getISODateString(date: Moment): string {
|
||||
toIso8601(date: Moment): string {
|
||||
return this.clone(date).format();
|
||||
}
|
||||
|
||||
fromIso8601(iso8601String: string): Moment | null {
|
||||
let d = moment(iso8601String, moment.ISO_8601).locale(this.locale);
|
||||
return this.isValid(d) ? d : null;
|
||||
}
|
||||
|
||||
isDateInstance(obj: any): boolean {
|
||||
return moment.isMoment(obj);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user