Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(moment-dateadapter): add option to create utc dates #11336

Merged
merged 9 commits into from
Jun 27, 2018
11 changes: 11 additions & 0 deletions src/lib/datepicker/datepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,17 @@ export class MyComponent {

<!-- example(datepicker-moment) -->

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
Expand Down
12 changes: 10 additions & 2 deletions src/material-moment-adapter/adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -21,7 +25,11 @@ 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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I found why the tests did not work. Now with the MAT_MOMENT_DATE_ADAPTER_OPTIONS in the deps all tests pass.

}
],
})
export class MomentDateModule {}
Expand Down
30 changes: 28 additions & 2 deletions src/material-moment-adapter/adapter/moment-date-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -143,7 +143,8 @@ 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([2017, JAN, 1]).format());
});

it('should not create Moment date with month over/under-flow', () => {
Expand All @@ -164,6 +165,10 @@ describe('MomentDateAdapter', () => {
expect(adapter.createDate(100, JAN, 1).year()).toBe(100);
});

it('should not create Moment date in utc format', () => {
expect(adapter.createDate(2017, JAN, 5).isUTC()).toEqual(false);
});

it("should get today's date", () => {
expect(adapter.sameDate(adapter.today(), moment()))
.toBe(true, "should be equal to today's date");
Expand Down Expand Up @@ -408,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();
});
});
37 changes: 34 additions & 3 deletions src/material-moment-adapter/adapter/moment-date-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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<MatMomentDateAdapterOptions>(
'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<T>(length: number, valueFunction: (index: number) => T): T[] {
Expand Down Expand Up @@ -48,7 +73,8 @@ export class MomentDateAdapter extends DateAdapter<Moment> {
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to make this not a breaking change for anyone who might be extending the MomentDateAdapter lets make this:

/** @deletion-target 8.0.0 make this param required */
@Optional() @Inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS) private options?: MatMomentDateAdapterOptions

super();
this.setLocale(dateLocale || moment.locale());
}
Expand Down Expand Up @@ -130,7 +156,12 @@ export class MomentDateAdapter extends DateAdapter<Moment> {
throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
}

let result = moment({year, month, date}).locale(this.locale);
let result;
if (this.options.useUtc) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.options && this.options.useUtc after making the change requested above

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()) {
Expand Down