From 1db44c31687a616a1a069f151a561132b4d62dc5 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" Date: Tue, 15 May 2018 12:36:11 +0200 Subject: [PATCH 1/8] fix(dateadapter): moment dates not in utc format Moment DateAdapter should create dates in utc format to avoid time zone collisions when passing date to servers Closes #7167 --- .../adapter/moment-date-adapter.spec.ts | 6 +++++- src/material-moment-adapter/adapter/moment-date-adapter.ts | 2 +- 2 files changed, 6 insertions(+), 2 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 feba4da338a9..8536bfe4e58f 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -143,7 +143,7 @@ describe('MomentDateAdapter', () => { }); it('should create Moment date', () => { - expect(adapter.createDate(2017, JAN, 1).format()).toEqual(moment([2017, JAN, 1]).format()); + expect(adapter.createDate(2017, JAN, 1).format()).toEqual(moment.utc([2017, JAN, 1]).format()); }); it('should not create Moment date with month over/under-flow', () => { @@ -164,6 +164,10 @@ describe('MomentDateAdapter', () => { expect(adapter.createDate(100, JAN, 1).year()).toBe(100); }); + it('should create Moment date in utc format', () => { + expect(adapter.createDate(2017, JAN, 5).isUTC()).toBeTruthy(); + }); + it("should get today's date", () => { expect(adapter.sameDate(adapter.today(), moment())) .toBe(true, "should be equal to today's date"); diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index caf36dfbf6ad..4e64a393105b 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -130,7 +130,7 @@ export class MomentDateAdapter extends DateAdapter { throw Error(`Invalid date "${date}". Date has to be greater than 0.`); } - let result = moment({year, month, date}).locale(this.locale); + let result = moment.utc({year, month, date}).locale(this.locale); // If the result isn't valid, the date must have been out of bounds for this month. if (!result.isValid()) { From e2fe907cbd901914defb6447c1766d43e6c129f5 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" Date: Tue, 15 May 2018 13:10:33 +0200 Subject: [PATCH 2/8] style(lint): break line at 100 chars --- .../adapter/moment-date-adapter.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8536bfe4e58f..9de903322f10 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -143,7 +143,8 @@ describe('MomentDateAdapter', () => { }); it('should create Moment date', () => { - expect(adapter.createDate(2017, JAN, 1).format()).toEqual(moment.utc([2017, JAN, 1]).format()); + expect(adapter.createDate(2017, JAN, 1).format()) + .toEqual(moment.utc([2017, JAN, 1]).format()); }); it('should not create Moment date with month over/under-flow', () => { From 1577506f336f0a3872889b29a90e51830c4780f8 Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Tue, 5 Jun 2018 11:18:54 +0200 Subject: [PATCH 3/8] feat(moment-dateadapter): add option to create utc dates Added MAT_MOMENT_DATE_ADAPTER_OPTIONS with the useUtc: boolean option to parse dates as utc The new option defaults to false and will not cause any breaking changes. Closes #7167 --- src/material-moment-adapter/adapter/index.ts | 9 ++++- .../adapter/moment-date-adapter.spec.ts | 29 +++++++++++++-- .../adapter/moment-date-adapter.ts | 37 +++++++++++++++++-- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/material-moment-adapter/adapter/index.ts b/src/material-moment-adapter/adapter/index.ts index 3f4e7077d4d0..f530a3c6222b 100644 --- a/src/material-moment-adapter/adapter/index.ts +++ b/src/material-moment-adapter/adapter/index.ts @@ -12,7 +12,11 @@ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS } from '@angular/material'; -import {MomentDateAdapter} from './moment-date-adapter'; +import { + MomentDateAdapter, + MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY, + MAT_MOMENT_DATE_ADAPTER_OPTIONS +} from './moment-date-adapter'; import {MAT_MOMENT_DATE_FORMATS} from './moment-date-formats'; export * from './moment-date-adapter'; @@ -21,7 +25,8 @@ export * from './moment-date-formats'; @NgModule({ providers: [ - {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]} + {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]}, + {provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useFactory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY} ], }) export class MomentDateModule {} 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 9de903322f10..5517df6da4c4 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -11,7 +11,7 @@ import {async, inject, TestBed} from '@angular/core/testing'; import {DateAdapter, DEC, FEB, JAN, MAR, MAT_DATE_LOCALE} from '@angular/material/core'; import * as moment from 'moment'; import {MomentDateModule} from './index'; -import {MomentDateAdapter} from './moment-date-adapter'; +import {MomentDateAdapter, MAT_MOMENT_DATE_ADAPTER_OPTIONS} from './moment-date-adapter'; describe('MomentDateAdapter', () => { @@ -144,7 +144,7 @@ describe('MomentDateAdapter', () => { it('should create Moment date', () => { expect(adapter.createDate(2017, JAN, 1).format()) - .toEqual(moment.utc([2017, JAN, 1]).format()); + .toEqual(moment([2017, JAN, 1]).format()); }); it('should not create Moment date with month over/under-flow', () => { @@ -165,8 +165,8 @@ describe('MomentDateAdapter', () => { expect(adapter.createDate(100, JAN, 1).year()).toBe(100); }); - it('should create Moment date in utc format', () => { - expect(adapter.createDate(2017, JAN, 5).isUTC()).toBeTruthy(); + it('should not create Moment date in utc format', () => { + expect(adapter.createDate(2017, JAN, 5).isUTC()).toEqual(false); }); it("should get today's date", () => { @@ -413,3 +413,24 @@ describe('MomentDateAdapter with LOCALE_ID override', () => { expect(adapter.format(moment([2017, JAN, 2]), 'll')).toEqual('2 janv. 2017'); }); }); + +describe('MomentDateAdapter with MAT_MOMENT_DATE_ADAPTER_OPTIONS override', () => { + let adapter: MomentDateAdapter; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MomentDateModule], + providers: [ + { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } + ] + }).compileComponents(); + })); + + beforeEach(inject([DateAdapter], (d: MomentDateAdapter) => { + adapter = d; + })); + + it('should create Moment date in utc format if option useUtc is set', () => { + expect(adapter.createDate(2017, JAN, 5).isUTC()).toBeTruthy(); + }); +}); diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index 4e64a393105b..e6efa1e4825d 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, Optional} from '@angular/core'; +import {Inject, Injectable, Optional, InjectionToken} from '@angular/core'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material'; // Depending on whether rollup is used, moment needs to be imported differently. // Since Moment.js doesn't have a default export, we normally need to import using the `* as` @@ -19,6 +19,31 @@ import {default as _rollupMoment, Moment} from 'moment'; const moment = _rollupMoment || _moment; +/** Configurable options for {@see MomentDateAdapter}. */ +export interface MatMomentDateAdapterOptions { + /** + * 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; +} + +/** InjectionToken for moment date adapter to configure options. */ +export const MAT_MOMENT_DATE_ADAPTER_OPTIONS = new InjectionToken( + 'MAT_MOMENT_DATE_ADAPTER_OPTIONS', { + providedIn: 'root', + factory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY +}); + + +/** @docs-private */ +export function MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY(): MatMomentDateAdapterOptions { + return { + useUtc: false + }; +} + /** Creates an array and fills it with values. */ function range(length: number, valueFunction: (index: number) => T): T[] { @@ -48,7 +73,8 @@ export class MomentDateAdapter extends DateAdapter { narrowDaysOfWeek: string[] }; - constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string) { + constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string, + @Inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS) private options: MatMomentDateAdapterOptions) { super(); this.setLocale(dateLocale || moment.locale()); } @@ -130,7 +156,12 @@ export class MomentDateAdapter extends DateAdapter { throw Error(`Invalid date "${date}". Date has to be greater than 0.`); } - let result = moment.utc({year, month, date}).locale(this.locale); + let result; + if (this.options.useUtc) { + result = moment.utc({ year, month, date }).locale(this.locale); + } else { + result = moment({ year, month, date }).locale(this.locale); + } // If the result isn't valid, the date must have been out of bounds for this month. if (!result.isValid()) { From d0abfc9e87b62ae9ec9ad07b4a23710eb1777b63 Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Tue, 5 Jun 2018 11:27:34 +0200 Subject: [PATCH 4/8] docs(moment-dateadapter): add docs for moment adapter options Describe how to set the MomentDateAdapter to parse dates as utc. Describe the default behaviour of the MomentDateAdapter. Add an example on how to configure MAT_MOMENT_DATA_ADAPTER_OPTIONS Closes #7167 --- src/lib/datepicker/datepicker.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/datepicker/datepicker.md b/src/lib/datepicker/datepicker.md index 0c7c08f2c537..33f4d3fd8bbf 100644 --- a/src/lib/datepicker/datepicker.md +++ b/src/lib/datepicker/datepicker.md @@ -246,6 +246,17 @@ export class MyComponent { +By default the `MomentDateAdapter` will creates dates in your time zone specific locale. You can change the default behaviour to parse dates as UTC by providing the `MAT_MOMENT_DATA_ADAPTER_OPTIONS` and setting it to `useUtc: true`. + +```ts +@NgModule({ + imports: [MatDatepickerModule, MatMomentDateModule], + providers: [ + { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } } + ] +}) +``` + It is also possible to create your own `DateAdapter` that works with any date format your app requires. This is accomplished by subclassing `DateAdapter` and providing your subclass as the `DateAdapter` implementation. You will also want to make sure that the `MAT_DATE_FORMATS` provided From 83ba2a0883ab72bd4fcbfc7d3f29ccd870303363 Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Tue, 5 Jun 2018 11:44:16 +0200 Subject: [PATCH 5/8] style(moment-dateadapter): fix lint styling --- src/material-moment-adapter/adapter/index.ts | 13 ++++++++++--- .../adapter/moment-date-adapter.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/material-moment-adapter/adapter/index.ts b/src/material-moment-adapter/adapter/index.ts index f530a3c6222b..e87996c00a50 100644 --- a/src/material-moment-adapter/adapter/index.ts +++ b/src/material-moment-adapter/adapter/index.ts @@ -14,7 +14,7 @@ import { } from '@angular/material'; import { MomentDateAdapter, - MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY, + MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from './moment-date-adapter'; import {MAT_MOMENT_DATE_FORMATS} from './moment-date-formats'; @@ -25,8 +25,15 @@ export * from './moment-date-formats'; @NgModule({ providers: [ - {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]}, - {provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useFactory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY} + { + provide: DateAdapter, + useClass: MomentDateAdapter, + deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] + }, + { + provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, + useFactory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY + } ], }) export class MomentDateModule {} diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index e6efa1e4825d..f664cb650ae4 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -21,7 +21,7 @@ const moment = _rollupMoment || _moment; /** Configurable options for {@see MomentDateAdapter}. */ export interface MatMomentDateAdapterOptions { - /** + /** * Turns the use of utc dates on or off. * Changing this will change how Angular Material components like DatePicker output dates. * {@default false} From cda28a1e9841253305d24fc1d3765849bd8fb7bc Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Wed, 6 Jun 2018 07:41:26 +0200 Subject: [PATCH 6/8] refactor(moment-dateadapter): remove module provider Only provide MAT_MOMENT_DATE_ADAPTER_OPTIONS in root --- src/material-moment-adapter/adapter/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/material-moment-adapter/adapter/index.ts b/src/material-moment-adapter/adapter/index.ts index e87996c00a50..cb6a33060137 100644 --- a/src/material-moment-adapter/adapter/index.ts +++ b/src/material-moment-adapter/adapter/index.ts @@ -29,10 +29,6 @@ export * from './moment-date-formats'; provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] - }, - { - provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, - useFactory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY } ], }) From abfb4e0cf110406bc22e2f4b11cf53b45664e495 Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Wed, 6 Jun 2018 07:41:26 +0200 Subject: [PATCH 7/8] refactor(moment-dateadapter): remove module provider Only provide MAT_MOMENT_DATE_ADAPTER_OPTIONS in root --- src/material-moment-adapter/adapter/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/material-moment-adapter/adapter/index.ts b/src/material-moment-adapter/adapter/index.ts index e87996c00a50..5add99fe0794 100644 --- a/src/material-moment-adapter/adapter/index.ts +++ b/src/material-moment-adapter/adapter/index.ts @@ -14,7 +14,6 @@ import { } from '@angular/material'; import { MomentDateAdapter, - MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from './moment-date-adapter'; import {MAT_MOMENT_DATE_FORMATS} from './moment-date-formats'; @@ -29,10 +28,6 @@ export * from './moment-date-formats'; provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] - }, - { - provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, - useFactory: MAT_MOMENT_DATE_ADAPTER_OPTIONS_FACTORY } ], }) From c7c14fb483ac3820c22aa7fd019f0a5b6cfd9565 Mon Sep 17 00:00:00 2001 From: Michael Reichenbach Date: Thu, 7 Jun 2018 08:05:21 +0200 Subject: [PATCH 8/8] refactor(material-dateadapter): make moment dateadapter options optional --- src/material-moment-adapter/adapter/moment-date-adapter.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index f664cb650ae4..96a9a64ad470 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -74,7 +74,9 @@ export class MomentDateAdapter extends DateAdapter { }; constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string, - @Inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS) private options: MatMomentDateAdapterOptions) { + @Optional() @Inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS) + private options?: MatMomentDateAdapterOptions) { + super(); this.setLocale(dateLocale || moment.locale()); } @@ -157,7 +159,7 @@ export class MomentDateAdapter extends DateAdapter { } let result; - if (this.options.useUtc) { + if (this.options && this.options.useUtc) { result = moment.utc({ year, month, date }).locale(this.locale); } else { result = moment({ year, month, date }).locale(this.locale);