components: DatePicker with MAT_DATE_LOCALE = en-GB parses incorrectly if the day is changed?

Bug:

With DatePicker, in the NgModule, I have the following settings:

  providers: [{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },         
  { provide: LOCALE_ID, useValue: "en-GB" },

If I input a string like “10/12/2017”, the picker will has 12 Oct 2017 selected, instead of 10 Dec 2017 since en-GB should use DD/MM/YYYY.

Others features of the DatePicker are working fine.

What are the steps to reproduce?

In the plnkr example provided at https://material.angular.io/components/datepicker/overview, in main.ts, just add settings to NgModule:

  providers: [{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },         
  { provide: LOCALE_ID, useValue: "en-GB" },

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 4.4.4, Material: Beta 12.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 5
  • Comments: 16 (2 by maintainers)

Most upvoted comments

+1 as Iuqeckr

I changed this moment date adapter a bit. This works for me:

import { DateAdapter, MatDateFormats } from '@angular/material';
import { isMoment, Moment } from 'moment';
import * as moment from 'moment';

export const MOMENT_DATE_FORMATS: MatDateFormats = {
  parse: {
    dateInput: 'D/MM/YYYY'
  },
  display: {
    dateInput: 'DD/MM/YYYY',
    monthYearLabel: 'MMMM Y',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM Y'
  }
};

const dateNames: string[] = [];
for (let date = 1; date <= 31; date++) {
  dateNames.push(String(date));
}

export class MomentDateAdapter extends DateAdapter<Moment> {

  private localeData = moment.localeData();

  getYear(date: Moment): number {
    return date.year();
  }

  getMonth(date: Moment): number {
    return date.month();
  }

  getDate(date: Moment): number {
    return date.date();
  }

  getDayOfWeek(date: Moment): number {
    return date.day();
  }

  getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
    switch (style) {
      case 'long':
        return this.localeData.months();
      case 'short':
        return this.localeData.monthsShort();
      case 'narrow':
        return this.localeData.monthsShort().map(month => month[0]);
    }
  }

  getDateNames(): string[] {
    return dateNames;
  }

  getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
    switch (style) {
      case 'long':
        return this.localeData.weekdays();
      case 'short':
        return this.localeData.weekdaysShort();
      case 'narrow':
        return this.localeData.weekdaysShort();
    }
  }

  getYearName(date: Moment): string {
    return String(date.year());
  }

  getFirstDayOfWeek(): number {
    return this.localeData.firstDayOfWeek();
  }

  getNumDaysInMonth(date: Moment): number {
    return date.daysInMonth();
  }

  clone(date: Moment): Moment {
    return date.clone();
  }

  createDate(year: number, month: number, date: number): Moment {
    return moment([year, month, date]);
  }

  today(): Moment {
    return moment();
  }

  parse(value: any, parseFormat: any): Moment {
    let m = moment(value, parseFormat, true);
    if (!m.isValid()) {
      m = moment(value);
    }
    if (m.isValid()) {
      return m;
    }
    return null;
  }

  format(date: Moment, displayFormat: any): string {
    if (date) {
      return date.format(displayFormat);
    }
    return '';
  }

  addCalendarYears(date: Moment, years: number): Moment {
    return date.clone().add(years, 'y');
  }

  addCalendarMonths(date: Moment, months: number): Moment {
    return date.clone().add(months, 'M');
  }

  addCalendarDays(date: Moment, days: number): Moment {
    return date.clone().add(days, 'd');
  }

  setLocale(locale: any): void {
    this.localeData = moment.localeData(locale);
  }

  compareDate(first: Moment, second: Moment): number {
    return first.diff(second, 'seconds', true);
  }

  sameDate(first: any | Moment, second: any | Moment): boolean {
    if (first == null) {
      return second == null;
    } else if (isMoment(first)) {
      return first.isSame(second);
    }
    return super.sameDate(first, second);
  }

  clampDate(date: Moment, min?: any | Moment, max?: any | Moment): Moment {
    if (min && date.isBefore(min)) {
      return min;
    } else if (max && date.isAfter(max)) {
      return max;
    }
    return date;
  }

  isValid(date: Moment): boolean {
    return date.isValid();
  };

  isDateInstance(obj: Object): boolean {
    return moment.isMoment(obj);
  };

  toIso8601(date: Moment): string {
    return date.format();
  };

  fromIso8601(iso8601String: string): Moment {
    return moment(iso8601String);
  };

}

And don’t forget to update main application module with providers:

{provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS},
{provide: DateAdapter, useClass: MomentDateAdapter}

I made some changes and it works for me now 😃 :

  • Add the Moment Date Adapter in your project called ‘moment-date-adapter.ts’ from https://github.com/angular/material2/blob/master/src/material-moment-adapter/adapter/moment-date-adapter.ts
  • In this adapter add a line in the “imports zone” for the locale : import 'moment/locale/fr';
  • Don’t forget to add this in your module : { provide: MAT_DATE_LOCALE, useValue: 'fr-FR' }, { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }, { provide: DateAdapter, useClass: MomentDateAdapter }, and of course import those variables with import { MomentDateAdapter, MOMENT_DATE_FORMATS } from './moment-date-adapter';

Stackblitz example : https://stackblitz.com/edit/angular-date-picker-sample-fr

you don’t have to include both MAT_DATE_LOCALE and LOCALE_ID

i also use dd/mm/yyyy format (id-ID locale) and has the same problem but the date picker toggle give me DD/MM/YYYY and it’s valid

when i try to type manually in the input element the format in input element still use MM/DD/YYYY

so 24/7/2017 give me invalid status, when typing manually but valid status when picking from the picker button.

i would like to see a fix to that please

The official material one is also published to npm now: https://www.npmjs.com/package/@angular/material-moment-adapter. Second to last example on the datepicker examples page shows it in action: https://material.angular.io/components/datepicker/examples

+1 same problem

you don’t have to include both MAT_DATE_LOCALE and LOCALE_ID

i also use dd/mm/yyyy format (id-ID locale) and has the same problem but the date picker toggle give me DD/MM/YYYY and it’s valid

when i try to type manually in the input element the format in input element still use MM/DD/YYYY

so 24/7/2017 give me invalid status, when typing manually but valid status when picking from the picker button.

I have the same issue as well. I wrote a custom adapter for it as well. When picking from the calendar, the date is valid, but entering the date manually makes it invalid

@NaifousBS sorry, I have no time right now, but I’ll try to do that ASAP.