Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datepicker works only for en locale #7143

Closed
Viktor-Bredihin opened this issue Sep 18, 2017 · 19 comments
Closed

datepicker works only for en locale #7143

Viktor-Bredihin opened this issue Sep 18, 2017 · 19 comments

Comments

@Viktor-Bredihin
Copy link

Bug, feature request, or proposal:

when you check datepicker from German locale, you can see the right representation of the date, also if you select date using datepicker - works fine. But when you fill in date manually (german format dd.mm.YYYY) it's considered as invalid and expecting mm.dd.YYYY

What is the expected behavior?

when my locale is different to en, I'm expecting not only representation to be changed, but also validation logic

What is the current behavior?

You can't fill in date manually in your local format

What are the steps to reproduce?

Providing a Plunker (or similar) is the best way to get the team to see your issue.
Plunker template: https://goo.gl/DlHd6U

What is the use-case or motivation for changing an existing behavior?

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

Is there anything else we should know?

@icampo-cic
Copy link

icampo-cic commented Sep 18, 2017

I was just going to open an issue for the same thing but with es-ES locale.
Date representation is as expected with dd/mm/yyyy but if you select a day of month between 1 and 12 the next time you open the calendar it has switched to the month that indicates the first number, wich is actualy the day.

Example: We are in september so you select the 2nd of september, it will result in 02/09/2017 in es-ES locale. If you reopen the calendar you will find that it is focused on february because it takes the first number to get the month.

Another problem i have changing the locale is that when i put a ngModel to the input and i set it to null through code the input is cleaned but the calendar dont and i can't reselect the same date because it is already selected.

I provide a plunker with the example. In it you can see both of my problems. The LOCALE_ID is set to "es-ES" in the NgModule. In order to reproduce the errors yo can follow this steps:

  1. Incorrect Month when opening the calendar.
    1.1 Open the calendar and select a day between 1 and 12. (and diferent from the number of the actual month). It should result in a date with the format dd/mm/yyyy
    1.2 Reopen the calendar, it will open in the month that indicates the day number in the previous step. (if selected the day 3 it will open in march)

  2. Incorrect clean of the calendar and unable to select the same date
    2.1 Open the calendar an select a date
    2.2 Clean the input with the clean button
    2.3 Reopen the calendar an select the samen date as in 2.1 (the calendar will close an no date will be selected)

In you do this with "en" locale no one of the previous bugs happen. It is expected for the calendar to work even when the locale is changed.

Plunker: https://plnkr.co/edit/lBexYUJOFSNWJszXIfTF?p=preview

@Viktor-Bredihin
Copy link
Author

I would add third problem
3. You can't fill in date without calendar, cause it's considered as invalid date

@julianobrasil
Copy link
Contributor

julianobrasil commented Sep 18, 2017

You need to build a custom date adapter with an apropriate parse() function. You can just extend the out-of-the-box NativeDateAdapter, overriding the parse(): https://plnkr.co/edit/FUVIYx?p=preview

@Viktor-Bredihin
Copy link
Author

but your solution doesn't work for any other locales, what's the point of your documentation section 'Setting the locale code' https://material.angular.io/components/datepicker/overview#setting-the-locale-code if it doesn't work properly and user can't fill in his locale date format?

@julianobrasil
Copy link
Contributor

@Viktor-Bredihin, the reason it works like this is explained in the bottom of the section you pointed out:

Please note: MdNativeDateModule is based off of the functionality available in JavaScript's native Date object, and is thus not suitable for many locales. One of the biggest shortcomings of the native Date object is the inability to set the parse format. We highly recommend using a custom DateAdapter that works with the formatting/parsing library of your choice.

The team is in the final stages of the MomentDateAdapter that is supposed to be more suitable to define formats.

@Viktor-Bredihin
Copy link
Author

Viktor-Bredihin commented Sep 19, 2017

@julianobrasil your are right, sorry. And your solution works for me, thank you very much

@Viktor-Bredihin
Copy link
Author

it's broken after update, the error is 'Datepicker: value not recognized as a date object by DateAdapter', parse function didn't call

@icampo-cic
Copy link

icampo-cic commented Sep 21, 2017

I finally got it working by creating my own date adapter for moment, i leave it here in case it works for you too:

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

// MomentJS
// http://momentjs.com/docs/

const MONTHS = {
  'long': [
    'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre',
    'Octubre', 'Noviembre', 'Diciembre'
  ],
  'short': ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
  'narrow': ['E', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D']
};
const DAYS = {
  'long': ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
  'short': ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
  'narrow': ['D', 'L', 'M', 'X', 'J', 'V', 'S']
}

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

export class MomentDateAdapter extends DateAdapter<Moment> {

  /**
   * Gets the year component of the given date.
   * @param date The date to extract the year from.
   * @returns The year component.
   */
  getYear(date: Moment): number {
    return date.year();
  }

  /**
   * Gets the month component of the given date.
   * @param date The date to extract the month from.
   * @returns The month component (0-indexed, 0 = January).
   */
  getMonth(date: Moment): number {
    return date.month();
  }

  /**
   * Gets the date of the month component of the given date.
   * @param date The date to extract the date of the month from.
   * @returns The month component (1-indexed, 1 = first of month).
   */
  getDate(date: Moment): number {
    return date.date();
  }

  /**
   * Gets the day of the week component of the given date.
   * @param date The date to extract the day of the week from.
   * @returns The month component (0-indexed, 0 = Sunday).
   */
  getDayOfWeek(date: Moment): number {
    return date.day();
  }

  /**
   * Gets a list of names for the months.
   * @param style The naming style (e.g. long = 'January', short = 'Jan', narrow = 'J').
   * @returns An ordered list of all month names, starting with January.
   */
  getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
    return MONTHS[style];
  }

  /**
   * Gets a list of names for the dates of the month.
   * @returns An ordered list of all date of the month names, starting with '1'.
   */
  getDateNames(): string[] {
    const dateNames = [];
    for (let i = 1; i <= 31; i++) {
      dateNames.push(i.toString());
    }

    return dateNames;
  }

  /**
   * Gets a list of names for the days of the week.
   * @param style The naming style (e.g. long = 'Sunday', short = 'Sun', narrow = 'S').
   * @returns An ordered list of all weekday names, starting with Sunday.
   */
  getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
    return DAYS[style];
  }

  /**
   * Gets the name for the year of the given date.
   * @param date The date to get the year name for.
   * @returns The name of the given year (e.g. '2017').
   */
  getYearName(date: Moment): string {
    return date.year().toString();
  }

  /**
   * Gets the first day of the week.
   * @returns The first day of the week (0-indexed, 0 = Sunday).
   */
  getFirstDayOfWeek(): number {
    return 1; // Monday
  }

  /**
   * 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.
   */
  getNumDaysInMonth(date: Moment): number {
    return date.daysInMonth();
  }

  /**
   * Clones the given date.
   * @param date The date to clone
   * @returns A new date equal to the given date.
   */
  clone(date: Moment): Moment {
    return date.clone();
  }

  /**
   * 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). 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.
   */
  createDate(year: number, month: number, date: number): Moment {
    const value = year + '-' + (month + 1) + '-' + date;
    const theDate = moment(value, 'YYYY-MM-DD');
    return theDate.isValid() ? theDate : null;
  }

  /**
   * Gets today's date.
   * @returns Today's date.
   */
  today(): Moment {
    return moment();
  }

  /**
   * Parses a date from a value.
   * @param value The value to parse.
   * @param parseFormat The expected format of the value being parsed
   *     (type is implementation-dependent).
   * @returns The parsed date, or null if date could not be parsed.
   */
  parse(value: any, parseFormat: any): Moment | null {
    return moment(value, parseFormat);
  }

  /**
   * Formats a date as a string.
   * @param date The value to parse.
   * @param displayFormat The format to use to display the date as a string.
   * @returns The parsed date, or null if date could not be parsed.
   */
  format(date: Moment, displayFormat: any): string {
    if (!date.isValid()) {
      throw Error('MomentDateAdapter: Cannot format invalid date.');
    }
    return date.locale('es').format(displayFormat);
  }

  /**
   * Adds the given number of years to the date. Years are counted as if flipping 12 pages on the
   * calendar for each year 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.
   * @param date The date to add years to.
   * @param years The number of years to add (may be negative).
   * @returns A new date equal to the given one with the specified number of years added.
   */
  addCalendarYears(date: Moment, years: number): Moment {
    return date.clone().add(years, 'years');
  }
  /**
   * Adds the given number of months to the date. Months are counted as if flipping a page on the
   * calendar for each month 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.
   * @param date The date to add months to.
   * @param months The number of months to add (may be negative).
   * @returns A new date equal to the given one with the specified number of months added.
   */
  addCalendarMonths(date: Moment, months: number): Moment {
    return date.clone().add(months, 'months');
  }

  /**
   * Adds the given number of days to the date. Days are counted as if moving one cell on the
   * calendar for each day.
   * @param date The date to add days to.
   * @param days The number of days to add (may be negative).
   * @returns A new date equal to the given one with the specified number of days added.
   */
  addCalendarDays(date: Moment, days: number): Moment {
    return date.clone().add(days, 'days');
  }

  /**
   * Gets the RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339)  for the given
   * date.
   * @param date The date to get the ISO date string for.
   * @returns The ISO date string date string.
   */
  getISODateString(date: Moment): string {
    return date.toISOString();
  }

  /**
   * Checks whether the given date is valid.
   * @param date The date to check.
   * @returns Whether the date is valid.
   */
  isValid(date: Moment): boolean {
    return date.isValid();
  }

  /**
   * Checks whether the given object is considered a date instance by this DateAdapter.
   * @param obj The object to check
   * @returns Whether the object is a date instance.
   */
  isDateInstance(date: Moment): boolean {
    return moment.isMoment(date);
  }
}

@julianobrasil
Copy link
Contributor

julianobrasil commented Sep 21, 2017

@Viktor-Bredihin, how are you trying to set Datepicker value? From beta.10 on it must be a Date object (before, it could be a string).

@Viktor-Bredihin
Copy link
Author

I'm trying to set up string, right

@Viktor-Bredihin
Copy link
Author

I get it from backend

@Viktor-Bredihin
Copy link
Author

is there any way to get it works with strings? I can't parse it each time from backend and transform to date object

@julianobrasil
Copy link
Contributor

before you can use it with Datepicker, you have to turn it in a Date object (alternatively, you can build your own CustomDateAdapter from scratch)

@julianobrasil
Copy link
Contributor

Take a look at here: #7167 (comment)

It helps if you decide to build your own Date Adapter.

@Viktor-Bredihin
Copy link
Author

@julianobrasil thanks, but for now I will just use datehelper for all my models to transform backend date to js date

@josephperrott
Copy link
Member

Closing as using a dd.mm.yyyy format will require a custom date adapter. The momentjs adapter should allow for this usage.

@icampo-cic
Copy link

icampo-cic commented Sep 27, 2017

I have seen the moment adapter in the feature section on the Changelog, but i didn't find any documentation on how to set this adapter for the use. @josephperrott can you explain how i can import it and use it or give some reference to a documentation that indicates how to do it?

For the moment i am using my date adapter posted here, I have tried to import the new adapter but it always says that 'angular/material' has no exported member named 'MomentDateModule', 'MdMomentDateModule', 'MomentDateAdapter', 'MdMomentDateAdapter' or similar... (I don't know how many names i have tried.. XD)

@julianobrasil
Copy link
Contributor

@icampo1103, see the issue #7262 and the PR #7272

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants