Skip to content

Commit

Permalink
[APPS-2108] migrate form field validators to date-fns (#8988)
Browse files Browse the repository at this point in the history
* migrate test

* migrate datetime validator

* date validator

* min/max date validators

* migrate form-field validators to date-fns

* [ci:force] update docs

* form-field-model date

* field model: datetime

* migrate form field model, extra tests and fixes
  • Loading branch information
DenysVuika authored Oct 11, 2023
1 parent 2f28ec9 commit 03a52dc
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 130 deletions.
86 changes: 71 additions & 15 deletions lib/core/src/lib/common/utils/date-fns-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@
* limitations under the License.
*/

import { isValid } from 'date-fns';
import { DateFnsUtils } from './date-fns-utils';

describe('Date Format Translations', () => {
it('should convert moment to date-fns format correctly', () => {
const momentFormat = 'YYYY-MM-DD';
const expectedDateFnsFormat = 'yyyy-MM-dd';
describe('DateFnsUtils', () => {
describe('convertMomentToDateFnsFormat', () => {
it('should convert moment date format', () => {
const dateFnsFormat = DateFnsUtils.convertMomentToDateFnsFormat('YYYY-MM-DD');
expect(dateFnsFormat).toBe('yyyy-MM-dd');
});

const result = DateFnsUtils.convertMomentToDateFnsFormat(momentFormat);
it('should convert moment datetime format', () => {
const dateFnsFormat = DateFnsUtils.convertMomentToDateFnsFormat('YYYY-MM-DDTHH:mm:ssZ');
expect(dateFnsFormat).toBe(`yyyy-MM-dd'T'HH:mm:ss'Z'`);
});

expect(result).toBe(expectedDateFnsFormat);
it('should convert custom moment datetime format', () => {
const dateFnsFormat = DateFnsUtils.convertMomentToDateFnsFormat('D-M-YYYY hh:mm A');
expect(dateFnsFormat).toBe('d-M-yyyy hh:mm a');
});
});

it('should convert date-fns to moment format correctly', () => {
const dateFnsFormat = 'yyyy-MM-dd';
const expectedMomentFormat = 'YYYY-MM-DD';

const result = DateFnsUtils.convertDateFnsToMomentFormat(dateFnsFormat);

expect(result).toBe(expectedMomentFormat);
});

it('should format a date correctly', () => {
const date = new Date('2023-09-22T12:00:00Z');
Expand All @@ -46,13 +47,68 @@ describe('Date Format Translations', () => {
expect(result).toBe(expectedFormattedDate);
});

it('should parse datetime', () => {
const parsed = DateFnsUtils.parseDate(
'09-02-9999 09:10 AM',
'dd-MM-yyyy hh:mm aa'
);
expect(isValid(parsed));
expect(parsed.toISOString()).toBe('9999-02-09T09:10:00.000Z');
});

it('should format datetime with custom moment format', () => {
const parsed = DateFnsUtils.formatDate(
new Date('9999-12-30T10:30:00.000Z'),
'MM-DD-YYYY HH:mm A'
);
expect(parsed).toBe('12-30-9999 10:30 AM');
});

it('should parse moment datetime ISO', () => {
const parsed = DateFnsUtils.parseDate(
'1982-03-13T10:00:00Z',
'YYYY-MM-DDTHH:mm:ssZ'
);
expect(parsed.toISOString()).toBe('1982-03-13T10:00:00.000Z');
});

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 = DateFnsUtils.parseDate(dateString, dateFormat);

expect(result).toEqual(expectedParsedDate);
});

it('should format ISO datetime from date', () => {
const result = DateFnsUtils.formatDate(
new Date('2023-10-10T18:28:50.082Z'),
`yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`
);
expect(result).toBe('2023-10-10T18:28:50.082Z');
});

it('should format ISO datetime from string', () => {
const result = DateFnsUtils.formatDate(
'2023-10-10T18:28:50.082Z',
`yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`
);
expect(result).toBe('2023-10-10T18:28:50.082Z');
});

it('should validate datetime with moment format', () => {
const result = DateFnsUtils.isValidDate('2021-06-09 14:10', 'YYYY-MM-DD HH:mm');
expect(result).toBeTrue();
});

it('should validate datetime with date-fns format', () => {
const result = DateFnsUtils.isValidDate('2021-06-09 14:10', 'yyyy-MM-dd HH:mm');
expect(result).toBeTrue();
});

it('should not validate datetime with custom moment format', () => {
const result = DateFnsUtils.isValidDate('2021-06-09 14:10', 'D-M-YYYY hh:mm A');
expect(result).toBeFalse();
});
});
110 changes: 74 additions & 36 deletions lib/core/src/lib/common/utils/date-fns-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { format, parse, parseISO } from 'date-fns';
import { format, parse, parseISO, isValid, isBefore, isAfter } from 'date-fns';
import { ar, cs, da, de, enUS, es, fi, fr, it, ja, nb, nl, pl, ptBR, ru, sv, zhCN } from 'date-fns/locale';

export class DateFnsUtils {
Expand Down Expand Up @@ -80,24 +80,14 @@ export class DateFnsUtils {
return dateFnsLocale;
}

/**
* A mapping of Moment.js format tokens to date-fns format tokens.
*/
static momentToDateFnsMap = {
private static momentToDateFnsMap = {
D: 'd',
Y: 'y',
AZ: 'aa',
A: 'a',
ll: 'PP'
};

/**
* A mapping of date-fns format tokens to Moment.js format tokens.
*/
static dateFnsToMomentMap = {
d: 'D',
y: 'Y',
a: 'A',
PP: 'll'
ll: 'PP',
T: `'T'`,
Z: `'Z'`
};

/**
Expand All @@ -108,23 +98,12 @@ export class DateFnsUtils {
*/
static convertMomentToDateFnsFormat(dateDisplayFormat: string): string {
if (dateDisplayFormat && dateDisplayFormat.trim() !== '') {
for (const [search, replace] of Object.entries(this.momentToDateFnsMap)) {
dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace);
}
return dateDisplayFormat;
}
return '';
}
// normalise the input to support double conversion of the same string
dateDisplayFormat = dateDisplayFormat
.replace(`'T'`, 'T')
.replace(`'Z'`, 'Z');

/**
* Converts a date-fns date format string to the equivalent Moment.js format string.
*
* @param dateDisplayFormat - The date-fns date format string to convert.
* @returns The equivalent Moment.js format string.
*/
static convertDateFnsToMomentFormat(dateDisplayFormat: string): string {
if (dateDisplayFormat && dateDisplayFormat.trim() !== '') {
for (const [search, replace] of Object.entries(this.dateFnsToMomentMap)) {
for (const [search, replace] of Object.entries(this.momentToDateFnsMap)) {
dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace);
}
return dateDisplayFormat;
Expand All @@ -137,7 +116,7 @@ export class DateFnsUtils {
*
* @param date - The date to format, can be a number or a Date object.
* @param dateFormat - The date format string to use for formatting.
* @returns The formatted date as a string.
* @returns The formatted date as a string
*/
static formatDate(date: number | Date | string, dateFormat: string): string {
if (typeof date === 'string') {
Expand All @@ -149,11 +128,70 @@ export class DateFnsUtils {
/**
* Parses a date string using the specified date format.
*
* @param value - The date string to parse.
* @param value - The date value to parse. Can be a string or a Date (for generic calls)
* @param dateFormat - The date format string to use for parsing.
* @param options - Additional options
* @param options.dateOnly - Strip the time and zone
* @returns The parsed Date object.
*/
static parseDate(value: string, dateFormat: string): Date {
return parse(value, this.convertMomentToDateFnsFormat(dateFormat), new Date());
static parseDate(value: string | Date, dateFormat: string, options?: { dateOnly?: boolean }): Date {
if (value) {
if (typeof value === 'string') {
if (options?.dateOnly && value.includes('T')) {
value = value.split('T')[0];
}

return parse(value, this.convertMomentToDateFnsFormat(dateFormat), new Date());
}
return value;
}
return new Date('error');
}

/**
* Parses a datetime string using the ISO format
*
* @param value - The date and time string to parse
* @returns returns the parsed Date object
*/
static parseDateTime(value: string): Date {
return parseISO(value);
}

/**
* Checks if the date string is a valid date according to the specified format
*
* @param dateValue Date value
* @param dateFormat The date format
* @returns `true` if the date is valid, otherwise `false`
*/
static isValidDate(dateValue: string, dateFormat: string): boolean {
if (dateValue) {
const date = this.parseDate(dateValue, dateFormat);
return isValid(date);
}
return false;
}

/**
* Validates a date is before another one
*
* @param source source date to compare
* @param target target date to compare
* @returns `true` if the source date is before the target one, otherwise `false`
*/
static isBeforeDate(source: Date, target: Date): boolean {
return isBefore(source, target);
}

/**
* Validates a date is after another one
*
* @param source source date to compare
* @param target target date to compare
* @returns `true` if the source date is after the target one, otherwise `false`
*/
static isAfterDate(source: Date, target: Date): boolean {
return isAfter(source, target);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1113,9 +1113,21 @@ describe('FormFieldValidator', () => {
it('should validate dateTime format with default format', () => {
const field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.DATETIME,
value: '2021-06-09 14:10'
value: '9-6-2021 11:10 AM'
});
expect(validator.validate(field)).toBeTruthy();
expect(field.value).toBe('9-6-2021 11:10 AM');
expect(field.dateDisplayFormat).toBe('D-M-YYYY hh:mm A');
expect(validator.validate(field)).toBeTrue();
});

it('should not validate dateTime format with default format', () => {
const field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.DATETIME,
value: '2021-06-09 14:10' // 14:10 does not conform to A
});
expect(field.value).toBe('2021-06-09 14:10');
expect(field.dateDisplayFormat).toBe('D-M-YYYY hh:mm A');
expect(validator.validate(field)).toBeFalse();
});
});
});
Loading

0 comments on commit 03a52dc

Please sign in to comment.