From dd4295608a9763518cd40b43b94059cc9c606fb4 Mon Sep 17 00:00:00 2001 From: Matthias Kunnen Date: Tue, 6 Aug 2019 21:46:50 +0200 Subject: [PATCH] feat(moment-date-adapter): implement strict mode (#16462) * feat(moment-date-adapter): implement strict mode Moment supports a strict flag which solves a lot of parsing errors. Examples: | Input | Format | Forgiving | Strict | |-----------------|--------------|------------|--------------| | 1/2/2017 | M/D/YYYY | 2017-01-02 | 2017-01-02 | | 1/2/2017 | MM/DD/YYYY | 2017-01-02 | Invalid date | | Januari 1, 2017 | MMMM D, YYYY | 2017-01-01 | Invalid date | | Jan 1, 2017 | MMMM D, YYYY | 2017-01-01 | Invalid date | | 2017-1-1 | MMMM D, YYYY | 2017-01-20 | Invalid date | * docs(datepicker): remove spaces in useUtc example * refactor(moment-date-adapter): change toEqual -> ToBe in strict mode boolean tests * refactor(moment-date-adapter): destructure options useUtc has been made optional to prevent the necessity for default values. * docs(datepicker): remove spaces around strict example braces * docs(datepicker): add links to Moment's strict/forgiving mode --- .../adapter/moment-date-adapter.spec.ts | 35 +++++++++++++++++++ .../adapter/moment-date-adapter.ts | 23 +++++++++--- src/material/datepicker/datepicker.md | 17 ++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts index 237f0e28b270..41a97f3a6dde 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -463,4 +463,39 @@ describe('MomentDateAdapter with MAT_MOMENT_DATE_ADAPTER_OPTIONS override', () = }); }); + describe('strict mode', () => { + + beforeEach(async(() => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [MomentDateModule], + providers: [ + { + provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, + useValue: { + strict: true, + }, + }, + ] + }).compileComponents(); + })); + + beforeEach(inject([DateAdapter], (d: MomentDateAdapter) => { + adapter = d; + })); + + it('should detect valid strings according to given format', () => { + expect(adapter.parse('1/2/2017', 'D/M/YYYY')!.format('l')) + .toEqual(moment([2017, FEB, 1]).format('l')); + expect(adapter.parse('February 1, 2017', 'MMMM D, YYYY')!.format('l')) + .toEqual(moment([2017, FEB, 1]).format('l')); + }); + + it('should detect invalid strings according to given format', () => { + expect(adapter.parse('2017-01-01', 'MM/DD/YYYY')!.isValid()).toBe(false); + expect(adapter.parse('1/2/2017', 'MM/DD/YYYY')!.isValid()).toBe(false); + expect(adapter.parse('Jan 5, 2017', 'MMMM D, YYYY')!.isValid()).toBe(false); + }); + + }); }); diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index 12a35fb1ac45..75b8a302b46b 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -15,18 +15,25 @@ import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; // TODO(mmalerba): See if we can clean this up at some point. import * as _moment from 'moment'; // tslint:disable-next-line:no-duplicate-imports -import {default as _rollupMoment, Moment} from 'moment'; +import {default as _rollupMoment, Moment, MomentFormatSpecification, MomentInput} from 'moment'; const moment = _rollupMoment || _moment; /** Configurable options for {@see MomentDateAdapter}. */ export interface MatMomentDateAdapterOptions { + + /** + * When enabled, the dates have to match the format exactly. + * See https://momentjs.com/guides/#/parsing/strict-mode/. + */ + strict?: boolean; + /** * Turns the use of utc dates on or off. * Changing this will change how Angular Material components like DatePicker output dates. * {@default false} */ - useUtc: boolean; + useUtc?: boolean; } /** InjectionToken for moment date adapter to configure options. */ @@ -241,7 +248,15 @@ export class MomentDateAdapter extends DateAdapter { } /** Creates a Moment instance while respecting the current UTC settings. */ - private _createMoment(...args: any[]): Moment { - return (this._options && this._options.useUtc) ? moment.utc(...args) : moment(...args); + private _createMoment( + date: MomentInput, + format?: MomentFormatSpecification, + locale?: string, + ): Moment { + const {strict, useUtc}: MatMomentDateAdapterOptions = this._options || {}; + + return useUtc + ? moment.utc(date, format, locale, strict) + : moment(date, format, locale, strict); } } diff --git a/src/material/datepicker/datepicker.md b/src/material/datepicker/datepicker.md index 71d69d90fc47..f3871c3460c3 100644 --- a/src/material/datepicker/datepicker.md +++ b/src/material/datepicker/datepicker.md @@ -252,7 +252,22 @@ By default the `MomentDateAdapter` will creates dates in your time zone specific @NgModule({ imports: [MatDatepickerModule, MatMomentDateModule], providers: [ - { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } + {provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: {useUtc: true}} + ] +}) +``` + +By default the `MomentDateAdapter` will parse dates in a +[forgiving way](https://momentjs.com/guides/#/parsing/forgiving-mode/). This may result in dates +being parsed incorrectly. You can change the default behaviour to +[parse dates strictly](https://momentjs.com/guides/#/parsing/strict-mode/) by providing +the `MAT_MOMENT_DATE_ADAPTER_OPTIONS` and setting it to `strict: true`. + +```ts +@NgModule({ + imports: [MatDatepickerModule, MatMomentDateModule], + providers: [ + {provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: {strict: true}} ] }) ```