15

In my angular 4 project I am using some matDatepicker, It works correctly if I select a date from datepicker but if I write manually the date in the input field I see the date in an incorrect Locale.

I am following this: github, but I only see correct locale when I chose the date and not if I write the date. Is it possible that the locale is overwritten?

This is the date-adapter:

    /**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import {Inject, Injectable, Optional} from '@angular/core';
import {DateAdapter, MAT_DATE_LOCALE, MatDateFormats} from '@angular/material';
// Depending on whether rollup is used, moment needs to be imported differently.
// Since Moment.js doesn't have a default export, we normally need to import using the `* as`
// syntax. However, rollup creates a synthetic default module and we thus need to import it using
// the `default as` syntax.
// TODO(mmalerba): See if we can clean this up at some point.
import * as _moment from 'moment';
// import {default as _rollupMoment, Moment} from 'moment';
import 'moment/locale/fr';
import { Moment } from 'moment';

const moment = _moment; // _rollupMoment || _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'
  }
};

/** Creates an array and fills it with values. */
function range<T>(length: number, valueFunction: (index: number) => T): T[] {
  const valuesArray = Array(length);
  for (let i = 0; i < length; i++) {
    valuesArray[i] = valueFunction(i);
  }
  return valuesArray;
}


/** Adapts Moment.js Dates for use with Angular Material. */
@Injectable()
export class MomentDateAdapter extends DateAdapter<Moment> {
  // Note: all of the methods that accept a `Moment` input parameter immediately call `this.clone`
  // on it. This is to ensure that we're working with a `Moment` that has the correct locale setting
  // while avoiding mutating the original object passed to us. Just calling `.locale(...)` on the
  // input would mutate the object.
  private _localeData: {
    firstDayOfWeek: number,
    longMonths: string[],
    shortMonths: string[],
    dates: string[],
    longDaysOfWeek: string[],
    shortDaysOfWeek: string[],
    narrowDaysOfWeek: string[]
  };

  constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string) {
    super();
    this.setLocale(dateLocale || moment.locale());
  }

  setLocale(locale: string) {
    super.setLocale(locale);

    let momentLocaleData = moment.localeData(locale);
    this._localeData = {
      firstDayOfWeek: momentLocaleData.firstDayOfWeek(),
      longMonths: momentLocaleData.months(),
      shortMonths: momentLocaleData.monthsShort(),
      dates: range(31, (i) => this.createDate(2017, 0, i + 1).format('D')),
      longDaysOfWeek: momentLocaleData.weekdays(),
      shortDaysOfWeek: momentLocaleData.weekdaysShort(),
      narrowDaysOfWeek: momentLocaleData.weekdaysMin(),
    };
  }

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

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

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

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

  getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
    // Moment.js doesn't support narrow month names, so we just use short if narrow is requested.
    return style == 'long' ? this._localeData.longMonths : this._localeData.shortMonths;
  }

  getDateNames(): string[] {
    return this._localeData.dates;
  }

  getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
    if (style == 'long') {
      return this._localeData.longDaysOfWeek;
    }
    if (style == 'short') {
      return this._localeData.shortDaysOfWeek;
    }
    return this._localeData.narrowDaysOfWeek;
  }

  getYearName(date: Moment): string {
    return this.clone(date).format('YYYY');
  }

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

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

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

  createDate(year: number, month: number, date: number): Moment {
    // Moment.js will create an invalid date if any of the components are out of bounds, but we
    // explicitly check each case so we can throw more descriptive errors.
    if (month < 0 || month > 11) {
      throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`);
    }

    if (date < 1) {
      throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
    }

    let result = moment({year, month, date}).locale(this.locale);

    // If the result isn't valid, the date must have been out of bounds for this month.
    if (!result.isValid()) {
      throw Error(`Invalid date "${date}" for month with index "${month}".`);
    }

    return result;
  }

  today(): Moment {
    return moment().locale(this.locale);
  }

  parse(value: any, parseFormat: string | string[]): Moment | null {
    if (value && typeof value == 'string') {
      return moment(value, parseFormat, this.locale);
    }
    return value ? moment(value).locale(this.locale) : null;
  }

  format(date: Moment, displayFormat: string): string {
    date = this.clone(date);
    if (!this.isValid(date)) {
      throw Error('MomentDateAdapter: Cannot format invalid date.');
    }
    return date.format(displayFormat);
  }

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

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

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

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

  /**
   * Returns the given value if given a valid Moment or null. Deserializes valid ISO 8601 strings
   * (https://www.ietf.org/rfc/rfc3339.txt) and valid Date objects into valid Moments and empty
   * string into null. Returns an invalid date for all other values.
   */
  deserialize(value: any): Moment | null {
    let date;
    if (value instanceof Date) {
      date = moment(value);
    }
    if (typeof value === 'string') {
      if (!value) {
        return null;
      }
      date = moment(value, moment.ISO_8601).locale(this.locale);
    }
    if (date && this.isValid(date)) {
      return date;
    }
    return super.deserialize(value);
  }

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

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

  invalid(): Moment {
    return moment.invalid();
  }
}

In my app.module

import { MomentDateAdapter, MOMENT_DATE_FORMATS } from './shared/moment-date-adapter/moment-date-adapter';

{ provide: MAT_DATE_LOCALE, useValue: 'it-IT' },
{ provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS },
{ provide: DateAdapter, useClass: MomentDateAdapter },

But still not working, this is the output: datepicker

first one is selected date, second date is written

Alessandro Celeghin
  • 4,039
  • 14
  • 49
  • 95

2 Answers2

4

I don't know what you're exactly trying to do there, but you don't need such an adapter to do that ... Create this first :

export const CUSTOM_DATE_FORMAT = {
  parse: {
    dateInput: 'DD/MM/YYYY',
  },
  display: {
    dateInput: 'DD/MM/YYYY',
    monthYearLabel: 'MMMM YYYY',
    dateA11yLabel: 'DD/MM/YYYY',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};

Then, in your module (fr is mine, replace it with whatever you want) :

import { MatMomentDateModule, MomentDateAdapter } from '@angular/material-moment-adapter';


imports: [MatMomentDateModule],
providers: [
  {provide: DateAdapter, useClass: MomentDateAdapter},
  {provide: MAT_DATE_FORMATS, useValue: CUSTOM_DATE_FORMAT},
  {provide: MAT_DATE_LOCALE, useValue: 'fr-FR'}
]
  • 1
    For the Italian language do I have to use: `useValue: 'it'` ? – Alessandro Celeghin Feb 13 '18 at 09:01
  • useValue: 'it-IT' – Pterrat Feb 13 '18 at 09:04
  • 2
    as @Pterrat said. You can find **[all the codes there](https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes)** –  Feb 13 '18 at 09:10
  • Sorry but still not working, I have edited the question – Alessandro Celeghin Feb 13 '18 at 09:19
  • Did you try using **my** custom date format ? Because we have the same format (france-Italy), and it works on my side ... –  Feb 13 '18 at 09:23
  • Yes I am using your `CUSTOM_DATE_FORMAT`, is there a way to debug it? – Alessandro Celeghin Feb 13 '18 at 09:31
  • I don't know ... But here is a **[plunkr](https://stackblitz.com/edit/angular-material2-issue-hovsjw?file=app/app.module.ts)** that you can test and compare to your project –  Feb 13 '18 at 09:34
  • Ok but do I need to put this code: `{provide: DateAdapter, useClass: MomentDateAdapter}, {provide: MAT_DATE_FORMATS, useValue: CUSTOM_DATE_FORMAT}, {provide: MAT_DATE_LOCALE, useValue: 'fr-FR'}` only in principal app.module? or in every module that use datepicker? – Alessandro Celeghin Feb 13 '18 at 09:40
  • 2
    Personally I created a `SharedModule` that I import in every module, that sets up all the material stuff (like the dates). I don't know your project, but you need to **make this setup on every module using the material date picker**. –  Feb 13 '18 at 09:43
  • Ok so my problem is that I imported it only in app.module – Alessandro Celeghin Feb 13 '18 at 09:46
  • Well if your date picker is in app component, this isn't an issue ! –  Feb 13 '18 at 09:49
  • another, question, now the Mat_DATE_LOCALE is hardcoded, but if I have a multilanguage application? – Alessandro Celeghin Feb 13 '18 at 10:40
  • 1
    Inject this into your component : `private adapter: DateAdapter`, and to change the locale, use `this.adapter.setLocale('fr-FR');`. –  Feb 13 '18 at 10:42
  • But it doesn't modify only the visualization? it modifies also the input parsing? – Alessandro Celeghin Feb 13 '18 at 10:51
  • I don't know ... I took it from **[the documentation](https://material.angular.io/components/datepicker/overview#setting-the-locale-code)**, I actually never faced the case. But hey, DD/MM/YYYY is the best, keep that and don't mind changing :P –  Feb 13 '18 at 10:52
4

I think you could add this in your module or your component.

providers: [
    {provide: MAT_DATE_LOCALE, useValue: 'fr-FR'},
    {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},
    {provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS},
  ],

When you change the input manually to the current locale format, it should be normally fine.

You can find an example in the Angular Material documentation.

You could use DateAdapter if you need to switch between Italian and other locales:

  constructor(private adapter: DateAdapter<any>) {}

  switchItalian() {
    this.adapter.setLocale('it');
  }

  switchFrench() {
    this.adapter.setLocale('fr');
  }
Niels R.
  • 7,260
  • 5
  • 32
  • 44
Pterrat
  • 1,682
  • 11
  • 18