Skip to content

Commit

Permalink
feat(moment-date-adapter): implement strict mode (#16462)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MatthiasKunnen authored and mmalerba committed Aug 6, 2019
1 parent 8658c30 commit dd42956
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 5 deletions.
35 changes: 35 additions & 0 deletions src/material-moment-adapter/adapter/moment-date-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

});
});
23 changes: 19 additions & 4 deletions src/material-moment-adapter/adapter/moment-date-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -241,7 +248,15 @@ export class MomentDateAdapter extends DateAdapter<Moment> {
}

/** 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);
}
}
17 changes: 16 additions & 1 deletion src/material/datepicker/datepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
]
})
```
Expand Down

0 comments on commit dd42956

Please sign in to comment.