Skip to content

Commit ff2e233

Browse files
feat(date-time-editor, date-range-picker, date-picker): ISO 8601 support #6994
1 parent 5d947c2 commit ff2e233

File tree

11 files changed

+275
-192
lines changed

11 files changed

+275
-192
lines changed

projects/igniteui-angular/src/lib/date-common/util/date-time.util.spec.ts

+74-68
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,20 @@ describe(`DateTimeUtil Unit tests`, () => {
5151
expect(resDict[DatePart.Year]).toEqual(jasmine.objectContaining({ start: 6, end: 10 }));
5252

5353
// TODO
54-
return;
55-
result = DateTimeUtil.parseDateTimeFormat('dd.MM.yyyyг');
56-
resDict = reduceToDictionary(result);
57-
expect(result.length).toEqual(6);
58-
expect(resDict[DatePart.Date]).toEqual(jasmine.objectContaining({ start: 0, end: 2 }));
59-
expect(resDict[DatePart.Month]).toEqual(jasmine.objectContaining({ start: 3, end: 5 }));
60-
expect(resDict[DatePart.Year]).toEqual(jasmine.objectContaining({ start: 6, end: 10 }));
61-
expect(result[5]?.format).toEqual('г');
62-
63-
result = DateTimeUtil.parseDateTimeFormat('yyyy/MM/d');
64-
resDict = reduceToDictionary(result);
65-
expect(result.length).toEqual(5);
66-
expect(resDict[DatePart.Year]).toEqual(jasmine.objectContaining({ start: 0, end: 4 }));
67-
expect(resDict[DatePart.Month]).toEqual(jasmine.objectContaining({ start: 5, end: 7 }));
68-
expect(resDict[DatePart.Date]).toEqual(jasmine.objectContaining({ start: 8, end: 10 }));
54+
// result = DateTimeUtil.parseDateTimeFormat('dd.MM.yyyyг');
55+
// resDict = reduceToDictionary(result);
56+
// expect(result.length).toEqual(6);
57+
// expect(resDict[DatePart.Date]).toEqual(jasmine.objectContaining({ start: 0, end: 2 }));
58+
// expect(resDict[DatePart.Month]).toEqual(jasmine.objectContaining({ start: 3, end: 5 }));
59+
// expect(resDict[DatePart.Year]).toEqual(jasmine.objectContaining({ start: 6, end: 10 }));
60+
// expect(result[5]?.format).toEqual('г');
61+
62+
// result = DateTimeUtil.parseDateTimeFormat('yyyy/MM/d');
63+
// resDict = reduceToDictionary(result);
64+
// expect(result.length).toEqual(5);
65+
// expect(resDict[DatePart.Year]).toEqual(jasmine.objectContaining({ start: 0, end: 4 }));
66+
// expect(resDict[DatePart.Month]).toEqual(jasmine.objectContaining({ start: 5, end: 7 }));
67+
// expect(resDict[DatePart.Date]).toEqual(jasmine.objectContaining({ start: 8, end: 10 }));
6968
});
7069

7170
it('should correctly parse boundary dates', () => {
@@ -422,30 +421,13 @@ describe(`DateTimeUtil Unit tests`, () => {
422421
expect(DateTimeUtil.greaterThanMaxValue(new Date(2010, 3, 2, 15, 15, 15), maxValue)).toBeFalse();
423422
expect(DateTimeUtil.greaterThanMaxValue(new Date(2009, 3, 2, 15, 15, 15), maxValue)).toBeFalse();
424423

425-
426424
// date excluded
427425
expect(DateTimeUtil.lessThanMinValue(new Date(2030, 3, 2, 11, 10, 9), minValue, true, false)).toBeTrue();
428426
expect(DateTimeUtil.greaterThanMaxValue(new Date(2000, 3, 2, 15, 15, 16), minValue, true, false)).toBeTrue();
429427

430428
// time excluded
431429
expect(DateTimeUtil.lessThanMinValue(new Date(2009, 3, 2, 11, 10, 10), minValue, false, true)).toBeTrue();
432430
expect(DateTimeUtil.greaterThanMaxValue(new Date(2011, 3, 2, 15, 15, 15), minValue, true, false)).toBeTrue();
433-
434-
// falsy values
435-
expect(DateTimeUtil.lessThanMinValue(new Date(NaN), new Date(NaN))).toBeFalse();
436-
expect(DateTimeUtil.greaterThanMaxValue(new Date(NaN), new Date(NaN))).toBeFalse();
437-
expect(DateTimeUtil.lessThanMinValue(new Date(NaN), null)).toBeFalse();
438-
expect(DateTimeUtil.greaterThanMaxValue(new Date(NaN), null)).toBeFalse();
439-
expect(DateTimeUtil.lessThanMinValue(null, new Date(NaN))).toBeFalse();
440-
expect(DateTimeUtil.greaterThanMaxValue(null, new Date(NaN))).toBeFalse();
441-
expect(DateTimeUtil.lessThanMinValue(new Date(NaN), undefined)).toBeFalse();
442-
expect(DateTimeUtil.greaterThanMaxValue(new Date(NaN), undefined)).toBeFalse();
443-
expect(DateTimeUtil.lessThanMinValue(undefined, new Date(NaN))).toBeFalse();
444-
expect(DateTimeUtil.greaterThanMaxValue(undefined, new Date(NaN))).toBeFalse();
445-
expect(DateTimeUtil.lessThanMinValue(new Date(NaN), new Date())).toBeFalse();
446-
expect(DateTimeUtil.greaterThanMaxValue(new Date(NaN), new Date())).toBeFalse();
447-
expect(DateTimeUtil.lessThanMinValue(new Date(), new Date(NaN))).toBeFalse();
448-
expect(DateTimeUtil.greaterThanMaxValue(new Date(), new Date(NaN))).toBeFalse();
449431
});
450432

451433
it('should return ValidationErrors for minValue and maxValue', () => {
@@ -459,46 +441,70 @@ describe(`DateTimeUtil Unit tests`, () => {
459441
minValue = new Date(2010, 3, 2, 10, 10, 10);
460442
maxValue = new Date(2010, 3, 2, 15, 15, 15);
461443

462-
// TODO: test with time portions as well
463-
return;
464-
expect(DateTimeUtil.validateMinMax(new Date(2010, 3, 2, 11, 11, 11), minValue, maxValue)).toEqual({});
444+
expect(DateTimeUtil.validateMinMax(new Date(2010, 3, 2, 10, 10, 10), minValue, maxValue)).toEqual({});
465445
expect(DateTimeUtil.validateMinMax(new Date(2010, 3, 2, 9, 11, 11), minValue, maxValue)).toEqual({ minValue: true });
466446
expect(DateTimeUtil.validateMinMax(new Date(2010, 3, 2, 16, 11, 11), minValue, maxValue)).toEqual({ maxValue: true });
447+
448+
// ignore date portion
449+
expect(DateTimeUtil.validateMinMax(new Date(2000, 0, 1, 10, 10, 10), minValue, maxValue, true, false)).toEqual({});
450+
expect(DateTimeUtil.validateMinMax(
451+
new Date(2020, 10, 10, 9, 10, 10), minValue, maxValue, true, false)).toEqual({ minValue: true });
452+
expect(DateTimeUtil.validateMinMax(
453+
new Date(2000, 0, 1, 16, 0, 0), minValue, maxValue, true, false)).toEqual({ maxValue: true });
454+
455+
// ignore time portion
456+
expect(DateTimeUtil.validateMinMax(new Date(2010, 3, 2, 9, 0, 0), minValue, maxValue, false, true)).toEqual({});
457+
expect(DateTimeUtil.validateMinMax(
458+
new Date(2009, 3, 2, 11, 11, 11), minValue, maxValue, false, true)).toEqual({ minValue: true });
459+
expect(DateTimeUtil.validateMinMax(
460+
new Date(2020, 3, 2, 0, 0, 0), minValue, maxValue, false, true)).toEqual({ maxValue: true });
467461
});
468462

469-
it('should parse dates correctly with parseDate', () => {
470-
pending('TODO: ISO implementation');
471-
// // ISO strings and numbers
472-
// expect(DateTimeUtil.parseDate('2012-12-12T12:12:12').getTime()).toEqual(new Date(2012, 11, 12, 12, 12, 12).getTime());
473-
// expect(DateTimeUtil.parseDate(new Date()).getTime()).toEqual(new Date().getTime());
474-
// expect(DateTimeUtil.parseDate(new Date().getTime()).getTime()).toEqual(new Date().getTime());
475-
476-
// // non ISO strings with mask and no dateTimeParts
477-
// let mask = 'dd/MM/yyyy HH:mm:ss';
478-
// expect(DateTimeUtil.parseDate('12/12/2012 12:12:12', null, mask)
479-
// .getTime()).toEqual(new Date(2012, 11, 12, 12, 12, 12).getTime());
480-
// mask = 'MM-dd-yyyy mm:ss';
481-
// expect(DateTimeUtil.parseDate('06/04/2012 44:12', null, mask).getTime()).toEqual(new Date(2012, 5, 4, 0, 44, 12).getTime());
482-
// mask = 'yy-dd-MM ss:HHmm';
483-
// expect(DateTimeUtil.parseDate('12/12/12 12:12:12', null, mask).getTime()).toEqual(new Date(2012, 11, 12, 12, 12, 12).getTime());
484-
// mask = 'dd///()#yy123/\\\/MM ___ ss(|::HH123456::123123 mm';
485-
// expect(DateTimeUtil.parseDate('12/12/12 12:12:12', null, mask).getTime()).toEqual(new Date(2012, 11, 12, 12, 12, 12).getTime());
486-
487-
// // non ISO strings with dateTimeParts and no mask
488-
// const dateTimeParts = DateTimeUtil.parseDateTimeFormat(mask);
489-
// expect(DateTimeUtil.parseDate('12/12/12 12:12:12', dateTimeParts).getTime())
490-
// .toEqual(new Date(2012, 11, 12, 12, 12, 12).getTime());
491-
492-
// // invalid values
493-
// expect(DateTimeUtil.parseDate(undefined)).toEqual(null);
494-
// expect(DateTimeUtil.parseDate(NaN)).toEqual(null);
495-
// expect(DateTimeUtil.parseDate([])).toEqual(null);
496-
// expect(DateTimeUtil.parseDate({})).toEqual(null);
497-
// expect(DateTimeUtil.parseDate('')).toEqual(null);
498-
// expect(DateTimeUtil.parseDate(new Date(NaN))).toEqual(null);
499-
// expect(DateTimeUtil.parseDate(null)).toBeInstanceOf(Date);
500-
// expect(DateTimeUtil.parseDate(true)).toBeInstanceOf(Date);
501-
// expect(DateTimeUtil.parseDate(false)).toBeInstanceOf(Date);
463+
it('should parse dates correctly with parseIsoDate', () => {
464+
const updateDate = (dateValue: Date, stringValue: string): Date => {
465+
const [datePart, timePart] = dateValue.toISOString().split('T');
466+
const newDate = new Date(`${datePart}T${stringValue + timePart.substr(stringValue.length, timePart.length)}`);
467+
newDate.setMilliseconds(0);
468+
return newDate;
469+
};
470+
471+
let date = new Date();
472+
date.setMilliseconds(0);
473+
// full iso string
474+
expect(DateTimeUtil.parseIsoDate(date.toISOString()).getTime()).toEqual(date.getTime());
475+
476+
// date only
477+
expect(DateTimeUtil.parseIsoDate('2012-12-10').getTime()).toEqual(new Date('2012-12-10T00:00:00').getTime());
478+
expect(DateTimeUtil.parseIsoDate('2023-13-15').getTime()).toEqual(new Date('2023-13-15T00:00:00').getTime());
479+
expect(DateTimeUtil.parseIsoDate('1524-01-02').getTime()).toEqual(new Date('1524-01-02T00:00:00').getTime());
480+
expect(DateTimeUtil.parseIsoDate('2012').getTime()).toEqual(new Date('2012-01-01T00:00:00').getTime());
481+
expect(DateTimeUtil.parseIsoDate('2012-02').getTime()).toEqual(new Date('2012-02-01T00:00:00').getTime());
482+
483+
// time only
484+
date = DateTimeUtil.parseIsoDate('12:14');
485+
date.setMilliseconds(0);
486+
expect(date.getTime()).toEqual(updateDate(new Date(), '12:14').getTime());
487+
488+
date = DateTimeUtil.parseIsoDate('15:18');
489+
date.setMilliseconds(0);
490+
expect(date.getTime()).toEqual(updateDate(new Date(), '15:18').getTime());
491+
492+
date = DateTimeUtil.parseIsoDate('06:03');
493+
date.setMilliseconds(0);
494+
expect(date.getTime()).toEqual(updateDate(new Date(), '06:03').getTime());
495+
496+
date = DateTimeUtil.parseIsoDate('00:00');
497+
date.setMilliseconds(0);
498+
expect(date.getTime()).toEqual(updateDate(new Date(), '00:00').getTime());
499+
500+
// falsy values
501+
expect(DateTimeUtil.parseIsoDate('')).toEqual(null);
502+
expect(DateTimeUtil.parseIsoDate('false')).toEqual(null);
503+
expect(DateTimeUtil.parseIsoDate('true')).toEqual(null);
504+
expect(DateTimeUtil.parseIsoDate('NaN')).toEqual(null);
505+
expect(DateTimeUtil.parseIsoDate(undefined)).toEqual(null);
506+
expect(DateTimeUtil.parseIsoDate(null)).toEqual(null);
507+
expect(DateTimeUtil.parseIsoDate(new Date().getTime().toString()).getTime()).toEqual(NaN);
502508
});
503509

504510
it('isValidDate should properly determine if a date is valid or not', () => {

projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts

+34-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { DatePart, DatePartInfo } from '../../directives/date-time-editor/date-time-editor.common';
22
import { formatDate, FormatWidth, getLocaleDateFormat } from '@angular/common';
33
import { ValidationErrors } from '@angular/forms';
4-
import { isDate, parseDate } from '../../core/utils';
4+
import { isDate } from '../../core/utils';
5+
import { MaskParsingService } from '../../directives/mask/mask-parsing.service';
56

67
/** @hidden */
78
const enum FormatDesc {
@@ -323,22 +324,47 @@ export abstract class DateTimeUtil {
323324
* @param minValue The lowest possible value that `value` can take
324325
* @param maxValue The largest possible value that `value` can take
325326
*/
326-
public static validateMinMax(value: Date, minValue: Date | string, maxValue: Date | string): ValidationErrors | null {
327+
public static validateMinMax(value: Date, minValue: Date | string, maxValue: Date | string,
328+
includeTime = true, includeDate = true): ValidationErrors {
327329
const errors = {};
328-
const min = parseDate(minValue);
329-
const max = parseDate(maxValue);
330-
if ((min && value && DateTimeUtil.lessThanMinValue(value, min, false))
331-
|| (min && value && DateTimeUtil.lessThanMinValue(value, min, false))) {
330+
const min = DateTimeUtil.isValidDate(minValue) ? minValue : DateTimeUtil.parseIsoDate(minValue);
331+
const max = DateTimeUtil.isValidDate(maxValue) ? maxValue : DateTimeUtil.parseIsoDate(maxValue);
332+
if ((min && value && DateTimeUtil.lessThanMinValue(value, min, includeTime, includeDate))
333+
|| (min && value && DateTimeUtil.lessThanMinValue(value, min, includeTime, includeDate))) {
332334
Object.assign(errors, { minValue: true });
333335
}
334-
if ((max && value && DateTimeUtil.greaterThanMaxValue(value, max, false))
335-
|| (max && value && DateTimeUtil.greaterThanMaxValue(value, max, false))) {
336+
if ((max && value && DateTimeUtil.greaterThanMaxValue(value, max, includeTime, includeDate))
337+
|| (max && value && DateTimeUtil.greaterThanMaxValue(value, max, includeTime, includeDate))) {
336338
Object.assign(errors, { maxValue: true });
337339
}
338340

339341
return errors;
340342
}
341343

344+
/** Parse an ISO string to a Date */
345+
public static parseIsoDate(value: string): Date | null {
346+
let regex = /^\d{4}/g;
347+
const timeLiteral = 'T';
348+
if (regex.test(value)) {
349+
return new Date(value + `${value.indexOf(timeLiteral) === -1 ? 'T00:00:00' : ''}`);
350+
}
351+
352+
regex = /^\d{2}/g;
353+
if (regex.test(value)) {
354+
const dateNow = new Date().toISOString();
355+
// eslint-disable-next-line prefer-const
356+
let [datePart, timePart] = dateNow.split(timeLiteral);
357+
// transform the provided value to a numeric mask
358+
// and use the mask parser to update it with the value
359+
const format = timePart.replace(/\d/g, '0');
360+
timePart = new MaskParsingService().replaceInMask(timePart, value,
361+
{ format, promptChar: '' }, 0, value.length).value;
362+
return new Date(`${datePart}T${timePart}`);
363+
}
364+
365+
return null;
366+
}
367+
342368
/**
343369
* Returns whether the input is valid date
344370
*

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IgxOverlayService, OverlayCancelableEventArgs, OverlayClosingEventArgs,
1414
import { AnimationMetadata, AnimationOptions } from '@angular/animations';
1515
import { EventEmitter, QueryList, Renderer2 } from '@angular/core';
1616

17-
fdescribe('IgxDatePicker', () => {
17+
describe('IgxDatePicker', () => {
1818
configureTestSuite();
1919
beforeAll(waitForAsync(() => {
2020
TestBed.configureTestingModule({
@@ -46,21 +46,21 @@ fdescribe('IgxDatePicker', () => {
4646
'registerOnValidatorChangeCb']);
4747
beforeEach(() => {
4848
mockFactoryResolver = {
49-
resolveComponentFactory: (c: any) => ({ // eslint-disable-line
50-
create: (i: any) => ({ // eslint-disable-line
49+
resolveComponentFactory: (c: any) => ({ // eslint-disable-line
50+
create: (i: any) => ({ // eslint-disable-line
5151
hostView: '',
5252
location: mockElementRef,
5353
changeDetectorRef: { detectChanges: () => { } },
5454
destroy: () => { }
5555
})
5656
})
5757
};
58-
ngModuleRef = ({ // eslint-disable-line
59-
injector: (...args: any[]) => { }, // eslint-disable-line
58+
ngModuleRef = ({ // eslint-disable-line
59+
injector: (...args: any[]) => { }, // eslint-disable-line
6060
componentFactoryResolver: mockFactoryResolver,
6161
instance: () => { },
6262
destroy: () => { },
63-
onDestroy: (fn: any) => { } // eslint-disable-line
63+
onDestroy: (fn: any) => { } // eslint-disable-line
6464
});
6565
mockElement = {
6666
style: { visibility: '', cursor: '', transitionDuration: '' },
@@ -70,7 +70,7 @@ fdescribe('IgxDatePicker', () => {
7070
addEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { }, // eslint-disable-line
7171
removeEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { }, // eslint-disable-line
7272
getBoundingClientRect: () => ({ width: 10, height: 10 }),
73-
insertBefore: (newChild: HTMLDivElement, refChild: Node) => { }, // eslint-disable-line
73+
insertBefore: (newChild: HTMLDivElement, refChild: Node) => { }, // eslint-disable-line
7474
contains: () => { }
7575
};
7676
mockElement.parent = mockElement;

0 commit comments

Comments
 (0)