diff --git a/src/lib/dl-date-time-picker/dl-date-adapter-string.ts b/src/lib/dl-date-time-picker/dl-date-adapter-string.ts new file mode 100644 index 00000000..60079d5e --- /dev/null +++ b/src/lib/dl-date-time-picker/dl-date-adapter-string.ts @@ -0,0 +1,72 @@ +import {DlDateAdapter} from './dl-date-adapter'; +import * as _moment from 'moment'; +import {Inject, InjectionToken} from '@angular/core'; + +/** + * Work around for moment namespace conflict when used with webpack and rollup. + * See https://github.com/dherges/ng-packagr/issues/163 + * + * 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`syntax. + * + * rollup creates a synthetic default module and we thus need to import it using + * the `default as` syntax. + * + * @internal + * + **/ +const moment = _moment; + + +/** + * InjectionToken for string dates that can be used to override default output format. + **/ +export const DL_STRING_DATE_OUTPUT_FORMAT = new InjectionToken('DL_STRING_DATE_OUTPUT_FORMAT'); + +/** + * InjectionToken for string dates that can be used to override default input formats. + **/ +export const DL_STRING_DATE_INPUT_FORMATS = new InjectionToken('DL_STRING_DATE_INPUT_FORMATS'); + +/** + * Adapts `string` to be usable as a date by date/time components that work with dates. + **/ +export class DlDateAdapterString extends DlDateAdapter { + + private readonly modelFormat: string; + private readonly inputFormats: string[]; + + constructor(@Inject(DL_STRING_DATE_INPUT_FORMATS) inputFormats: string[], + @Inject(DL_STRING_DATE_OUTPUT_FORMAT) modelFormat: string) { + super(); + this.inputFormats = inputFormats; + this.modelFormat = modelFormat; + } + + /** + * Returns the specified number. + * @param milliseconds + * a moment time time. + * @returns + * the specified moment in time. + */ + fromMilliseconds(milliseconds: number): string { + return moment(milliseconds).format(this.modelFormat); + } + + /** + * Returns the specified number. + * @param value + * a moment time time or `null` + * @returns + * the milliseconds for the specified value or `null` + * `null` is returned when value is not a valid input date string + */ + toMilliseconds(value: string | null): number | null { + if (value !== undefined && value !== null) { + const newMoment = moment(value, this.inputFormats, true); + return newMoment.isValid() ? newMoment.valueOf() : undefined; + } + } +} diff --git a/src/lib/dl-date-time-picker/dl-date-time-picker.component.md b/src/lib/dl-date-time-picker/dl-date-time-picker.component.md index 385324c3..e21cc75e 100644 --- a/src/lib/dl-date-time-picker/dl-date-time-picker.component.md +++ b/src/lib/dl-date-time-picker/dl-date-time-picker.component.md @@ -15,17 +15,44 @@ The following keyboard shortcuts are supported in in all views: | `PAGE_DOWN` | Go to the same cell in the next time period | | `ENTER` or `SPACE` | Select current cell | -## Date Adapter +## Supported date types + +Import the module corresponding to the desired data type of the date in the model. +* Native JavaScript Date: import 'DlDateTimePickerDateModule' +* Moment Date: import 'DlDateTimePickerMomentModule' +* Milliseconds (local): import 'DlDateTimePickerNumberModule' +* String (local): import 'DlDateTimePickerStringModule' A `DateAdapter` is used to adapt the data type in the model to the `number` data type used internally by the date/time picker. - + If you need a different data type than what is currently supported, you can extend `DlDateAdapter` to provide the data type you need then override the `DlDateAdapter` provider in `app.module.ts` to use your new class. `providers: [{provide: DlDateAdapter, useClass: MyCustomDateAdapter}],` +### String Date Adapter Formats +The input and output formats for dates are injected into the `DlDateAdapterString` class +using the `DL_STRING_DATE_INPUT_FORMATS` and `DL_STRING_DATE_OUTPUT_FORMAT` tokens. + +`DL_STRING_DATE_OUTPUT_FORMAT` defaults to `moment`'s `lll` long date format. + +`DL_STRING_DATE_INPUT_FORMATS` defaults to the following: +```typescript +[ + moment.localeData().longDateFormat('lll'), + 'YYYY-MM-DDTHH:mm', + 'YYYY-MM-DDTHH:mm:ss', + 'YYYY-MM-DDTHH:mm:ss.SSS', + 'YYYY-MM-DD', + 'YYYY-MM-DDTHH:mm:ss.SSS[Z]' // ISO_8601 +] +``` + +If you want different formats, override the injection tokens in `app.module.ts` +i.e `{provide: DL_STRING_DATE_OUTPUT_FORMAT, useValue: ''}` + ## Model Provider `ModelProvider`s are used to create the `Model` for each of the `views` in the date/time picker. diff --git a/src/lib/dl-date-time-picker/dl-date-time-picker.component.ts b/src/lib/dl-date-time-picker/dl-date-time-picker.component.ts index fee370d9..13015cc3 100644 --- a/src/lib/dl-date-time-picker/dl-date-time-picker.component.ts +++ b/src/lib/dl-date-time-picker/dl-date-time-picker.component.ts @@ -455,7 +455,7 @@ export class DlDateTimePickerComponent implements OnChanges, OnInit, ControlV **/ private getStartDate() { if (hasValue(this._value)) { - return this._value; + return this._dateAdapter.toMilliseconds(this._value); } if (hasValue(this.startDate)) { return this.startDate; diff --git a/src/lib/dl-date-time-picker/dl-date-time-picker.module.ts b/src/lib/dl-date-time-picker/dl-date-time-picker.module.ts index 7f9c5302..2ba6441e 100644 --- a/src/lib/dl-date-time-picker/dl-date-time-picker.module.ts +++ b/src/lib/dl-date-time-picker/dl-date-time-picker.module.ts @@ -19,6 +19,25 @@ import {DlMonthModelProvider} from './dl-model-provider-month'; import {DlDayModelProvider} from './dl-model-provider-day'; import {DlHourModelProvider} from './dl-model-provider-hour'; import {DlMinuteModelProvider} from './dl-model-provider-minute'; +import {DL_STRING_DATE_INPUT_FORMATS, DL_STRING_DATE_OUTPUT_FORMAT, DlDateAdapterString} from './dl-date-adapter-string'; + +import * as _moment from 'moment'; + +/** + * Work around for moment namespace conflict when used with webpack and rollup. + * See https://github.com/dherges/ng-packagr/issues/163 + * + * 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`syntax. + * + * rollup creates a synthetic default module and we thus need to import it using + * the `default as` syntax. + * + * @internal + * + **/ +const moment = _moment; /** * Import this module to supply your own `DateAdapter` provider. @@ -72,3 +91,28 @@ export class DlDateTimePickerDateModule { }) export class DlDateTimePickerMomentModule { } + +/** + * Import this module to store a `string` in the model. + */ +@NgModule({ + imports: [DlDateTimePickerModule], + exports: [DlDateTimePickerComponent], + providers: [ + { + provide: DL_STRING_DATE_INPUT_FORMATS, useValue: [ + moment.localeData().longDateFormat('lll'), + 'YYYY-MM-DDTHH:mm', + 'YYYY-MM-DDTHH:mm:ss', + 'YYYY-MM-DDTHH:mm:ss.SSS', + 'YYYY-MM-DD', + moment.ISO_8601 + ] + }, + {provide: DL_STRING_DATE_OUTPUT_FORMAT, useValue: moment.localeData().longDateFormat('lll')}, + {provide: DlDateAdapter, useClass: DlDateAdapterString} + ], +}) +export class DlDateTimePickerStringModule { +} + diff --git a/src/lib/dl-date-time-picker/specs/date-adapter/date-adapter-string.spec.ts b/src/lib/dl-date-time-picker/specs/date-adapter/date-adapter-string.spec.ts new file mode 100644 index 00000000..2f882d18 --- /dev/null +++ b/src/lib/dl-date-time-picker/specs/date-adapter/date-adapter-string.spec.ts @@ -0,0 +1,68 @@ +import {DlDateAdapterString} from '../../dl-date-adapter-string'; +import {DlDateAdapter} from '../../dl-date-adapter'; + +import * as _moment from 'moment'; +import {Moment} from 'moment'; + +/** + * Work around for moment namespace conflict when used with webpack and rollup. + * See https://github.com/dherges/ng-packagr/issues/163 + * + * 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`syntax. + * + * rollup creates a synthetic default module and we thus need to import it using + * the `default as` syntax. + * + * @internal + * + **/ +const moment = _moment; + +/** + * @license + * Copyright 2013-present Dale Lotts All Rights Reserved. + * http://www.dalelotts.com + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE + */ + + + +describe('DlDateAdapterString', () => { + + describe('default configuration', () => { + let dateAdapter: DlDateAdapterString; + let testMoment: Moment; + beforeEach(() => { + testMoment = moment(1523077200000); + dateAdapter = new DlDateAdapterString([ + moment.localeData().longDateFormat('lll'), + 'YYYY-MM-DDTHH:mm', + 'YYYY-MM-DDTHH:mm:ss', + 'YYYY-MM-DDTHH:mm:ss.SSS', + 'YYYY-MM-DD', + 'YYYY-MM-DDTHH:mm:ss.SSS[Z]' // ISO_8601 + ], + moment.localeData().longDateFormat('lll') + ); + }); + + describe('fromMilliseconds', () => { + it('should return "lll" moment format', () => { + expect(dateAdapter.fromMilliseconds(1523077200000)).toEqual(testMoment.format('lll')); + }); + }); + + describe('toMilliseconds', () => { + it('should accept "lll" moment format', () => { + expect(dateAdapter.toMilliseconds(testMoment.format('lll'))).toEqual(1523077200000); + }); + it('should return undefined for invalid date value', () => { + expect(dateAdapter.toMilliseconds('Aor 7, 2018 12:00 AM')).toBeUndefined(); + }); + }); + }); +}); diff --git a/src/lib/dl-date-time-picker/specs/model-type/model-type-string.spec.ts b/src/lib/dl-date-time-picker/specs/model-type/model-type-string.spec.ts new file mode 100644 index 00000000..b9be4d2a --- /dev/null +++ b/src/lib/dl-date-time-picker/specs/model-type/model-type-string.spec.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2013-present Dale Lotts All Rights Reserved. + * http://www.dalelotts.com + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE + */ + +import {DlDateTimePickerComponent} from '../../dl-date-time-picker.component'; +import {Component, DebugElement, ViewChild} from '@angular/core'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {DlDateTimePickerStringModule} from '../../index'; + + +@Component({ + template: '' +}) +class ModelTypeComponent { + @ViewChild(DlDateTimePickerComponent) picker: DlDateTimePickerComponent; +} + +describe('DlDateTimePickerComponent modelType', () => { + + beforeEach(async(() => { + return TestBed.configureTestingModule({ + imports: [ + DlDateTimePickerStringModule + ], + declarations: [ + ModelTypeComponent, + ] + }) + .compileComponents(); + })); + + describe('string formatted Date', () => { + let component: ModelTypeComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + let nativeElement: any; + + beforeEach(async(() => { + fixture = TestBed.createComponent(ModelTypeComponent); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + nativeElement = debugElement.nativeElement; + }); + })); + + it('should be string type', () => { + const nowElement = fixture.debugElement.query(By.css('.dl-abdtp-now')); + nowElement.nativeElement.click(); + + expect(component.picker.value).toEqual(jasmine.any(String)); + }); + }); +});