From the field notes
Dutch dates with a custom Angular Material date adapter
In the same project as my date, time and datetime controls, we also wanted users to enter dates manually. Here in the Netherlands, we use DD-MM-YYYY. Typing a date shouldn’t mean switching to MM/DD/YYYY just to keep the input happy.
Angular Material’s datepicker already lets you type into its input. The part we needed to change was how it interpreted that text. It turned out to be a small custom date adapter.1
Try the working example and explore its code.
This is the same nested reactive form as before, reduced to one date control. Try typing 25-10-2026, then choose another date from the calendar. Both use the same format. CodePen’s index.ts combines the files shown below.
Teach the adapter our format
We still want JavaScript Date objects in the form, so we can extend NativeDateAdapter. We only need to change parsing and the input’s display format:
import { Injectable } from '@angular/core';
import { NativeDateAdapter } from '@angular/material/core';
@Injectable()
export class DutchDateAdapter extends NativeDateAdapter {
override parse(value: unknown): Date | null {
if (value == null || value === '') return null;
if (value instanceof Date) return this.clone(value);
if (typeof value !== 'string') return this.invalid();
const match = /^(\d{1,2})-(\d{1,2})-(\d{4})$/.exec(value.trim());
if (!match) return this.invalid();
const [, day, month, year] = match.map(Number);
const date = new Date(0);
date.setFullYear(year, month - 1, day);
date.setHours(0, 0, 0, 0);
return date.getFullYear() === year &&
date.getMonth() === month - 1 &&
date.getDate() === day
? date
: this.invalid();
}
override format(date: Date, displayFormat: object | 'dutch-date'): string {
if (displayFormat !== 'dutch-date') return super.format(date, displayFormat);
return [
String(date.getDate()).padStart(2, '0'),
String(date.getMonth() + 1).padStart(2, '0'),
String(date.getFullYear()).padStart(4, '0'),
].join('-');
}
}The parser accepts one or two digits for the day and month, and four for the year. Material calls the adapter’s format() when a valid date input loses focus, so 1-2-2026 becomes 01-02-2026 without an extra blur handler. An empty input returns null; malformed input returns an invalid Date, allowing Material to report a parse error.
The comparison after creating the date matters. JavaScript happily turns 31 February into a date in March. Checking the resulting parts rejects that overflow, including invalid leap days. Using setFullYear also avoids the constructor’s special treatment of years below 100.
Register it and add a hint
Replace the previous example’s provideNativeDateAdapter() with these providers on the app component. Keep its other component settings:
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
MAT_NATIVE_DATE_FORMATS,
} from '@angular/material/core';
import { DutchDateAdapter } from './dutch-date-adapter';
// Inside @Component({ ... })
providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'nl-NL' },
{ provide: DateAdapter, useClass: DutchDateAdapter },
{
provide: MAT_DATE_FORMATS,
useValue: {
...MAT_NATIVE_DATE_FORMATS,
display: { ...MAT_NATIVE_DATE_FORMATS.display, dateInput: 'dutch-date' },
},
},
],dutch-date is our own marker, not a built-in format string. Only the input uses it; calendar headings and accessible date labels still go through the native formatter with the Dutch locale. Our parser always uses day-month-year order, so it doesn’t need a configurable parse format. Setting the locale alone doesn’t make the native parser understand that format.
Finally, add a placeholder to the existing date input, keeping its form-control and picker bindings:
<input matInput [matDatepicker]="picker" [formControl]="date"
placeholder="DD-MM-YYYY" [required]="required"
[errorStateMatcher]="errorMatcher" (blur)="onTouched()">We added that little hint just in case. The calendar still works, and the parent form still receives a Date rather than the formatted string.
Footnotes
-
Angular Material’s date implementation and format settings (opens in a new tab) describe custom adapters and
MAT_DATE_FORMATS. ↩