diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts index 5c681a100b8f..ffd03c9ca1ea 100644 --- a/src/demo-app/datepicker/datepicker-demo.ts +++ b/src/demo-app/datepicker/datepicker-demo.ts @@ -1,5 +1,4 @@ import {Component} from '@angular/core'; -import {SimpleDate} from '@angular/material'; @Component({ @@ -9,8 +8,9 @@ import {SimpleDate} from '@angular/material'; styleUrls: ['datepicker-demo.css'], }) export class DatepickerDemo { - date: SimpleDate; + date: Date; touch = false; - dateFilter = (date: SimpleDate) => !this._blacklistedMonths.has(date.month) && date.date % 2 == 0; + dateFilter = + (date: Date) => !this._blacklistedMonths.has(date.getMonth()) && date.getDate() % 2 == 0 private _blacklistedMonths = new Set([2, 3]); } diff --git a/src/lib/core/datetime/calendar-locale.spec.ts b/src/lib/core/datetime/calendar-locale.spec.ts deleted file mode 100644 index d37b0e072e9b..000000000000 --- a/src/lib/core/datetime/calendar-locale.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import {inject, TestBed, async} from '@angular/core/testing'; -import {CalendarLocale} from './calendar-locale'; -import {DatetimeModule} from './index'; -import {SimpleDate} from './simple-date'; - - -describe('DefaultCalendarLocale', () => { - let calendarLocale: CalendarLocale; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [DatetimeModule], - }); - - TestBed.compileComponents(); - })); - - beforeEach(inject([CalendarLocale], (cl: CalendarLocale) => { - calendarLocale = cl; - })); - - it('lists months', () => { - expect(calendarLocale.months).toEqual([ - 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', - 'October', 'November', 'December' - ]); - }); - - it('lists short months', () => { - expect(calendarLocale.shortMonths).toEqual([ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' - ]); - }); - - it('lists narrow months', () => { - expect(calendarLocale.narrowMonths).toEqual([ - 'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D' - ]); - }); - - it('lists days', () => { - expect(calendarLocale.days).toEqual([ - 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' - ]); - }); - - it('lists short days', () => { - expect(calendarLocale.shortDays).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); - }); - - it('lists narrow days', () => { - expect(calendarLocale.narrowDays).toEqual(['S', 'M', 'T', 'W', 'T', 'F', 'S']); - }); - - it('lists dates', () => { - expect(calendarLocale.dates).toEqual([ - null, '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', - '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31' - ]); - }); - - it('has first day of the week', () => { - expect(calendarLocale.firstDayOfWeek).toBe(0); - }); - - it('has calendar label', () => { - expect(calendarLocale.calendarLabel).toBe('Calendar'); - }); - - it('has open calendar label', () => { - expect(calendarLocale.openCalendarLabel).toBe('Open calendar'); - }); - - it('parses SimpleDate from string', () => { - expect(calendarLocale.parseDate('1/1/2017')).toEqual(new SimpleDate(2017, 0, 1)); - }); - - it('parses SimpleDate from number', () => { - let timestamp = new Date().getTime(); - expect(calendarLocale.parseDate(timestamp)) - .toEqual(SimpleDate.fromNativeDate(new Date(timestamp))); - }); - - it ('parses SimpleDate from SimpleDate by copying', () => { - let originalSimpleDate = new SimpleDate(2017, 0, 1); - expect(calendarLocale.parseDate(originalSimpleDate)).toEqual(originalSimpleDate); - }); - - it('parses null for invalid dates', () => { - expect(calendarLocale.parseDate('hello')).toBeNull(); - }); - - it('formats SimpleDates', () => { - expect(calendarLocale.formatDate(new SimpleDate(2017, 0, 1))).toEqual('1/1/2017'); - }); - - it('gets header label for calendar month', () => { - expect(calendarLocale.getCalendarMonthHeaderLabel(new SimpleDate(2017, 0, 1))) - .toEqual('Jan 2017'); - }); - - it('gets header label for calendar year', () => { - expect(calendarLocale.getCalendarYearHeaderLabel(new SimpleDate(2017, 0, 1))).toBe('2017'); - }); -}); diff --git a/src/lib/core/datetime/calendar-locale.ts b/src/lib/core/datetime/calendar-locale.ts deleted file mode 100644 index 6061ec49baa3..000000000000 --- a/src/lib/core/datetime/calendar-locale.ts +++ /dev/null @@ -1,201 +0,0 @@ -import {SimpleDate} from './simple-date'; -import {Injectable} from '@angular/core'; - - -/** Whether the browser supports the Intl API. */ -const SUPPORTS_INTL_API = typeof Intl != 'undefined'; - - -/** Creates an array and fills it with values. */ -function range(length: number, valueFunction: (index: number) => T): T[] { - return Array.apply(null, Array(length)).map((v: undefined, i: number) => valueFunction(i)); -} - - -/** - * This class encapsulates the details of how to localize all information needed for displaying a - * calendar. It is used by md-datepicker to render a properly localized calendar. Unless otherwise - * specified by the user DefaultCalendarLocale will be provided as the CalendarLocale. - */ -@Injectable() -export abstract class CalendarLocale { - /** Labels to use for the long form of the month. (e.g. 'January') */ - months: string[]; - - /** Labels to use for the short form of the month. (e.g. 'Jan') */ - shortMonths: string[]; - - /** Labels to use for the narrow form of the month. (e.g. 'J') */ - narrowMonths: string[]; - - /** Labels to use for the long form of the week days. (e.g. 'Sunday') */ - days: string[]; - - /** Labels to use for the short form of the week days. (e.g. 'Sun') */ - shortDays: string[]; - - /** Labels to use for the narrow form of the week days. (e.g. 'S') */ - narrowDays: string[]; - - /** - * Labels to use for the dates of the month. (e.g. null, '1', '2', ..., '31'). - * Note that the 0th index is null, since there is no January 0th. - */ - dates: string[]; - - /** The first day of the week. (e.g. 0 = Sunday, 6 = Saturday). */ - firstDayOfWeek: number; - - /** A label for the calendar popup (used by screen readers). */ - calendarLabel: string; - - /** A label for the button used to open the calendar popup (used by screen readers). */ - openCalendarLabel: string; - - /** A label for the previous month button (used by screen readers). */ - prevMonthLabel: string; - - /** A label for the next month button (used by screen readers). */ - nextMonthLabel: string; - - /** A label for the previous year button (used by screen readers). */ - prevYearLabel: string; - - /** A label for the next year button (used by screen readers). */ - nextYearLabel: string; - - /** A label for the 'switch to month view' button (used by screen readers). */ - switchToMonthViewLabel: string; - - /** A label for the 'switch to year view' button (used by screen readers). */ - switchToYearViewLabel: string; - - /** - * Parses a SimpleDate from a value. - * @param value The value to parse. - */ - parseDate: (value: any) => SimpleDate; - - /** - * Formats a SimpleDate to a string. - * @param date The date to format. - */ - formatDate: (date: SimpleDate) => string; - - /** - * Gets a label to display as the heading for the specified calendar month. - * @param date A date that falls within the month to be labeled. - */ - getCalendarMonthHeaderLabel: (date: SimpleDate) => string; - - /** - * Gets a label to display as the heading for the specified calendar year. - * @param date A date that falls within the year to be labeled. - */ - getCalendarYearHeaderLabel: (date: SimpleDate) => string; -} - - -/** - * The default implementation of CalendarLocale. This implementation is a best attempt at - * localization using only the functionality natively available in JS. If more robust localization - * is needed, an alternate class can be provided as the CalendarLocale for the app. - */ -export class DefaultCalendarLocale implements CalendarLocale { - months = SUPPORTS_INTL_API ? this._createMonthsArray('long') : - [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ]; - - shortMonths = SUPPORTS_INTL_API ? this._createMonthsArray('short') : - ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - - narrowMonths = SUPPORTS_INTL_API ? this._createMonthsArray('narrow') : - ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D']; - - days = SUPPORTS_INTL_API ? this._createDaysArray('long') : - ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - - shortDays = SUPPORTS_INTL_API ? this._createDaysArray('short') : - ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - - narrowDays = SUPPORTS_INTL_API ? this._createDaysArray('narrow') : - ['S', 'M', 'T', 'W', 'T', 'F', 'S']; - - dates = [null].concat( - SUPPORTS_INTL_API ? this._createDatesArray('numeric') : range(31, i => String(i + 1))); - - firstDayOfWeek = 0; - - calendarLabel = 'Calendar'; - - openCalendarLabel = 'Open calendar'; - - prevMonthLabel = 'Previous month'; - - nextMonthLabel = 'Next month'; - - prevYearLabel = 'Previous year'; - - nextYearLabel = 'Next year'; - - switchToMonthViewLabel = 'Change to month view'; - - switchToYearViewLabel = 'Change to year view'; - - parseDate(value: any) { - if (value instanceof SimpleDate) { - return value; - } - let timestamp = typeof value == 'number' ? value : Date.parse(value); - return isNaN(timestamp) ? null : SimpleDate.fromNativeDate(new Date(timestamp)); - } - - formatDate = this._createFormatFunction(undefined) || - ((date: SimpleDate) => date.toNativeDate().toDateString()); - - getCalendarMonthHeaderLabel = this._createFormatFunction({month: 'short', year: 'numeric'}) || - ((date: SimpleDate) => this.shortMonths[date.month] + ' ' + date.year); - - getCalendarYearHeaderLabel = this._createFormatFunction({year: 'numeric'}) || - ((date: SimpleDate) => String(date.year)); - - private _createMonthsArray(format: string) { - let dtf = new Intl.DateTimeFormat(undefined, {month: format}); - return range(12, i => dtf.format(new Date(2017, i, 1))); - } - - private _createDaysArray(format: string) { - let dtf = new Intl.DateTimeFormat(undefined, {weekday: format}); - return range(7, i => dtf.format(new Date(2017, 0, i + 1))); - } - - private _createDatesArray(format: string) { - let dtf = new Intl.DateTimeFormat(undefined, {day: format}); - return range(31, i => dtf.format(new Date(2017, 0, i + 1))); - } - - /** - * Creates a function to format SimpleDates as strings using Intl.DateTimeFormat. - * @param options The options to use for Intl.DateTimeFormat. - * @returns The newly created format function, or null if the Intl API is not available. - */ - private _createFormatFunction(options: Object): (date: SimpleDate) => string { - if (SUPPORTS_INTL_API) { - let dtf = new Intl.DateTimeFormat(undefined, options); - return (date: SimpleDate) => dtf.format(date.toNativeDate()); - } - return null; - } -} diff --git a/src/lib/core/datetime/date-adapter.ts b/src/lib/core/datetime/date-adapter.ts index 6fab81e943ba..c7d9d1274bf9 100644 --- a/src/lib/core/datetime/date-adapter.ts +++ b/src/lib/core/datetime/date-adapter.ts @@ -73,6 +73,13 @@ export abstract class DateAdapter { */ abstract getFirstDayOfWeek(): number; + /** + * Gets the number of days in the month of the given date. + * @param date The date whose month should be checked. + * @returns The number of days in the month of the given date. + */ + abstract getNumDaysInMonth(date: D): number; + /** * Gets a set of default formats to use for displaying the date in different contexts. * @returns An object with the following default formats: @@ -88,13 +95,12 @@ export abstract class DateAdapter { abstract clone(date: D): D; /** - * Creates a date with the given year, month, and date. + * Creates a date with the given year, month, and date. Does not allow over/under-flow of the + * month and date. * @param year The full year of the date. (e.g. 89 means the year 89, not the year 1989). - * @param month The month of the date (0-indexed, 0 = January). If `month` is less than 0 or - * greater than 11, it should roll into the previous / next year. - * @param date The date of month of the date. If `date` is less than 1 or greater than the number - * of days in the `month`, it should roll into the previous / next month. - * @returns The new date. + * @param month The month of the date (0-indexed, 0 = January). Must be an integer 0 - 11. + * @param date The date of month of the date. Must be an integer 1 - length of the given month. + * @returns The new date, or null if invalid. */ abstract createDate(year: number, month: number, date: number): D; diff --git a/src/lib/core/datetime/index.ts b/src/lib/core/datetime/index.ts index 1c4aa3500008..9dd521c46bd2 100644 --- a/src/lib/core/datetime/index.ts +++ b/src/lib/core/datetime/index.ts @@ -1,14 +1,13 @@ import {NgModule} from '@angular/core'; -import {DefaultCalendarLocale, CalendarLocale} from './calendar-locale'; +import {DateAdapter} from './date-adapter'; +import {NativeDateAdapter} from './native-date-adapter'; -export * from './calendar-locale'; export * from './date-adapter'; -export * from './simple-date'; export * from './native-date-adapter'; @NgModule({ - providers: [{provide: CalendarLocale, useClass: DefaultCalendarLocale}], + providers: [{provide: DateAdapter, useClass: NativeDateAdapter}], }) export class DatetimeModule {} diff --git a/src/lib/core/datetime/native-date-adapter.spec.ts b/src/lib/core/datetime/native-date-adapter.spec.ts index 44d0c877fa9b..84b013b050e9 100644 --- a/src/lib/core/datetime/native-date-adapter.spec.ts +++ b/src/lib/core/datetime/native-date-adapter.spec.ts @@ -134,12 +134,14 @@ describe('NativeDateAdapter', () => { expect(adapter.createDate(2017, JAN, 1)).toEqual(new Date(2017, JAN, 1)); }); - it('should create Date with month and date overflow', () => { - expect(adapter.createDate(2017, DEC + 1, 32)).toEqual(new Date(2018, FEB, 1)); + it('should not create Date with month over/under-flow', () => { + expect(adapter.createDate(2017, DEC + 1, 1)).toBeNull(); + expect(adapter.createDate(2017, JAN - 1, 1)).toBeNull(); }); - it('should create Date with month date underflow', () => { - expect(adapter.createDate(2017, JAN - 1, 0)).toEqual(new Date(2016, NOV, 30)); + it('should not create Date with date over/under-flow', () => { + expect(adapter.createDate(2017, JAN, 32)).toBeNull(); + expect(adapter.createDate(2017, JAN, 0)).toBeNull(); }); it('should create Date with low year number', () => { @@ -150,13 +152,6 @@ describe('NativeDateAdapter', () => { expect(adapter.createDate(100, JAN, 1).getFullYear()).toBe(100); }); - it('should create Date with low year number and over/under-flow', () => { - expect(adapter.createDate(50, 12 * 51, 1).getFullYear()).toBe(101); - expect(adapter.createDate(50, 12, 1).getFullYear()).toBe(51); - expect(adapter.createDate(50, -12, 1).getFullYear()).toBe(49); - expect(adapter.createDate(50, -12 * 51, 1).getFullYear()).toBe(-1); - }); - it("should get today's date", () => { expect(adapter.sameDate(adapter.today(), new Date())) .toBe(true, "should be equal to today's date"); diff --git a/src/lib/core/datetime/native-date-adapter.ts b/src/lib/core/datetime/native-date-adapter.ts index 08c22fee11ff..57a7461f9e6c 100644 --- a/src/lib/core/datetime/native-date-adapter.ts +++ b/src/lib/core/datetime/native-date-adapter.ts @@ -99,6 +99,11 @@ export class NativeDateAdapter extends DateAdapter { return 0; } + getNumDaysInMonth(date: Date): number { + return this.getDate(this._createDateWithOverflow( + this.getYear(date), this.getMonth(date) + 1, 0)); + } + getDefaultFormats(): {date: Object} { return { date: { @@ -114,12 +119,20 @@ export class NativeDateAdapter extends DateAdapter { } createDate(year: number, month: number, date: number): Date { - let result = new Date(year, month, date); - // We need to correct for the fact that JS native Date treats years in range [0, 99] as - // abbreviations for 19xx. - if (year >= 0 && year < 100) { - result.setFullYear(this.getYear(result) - 1900); + // Check for invalid month and date (except upper bound on date which we have to check after + // creating the Date). + if (month < 0 || month > 11 || date < 1) { + return null; } + + let result = this._createDateWithOverflow(year, month, date); + + // Check that the date wasn't above the upper bound for the month, causing the month to + // overflow. + if (result.getMonth() != month) { + return null; + } + return result; } @@ -147,21 +160,34 @@ export class NativeDateAdapter extends DateAdapter { } addCalendarMonths(date: Date, months: number): Date { - let newDate = - this.createDate(this.getYear(date), this.getMonth(date) + months, this.getDate(date)); + let newDate = this._createDateWithOverflow( + this.getYear(date), this.getMonth(date) + months, this.getDate(date)); // It's possible to wind up in the wrong month if the original month has more days than the new // month. In this case we want to go to the last day of the desired month. // Note: the additional + 12 % 12 ensures we end up with a positive number, since JS % doesn't // guarantee this. if (this.getMonth(newDate) != ((this.getMonth(date) + months) % 12 + 12) % 12) { - newDate = this.createDate(this.getYear(newDate), this.getMonth(newDate), 0); + newDate = this._createDateWithOverflow(this.getYear(newDate), this.getMonth(newDate), 0); } return newDate; } addCalendarDays(date: Date, days: number): Date { - return this.createDate(this.getYear(date), this.getMonth(date), this.getDate(date) + days); + return this._createDateWithOverflow( + this.getYear(date), this.getMonth(date), this.getDate(date) + days); + } + + /** Creates a date but allows the month and date to overflow. */ + private _createDateWithOverflow(year: number, month: number, date: number) { + let result = new Date(year, month, date); + + // We need to correct for the fact that JS native Date treats years in range [0, 99] as + // abbreviations for 19xx. + if (year >= 0 && year < 100) { + result.setFullYear(this.getYear(result) - 1900); + } + return result; } } diff --git a/src/lib/core/datetime/simple-date.spec.ts b/src/lib/core/datetime/simple-date.spec.ts deleted file mode 100644 index 9f49d2e3038c..000000000000 --- a/src/lib/core/datetime/simple-date.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {SimpleDate} from './simple-date'; - - -describe('SimpleDate', () => { - it('can be created from native Date', () => { - expect(SimpleDate.fromNativeDate(new Date(2017, 0, 1))).toEqual(new SimpleDate(2017, 0, 1)); - }); - - it('can be converted to native Date', () => { - expect(new SimpleDate(2017, 0, 1).toNativeDate()).toEqual(new Date(2017, 0, 1)); - }); - - it('handles month and date overflow', () => { - expect(new SimpleDate(2017, 12, 32)).toEqual(new SimpleDate(2018, 1, 1)); - }); - - it('handles month and date underflow', () => { - expect(new SimpleDate(2017, -1, 0)).toEqual(new SimpleDate(2016, 10, 30)); - }); - - it('handles low year numbers', () => { - expect(new SimpleDate(-1, 0, 1).year).toBe(-1); - expect(new SimpleDate(0, 0, 1).year).toBe(0); - expect(new SimpleDate(50, 0, 1).year).toBe(50); - expect(new SimpleDate(99, 0, 1).year).toBe(99); - expect(new SimpleDate(100, 0, 1).year).toBe(100); - }); - - it('handles low year number with over/under-flow', () => { - expect(new SimpleDate(50, 12 * 51, 1).year).toBe(101); - expect(new SimpleDate(50, 12, 1).year).toBe(51); - expect(new SimpleDate(50, -12, 1).year).toBe(49); - expect(new SimpleDate(50, -12 * 51, 1).year).toBe(-1); - }); - - it('adds years, months, and days', () => { - expect(new SimpleDate(2017, 0, 1).add({years: 1, months: 1, days: 1})) - .toEqual(new SimpleDate(2018, 1, 2)); - }); - - it('clamps date at lower bound', () => { - let date = new SimpleDate(2017, 0, 1); - let lower = new SimpleDate(2018, 1, 2); - let upper = new SimpleDate(2019, 2, 3); - expect(date.clamp(lower, upper)).toEqual(lower); - }); - - it('clamps date at upper bound', () => { - let date = new SimpleDate(2020, 0, 1); - let lower = new SimpleDate(2018, 1, 2); - let upper = new SimpleDate(2019, 2, 3); - expect(date.clamp(lower, upper)).toEqual(upper); - }); - - it('clamp treats null as unbounded', () => { - let date = new SimpleDate(2017, 0, 1); - expect(date.clamp(null, null)).toEqual(date); - }); -}); diff --git a/src/lib/core/datetime/simple-date.ts b/src/lib/core/datetime/simple-date.ts deleted file mode 100644 index b4fe008d4ded..000000000000 --- a/src/lib/core/datetime/simple-date.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * A wrapper for the native JS Date class that deals with some quirks for us: - * 1) The native Date constructor treats years in the range [0-99] as 19xx not 00xx. - * 2) ... (Eventually need to add support for other quirks related to time zones, DST). - */ -export class SimpleDate { - /** - * Create a SimpleDate from a native JS Date object. - * @param nativeDate The native JS Date object to convert. - */ - static fromNativeDate(nativeDate: Date): SimpleDate { - return new SimpleDate(nativeDate.getFullYear(), nativeDate.getMonth(), nativeDate.getDate()); - } - - /** Creates a SimpleDate object representing today. */ - static today(): SimpleDate { - return SimpleDate.fromNativeDate(new Date()); - } - - /** - * Checks whether the given dates are equal. Null dates are considered equal to other null dates. - */ - static equals(first: SimpleDate, second: SimpleDate): boolean { - return first && second ? !first.compare(second) : first == second; - } - - /** The native JS Date. */ - private _date: Date; - - constructor(year: number, month: number, date: number) { - this._date = new Date(year, month, date); - // We need to correct for the fact that JS native Date treats years in range [0, 99] as - // abbreviations for 19xx. - if (year >= 0 && year < 100) { - this._date = new Date(this._date.setFullYear(this.year - 1900)); - } - } - - /** The year component of this date. */ - get year(): number { - return this._date.getFullYear(); - } - - /** The month component of this date. (0-indexed, 0 = January). */ - get month(): number { - return this._date.getMonth(); - } - - /** The date component of this date. (1-indexed, 1 = 1st of month). */ - get date(): number { - return this._date.getDate(); - } - - /** The day component of this date. (0-indexed, 0 = Sunday) */ - get day(): number { - return this._date.getDay(); - } - - /** - * Adds an amount of time (in days, months, and years) to the date. - * @param amount The amount of time to add. - * @returns A new SimpleDate with the given amount of time added. - */ - add(amount: {days?: number, months?: number, years?: number}): SimpleDate { - return new SimpleDate( - this.year + (amount.years || 0), - this.month + (amount.months || 0), - this.date + (amount.days || 0)); - } - - /** - * Compares this SimpleDate with another SimpleDate. - * @param other The other SimpleDate - * @returns 0 if the dates are equal, a number less than 0 if this date is earlier, - * a number greater than 0 if this date is greater. - */ - compare(other: SimpleDate): number { - return this.year - other.year || this.month - other.month || this.date - other.date; - } - - /** - * Clamps the date between the given min and max dates. - * @param min The minimum date - * @param max The maximum date - * @returns A new SimpleDate equal to this one clamped between the given min and max dates. - */ - clamp(min: SimpleDate, max: SimpleDate): SimpleDate { - let clampedDate: SimpleDate = this; - if (min && this.compare(min) < 0) { - clampedDate = min; - } - if (max && this.compare(max) > 0) { - clampedDate = max; - } - return new SimpleDate(clampedDate.year, clampedDate.month, clampedDate.date); - } - - /** Converts the SimpleDate to a native JS Date object. */ - toNativeDate(): Date { - return new Date(this.year, this.month, this.date); - } -} diff --git a/src/lib/datepicker/calendar-table.spec.ts b/src/lib/datepicker/calendar-body.spec.ts similarity index 67% rename from src/lib/datepicker/calendar-table.spec.ts rename to src/lib/datepicker/calendar-body.spec.ts index a0fea7c9c430..31a00e24406f 100644 --- a/src/lib/datepicker/calendar-table.spec.ts +++ b/src/lib/datepicker/calendar-body.spec.ts @@ -1,64 +1,63 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component} from '@angular/core'; -import {MdCalendarCell, MdCalendarBody} from './calendar-body'; +import {MdCalendarBody, MdCalendarCell} from './calendar-body'; import {By} from '@angular/platform-browser'; -import {SimpleDate} from '../core/datetime/simple-date'; -describe('MdCalendarTable', () => { +describe('MdCalendarBody', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ MdCalendarBody, // Test components. - StandardCalendarTable, - CalendarTableWithDisabledCells, + StandardCalendarBody, + CalendarBodyWithDisabledCells, ], }); TestBed.compileComponents(); })); - describe('standard calendar table', () => { - let fixture: ComponentFixture; - let testComponent: StandardCalendarTable; - let calendarTableNativeElement: Element; + describe('standard calendar body', () => { + let fixture: ComponentFixture; + let testComponent: StandardCalendarBody; + let calendarBodyNativeElement: Element; let rowEls: NodeListOf; let labelEls: NodeListOf; let cellEls: NodeListOf; let refreshElementLists = () => { - rowEls = calendarTableNativeElement.querySelectorAll('tr'); - labelEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-body-label'); - cellEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-body-cell'); + rowEls = calendarBodyNativeElement.querySelectorAll('tr'); + labelEls = calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-label'); + cellEls = calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-cell'); }; beforeEach(() => { - fixture = TestBed.createComponent(StandardCalendarTable); + fixture = TestBed.createComponent(StandardCalendarBody); fixture.detectChanges(); - let calendarTableDebugElement = fixture.debugElement.query(By.directive(MdCalendarBody)); - calendarTableNativeElement = calendarTableDebugElement.nativeElement; + let calendarBodyDebugElement = fixture.debugElement.query(By.directive(MdCalendarBody)); + calendarBodyNativeElement = calendarBodyDebugElement.nativeElement; testComponent = fixture.componentInstance; refreshElementLists(); }); - it('creates table', () => { + it('creates body', () => { expect(rowEls.length).toBe(3); expect(labelEls.length).toBe(1); expect(cellEls.length).toBe(14); }); it('highlights today', () => { - let todayCell = calendarTableNativeElement.querySelector('.mat-calendar-body-today'); + let todayCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-today'); expect(todayCell).not.toBeNull(); expect(todayCell.innerHTML.trim()).toBe('3'); }); it('highlights selected', () => { - let selectedCell = calendarTableNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-selected'); expect(selectedCell).not.toBeNull(); expect(selectedCell.innerHTML.trim()).toBe('4'); }); @@ -79,7 +78,7 @@ describe('MdCalendarTable', () => { it('cell should be selected on click', () => { let todayElement = - calendarTableNativeElement.querySelector('.mat-calendar-body-today') as HTMLElement; + calendarBodyNativeElement.querySelector('.mat-calendar-body-today') as HTMLElement; todayElement.click(); fixture.detectChanges(); @@ -93,20 +92,20 @@ describe('MdCalendarTable', () => { }); }); - describe('calendar table with disabled cells', () => { - let fixture: ComponentFixture; - let testComponent: CalendarTableWithDisabledCells; - let calendarTableNativeElement: Element; + describe('calendar body with disabled cells', () => { + let fixture: ComponentFixture; + let testComponent: CalendarBodyWithDisabledCells; + let calendarBodyNativeElement: Element; let cellEls: NodeListOf; beforeEach(() => { - fixture = TestBed.createComponent(CalendarTableWithDisabledCells); + fixture = TestBed.createComponent(CalendarBodyWithDisabledCells); fixture.detectChanges(); - let calendarTableDebugElement = fixture.debugElement.query(By.directive(MdCalendarBody)); - calendarTableNativeElement = calendarTableDebugElement.nativeElement; + let calendarBodyDebugElement = fixture.debugElement.query(By.directive(MdCalendarBody)); + calendarBodyNativeElement = calendarBodyDebugElement.nativeElement; testComponent = fixture.componentInstance; - cellEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-body-cell'); + cellEls = calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-cell'); }); it('should only allow selection of disabled cells when allowDisabledSelection is true', () => { @@ -139,7 +138,7 @@ describe('MdCalendarTable', () => { (selectedValueChange)="onSelect($event)"> `, }) -class StandardCalendarTable { +class StandardCalendarBody { label = 'Jan 2017'; rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(r => r.map(createCell)); todayValue = 3; @@ -160,14 +159,14 @@ class StandardCalendarTable { (selectedValueChange)="selected = $event"> ` }) -class CalendarTableWithDisabledCells { +class CalendarBodyWithDisabledCells { rows = [[1, 2, 3, 4]].map(r => r.map(d => { let cell = createCell(d); cell.enabled = d % 2 == 0; return cell; })); allowDisabledSelection = false; - selected: SimpleDate; + selected: Date; } diff --git a/src/lib/datepicker/calendar-body.ts b/src/lib/datepicker/calendar-body.ts index b20376a95604..d46f2a56eb09 100644 --- a/src/lib/datepicker/calendar-body.ts +++ b/src/lib/datepicker/calendar-body.ts @@ -1,10 +1,10 @@ import { - Component, - ViewEncapsulation, ChangeDetectionStrategy, - Input, + Component, EventEmitter, - Output + Input, + Output, + ViewEncapsulation } from '@angular/core'; diff --git a/src/lib/datepicker/calendar.spec.ts b/src/lib/datepicker/calendar.spec.ts index 60e2ce027817..1faaeba88769 100644 --- a/src/lib/datepicker/calendar.spec.ts +++ b/src/lib/datepicker/calendar.spec.ts @@ -1,6 +1,5 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component} from '@angular/core'; -import {SimpleDate} from '../core/datetime/simple-date'; import {MdCalendar} from './calendar'; import {By} from '@angular/platform-browser'; import {MdMonthView} from './month-view'; @@ -23,6 +22,13 @@ import { RIGHT_ARROW, UP_ARROW } from '../core/keyboard/keycodes'; +import {MdDatepickerIntl} from './datepicker-intl'; + + +// When constructing a Date, the month is zero-based. This can be confusing, since people are +// used to seeing them one-based. So we create these aliases to make reading the tests easier. +const JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9, + NOV = 10, DEC = 11; describe('MdCalendar', () => { @@ -42,6 +48,9 @@ describe('MdCalendar', () => { CalendarWithMinMax, CalendarWithDateFilter, ], + providers: [ + MdDatepickerIntl, + ], }); TestBed.compileComponents(); @@ -54,7 +63,7 @@ describe('MdCalendar', () => { let periodButton: HTMLElement; let prevButton: HTMLElement; let nextButton: HTMLElement; - let calendarInstance: MdCalendar; + let calendarInstance: MdCalendar; beforeEach(() => { fixture = TestBed.createComponent(StandardCalendar); @@ -72,7 +81,7 @@ describe('MdCalendar', () => { it('should be in month view with specified month active', () => { expect(calendarInstance._monthView).toBe(true, 'should be in month view'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); }); it('should toggle view when period clicked', () => { @@ -90,17 +99,17 @@ describe('MdCalendar', () => { }); it('should go to next and previous month', () => { - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); nextButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); prevButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 28)); }); it('should go to previous and next year', () => { @@ -108,17 +117,17 @@ describe('MdCalendar', () => { fixture.detectChanges(); expect(calendarInstance._monthView).toBe(false, 'should be in year view'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); nextButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 31)); prevButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); }); it('should go back to month view after selecting month in year view', () => { @@ -126,14 +135,14 @@ describe('MdCalendar', () => { fixture.detectChanges(); expect(calendarInstance._monthView).toBe(false, 'should be in year view'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell'); (monthCells[monthCells.length - 1] as HTMLElement).click(); fixture.detectChanges(); expect(calendarInstance._monthView).toBe(true, 'should be in month view'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31)); expect(testComponent.selected).toBeFalsy('no date should be selected yet'); }); @@ -143,7 +152,7 @@ describe('MdCalendar', () => { fixture.detectChanges(); expect(calendarInstance._monthView).toBe(true, 'should be in month view'); - expect(testComponent.selected).toEqual(new SimpleDate(2017, 0, 31)); + expect(testComponent.selected).toEqual(new Date(2017, JAN, 31)); }); describe('a11y', () => { @@ -159,7 +168,7 @@ describe('MdCalendar', () => { }); it('should initially set start date active', () => { - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); }); describe('month view', () => { @@ -167,104 +176,104 @@ describe('MdCalendar', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 30)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30)); - calendarInstance._activeDate = new SimpleDate(2017, 0, 1); + calendarInstance._activeDate = new Date(2017, JAN, 1); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31)); }); it('should increment date on right arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 1)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 2)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 2)); }); it('should go up a row on up arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 24)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 24)); - calendarInstance._activeDate = new SimpleDate(2017, 0, 7); + calendarInstance._activeDate = new Date(2017, JAN, 7); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31)); }); it('should go down a row on down arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 7)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 7)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 14)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 14)); }); it('should go to beginning of the month on home press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1)); }); it('should go to end of the month on end press', () => { - calendarInstance._activeDate = new SimpleDate(2017, 0, 10); + calendarInstance._activeDate = new Date(2017, JAN, 10); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31)); }); it('should go back one month on page up press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 10, 30)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, NOV, 30)); }); it('should go forward one month on page down press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 2, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 28)); }); it('should select active date on enter', () => { @@ -276,7 +285,7 @@ describe('MdCalendar', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); - expect(testComponent.selected).toEqual(new SimpleDate(2017, 0, 30)); + expect(testComponent.selected).toEqual(new Date(2017, JAN, 30)); }); }); @@ -292,127 +301,127 @@ describe('MdCalendar', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 10, 30)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, NOV, 30)); }); it('should increment month on right arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 2, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 28)); }); it('should go up a row on up arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 7, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, AUG, 31)); - calendarInstance._activeDate = new SimpleDate(2017, 6, 1); + calendarInstance._activeDate = new Date(2017, JUL, 1); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 6, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, JUL, 1)); - calendarInstance._activeDate = new SimpleDate(2017, 11, 10); + calendarInstance._activeDate = new Date(2017, DEC, 10); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 4, 10)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, MAY, 10)); }); it('should go down a row on down arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 7, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, AUG, 31)); - calendarInstance._activeDate = new SimpleDate(2017, 5, 1); + calendarInstance._activeDate = new Date(2017, JUN, 1); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 5, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, JUN, 1)); - calendarInstance._activeDate = new SimpleDate(2017, 8, 30); + calendarInstance._activeDate = new Date(2017, SEP, 30); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, FEB, 28)); }); it('should go to first month of the year on home press', () => { - calendarInstance._activeDate = new SimpleDate(2017, 8, 30); + calendarInstance._activeDate = new Date(2017, SEP, 30); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 30)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 30)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30)); }); it('should go to last month of the year on end press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 11, 31)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31)); }); it('should go back one year on page up press', () => { - calendarInstance._activeDate = new SimpleDate(2016, 1, 29); + calendarInstance._activeDate = new Date(2016, FEB, 29); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2015, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2015, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2014, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2014, FEB, 28)); }); it('should go forward one year on page down press', () => { - calendarInstance._activeDate = new SimpleDate(2016, 1, 29); + calendarInstance._activeDate = new Date(2016, FEB, 29); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, FEB, 28)); }); it('should return to month view on enter', () => { @@ -423,7 +432,7 @@ describe('MdCalendar', () => { fixture.detectChanges(); expect(calendarInstance._monthView).toBe(true); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 1, 28)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); expect(testComponent.selected).toBeNull(); }); }); @@ -437,7 +446,7 @@ describe('MdCalendar', () => { let calendarElement: HTMLElement; let prevButton: HTMLButtonElement; let nextButton: HTMLButtonElement; - let calendarInstance: MdCalendar; + let calendarInstance: MdCalendar; beforeEach(() => { fixture = TestBed.createComponent(CalendarWithMinMax); @@ -452,55 +461,55 @@ describe('MdCalendar', () => { }); it('should clamp startAt value below min date', () => { - testComponent.startAt = new SimpleDate(2000, 0, 1); + testComponent.startAt = new Date(2000, JAN, 1); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1)); }); it('should clamp startAt value above max date', () => { - testComponent.startAt = new SimpleDate(2020, 0, 1); + testComponent.startAt = new Date(2020, JAN, 1); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1)); }); it('should not go back past min date', () => { - testComponent.startAt = new SimpleDate(2016, 1, 1); + testComponent.startAt = new Date(2016, FEB, 1); fixture.detectChanges(); expect(prevButton.disabled).toBe(false, 'previous button should not be disabled'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 1, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, FEB, 1)); prevButton.click(); fixture.detectChanges(); expect(prevButton.disabled).toBe(true, 'previous button should be disabled'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1)); prevButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2016, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1)); }); it('should not go forward past max date', () => { - testComponent.startAt = new SimpleDate(2017, 11, 1); + testComponent.startAt = new Date(2017, DEC, 1); fixture.detectChanges(); expect(nextButton.disabled).toBe(false, 'next button should not be disabled'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 11, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 1)); nextButton.click(); fixture.detectChanges(); expect(nextButton.disabled).toBe(true, 'next button should be disabled'); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1)); nextButton.click(); fixture.detectChanges(); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2018, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1)); }); }); @@ -508,7 +517,7 @@ describe('MdCalendar', () => { let fixture: ComponentFixture; let testComponent: CalendarWithDateFilter; let calendarElement: HTMLElement; - let calendarInstance: MdCalendar; + let calendarInstance: MdCalendar; beforeEach(() => { fixture = TestBed.createComponent(CalendarWithDateFilter); @@ -530,7 +539,7 @@ describe('MdCalendar', () => { (cells[1] as HTMLElement).click(); fixture.detectChanges(); - expect(testComponent.selected).toEqual(new SimpleDate(2017, 0, 2)); + expect(testComponent.selected).toEqual(new Date(2017, JAN, 2)); }); describe('a11y', () => { @@ -546,7 +555,7 @@ describe('MdCalendar', () => { it('should not allow selection of disabled date in month view', () => { expect(calendarInstance._monthView).toBe(true); - expect(calendarInstance._activeDate).toEqual(new SimpleDate(2017, 0, 1)); + expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); @@ -560,7 +569,7 @@ describe('MdCalendar', () => { dispatchMouseEvent(periodButton, 'click'); fixture.detectChanges(); - calendarInstance._activeDate = new SimpleDate(2017, 10, 1); + calendarInstance._activeDate = new Date(2017, NOV, 1); fixture.detectChanges(); expect(calendarInstance._monthView).toBe(false); @@ -580,7 +589,7 @@ describe('MdCalendar', () => { template: `` }) class StandardCalendar { - selected: SimpleDate = null; + selected: Date = null; } @@ -588,7 +597,7 @@ class StandardCalendar { template: `` }) class CalendarWithMinMax { - startAt: SimpleDate; + startAt: Date; } @@ -598,9 +607,9 @@ class CalendarWithMinMax { ` }) class CalendarWithDateFilter { - selected: SimpleDate = null; + selected: Date = null; - dateFilter (date: SimpleDate) { - return date.date % 2 == 0 && date.month != 10; + dateFilter (date: Date) { + return date.getDate() % 2 == 0 && date.getMonth() != NOV; } } diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 78007f3fa427..fd2ccf7579cd 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -7,11 +7,10 @@ import { Output, ViewEncapsulation } from '@angular/core'; -import {SimpleDate} from '../core/datetime/simple-date'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; import { DOWN_ARROW, - END, ENTER, + END, + ENTER, HOME, LEFT_ARROW, PAGE_DOWN, @@ -19,6 +18,8 @@ import { RIGHT_ARROW, UP_ARROW } from '../core/keyboard/keycodes'; +import {DateAdapter} from '../core/datetime/index'; +import {MdDatepickerIntl} from './datepicker-intl'; /** @@ -36,57 +37,57 @@ import { encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdCalendar implements AfterContentInit { +export class MdCalendar implements AfterContentInit { /** A date representing the period (month or year) to start the calendar in. */ @Input() - get startAt() { return this._startAt; } - set startAt(value: any) { this._startAt = this._locale.parseDate(value); } - private _startAt: SimpleDate; + get startAt(): D { return this._startAt; } + set startAt(value: D) { this._startAt = this._dateAdapter.parse(value); } + private _startAt: D; /** Whether the calendar should be started in month or year view. */ @Input() startView: 'month' | 'year' = 'month'; /** The currently selected date. */ @Input() - get selected() { return this._selected; } - set selected(value: any) { this._selected = this._locale.parseDate(value); } - private _selected: SimpleDate; + get selected(): D { return this._selected; } + set selected(value: D) { this._selected = this._dateAdapter.parse(value); } + private _selected: D; /** The minimum selectable date. */ @Input() - get minDate(): SimpleDate { return this._minDate; } - set minDate(date: SimpleDate) { this._minDate = this._locale.parseDate(date); } - private _minDate: SimpleDate; + get minDate(): D { return this._minDate; } + set minDate(date: D) { this._minDate = this._dateAdapter.parse(date); } + private _minDate: D; /** The maximum selectable date. */ @Input() - get maxDate(): SimpleDate { return this._maxDate; } - set maxDate(date: SimpleDate) { this._maxDate = this._locale.parseDate(date); } - private _maxDate: SimpleDate; + get maxDate(): D { return this._maxDate; } + set maxDate(date: D) { this._maxDate = this._dateAdapter.parse(date); } + private _maxDate: D; /** A function used to filter which dates are selectable. */ - @Input() dateFilter: (date: SimpleDate) => boolean; + @Input() dateFilter: (date: D) => boolean; /** Emits when the currently selected date changes. */ - @Output() selectedChange = new EventEmitter(); + @Output() selectedChange = new EventEmitter(); /** Date filter for the month and year views. */ - _dateFilterForViews = (date: SimpleDate) => { + _dateFilterForViews = (date: D) => { return !!date && (!this.dateFilter || this.dateFilter(date)) && - (!this.minDate || date.compare(this.minDate) >= 0) && - (!this.maxDate || date.compare(this.maxDate) <= 0); + (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) && + (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0); } /** * The current active date. This determines which time period is shown and which date is * highlighted when using keyboard navigation. */ - get _activeDate() { return this._clampedActiveDate; } - set _activeDate(value: SimpleDate) { - this._clampedActiveDate = value.clamp(this.minDate, this.maxDate); + get _activeDate(): D { return this._clampedActiveDate; } + set _activeDate(value: D) { + this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate); } - private _clampedActiveDate: SimpleDate; + private _clampedActiveDate: D; /** Whether the calendar is in month view. */ _monthView: boolean; @@ -94,42 +95,40 @@ export class MdCalendar implements AfterContentInit { /** The label for the current calendar view. */ get _periodButtonText(): string { return this._monthView ? - this._locale.getCalendarMonthHeaderLabel(this._activeDate).toLocaleUpperCase() : - this._locale.getCalendarYearHeaderLabel(this._activeDate); + this._dateAdapter.getMonthYearName(this._activeDate, 'short').toLocaleUpperCase() : + this._dateAdapter.getYearName(this._activeDate); } get _periodButtonLabel(): string { - return this._monthView ? - this._locale.switchToYearViewLabel : - this._locale.switchToMonthViewLabel; + return this._monthView ? this._intl.switchToYearViewLabel : this._intl.switchToMonthViewLabel; } /** The label for the the previous button. */ get _prevButtonLabel(): string { - return this._monthView ? this._locale.prevMonthLabel : this._locale.prevYearLabel; + return this._monthView ? this._intl.prevMonthLabel : this._intl.prevYearLabel; } /** The label for the the next button. */ get _nextButtonLabel(): string { - return this._monthView ? this._locale.nextMonthLabel : this._locale.nextYearLabel; + return this._monthView ? this._intl.nextMonthLabel : this._intl.nextYearLabel; } - constructor(private _locale: CalendarLocale) {} + constructor(private _dateAdapter: DateAdapter, private _intl: MdDatepickerIntl) {} ngAfterContentInit() { - this._activeDate = this.startAt || SimpleDate.today(); + this._activeDate = this.startAt || this._dateAdapter.today(); this._monthView = this.startView != 'year'; } /** Handles date selection in the month view. */ - _dateSelected(date: SimpleDate): void { - if (!SimpleDate.equals(date, this.selected)) { + _dateSelected(date: D): void { + if (!this._dateAdapter.sameDate(date, this.selected)) { this.selectedChange.emit(date); } } /** Handles month selection in the year view. */ - _monthSelected(month: SimpleDate): void { + _monthSelected(month: D): void { this._activeDate = month; this._monthView = true; } @@ -142,14 +141,15 @@ export class MdCalendar implements AfterContentInit { /** Handles user clicks on the previous button. */ _previousClicked(): void { this._activeDate = this._monthView ? - this._addCalendarMonths(this._activeDate, -1) : - this._addCalendarYears(this._activeDate, -1); + this._dateAdapter.addCalendarMonths(this._activeDate, -1) : + this._dateAdapter.addCalendarYears(this._activeDate, -1); } /** Handles user clicks on the next button. */ _nextClicked(): void { this._activeDate = this._monthView ? - this._addCalendarMonths(this._activeDate, 1) : this._addCalendarYears(this._activeDate, 1); + this._dateAdapter.addCalendarMonths(this._activeDate, 1) : + this._dateAdapter.addCalendarYears(this._activeDate, 1); } /** Whether the previous period button is enabled. */ @@ -166,10 +166,11 @@ export class MdCalendar implements AfterContentInit { } /** Whether the two dates represent the same view in the current view mode (month or year). */ - private _isSameView(date1: SimpleDate, date2: SimpleDate): boolean { + private _isSameView(date1: D, date2: D): boolean { return this._monthView ? - date1.year == date2.year && date1.month == date2.month : - date1.year == date2.year; + this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) && + this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2) : + this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2); } /** Handles keydown events on the calendar body. */ @@ -188,32 +189,35 @@ export class MdCalendar implements AfterContentInit { private _handleCalendarBodyKeydownInMonthView(event: KeyboardEvent): void { switch (event.keyCode) { case LEFT_ARROW: - this._activeDate = this._addCalendarDays(this._activeDate, -1); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -1); break; case RIGHT_ARROW: - this._activeDate = this._addCalendarDays(this._activeDate, 1); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 1); break; case UP_ARROW: - this._activeDate = this._addCalendarDays(this._activeDate, -7); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7); break; case DOWN_ARROW: - this._activeDate = this._addCalendarDays(this._activeDate, 7); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7); break; case HOME: - this._activeDate = new SimpleDate(this._activeDate.year, this._activeDate.month, 1); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, + 1 - this._dateAdapter.getDate(this._activeDate)); break; case END: - this._activeDate = new SimpleDate(this._activeDate.year, this._activeDate.month + 1, 0); + this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, + (this._dateAdapter.getNumDaysInMonth(this._activeDate) - + this._dateAdapter.getDate(this._activeDate))); break; case PAGE_UP: this._activeDate = event.altKey ? - this._addCalendarYears(this._activeDate, -1) : - this._addCalendarMonths(this._activeDate, -1); + this._dateAdapter.addCalendarYears(this._activeDate, -1) : + this._dateAdapter.addCalendarMonths(this._activeDate, -1); break; case PAGE_DOWN: this._activeDate = event.altKey ? - this._addCalendarYears(this._activeDate, 1) : - this._addCalendarMonths(this._activeDate, 1); + this._dateAdapter.addCalendarYears(this._activeDate, 1) : + this._dateAdapter.addCalendarMonths(this._activeDate, 1); break; case ENTER: if (this._dateFilterForViews(this._activeDate)) { @@ -233,10 +237,10 @@ export class MdCalendar implements AfterContentInit { private _handleCalendarBodyKeydownInYearView(event: KeyboardEvent): void { switch (event.keyCode) { case LEFT_ARROW: - this._activeDate = this._addCalendarMonths(this._activeDate, -1); + this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -1); break; case RIGHT_ARROW: - this._activeDate = this._addCalendarMonths(this._activeDate, 1); + this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 1); break; case UP_ARROW: this._activeDate = this._prevMonthInSameCol(this._activeDate); @@ -245,16 +249,20 @@ export class MdCalendar implements AfterContentInit { this._activeDate = this._nextMonthInSameCol(this._activeDate); break; case HOME: - this._activeDate = this._addCalendarMonths(this._activeDate, -this._activeDate.month); + this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, + -this._dateAdapter.getMonth(this._activeDate)); break; case END: - this._activeDate = this._addCalendarMonths(this._activeDate, 11 - this._activeDate.month); + this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, + 11 - this._dateAdapter.getMonth(this._activeDate)); break; case PAGE_UP: - this._activeDate = this._addCalendarYears(this._activeDate, event.altKey ? -10 : -1); + this._activeDate = + this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1); break; case PAGE_DOWN: - this._activeDate = this._addCalendarYears(this._activeDate, event.altKey ? 10 : 1); + this._activeDate = + this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1); break; case ENTER: this._monthSelected(this._activeDate); @@ -267,58 +275,27 @@ export class MdCalendar implements AfterContentInit { event.preventDefault(); } - /** Adds the given number of days to the date. */ - private _addCalendarDays(date: SimpleDate, days: number): SimpleDate { - return date.add({days}); - } - - /** - * Adds the given number of months to the date. Months are counted as if flipping pages on a - * calendar and then finding the closest date in the new month. For example when adding 1 month to - * Jan 31, 2017, the resulting date will be Feb 28, 2017. - */ - private _addCalendarMonths(date: SimpleDate, months: number): SimpleDate { - let newDate = date.add({months}); - - // It's possible to wind up in the wrong month if the original month has more days than the new - // month. In this case we want to go to the last day of the desired month. - // Note: the additional + 12 % 12 ensures we end up with a positive number, since JS % doesn't - // guarantee this. - if (newDate.month != ((date.month + months) % 12 + 12) % 12) { - newDate = new SimpleDate(newDate.year, newDate.month, 0); - } - - return newDate; - } - - /** - * Adds the given number of months to the date. Months are counted as if flipping 12 pages for - * each year on a calendar and then finding the closest date in the new month. For example when - * adding 1 year to Feb 29, 2016, the resulting date will be Feb 28, 2017. - */ - private _addCalendarYears(date: SimpleDate, years: number): SimpleDate { - return this._addCalendarMonths(date, years * 12); - } - /** * Determine the date for the month that comes before the given month in the same column in the * calendar table. */ - private _prevMonthInSameCol(date: SimpleDate) { + private _prevMonthInSameCol(date: D): D { // Determine how many months to jump forward given that there are 2 empty slots at the beginning // of each year. - let increment = date.month <= 4 ? -5 : (date.month >= 7 ? -7 : -12); - return this._addCalendarMonths(date, increment); + let increment = this._dateAdapter.getMonth(date) <= 4 ? -5 : + (this._dateAdapter.getMonth(date) >= 7 ? -7 : -12); + return this._dateAdapter.addCalendarMonths(date, increment); } /** * Determine the date for the month that comes after the given month in the same column in the * calendar table. */ - private _nextMonthInSameCol(date: SimpleDate): SimpleDate { + private _nextMonthInSameCol(date: D): D { // Determine how many months to jump forward given that there are 2 empty slots at the beginning // of each year. - let increment = date.month <= 4 ? 7 : (date.month >= 7 ? 5 : 12); - return this._addCalendarMonths(date, increment); + let increment = this._dateAdapter.getMonth(date) <= 4 ? 7 : + (this._dateAdapter.getMonth(date) >= 7 ? 5 : 12); + return this._dateAdapter.addCalendarMonths(date, increment); } } diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 1bde8857b881..ea189ed57de1 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -11,12 +11,10 @@ import { } from '@angular/core'; import {MdDatepicker} from './datepicker'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {SimpleDate} from '../core/datetime/simple-date'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; import {Subscription} from 'rxjs/Subscription'; import {MdInputContainer} from '../input/input-container'; import {DOWN_ARROW} from '../core/keyboard/keycodes'; -import {Observable} from 'rxjs/Observable'; +import {DateAdapter} from '../core/datetime/index'; export const MD_DATEPICKER_VALUE_ACCESSOR: any = { @@ -34,58 +32,56 @@ export const MD_DATEPICKER_VALUE_ACCESSOR: any = { '[attr.aria-expanded]': '_datepicker?.opened || "false"', '[attr.aria-haspopup]': 'true', '[attr.aria-owns]': '_datepicker?.id', - '[min]': '_min?.toNativeDate()', - '[max]': '_max?.toNativeDate()', + '[min]': '_min', + '[max]': '_max', '(input)': '_onInput($event.target.value)', '(blur)': '_onTouched()', '(keydown)': '_onKeydown($event)', } }) -export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor, OnDestroy { +export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor, OnDestroy { /** The datepicker that this input is associated with. */ @Input() - set mdDatepicker(value: MdDatepicker) { + set mdDatepicker(value: MdDatepicker) { if (value) { this._datepicker = value; this._datepicker._registerInput(this); } } - _datepicker: MdDatepicker; + _datepicker: MdDatepicker; @Input() - set matDatepicker(value: MdDatepicker) { this.mdDatepicker = value; } + set matDatepicker(value: MdDatepicker) { this.mdDatepicker = value; } /** The value of the input. */ @Input() - get value(): SimpleDate { - return this._locale.parseDate(this._elementRef.nativeElement.value); + get value(): D { + return this._dateAdapter.parse(this._elementRef.nativeElement.value); } - set value(value: SimpleDate) { - let date = this._locale.parseDate(value); + set value(value: D) { + let date = this._dateAdapter.parse(value); let oldDate = this.value; this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', - date ? this._locale.formatDate(date) : ''); - if (!SimpleDate.equals(oldDate, date)) { - this._valueChangeEmitter.emit(date); + date ? this._dateAdapter.format(date) : ''); + if (!this._dateAdapter.sameDate(oldDate, date)) { + this._valueChange.emit(date); } } /** The minimum valid date. */ @Input() - get min(): SimpleDate { return this._min; } - set min(value: SimpleDate) { this._min = this._locale.parseDate(value); } - private _min: SimpleDate; + get min(): D { return this._min; } + set min(value: D) { this._min = this._dateAdapter.parse(value); } + private _min: D; /** The maximum valid date. */ @Input() - get max(): SimpleDate { return this._max; } - set max(value: SimpleDate) { this._max = this._locale.parseDate(value); } - private _max: SimpleDate; - - private _valueChangeEmitter = new EventEmitter(); + get max(): D { return this._max; } + set max(value: D) { this._max = this._dateAdapter.parse(value); } + private _max: D; /** Emits when the value changes (either due to user input or programmatic change). */ - _valueChange: Observable = this._valueChangeEmitter.asObservable(); + _valueChange = new EventEmitter(); _onChange = (value: any) => {}; @@ -96,13 +92,13 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor constructor( private _elementRef: ElementRef, private _renderer: Renderer, - private _locale: CalendarLocale, + private _dateAdapter: DateAdapter, @Optional() private _mdInputContainer: MdInputContainer) {} ngAfterContentInit() { if (this._datepicker) { this._datepickerSubscription = - this._datepicker.selectedChanged.subscribe((selected: SimpleDate) => { + this._datepicker.selectedChanged.subscribe((selected: D) => { this.value = selected; this._onChange(selected); }); @@ -124,7 +120,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor } // Implemented as part of ControlValueAccessor - writeValue(value: SimpleDate): void { + writeValue(value: D): void { this.value = value; } @@ -151,8 +147,8 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor } _onInput(value: string) { - let date = this._locale.parseDate(value); + let date = this._dateAdapter.parse(value); this._onChange(date); - this._valueChangeEmitter.emit(date); + this._valueChange.emit(date); } } diff --git a/src/lib/datepicker/datepicker-intl.ts b/src/lib/datepicker/datepicker-intl.ts new file mode 100644 index 000000000000..a96ea07b12f7 --- /dev/null +++ b/src/lib/datepicker/datepicker-intl.ts @@ -0,0 +1,36 @@ +import {Injectable} from '@angular/core'; + + +/** Datepicker data that requires internationalization. */ +@Injectable() +export class MdDatepickerIntl { + /** A label for the calendar popup (used by screen readers). */ + calendarLabel = 'Calendar'; + + /** A label for the button used to open the calendar popup (used by screen readers). */ + openCalendarLabel = 'Open calendar'; + + /** A label for the previous month button (used by screen readers). */ + prevMonthLabel = 'Previous month'; + + /** A label for the next month button (used by screen readers). */ + nextMonthLabel = 'Next month'; + + /** A label for the previous year button (used by screen readers). */ + prevYearLabel = 'Previous year'; + + /** A label for the next year button (used by screen readers). */ + nextYearLabel = 'Next year'; + + /** A label for the 'switch to month view' button (used by screen readers). */ + switchToMonthViewLabel = 'Change to month view'; + + /** A label for the 'switch to year view' button (used by screen readers). */ + switchToYearViewLabel = 'Change to year view'; + + /** + * The format to use when displaying dates without time information. If unspecified the `date` + * format supplied by {@link DateAdapter#getDefaultFormats} will be used. + */ + dateFormat: any; +} diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts index 485d118453b5..c00f6da5686f 100644 --- a/src/lib/datepicker/datepicker-toggle.ts +++ b/src/lib/datepicker/datepicker-toggle.ts @@ -1,6 +1,6 @@ import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core'; import {MdDatepicker} from './datepicker'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; +import {MdDatepickerIntl} from './datepicker-intl'; @Component({ @@ -10,20 +10,20 @@ import {CalendarLocale} from '../core/datetime/calendar-locale'; styleUrls: ['datepicker-toggle.css'], host: { '[class.mat-datepicker-toggle]': 'true', - '[attr.aria-label]': '_locale.openCalendarLabel', + '[attr.aria-label]': '_intl.openCalendarLabel', '(click)': '_open($event)', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdDatepickerToggle { - @Input('mdDatepickerToggle') datepicker: MdDatepicker; +export class MdDatepickerToggle { + @Input('mdDatepickerToggle') datepicker: MdDatepicker; @Input('matDatepickerToggle') get _datepicker() { return this.datepicker; } - set _datepicker(v: MdDatepicker) { this.datepicker = v; } + set _datepicker(v: MdDatepicker) { this.datepicker = v; } - constructor(public _locale: CalendarLocale) {} + constructor(public _intl: MdDatepickerIntl) {} _open(event: Event): void { if (this.datepicker) { diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 455a7761b4ff..dd8872161be9 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -3,7 +3,6 @@ import {MdDatepickerModule} from './index'; import {Component, ViewChild} from '@angular/core'; import {MdDatepicker} from './datepicker'; import {MdDatepickerInput} from './datepicker-input'; -import {SimpleDate} from '../core/datetime/simple-date'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; import {dispatchFakeEvent, dispatchMouseEvent} from '../core/testing/dispatch-events'; @@ -11,6 +10,12 @@ import {MdInputModule} from '../input/index'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +// When constructing a Date, the month is zero-based. This can be confusing, since people are +// used to seeing them one-based. So we create these aliases to make reading the tests easier. +const JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9, + NOV = 10, DEC = 11; + + describe('MdDatepicker', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -115,7 +120,7 @@ describe('MdDatepicker', () => { fixture.detectChanges(); expect(document.querySelector('md-dialog-container')).not.toBeNull(); - expect(testComponent.datepickerInput.value).toEqual(new SimpleDate(2020, 0, 1)); + expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); let cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[1], 'click'); @@ -123,12 +128,12 @@ describe('MdDatepicker', () => { fixture.whenStable().then(() => { expect(document.querySelector('md-dialog-container')).toBeNull(); - expect(testComponent.datepickerInput.value).toEqual(new SimpleDate(2020, 0, 2)); + expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); }); })); it('startAt should fallback to input value', () => { - expect(testComponent.datepicker.startAt).toEqual(new SimpleDate(2020, 0, 1)); + expect(testComponent.datepicker.startAt).toEqual(new Date(2020, JAN, 1)); }); it('should attach popup to native input', () => { @@ -183,7 +188,7 @@ describe('MdDatepicker', () => { })); it('explicit startAt should override input value', () => { - expect(testComponent.datepicker.startAt).toEqual(new SimpleDate(2010, 0, 1)); + expect(testComponent.datepicker.startAt).toEqual(new Date(2010, JAN, 1)); }); }); @@ -211,7 +216,7 @@ describe('MdDatepicker', () => { expect(testComponent.datepickerInput.value).toBeNull(); expect(testComponent.datepicker._selected).toBeNull(); - let selected = new SimpleDate(2017, 0, 1); + let selected = new Date(2017, JAN, 1); testComponent.selected = selected; fixture.detectChanges(); @@ -227,7 +232,7 @@ describe('MdDatepicker', () => { expect(testComponent.selected).toBeNull(); expect(testComponent.datepickerInput.value).toBeNull(); - let selected = new SimpleDate(2017, 0, 1); + let selected = new Date(2017, JAN, 1); testComponent.datepicker._selectAndClose(selected); fixture.detectChanges(); @@ -255,7 +260,7 @@ describe('MdDatepicker', () => { expect(inputEl.classList).toContain('ng-pristine'); - testComponent.datepicker._selectAndClose(new SimpleDate(2017, 0, 1)); + testComponent.datepicker._selectAndClose(new Date(2017, JAN, 1)); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -270,7 +275,7 @@ describe('MdDatepicker', () => { expect(inputEl.classList).toContain('ng-pristine'); - testComponent.selected = new SimpleDate(2017, 0, 1); + testComponent.selected = new Date(2017, JAN, 1); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -317,7 +322,7 @@ describe('MdDatepicker', () => { expect(testComponent.datepickerInput.value).toBeNull(); expect(testComponent.datepicker._selected).toBeNull(); - let selected = new SimpleDate(2017, 0, 1); + let selected = new Date(2017, JAN, 1); testComponent.formControl.setValue(selected); fixture.detectChanges(); @@ -329,7 +334,7 @@ describe('MdDatepicker', () => { expect(testComponent.formControl.value).toBeNull(); expect(testComponent.datepickerInput.value).toBeNull(); - let selected = new SimpleDate(2017, 0, 1); + let selected = new Date(2017, JAN, 1); testComponent.datepicker._selectAndClose(selected); fixture.detectChanges(); @@ -416,8 +421,8 @@ describe('MdDatepicker', () => { })); it('should use min and max dates specified by the input', () => { - expect(testComponent.datepicker._minDate).toEqual(new SimpleDate(2010, 0, 1)); - expect(testComponent.datepicker._maxDate).toEqual(new SimpleDate(2020, 0, 1)); + expect(testComponent.datepicker._minDate).toEqual(new Date(2010, JAN, 1)); + expect(testComponent.datepicker._maxDate).toEqual(new Date(2020, JAN, 1)); }); }); }); @@ -431,8 +436,8 @@ describe('MdDatepicker', () => { }) class StandardDatepicker { touch = false; - @ViewChild('d') datepicker: MdDatepicker; - @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; + @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } @@ -448,7 +453,7 @@ class MultiInputDatepicker {} template: ``, }) class NoInputDatepicker { - @ViewChild('d') datepicker: MdDatepicker; + @ViewChild('d') datepicker: MdDatepicker; } @@ -459,7 +464,7 @@ class NoInputDatepicker { `, }) class DatepickerWithStartAt { - @ViewChild('d') datepicker: MdDatepicker; + @ViewChild('d') datepicker: MdDatepicker; } @@ -467,9 +472,9 @@ class DatepickerWithStartAt { template: ``, }) class DatepickerWithNgModel { - selected: SimpleDate = null; - @ViewChild('d') datepicker: MdDatepicker; - @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; + selected: Date = null; + @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } @@ -481,8 +486,8 @@ class DatepickerWithNgModel { }) class DatepickerWithFormControl { formControl = new FormControl(); - @ViewChild('d') datepicker: MdDatepicker; - @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; + @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } @@ -494,7 +499,7 @@ class DatepickerWithFormControl { `, }) class DatepickerWithToggle { - @ViewChild('d') datepicker: MdDatepicker; + @ViewChild('d') datepicker: MdDatepicker; } @@ -507,8 +512,8 @@ class DatepickerWithToggle { `, }) class InputContainerDatepicker { - @ViewChild('d') datepicker: MdDatepicker; - @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; + @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } @@ -519,5 +524,5 @@ class InputContainerDatepicker { `, }) class DatepickerWithMinAndMax { - @ViewChild('d') datepicker: MdDatepicker; + @ViewChild('d') datepicker: MdDatepicker; } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 63153fdc4762..9fb7129294ea 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -25,12 +25,11 @@ import { OriginConnectionPosition, OverlayConnectionPosition } from '../core/overlay/position/connected-position'; -import {SimpleDate} from '../core/datetime/simple-date'; import {MdDatepickerInput} from './datepicker-input'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; import 'rxjs/add/operator/first'; import {Subscription} from 'rxjs/Subscription'; import {MdDialogConfig} from '../dialog/dialog-config'; +import {DateAdapter} from '../core/datetime/index'; /** Used to generate a unique ID for each datepicker instance. */ @@ -56,8 +55,8 @@ let datepickerUid = 0; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdDatepickerContent implements AfterContentInit { - datepicker: MdDatepicker; +export class MdDatepickerContent implements AfterContentInit { + datepicker: MdDatepicker; constructor(private _elementRef: ElementRef) {} @@ -76,16 +75,16 @@ export class MdDatepickerContent implements AfterContentInit { selector: 'md-datepicker, mat-datepicker', template: '', }) -export class MdDatepicker implements OnDestroy { +export class MdDatepicker implements OnDestroy { /** The date to open the calendar to initially. */ @Input() - get startAt(): SimpleDate { + get startAt(): D { // If an explicit startAt is set we start there, otherwise we start at whatever the currently // selected value is. return this._startAt || (this._datepickerInput ? this._datepickerInput.value : null); } - set startAt(date: SimpleDate) { this._startAt = this._locale.parseDate(date); } - private _startAt: SimpleDate; + set startAt(date: D) { this._startAt = this._dateAdapter.parse(date); } + private _startAt: D; /** * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather @@ -96,10 +95,10 @@ export class MdDatepicker implements OnDestroy { /** A function used to filter which dates are selectable. */ @Input() - dateFilter: (date: SimpleDate) => boolean; + dateFilter: (date: D) => boolean; /** Emits new selected date when selected date changes. */ - @Output() selectedChanged = new EventEmitter(); + @Output() selectedChanged = new EventEmitter(); /** Whether the calendar is open. */ opened = false; @@ -108,15 +107,15 @@ export class MdDatepicker implements OnDestroy { id = `md-datepicker-${datepickerUid++}`; /** The currently selected date. */ - _selected: SimpleDate = null; + _selected: D = null; /** The minimum selectable date. */ - get _minDate(): SimpleDate { + get _minDate(): D { return this._datepickerInput && this._datepickerInput.min; } /** The maximum selectable date. */ - get _maxDate(): SimpleDate { + get _maxDate(): D { return this._datepickerInput && this._datepickerInput.max; } @@ -127,15 +126,15 @@ export class MdDatepicker implements OnDestroy { private _dialogRef: MdDialogRef; /** A portal containing the calendar for this datepicker. */ - private _calendarPortal: ComponentPortal; + private _calendarPortal: ComponentPortal>; /** The input element this datepicker is associated with. */ - private _datepickerInput: MdDatepickerInput; + private _datepickerInput: MdDatepickerInput; private _inputSubscription: Subscription; constructor(private _dialog: MdDialog, private _overlay: Overlay, - private _viewContainerRef: ViewContainerRef, private _locale: CalendarLocale, + private _viewContainerRef: ViewContainerRef, private _dateAdapter: DateAdapter, @Optional() private _dir: Dir) {} ngOnDestroy() { @@ -149,10 +148,10 @@ export class MdDatepicker implements OnDestroy { } /** Selects the given date and closes the currently open popup or dialog. */ - _selectAndClose(date: SimpleDate): void { + _selectAndClose(date: D): void { let oldValue = this._selected; this._selected = date; - if (!SimpleDate.equals(oldValue, this._selected)) { + if (!this._dateAdapter.sameDate(oldValue, this._selected)) { this.selectedChanged.emit(date); } this.close(); @@ -162,13 +161,13 @@ export class MdDatepicker implements OnDestroy { * Register an input with this datepicker. * @param input The datepicker input to register with this datepicker. */ - _registerInput(input: MdDatepickerInput): void { + _registerInput(input: MdDatepickerInput): void { if (this._datepickerInput) { throw new MdError('An MdDatepicker can only be associated with a single input.'); } this._datepickerInput = input; this._inputSubscription = - this._datepickerInput._valueChange.subscribe((value: SimpleDate) => this._selected = value); + this._datepickerInput._valueChange.subscribe((value: D) => this._selected = value); } /** Open the calendar. */ @@ -223,7 +222,7 @@ export class MdDatepicker implements OnDestroy { } if (!this._popupRef.hasAttached()) { - let componentRef: ComponentRef = + let componentRef: ComponentRef> = this._popupRef.attach(this._calendarPortal); componentRef.instance.datepicker = this; } diff --git a/src/lib/datepicker/index.ts b/src/lib/datepicker/index.ts index 8526e5f8cd0e..a5fae36c6df4 100644 --- a/src/lib/datepicker/index.ts +++ b/src/lib/datepicker/index.ts @@ -12,12 +12,15 @@ import {MdCalendar} from './calendar'; import {MdDatepickerToggle} from './datepicker-toggle'; import {StyleModule} from '../core/style/index'; import {MdButtonModule} from '../button/index'; +import {MdDatepickerIntl} from './datepicker-intl'; export * from './calendar'; export * from './calendar-body'; export * from './datepicker'; export * from './datepicker-input'; +export * from './datepicker-intl'; +export * from './datepicker-toggle'; export * from './month-view'; export * from './year-view'; @@ -47,6 +50,9 @@ export * from './year-view'; MdMonthView, MdYearView, ], + providers: [ + MdDatepickerIntl, + ], entryComponents: [ MdDatepickerContent, ] diff --git a/src/lib/datepicker/month-view.html b/src/lib/datepicker/month-view.html index a572c8ded789..15991190d67d 100644 --- a/src/lib/datepicker/month-view.html +++ b/src/lib/datepicker/month-view.html @@ -9,7 +9,7 @@ [todayValue]="_todayDate" [selectedValue]="_selectedDate" [labelMinRequiredCells]="3" - [activeCell]="activeDate.date - 1" + [activeCell]="_dateAdapter.getDate(activeDate) - 1" (selectedValueChange)="_dateSelected($event)"> diff --git a/src/lib/datepicker/month-view.spec.ts b/src/lib/datepicker/month-view.spec.ts index ee544470a8a3..a06f21079dbd 100644 --- a/src/lib/datepicker/month-view.spec.ts +++ b/src/lib/datepicker/month-view.spec.ts @@ -2,11 +2,16 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; import {MdMonthView} from './month-view'; -import {SimpleDate} from '../core/datetime/simple-date'; import {MdCalendarBody} from './calendar-body'; import {DatetimeModule} from '../core/datetime/index'; +// When constructing a Date, the month is zero-based. This can be confusing, since people are +// used to seeing them one-based. So we create these aliases to make reading the tests easier. +const JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9, + NOV = 10, DEC = 11; + + describe('MdMonthView', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -56,7 +61,7 @@ describe('MdMonthView', () => { }); it('does not show selected date if in different month', () => { - testComponent.selected = new SimpleDate(2017, 2, 10); + testComponent.selected = new Date(2017, MAR, 10); fixture.detectChanges(); let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected'); @@ -106,8 +111,8 @@ describe('MdMonthView', () => { template: ``, }) class StandardMonthView { - date = new SimpleDate(2017, 0, 5); - selected = new SimpleDate(2017, 0, 10); + date = new Date(2017, JAN, 5); + selected = new Date(2017, JAN, 10); } @@ -115,7 +120,7 @@ class StandardMonthView { template: `` }) class MonthViewWithDateFilter { - dateFilter(date: SimpleDate) { - return date.date % 2 == 0; + dateFilter(date: Date) { + return date.getDate() % 2 == 0; } } diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 94ea70d7fc18..8313085812bf 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -1,15 +1,14 @@ import { - Component, - ViewEncapsulation, + AfterContentInit, ChangeDetectionStrategy, - Input, + Component, EventEmitter, + Input, Output, - AfterContentInit + ViewEncapsulation } from '@angular/core'; import {MdCalendarCell} from './calendar-body'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; -import {SimpleDate} from '../core/datetime/simple-date'; +import {DateAdapter} from '../core/datetime/index'; const DAYS_PER_WEEK = 7; @@ -26,35 +25,35 @@ const DAYS_PER_WEEK = 7; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdMonthView implements AfterContentInit { +export class MdMonthView implements AfterContentInit { /** * The date to display in this month view (everything other than the month and year is ignored). */ @Input() - get activeDate() { return this._activeDate; } - set activeDate(value) { + get activeDate(): D { return this._activeDate; } + set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = this._locale.parseDate(value) || SimpleDate.today(); + this._activeDate = this._dateAdapter.parse(value) || this._dateAdapter.today(); if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); } } - private _activeDate = SimpleDate.today(); + private _activeDate: D; /** The currently selected date. */ @Input() - get selected() { return this._selected; } - set selected(value) { - this._selected = this._locale.parseDate(value); + get selected(): D { return this._selected; } + set selected(value: D) { + this._selected = this._dateAdapter.parse(value); this._selectedDate = this._getDateInCurrentMonth(this.selected); } - private _selected: SimpleDate; + private _selected: D; /** A function used to filter which dates are selectable. */ - @Input() dateFilter: (date: SimpleDate) => boolean; + @Input() dateFilter: (date: D) => boolean; /** Emits when a new date is selected. */ - @Output() selectedChange = new EventEmitter(); + @Output() selectedChange = new EventEmitter(); /** The label for this month (e.g. "January 2017"). */ _monthLabel: string; @@ -77,10 +76,14 @@ export class MdMonthView implements AfterContentInit { /** The names of the weekdays. */ _weekdays: string[]; - constructor(private _locale: CalendarLocale) { + constructor(public _dateAdapter: DateAdapter) { + const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek(); + const weekdays = this._dateAdapter.getDayOfWeekNames('narrow'); + // Rotate the labels for days of the week based on the configured first day of the week. - this._weekdays = this._locale.narrowDays.slice(this._locale.firstDayOfWeek) - .concat(this._locale.narrowDays.slice(0, this._locale.firstDayOfWeek)); + this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); + + this._activeDate = this._dateAdapter.today(); } ngAfterContentInit(): void { @@ -92,25 +95,32 @@ export class MdMonthView implements AfterContentInit { if (this._selectedDate == date) { return; } - this.selectedChange.emit(new SimpleDate(this.activeDate.year, this.activeDate.month, date)); + this.selectedChange.emit(this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), this._dateAdapter.getMonth(this.activeDate), + date)); } /** Initializes this month view. */ private _init() { this._selectedDate = this._getDateInCurrentMonth(this.selected); - this._todayDate = this._getDateInCurrentMonth(SimpleDate.today()); - this._monthLabel = this._locale.shortMonths[this.activeDate.month].toLocaleUpperCase(); + this._todayDate = this._getDateInCurrentMonth(this._dateAdapter.today()); + this._monthLabel = + this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)] + .toLocaleUpperCase(); - let firstOfMonth = new SimpleDate(this.activeDate.year, this.activeDate.month, 1); + let firstOfMonth = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), + this._dateAdapter.getMonth(this.activeDate), 1); this._firstWeekOffset = - (DAYS_PER_WEEK + firstOfMonth.day - this._locale.firstDayOfWeek) % DAYS_PER_WEEK; + (DAYS_PER_WEEK + this._dateAdapter.getDayOfWeek(firstOfMonth) - + this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK; this._createWeekCells(); } /** Creates MdCalendarCells for the dates in this month. */ private _createWeekCells() { - let daysInMonth = new SimpleDate(this.activeDate.year, this.activeDate.month + 1, 0).date; + let daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); + let dateNames = this._dateAdapter.getDateNames(); this._weeks = [[]]; for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) { if (cell == DAYS_PER_WEEK) { @@ -118,9 +128,11 @@ export class MdMonthView implements AfterContentInit { cell = 0; } let enabled = !this.dateFilter || - this.dateFilter(new SimpleDate(this.activeDate.year, this.activeDate.month, i + 1)); + this.dateFilter(this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), + this._dateAdapter.getMonth(this.activeDate), i + 1)); this._weeks[this._weeks.length - 1] - .push(new MdCalendarCell(i + 1, this._locale.dates[i + 1], enabled)); + .push(new MdCalendarCell(i + 1, dateNames[i], enabled)); } } @@ -128,12 +140,14 @@ export class MdMonthView 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: SimpleDate): number { - return this._hasSameMonthAndYear(date, this.activeDate) ? date.date : null; + private _getDateInCurrentMonth(date: D): number { + return 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: SimpleDate, d2: SimpleDate): boolean { - return !!(d1 && d2 && d1.month == d2.month && d1.year == d2.year); + private _hasSameMonthAndYear(d1: D, d2: D): boolean { + return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) && + this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2)); } } diff --git a/src/lib/datepicker/year-view.html b/src/lib/datepicker/year-view.html index d8a7b447cb89..fe68455874c0 100644 --- a/src/lib/datepicker/year-view.html +++ b/src/lib/datepicker/year-view.html @@ -9,7 +9,7 @@ [todayValue]="_todayMonth" [selectedValue]="_selectedMonth" [labelMinRequiredCells]="2" - [activeCell]="activeDate.month" + [activeCell]="_dateAdapter.getMonth(activeDate)" (selectedValueChange)="_monthSelected($event)"> diff --git a/src/lib/datepicker/year-view.spec.ts b/src/lib/datepicker/year-view.spec.ts index 5511aafac9b4..3247bfea711c 100644 --- a/src/lib/datepicker/year-view.spec.ts +++ b/src/lib/datepicker/year-view.spec.ts @@ -2,11 +2,16 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; import {MdYearView} from './year-view'; -import {SimpleDate} from '../core/datetime/simple-date'; import {MdCalendarBody} from './calendar-body'; import {DatetimeModule} from '../core/datetime/index'; +// When constructing a Date, the month is zero-based. This can be confusing, since people are +// used to seeing them one-based. So we create these aliases to make reading the tests easier. +const JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9, + NOV = 10, DEC = 11; + + describe('MdYearView', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -56,7 +61,7 @@ describe('MdYearView', () => { }); it('does not show selected month if in different year', () => { - testComponent.selected = new SimpleDate(2016, 2, 10); + testComponent.selected = new Date(2016, MAR, 10); fixture.detectChanges(); let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected'); @@ -107,8 +112,8 @@ describe('MdYearView', () => { `, }) class StandardYearView { - date = new SimpleDate(2017, 0, 5); - selected = new SimpleDate(2017, 2, 10); + date = new Date(2017, JAN, 5); + selected = new Date(2017, MAR, 10); } @@ -116,11 +121,11 @@ class StandardYearView { template: `` }) class YearViewWithDateFilter { - dateFilter(date: SimpleDate) { - if (date.month == 0) { - return date.date == 10; + dateFilter(date: Date) { + if (date.getMonth() == JAN) { + return date.getDate() == 10; } - if (date.month == 1) { + if (date.getMonth() == FEB) { return false; } return true; diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index a9bc7616988a..5792ae263560 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -1,15 +1,14 @@ import { - Component, - ViewEncapsulation, + AfterContentInit, ChangeDetectionStrategy, + Component, + EventEmitter, Input, - AfterContentInit, Output, - EventEmitter + ViewEncapsulation } from '@angular/core'; import {MdCalendarCell} from './calendar-body'; -import {CalendarLocale} from '../core/datetime/calendar-locale'; -import {SimpleDate} from '../core/datetime/simple-date'; +import {DateAdapter} from '../core/datetime/index'; /** @@ -23,33 +22,33 @@ import {SimpleDate} from '../core/datetime/simple-date'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdYearView implements AfterContentInit { +export class MdYearView implements AfterContentInit { /** The date to display in this year view (everything other than the year is ignored). */ @Input() - get activeDate() { return this._activeDate; } - set activeDate(value) { + get activeDate(): D { return this._activeDate; } + set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = this._locale.parseDate(value) || SimpleDate.today(); - if (oldActiveDate.year != this._activeDate.year) { + this._activeDate = this._dateAdapter.parse(value) || this._dateAdapter.today(); + if (this._dateAdapter.getYear(oldActiveDate) != this._dateAdapter.getYear(this._activeDate)) { this._init(); } } - private _activeDate = SimpleDate.today(); + private _activeDate: D; /** The currently selected date. */ @Input() - get selected() { return this._selected; } - set selected(value) { - this._selected = this._locale.parseDate(value); + get selected(): D { return this._selected; } + set selected(value: D) { + this._selected = this._dateAdapter.parse(value); this._selectedMonth = this._getMonthInCurrentYear(this.selected); } - private _selected: SimpleDate; + private _selected: D; /** A function used to filter which dates are selectable. */ - @Input() dateFilter: (date: SimpleDate) => boolean; + @Input() dateFilter: (date: D) => boolean; /** Emits when a new month is selected. */ - @Output() selectedChange = new EventEmitter(); + @Output() selectedChange = new EventEmitter(); /** Grid of calendar cells representing the months of the year. */ _months: MdCalendarCell[][]; @@ -66,7 +65,9 @@ export class MdYearView implements AfterContentInit { */ _selectedMonth: number; - constructor(private _locale: CalendarLocale) {} + constructor(public _dateAdapter: DateAdapter) { + this._activeDate = this._dateAdapter.today(); + } ngAfterContentInit() { this._init(); @@ -74,32 +75,36 @@ export class MdYearView implements AfterContentInit { /** Handles when a new month is selected. */ _monthSelected(month: number) { - this.selectedChange.emit(new SimpleDate(this.activeDate.year, month, this._activeDate.date)); + this.selectedChange.emit(this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), month, + this._dateAdapter.getDate(this.activeDate))); } /** Initializes this month view. */ private _init() { this._selectedMonth = this._getMonthInCurrentYear(this.selected); - this._todayMonth = this._getMonthInCurrentYear(SimpleDate.today()); - this._yearLabel = this._locale.getCalendarYearHeaderLabel(this.activeDate); + this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today()); + this._yearLabel = this._dateAdapter.getYearName(this.activeDate); + let monthNames = this._dateAdapter.getMonthNames('short'); // First row of months only contains 5 elements so we can fit the year label on the same row. this._months = [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11]].map(row => row.map( - month => this._createCellForMonth(month))); + month => this._createCellForMonth(month, monthNames[month]))); } /** * 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: SimpleDate) { - return date && date.year == this.activeDate.year ? date.month : null; + private _getMonthInCurrentYear(date: D) { + return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ? + this._dateAdapter.getMonth(date) : null; } /** Creates an MdCalendarCell for the given month. */ - private _createCellForMonth(month: number) { + private _createCellForMonth(month: number, monthName: string) { return new MdCalendarCell( - month, this._locale.shortMonths[month].toLocaleUpperCase(), this._isMonthEnabled(month)); + month, monthName.toLocaleUpperCase(), this._isMonthEnabled(month)); } /** Whether the given month is enabled. */ @@ -108,9 +113,12 @@ export class MdYearView implements AfterContentInit { return true; } + let firstOfMonth = this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), month, 1); + // If any date in the month is enabled count the month as enabled. - for (let date = new SimpleDate(this.activeDate.year, month, 1); date.month === month; - date = date.add({days: 1})) { + for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month; + date = this._dateAdapter.addCalendarDays(date, 1)) { if (this.dateFilter(date)) { return true; }