From ef587f576b32e4ca3da7d155a599d155091a5063 Mon Sep 17 00:00:00 2001 From: SheenaMalhotra182 Date: Fri, 22 Sep 2023 20:46:32 +0530 Subject: [PATCH] [ACS-5857] migrated adfMomentDate Pipe to date-fns equivalent --- .../widgets/core/form-field-validator.ts | 111 ++++++++++++------ .../widgets/core/form-field.model.spec.ts | 13 +- .../widgets/core/form-field.model.ts | 43 +++++-- .../components/widgets/date/date.widget.html | 6 +- .../widgets/date/date.widget.spec.ts | 14 ++- .../components/widgets/date/date.widget.ts | 48 ++++---- lib/core/src/lib/form/public-api.ts | 1 + .../date-format-translation.service.spec.ts | 72 ++++++++++++ .../date-format-translation.service.ts | 60 ++++++++++ lib/core/src/lib/pipes/date.pipe.spec.ts | 74 ++++++++++++ lib/core/src/lib/pipes/date.pipe.ts | 30 +++++ lib/core/src/lib/pipes/pipe.module.ts | 4 + lib/core/src/lib/pipes/public-api.ts | 1 + .../widgets/date/date-cloud.widget.html | 8 +- .../widgets/date/date-cloud.widget.spec.ts | 37 +++--- .../widgets/date/date-cloud.widget.ts | 54 ++++----- .../src/lib/models/date-format-cloud.model.ts | 2 +- 17 files changed, 446 insertions(+), 132 deletions(-) create mode 100644 lib/core/src/lib/form/services/date-format-translation.service.spec.ts create mode 100644 lib/core/src/lib/form/services/date-format-translation.service.ts create mode 100644 lib/core/src/lib/pipes/date.pipe.spec.ts create mode 100644 lib/core/src/lib/pipes/date.pipe.ts diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts index cf63d348a0..7a696d019a 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts @@ -21,6 +21,7 @@ import moment from 'moment'; import { FormFieldTypes } from './form-field-types'; import { isNumberValue } from './form-field-utils'; import { FormFieldModel } from './form-field.model'; +import { format, isAfter, isBefore, isValid, parse } from 'date-fns'; export interface FormFieldValidator { @@ -145,10 +146,10 @@ export class DateFieldValidator implements FormFieldValidator { ]; // Validates that the input string is a valid date formatted as (default D-M-YYYY) - static isValidDate(inputDate: string, dateFormat: string = 'D-M-YYYY'): boolean { + static isValidDate(inputDate: string, dateFormat: string = 'd-M-yyyy'): boolean { if (inputDate) { - const d = moment(inputDate, dateFormat, true); - return d.isValid(); + const d = parseUpdate(inputDate, dateFormat); + return isValid(d); } return false; @@ -204,26 +205,26 @@ export class DateTimeFieldValidator implements FormFieldValidator { export abstract class BoundaryDateFieldValidator implements FormFieldValidator { - DATE_FORMAT_CLOUD = 'YYYY-MM-DD'; - DATE_FORMAT = 'DD-MM-YYYY'; + DATE_FORMAT_CLOUD = 'yyyy-MM-dd'; + DATE_FORMAT = 'dd-MM-yyyy'; supportedTypes = [ FormFieldTypes.DATE ]; validate(field: FormFieldModel): boolean { - let isValid = true; + let isFieldValid = true; if (this.isSupported(field) && field.value && field.isVisible) { const dateFormat = field.dateDisplayFormat; if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE'; - isValid = false; + isFieldValid = false; } else { - isValid = this.checkDate(field, dateFormat); + isFieldValid = this.checkDate(field, dateFormat); } } - return isValid; + return isFieldValid; } extractDateFormat(date: string): string { @@ -240,24 +241,24 @@ export class MinDateFieldValidator extends BoundaryDateFieldValidator { checkDate(field: FormFieldModel, dateFormat: string): boolean { - let isValid = true; + let isFieldValid = true; // remove time and timezone info let fieldValueData; if (typeof field.value === 'string') { - fieldValueData = moment(field.value.split('T')[0], dateFormat); + fieldValueData = parseUpdate(field.value.split('T')[0], dateFormat); } else { fieldValueData = field.value; } const minValueDateFormat = this.extractDateFormat(field.minValue); - const min = moment(field.minValue, minValueDateFormat); + const min = parseUpdate(field.minValue, minValueDateFormat); - if (fieldValueData.isBefore(min)) { + if (isBefore(fieldValueData, min)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`; - field.validationSummary.attributes.set('minValue', min.format(field.dateDisplayFormat).toLocaleUpperCase()); - isValid = false; + field.validationSummary.attributes.set('minValue', formatUpdate(min, field.dateDisplayFormat).toLocaleUpperCase()); + isFieldValid = false; } - return isValid; + return isFieldValid; } isSupported(field: FormFieldModel): boolean { @@ -270,24 +271,24 @@ export class MaxDateFieldValidator extends BoundaryDateFieldValidator { checkDate(field: FormFieldModel, dateFormat: string): boolean { - let isValid = true; + let isFieldValid = true; // remove time and timezone info let fieldValueData; if (typeof field.value === 'string') { - fieldValueData = moment(field.value.split('T')[0], dateFormat); + fieldValueData = parseUpdate(field.value.split('T')[0], dateFormat); } else { fieldValueData = field.value; } const maxValueDateFormat = this.extractDateFormat(field.maxValue); - const max = moment(field.maxValue, maxValueDateFormat); + const max = parseUpdate(field.maxValue, maxValueDateFormat); - if (fieldValueData.isAfter(max)) { + if (isAfter(fieldValueData, max)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`; - field.validationSummary.attributes.set('maxValue', max.format(field.dateDisplayFormat).toLocaleUpperCase()); - isValid = false; + field.validationSummary.attributes.set('maxValue', formatUpdate(max, field.dateDisplayFormat).toLocaleUpperCase()); + isFieldValid = false; } - return isValid; + return isFieldValid; } isSupported(field: FormFieldModel): boolean { @@ -309,22 +310,22 @@ export class MinDateTimeFieldValidator implements FormFieldValidator { } validate(field: FormFieldModel): boolean { - let isValid = true; + let isFieldValid = true; if (this.isSupported(field) && field.value && field.isVisible) { const dateFormat = field.dateDisplayFormat; if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE'; - isValid = false; + isFieldValid = false; } else { - isValid = this.checkDateTime(field, dateFormat); + isFieldValid = this.checkDateTime(field, dateFormat); } } - return isValid; + return isFieldValid; } private checkDateTime(field: FormFieldModel, dateFormat: string): boolean { - let isValid = true; + let isFieldValid = true; let fieldValueDate; if (typeof field.value === 'string') { fieldValueDate = moment(field.value, dateFormat); @@ -336,9 +337,9 @@ export class MinDateTimeFieldValidator implements FormFieldValidator { if (fieldValueDate.isBefore(min)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`; field.validationSummary.attributes.set('minValue', min.format(field.dateDisplayFormat).replace(':', '-')); - isValid = false; + isFieldValid = false; } - return isValid; + return isFieldValid; } } @@ -355,22 +356,22 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator { } validate(field: FormFieldModel): boolean { - let isValid = true; + let isFieldValid = true; if (this.isSupported(field) && field.value && field.isVisible) { const dateFormat = field.dateDisplayFormat; if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE'; - isValid = false; + isFieldValid = false; } else { - isValid = this.checkDateTime(field, dateFormat); + isFieldValid = this.checkDateTime(field, dateFormat); } } - return isValid; + return isFieldValid; } private checkDateTime(field: FormFieldModel, dateFormat: string): boolean { - let isValid = true; + let isFieldValid = true; let fieldValueDate; if (typeof field.value === 'string') { @@ -383,9 +384,9 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator { if (fieldValueDate.isAfter(max)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`; field.validationSummary.attributes.set('maxValue', max.format(field.dateDisplayFormat).replace(':', '-')); - isValid = false; + isFieldValid = false; } - return isValid; + return isFieldValid; } } @@ -583,3 +584,39 @@ export const FORM_FIELD_VALIDATORS = [ new MinDateTimeFieldValidator(), new MaxDateTimeFieldValidator() ]; + +export const momentToDateFnsMap = { + M: 'M', + D: 'd', + Y: 'y', + A: 'a' +}; + +export const dateFnsToMomentMap = { + M: 'M', + d: 'D', + y: 'Y', + a: 'A' +}; + +export function convertMomentToDateFnsFormat(dateDisplayFormat: string): string { + for (const [search, replace] of Object.entries(momentToDateFnsMap)) { + dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace); + } + return dateDisplayFormat; +} + +export function convertDateFnsToMomentFormat(dateDisplayFormat: string): string { + for (const [search, replace] of Object.entries(dateFnsToMomentMap)) { + dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace); + } + return dateDisplayFormat; +} + +export function formatUpdate(date: number|Date, dateFormat: string): string { + return format(date, convertMomentToDateFnsFormat(dateFormat)); +} + +export function parseUpdate(value: string, dateFormat: string, date = new Date()): Date { + return parse(value, convertMomentToDateFnsFormat(dateFormat), date); +} diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index 99efcbd8b9..0537d098a8 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -19,9 +19,16 @@ import moment from 'moment'; import { FormFieldTypes } from './form-field-types'; import { FormFieldModel } from './form-field.model'; import { FormModel } from './form.model'; +import { DateFormatTranslationService } from '../../../public-api'; describe('FormFieldModel', () => { + let dateFormatTranslationService: DateFormatTranslationService; + + beforeEach(() => { + dateFormatTranslationService = new DateFormatTranslationService(); + }); + it('should store the form reference', () => { const form = new FormModel(); const model = new FormFieldModel(form); @@ -273,9 +280,9 @@ describe('FormFieldModel', () => { dateDisplayFormat: 'DD-MM-YYYY' }); - const currentDate = moment(new Date()); - const expectedDate = moment(currentDate).format('DD-MM-YYYY'); - const expectedDateFormat = `${currentDate.format('YYYY-MM-DD')}T00:00:00.000Z`; + const currentDate = new Date(); + const expectedDate = dateFormatTranslationService.format(currentDate, 'DD-MM-YYYY'); + const expectedDateFormat = `${dateFormatTranslationService.format(currentDate, 'YYYY-MM-DD')}T00:00:00.000Z`; expect(field.value).toBe(expectedDate); expect(form.values['ddmmyyy']).toEqual(expectedDateFormat); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index dd2c0b2f95..a85fb69e8a 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -29,15 +29,20 @@ import { ProcessFormModel } from './process-form-model.interface'; import { isNumberValue } from './form-field-utils'; import { VariableConfig } from './form-field-variable-options'; import { DataColumn } from '../../../../datatable/data/data-column.model'; +import { isValid } from 'date-fns'; +import { DateFormatTranslationService } from '../../../services/date-format-translation.service'; // Maps to FormFieldRepresentation + +export const dateFormatTranslationService = new DateFormatTranslationService(); + export class FormFieldModel extends FormWidgetModel { private _value: string; private _readOnly: boolean = false; private _isValid: boolean = true; private _required: boolean = false; - readonly defaultDateFormat: string = 'D-M-YYYY'; + readonly defaultDateFormat: string = 'd-M-yyyy'; readonly defaultDateTimeFormat: string = 'D-M-YYYY hh:mm A'; // model members @@ -239,6 +244,15 @@ export class FormFieldModel extends FormWidgetModel { } private getDefaultDateFormat(jsonField: any): string { + if(jsonField.fields) { + Object.keys(jsonField.fields).forEach((el) => { + if(jsonField.fields[el]) { + jsonField.fields[el].forEach((element) => { + element.dateDisplayFormat = element.dateDisplayFormat? dateFormatTranslationService.convertMomentToDateFnsFormat(element.dateDisplayFormat): element.dateDisplayFormat; + }); + } + }); + } let originalType = jsonField.type; if (FormFieldTypes.isReadOnlyType(jsonField.type) && jsonField.params && jsonField.params.field) { originalType = jsonField.params.field.type; @@ -335,13 +349,13 @@ export class FormFieldModel extends FormWidgetModel { This is needed due to Activiti displaying/editing dates in d-M-YYYY format but storing on server in ISO8601 format (i.e. 2013-02-04T22:44:30.652Z) */ - if (this.isDateField(json) || this.isDateTimeField(json)) { + if (this.isDateTimeField(json)) { if (value) { let dateValue; if (isNumberValue(value)) { dateValue = moment(value); } else { - dateValue = this.isDateTimeField(json) ? moment.utc(value, 'YYYY-MM-DD hh:mm A') : moment.utc(value.split('T')[0], 'YYYY-M-D'); + dateValue = moment.utc(value, 'YYYY-MM-DD hh:mm A'); } if (dateValue?.isValid()) { value = dateValue.utc().format(this.dateDisplayFormat); @@ -349,6 +363,20 @@ export class FormFieldModel extends FormWidgetModel { } } + if (this.isDateField(json)) { + if (value) { + let dateValue; + if (isNumberValue(value)) { + dateValue = new Date(value); + } else { + dateValue = dateFormatTranslationService.parse(value.split('T')[0], 'YYYY-M-D'); + } + if (isValid(dateValue)) { + value = dateFormatTranslationService.format(dateValue, this.dateDisplayFormat); + } + } + } + if (this.isCheckboxField(json)) { value = json.value === 'true' || json.value === true; } @@ -417,12 +445,13 @@ export class FormFieldModel extends FormWidgetModel { } case FormFieldTypes.DATE: { if (typeof this.value === 'string' && this.value === 'today') { - this.value = moment(new Date()).format(this.dateDisplayFormat); + this.value = dateFormatTranslationService.format(new Date(), this.dateDisplayFormat); } - const dateValue = moment(this.value, this.dateDisplayFormat, true); - if (dateValue?.isValid()) { - this.form.values[this.id] = `${dateValue.format('YYYY-MM-DD')}T00:00:00.000Z`; + const dateValue = dateFormatTranslationService.parse(this.value, this.dateDisplayFormat); + + if (isValid(dateValue)) { + this.form.values[this.id] = `${dateFormatTranslationService.format(dateValue, 'YYYY-MM-DD')}T00:00:00.000Z`; } else { this.form.values[this.id] = null; this._value = this.value; diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.html b/lib/core/src/lib/form/components/widgets/date/date.widget.html index 266e80e635..309510e9af 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.html +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.html @@ -1,6 +1,6 @@
- - + { @@ -63,8 +63,9 @@ describe('DateWidgetComponent', () => { widget.ngOnInit(); - const expected = moment(minValue, widget.field.dateDisplayFormat); - expect(widget.minDate.isSame(expected)).toBeTruthy(); + const expected = parse(minValue, widget.field.dateDisplayFormat, new Date()); + const widgetDate = parse(widget.minDate, widget.field.dateDisplayFormat, new Date()); + expect(isSameDay(widgetDate, expected)).toBeTruthy(); }); it('should date field be present', () => { @@ -86,8 +87,9 @@ describe('DateWidgetComponent', () => { }); widget.ngOnInit(); - const expected = moment(maxValue, widget.field.dateDisplayFormat); - expect(widget.maxDate.isSame(expected)).toBeTruthy(); + const expected = parse(maxValue, widget.field.dateDisplayFormat, new Date()); + const widgetDate = parse(widget.maxDate, widget.field.dateDisplayFormat, new Date()); + expect(isSameDay(widgetDate, expected)).toBeTruthy(); }); it('should eval visibility on date changed', () => { @@ -101,7 +103,7 @@ describe('DateWidgetComponent', () => { readOnly: 'false' }); widget.field = field; - widget.onDateChanged({ value: moment('12/12/2012', widget.field.dateDisplayFormat) }); + widget.onDateChanged({ value: format(new Date('12/12/2012'), widget.field.dateDisplayFormat) }); expect(widget.onFieldChanged).toHaveBeenCalledWith(field); }); diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.ts b/lib/core/src/lib/form/components/widgets/date/date.widget.ts index afe9bf2109..9abe395767 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.ts +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.ts @@ -18,21 +18,25 @@ /* eslint-disable @angular-eslint/component-selector */ import { UserPreferencesService, UserPreferenceValues } from '../../../../common/services/user-preferences.service'; -import { MomentDateAdapter } from '../../../../common/utils/moment-date-adapter'; -import { MOMENT_DATE_FORMATS } from '../../../../common/utils/moment-date-formats.model'; -import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; -import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; -import moment, { Moment } from 'moment'; +import { Component, OnInit, ViewEncapsulation, OnDestroy, Inject } from '@angular/core'; +import { DateAdapter, MAT_DATE_FORMATS, MatDateFormats } from '@angular/material/core'; import { FormService } from '../../../services/form.service'; import { WidgetComponent } from '../widget.component'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { DateFnsAdapter, + MAT_DATE_FNS_FORMATS +} from '@angular/material-date-fns-adapter'; +import { DateFnsUtils } from '../../../../common/utils/date-fns-utils'; +import { isValid } from 'date-fns'; +import { DateFormatTranslationService } from '../../../services/date-format-translation.service'; @Component({ selector: 'date-widget', providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }], + { provide: DateAdapter, useClass: DateFnsAdapter }, + { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS } + ], templateUrl: './date.widget.html', styleUrls: ['./date.widget.scss'], host: { @@ -52,14 +56,16 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDe DATE_FORMAT = 'DD-MM-YYYY'; - minDate: Moment; - maxDate: Moment; + minDate: string; + maxDate: string; private onDestroy$ = new Subject(); constructor(public formService: FormService, - private dateAdapter: DateAdapter, - private userPreferencesService: UserPreferencesService) { + private dateAdapter: DateAdapter, + private userPreferencesService: UserPreferencesService, + @Inject(MAT_DATE_FORMATS) private dateFormatConfig: MatDateFormats, + protected dateFormatTranslationService: DateFormatTranslationService) { super(formService); } @@ -67,19 +73,13 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDe this.userPreferencesService .select(UserPreferenceValues.Locale) .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.dateAdapter.setLocale(locale)); + .subscribe(locale => this.dateAdapter.setLocale(DateFnsUtils.getLocaleFromString(locale))); - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; + this.dateFormatConfig.display.dateInput = this.field.dateDisplayFormat; if (this.field) { - if (this.field.minValue) { - this.minDate = moment(this.field.minValue, this.DATE_FORMAT); - } - - if (this.field.maxValue) { - this.maxDate = moment(this.field.maxValue, this.DATE_FORMAT); - } + this.minDate = isValid(this.field.minValue) ? this.dateFormatTranslationService.format(new Date(this.field.minValue), this.DATE_FORMAT) : this.field.minValue; + this.maxDate = isValid(this.field.maxValue) ? this.dateFormatTranslationService.format(new Date(this.field.maxValue), this.DATE_FORMAT) : this.field.maxValue; } } @@ -89,9 +89,9 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDe } onDateChanged(newDateValue) { - const date = moment(newDateValue, this.field.dateDisplayFormat, true); - if (date.isValid()) { - this.field.value = date.format(this.field.dateDisplayFormat); + const date = new Date(newDateValue); + if (isValid(date)) { + this.field.value = this.dateFormatTranslationService.format(date, this.field.dateDisplayFormat); } else { this.field.value = newDateValue; } diff --git a/lib/core/src/lib/form/public-api.ts b/lib/core/src/lib/form/public-api.ts index fa80753e8b..15f0303eca 100644 --- a/lib/core/src/lib/form/public-api.ts +++ b/lib/core/src/lib/form/public-api.ts @@ -26,6 +26,7 @@ export * from './services/form-rendering.service'; export * from './services/form.service'; export * from './services/form-validation-service.interface'; export * from './services/widget-visibility.service'; +export * from './services/date-format-translation.service'; export * from './events'; diff --git a/lib/core/src/lib/form/services/date-format-translation.service.spec.ts b/lib/core/src/lib/form/services/date-format-translation.service.spec.ts new file mode 100644 index 0000000000..171aa4e044 --- /dev/null +++ b/lib/core/src/lib/form/services/date-format-translation.service.spec.ts @@ -0,0 +1,72 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { DateFormatTranslationService } from './date-format-translation.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreTestingModule } from '../../testing/core.testing.module'; + +describe('DateFormatTranslationService', () => { + let service: DateFormatTranslationService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), CoreTestingModule], + providers: [DateFormatTranslationService] + }); + + service = TestBed.inject(DateFormatTranslationService); + }); + + it('should convert moment to date-fns format correctly', () => { + const momentFormat = 'YYYY-MM-DD'; + const expectedDateFnsFormat = 'yyyy-MM-dd'; + + const result = service.convertMomentToDateFnsFormat(momentFormat); + + expect(result).toBe(expectedDateFnsFormat); + }); + + it('should convert date-fns to moment format correctly', () => { + const dateFnsFormat = 'yyyy-MM-dd'; + const expectedMomentFormat = 'YYYY-MM-DD'; + + const result = service.convertDateFnsToMomentFormat(dateFnsFormat); + + expect(result).toBe(expectedMomentFormat); + }); + + it('should format a date correctly', () => { + const date = new Date('2023-09-22T12:00:00Z'); + const dateFormat = 'yyyy-MM-dd'; + const expectedFormattedDate = '2023-09-22'; + + const result = service.format(date, dateFormat); + + expect(result).toBe(expectedFormattedDate); + }); + + it('should parse a date correctly', () => { + const dateString = '2023-09-22'; + const dateFormat = 'yyyy-MM-dd'; + const expectedParsedDate = new Date('2023-09-22T00:00:00Z'); + + const result = service.parse(dateString, dateFormat); + + expect(result).toEqual(expectedParsedDate); + }); +}); diff --git a/lib/core/src/lib/form/services/date-format-translation.service.ts b/lib/core/src/lib/form/services/date-format-translation.service.ts new file mode 100644 index 0000000000..4016f96bda --- /dev/null +++ b/lib/core/src/lib/form/services/date-format-translation.service.ts @@ -0,0 +1,60 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { format, parse } from 'date-fns'; + +@Injectable({ + providedIn: 'root' +}) +export class DateFormatTranslationService { + private momentToDateFnsMap = { + M: 'M', + D: 'd', + Y: 'y', + A: 'a' + }; + + private dateFnsToMomentMap = { + M: 'M', + d: 'D', + y: 'Y', + a: 'A' + }; + + convertMomentToDateFnsFormat(dateDisplayFormat: string): string { + for (const [search, replace] of Object.entries(this.momentToDateFnsMap)) { + dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace); + } + return dateDisplayFormat; + } + + convertDateFnsToMomentFormat(dateDisplayFormat: string): string { + for (const [search, replace] of Object.entries(this.dateFnsToMomentMap)) { + dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace); + } + return dateDisplayFormat; + } + + format(date: number|Date, dateFormat: string): string { + return format(date, this.convertMomentToDateFnsFormat(dateFormat)); + } + + parse(value: string, dateFormat: string, date = new Date()): Date { + return parse(value, this.convertMomentToDateFnsFormat(dateFormat), date); + } +} diff --git a/lib/core/src/lib/pipes/date.pipe.spec.ts b/lib/core/src/lib/pipes/date.pipe.spec.ts new file mode 100644 index 0000000000..a2ccc3ed65 --- /dev/null +++ b/lib/core/src/lib/pipes/date.pipe.spec.ts @@ -0,0 +1,74 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { CoreTestingModule } from '../testing/core.testing.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { DatePipe } from './date.pipe'; +import { DateFormatTranslationService } from '../form/public-api'; + +describe('DatePipe', () => { + let datePipe: DatePipe; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), CoreTestingModule], + providers: [DatePipe, DateFormatTranslationService] + }); + datePipe = TestBed.inject(DatePipe); + }); + + it('should return the formatted date string when given a valid date object', () => { + const inputDate = new Date('2023-08-14'); + const dateFormat = 'DD-MM-YYYY'; + const expectedOutput = '14-08-2023'; + + const result = datePipe.transform(inputDate, dateFormat); + + expect(result).toBe(expectedOutput); + }); + + it('should return the input value when given an invalid date object', () => { + const inputDate = new Date('invalid'); + const dateFormat = 'DD-MM-YYYY'; + const expectedOutput = inputDate.toString(); + + const result = datePipe.transform(inputDate, dateFormat); + + expect(result).toBe(expectedOutput); + }); + + it('should return the formatted date string when given a valid date string', () => { + const inputDate = '2023-08-14'; + const dateFormat = 'DD-MM-YYYY'; + const expectedOutput = '14-08-2023'; + + const result = datePipe.transform(inputDate, dateFormat); + + expect(result).toBe(expectedOutput); + }); + + it('should return the input value when given an invalid date string', () => { + const inputDate = 'not_a_valid_date'; + const dateFormat = 'DD-MM-YYYY'; + const expectedOutput = inputDate; + + const result = datePipe.transform(inputDate, dateFormat); + + expect(result).toBe(expectedOutput); + }); +}); diff --git a/lib/core/src/lib/pipes/date.pipe.ts b/lib/core/src/lib/pipes/date.pipe.ts new file mode 100644 index 0000000000..6bb180984d --- /dev/null +++ b/lib/core/src/lib/pipes/date.pipe.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Pipe, PipeTransform } from '@angular/core'; +import { isValid } from 'date-fns'; +import { DateFormatTranslationService } from '../form/services/date-format-translation.service'; + +@Pipe({ name: 'adfDate' }) +export class DatePipe implements PipeTransform { + constructor(private dateFormatTranslationService: DateFormatTranslationService) {} + + transform(value: Date | string, dateFormat: string): string { + const date = value instanceof Date ? value : new Date(value); + return isValid(date) ? this.dateFormatTranslationService.format(date, dateFormat) : value.toString(); + } +} diff --git a/lib/core/src/lib/pipes/pipe.module.ts b/lib/core/src/lib/pipes/pipe.module.ts index 29bbf9a156..792920117d 100644 --- a/lib/core/src/lib/pipes/pipe.module.ts +++ b/lib/core/src/lib/pipes/pipe.module.ts @@ -36,6 +36,7 @@ import { MomentDateTimePipe } from './moment-datetime.pipe'; import { FilterStringPipe } from './filter-string.pipe'; import { FilterOutArrayObjectsByPropPipe } from './filter-out-every-object-by-prop.pipe'; import { DateTimePipe } from './date-time.pipe'; +import { DatePipe } from './date.pipe'; @NgModule({ imports: [ @@ -56,6 +57,7 @@ import { DateTimePipe } from './date-time.pipe'; DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, + DatePipe, MomentDateTimePipe, DateTimePipe, FilterStringPipe, @@ -74,6 +76,7 @@ import { DateTimePipe } from './date-time.pipe'; DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, + DatePipe, MomentDateTimePipe, DateTimePipe, FilterStringPipe, @@ -93,6 +96,7 @@ import { DateTimePipe } from './date-time.pipe'; DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, + DatePipe, MomentDateTimePipe, DateTimePipe, FilterStringPipe, diff --git a/lib/core/src/lib/pipes/public-api.ts b/lib/core/src/lib/pipes/public-api.ts index 8424f55653..d85dc947aa 100644 --- a/lib/core/src/lib/pipes/public-api.ts +++ b/lib/core/src/lib/pipes/public-api.ts @@ -29,6 +29,7 @@ export * from './user-initial.pipe'; export * from './localized-role.pipe'; export * from './pipe.module'; export * from './moment-date.pipe'; +export * from './date.pipe'; export * from './moment-datetime.pipe'; export * from './date-time.pipe'; export * from './filter-string.pipe'; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html index f65fb13fa0..e8fe51b499 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html @@ -1,11 +1,11 @@
-
- - + { @@ -52,8 +52,8 @@ describe('DateWidgetComponent', () => { widget.ngOnInit(); - const expected = moment(minValue, DATE_FORMAT_CLOUD); - expect(widget.minDate.isSame(expected)).toBeTruthy(); + const expected = format(new Date(minValue), DATE_FORMAT_CLOUD); + expect(widget.minDate).toEqual(expected); }); it('should date field be present', () => { @@ -75,8 +75,8 @@ describe('DateWidgetComponent', () => { }); widget.ngOnInit(); - const expected = moment(maxValue, DATE_FORMAT_CLOUD); - expect(widget.maxDate.isSame(expected)).toBeTruthy(); + const expected = format(new Date(maxValue), DATE_FORMAT_CLOUD); + expect(widget.maxDate).toEqual(expected); }); it('should eval visibility on date changed', () => { @@ -91,8 +91,8 @@ describe('DateWidgetComponent', () => { }); widget.field = field; - const todayDate = moment().format(DATE_FORMAT_CLOUD); - widget.onDateChanged({ value: todayDate }); + const todayDate = new Date(); + widget.onDateChanged({ value: format(todayDate, DATE_FORMAT_CLOUD) }); expect(widget.onFieldChanged).toHaveBeenCalledWith(field); }); @@ -293,8 +293,8 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const todayDate = moment().format(DATE_FORMAT_CLOUD); - const expected = moment(todayDate).subtract(widget.field.minDateRangeValue, 'days'); + const todayDate = new Date(); + const expected = format(subDays(todayDate, widget.field.minDateRangeValue), DATE_FORMAT_CLOUD); expect(widget.minDate).toEqual(expected); }); @@ -343,8 +343,8 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const todayDate = moment().format(DATE_FORMAT_CLOUD); - const expected = moment(todayDate).add(widget.field.maxDateRangeValue, 'days'); + const todayDate = new Date(); + const expected = format(addDays(todayDate, widget.field.maxDateRangeValue), DATE_FORMAT_CLOUD); expect(widget.maxDate).toEqual(expected); }); @@ -392,7 +392,6 @@ describe('DateWidgetComponent', () => { describe('check date validation by dynamic date ranges', () => { it('should minValue be equal to today date minus minDateRangeValue', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); widget.field = new FormFieldModel(null, { dynamicDateRangeSelection: true, maxDateRangeValue: null, @@ -404,7 +403,8 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMinValueString = '2022-07-21'; + const currentDate = new Date(); + const expectedMinValueString = format(subDays(currentDate, 1), DATE_FORMAT_CLOUD); expect(widget.field.minValue).toEqual(expectedMinValueString); expect(widget.maxDate).toBeUndefined(); @@ -412,7 +412,6 @@ describe('DateWidgetComponent', () => { }); it('should maxValue be equal to today date plus maxDateRangeValue', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); widget.field = new FormFieldModel(null, { dynamicDateRangeSelection: true, maxDateRangeValue: 8, @@ -424,7 +423,8 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMaxValueString = '2022-07-30'; + const currentDate = new Date(); + const expectedMaxValueString = format(addDays(currentDate, 8), DATE_FORMAT_CLOUD); expect(widget.field.maxValue).toEqual(expectedMaxValueString); expect(widget.minDate).toBeUndefined(); @@ -432,7 +432,6 @@ describe('DateWidgetComponent', () => { }); it('should maxValue and minValue be null if maxDateRangeValue and minDateRangeValue are null', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); widget.field = new FormFieldModel(null, { dynamicDateRangeSelection: true, maxDateRangeValue: null, @@ -451,7 +450,6 @@ describe('DateWidgetComponent', () => { }); it('should maxValue and minValue not be null if maxDateRangeVale and minDateRangeValue are not null', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); widget.field = new FormFieldModel(null, { dynamicDateRangeSelection: true, maxDateRangeValue: 8, @@ -463,8 +461,9 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMaxValueString = '2022-07-30'; - const expectedMinValueString = '2022-07-12'; + const currentDate = new Date(); + const expectedMaxValueString = format(addDays(currentDate, 8), DATE_FORMAT_CLOUD); + const expectedMinValueString = format(subDays(currentDate, 10), DATE_FORMAT_CLOUD); expect(widget.field.maxValue).toEqual(expectedMaxValueString); expect(widget.field.minValue).toEqual(expectedMinValueString); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts index 8d62144249..a8a9d292bd 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts @@ -17,22 +17,23 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; -import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; -import moment, { Moment } from 'moment'; +import { Component, OnInit, ViewEncapsulation, OnDestroy, Inject } from '@angular/core'; +import { DateAdapter, MAT_DATE_FORMATS, MatDateFormats } from '@angular/material/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { - MOMENT_DATE_FORMATS, MomentDateAdapter, WidgetComponent, - UserPreferencesService, UserPreferenceValues, FormService + WidgetComponent, + UserPreferencesService, UserPreferenceValues, FormService, DateFormatTranslationService, DateFnsUtils } from '@alfresco/adf-core'; import { DATE_FORMAT_CLOUD } from '../../../../models/date-format-cloud.model'; +import { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter'; +import { addDays, isValid, subDays } from 'date-fns'; @Component({ selector: 'date-widget', providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }], + { provide: DateAdapter, useClass: DateFnsAdapter }, + { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS }], templateUrl: './date-cloud.widget.html', styleUrls: ['./date-cloud.widget.scss'], host: { @@ -51,14 +52,16 @@ import { DATE_FORMAT_CLOUD } from '../../../../models/date-format-cloud.model'; export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, OnDestroy { typeId = 'DateCloudWidgetComponent'; - minDate: Moment; - maxDate: Moment; + minDate: string; + maxDate: string; private onDestroy$ = new Subject(); constructor(public formService: FormService, - private dateAdapter: DateAdapter, - private userPreferencesService: UserPreferencesService) { + private dateAdapter: DateAdapter, + private userPreferencesService: UserPreferencesService, + @Inject(MAT_DATE_FORMATS) private dateFormatConfig: MatDateFormats, + protected dateFormatTranslationService: DateFormatTranslationService) { super(formService); } @@ -66,47 +69,42 @@ export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, this.userPreferencesService .select(UserPreferenceValues.Locale) .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.dateAdapter.setLocale(locale)); + .subscribe(locale => this.dateAdapter.setLocale(DateFnsUtils.getLocaleFromString(locale))); - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; + this.dateFormatConfig.display.dateInput = this.field.dateDisplayFormat; if (this.field) { if (this.field.dynamicDateRangeSelection) { - const today = this.getTodaysFormattedDate(); + const today = new Date(); if (Number.isInteger(this.field.minDateRangeValue)) { - this.minDate = moment(today).subtract(this.field.minDateRangeValue, 'days'); - this.field.minValue = this.minDate.format(DATE_FORMAT_CLOUD); + this.minDate = this.dateFormatTranslationService.format(subDays(today, this.field.minDateRangeValue), DATE_FORMAT_CLOUD); + this.field.minValue = this.minDate; } if (Number.isInteger(this.field.maxDateRangeValue)) { - this.maxDate = moment(today).add(this.field.maxDateRangeValue, 'days'); - this.field.maxValue = this.maxDate.format(DATE_FORMAT_CLOUD); + this.maxDate = this.dateFormatTranslationService.format(addDays(today, this.field.maxDateRangeValue), DATE_FORMAT_CLOUD); + this.field.maxValue = this.maxDate; } } else { if (this.field.minValue) { - this.minDate = moment(this.field.minValue, DATE_FORMAT_CLOUD); + this.minDate = this.dateFormatTranslationService.format(new Date(this.field.minValue), DATE_FORMAT_CLOUD); } if (this.field.maxValue) { - this.maxDate = moment(this.field.maxValue, DATE_FORMAT_CLOUD); + this.maxDate = this.dateFormatTranslationService.format(new Date(this.field.maxValue), DATE_FORMAT_CLOUD); } } } } - getTodaysFormattedDate() { - return moment().format(DATE_FORMAT_CLOUD); - } - ngOnDestroy() { this.onDestroy$.next(true); this.onDestroy$.complete(); } onDateChanged(newDateValue) { - const date = moment(newDateValue, this.field.dateDisplayFormat, true); - if (date.isValid()) { - this.field.value = date.format(this.field.dateDisplayFormat); + const date = new Date(newDateValue); + if (isValid(date)) { + this.field.value = this.dateFormatTranslationService.format(date, this.field.dateDisplayFormat); } else { this.field.value = newDateValue; } diff --git a/lib/process-services-cloud/src/lib/models/date-format-cloud.model.ts b/lib/process-services-cloud/src/lib/models/date-format-cloud.model.ts index 786c8c316b..79f48c0033 100644 --- a/lib/process-services-cloud/src/lib/models/date-format-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/models/date-format-cloud.model.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export const DATE_FORMAT_CLOUD = 'YYYY-MM-DD'; +export const DATE_FORMAT_CLOUD = 'yyyy-MM-dd';