Search code examples
angularunicodeangular-materialdate-fns

parsing a short year format with date-fns?


How can I use the Date-Fns Adapter for Angular Material to parse short date formats without an explicit separator? For example, 010123 => 01.01.2023. With the whole year it works as expected: 01012023 => 01.01.2023

My actual thought process would be ddMMyy but according to the Unicode Documentation:

However, "yy" requests just the two low-order digits of the year, zero-padded as necessary.

So the Unicode tokens would reference the year 0023 instead of 2023.

My question is how can I adjust this context to get this desired behavior?

Here is my CUSTOM_FORMAT:

export const CUSTOM_FORMAT: MatDateFormats = {
  parse: {
    dateInput: ["dd.MM.yyyy", "dd/MM/yyyy", "dd,MM,yyyy", "ddMMyyyy", "ddMMyy"],
  },
  display: {
    dateInput: "dd.MM.yyyy",
    monthYearLabel: "MMM yyyy",
    dateA11yLabel: "MMMM d, y",
    monthYearA11yLabel: "MMM yyyy"
  },
};

Solution

  • I fixed my problem by writing a CustomDateAdapter that overrides the default parse function of the DateFnsAdapter.

    In my CustomDateAdapter:

    override parse(value: any, parseFormat: string | string[]): Date | null {
            if (typeof value === 'string' && value.length > 0) {
                if (!parseFormat.length) {
                    throw Error('Formats array must not be empty.');
                }
    
                // Check if the input value is in the format "ddMMyy" or "d.M.yy" or "dd.MM.yy"
                const matchWithSeparator: RegExpMatchArray = value.match(/^(\d{1,2})[.\-/](\d{1,2})[.\-/](\d{2})$/);
                const matchWithoutSeparator: RegExpMatchArray = value.match(/^(\d{2})(\d{2})(\d{2})$/);
    
                // Gets first two digits of current year 
                const firstTwoDigitsOfCurrentYear: string = new Date().getFullYear().toString().slice(0,2);
    
                // Convert the matched parts to a valid date string "DD.MM.YYYY"
                if (matchWithSeparator) {
                    value = `${matchWithSeparator[1]}.${matchWithSeparator[2]}.${firstTwoDigitsOfCurrentYear}${matchWithSeparator[3]}`;
                } else if (matchWithoutSeparator) {
                    value = `${matchWithoutSeparator[1]}.${matchWithoutSeparator[2]}.${firstTwoDigitsOfCurrentYear}${matchWithoutSeparator[3]}`;
                }
    
                // General parsing logic for multiple formats declared in shared.module
                for (const currentFormat of parseFormat) {
                    const fromFormat: Date = parse(value, currentFormat, new Date(), { locale: this.locale });
    
                    if (this.isValid(fromFormat)) {
                        return fromFormat;
                    }
                }
    
                return this.invalid();
            } else if (typeof value === 'number') {
                return new Date(value);
            } else if (value instanceof Date) {
                return this.clone(value);
            }
    
            return null;
        }