Skip to content

Commit d40deb2

Browse files
feat(date-time-editor): implementation #6271
1 parent ff0f4d7 commit d40deb2

16 files changed

+1201
-35
lines changed

projects/igniteui-angular/src/lib/core/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ export const enum KEYS {
167167
DOWN_ARROW = 'ArrowDown',
168168
DOWN_ARROW_IE = 'Down',
169169
F2 = 'F2',
170-
TAB = 'Tab'
170+
TAB = 'Tab',
171+
SEMICOLON = ';'
171172
}
172173

173174
/**

projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts

+296-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isIE } from '../core/utils';
2+
import { DatePart, DatePartInfo } from '../directives/date-time-editor/date-time-editor.common';
23

34
/**
45
* This enum is used to keep the date validation result.
@@ -18,6 +19,11 @@ const enum FormatDesc {
1819
TwoDigits = '2-digit'
1920
}
2021

22+
export interface DateTimeValue {
23+
state: DateState;
24+
value: Date;
25+
}
26+
2127
/**
2228
*@hidden
2329
*/
@@ -27,6 +33,9 @@ const enum DateChars {
2733
DayChar = 'd'
2834
}
2935

36+
const TimeCharsArr = ['h', 'H', 'm', 's', 'S', 't', 'T'];
37+
const DateCharsArr = ['d', 'D', 'M', 'y', 'Y'];
38+
3039
/**
3140
*@hidden
3241
*/
@@ -37,7 +46,7 @@ const enum DateParts {
3746
}
3847

3948
/**
40-
*@hidden
49+
* @hidden
4150
*/
4251
export abstract class DatePickerUtil {
4352
private static readonly SHORT_DATE_MASK = 'MM/dd/yy';
@@ -46,6 +55,282 @@ export abstract class DatePickerUtil {
4655
private static readonly PROMPT_CHAR = '_';
4756
private static readonly DEFAULT_LOCALE = 'en';
4857

58+
public static parseDateTimeArray(dateTimeParts: DatePartInfo[], inputData: string): DateTimeValue {
59+
const parts: { [key in DatePart]: number } = {} as any;
60+
dateTimeParts.forEach(dp => {
61+
let value = parseInt(this.getCleanVal(inputData, dp), 10);
62+
if (!value) {
63+
value = dp.type === DatePart.Date || dp.type === DatePart.Month ? 1 : 0;
64+
}
65+
parts[dp.type] = value;
66+
});
67+
68+
if (parts[DatePart.Month] < 1 || 12 < parts[DatePart.Month]) {
69+
return { state: DateState.Invalid, value: new Date(NaN) };
70+
}
71+
72+
// TODO: Century threshold
73+
if (parts[DatePart.Year] < 50) {
74+
parts[DatePart.Year] += 2000;
75+
}
76+
77+
if (parts[DatePart.Date] > DatePickerUtil.daysInMonth(parts[DatePart.Year], parts[DatePart.Month])) {
78+
return { state: DateState.Invalid, value: new Date(NaN) };
79+
}
80+
81+
if (parts[DatePart.Hours] > 23 || parts[DatePart.Minutes] > 59 || parts[DatePart.Seconds] > 59) {
82+
return { state: DateState.Invalid, value: new Date(NaN) };
83+
}
84+
85+
return {
86+
state: DateState.Valid,
87+
value: new Date(
88+
parts[DatePart.Year] || 2000,
89+
parts[DatePart.Month] - 1 || 0,
90+
parts[DatePart.Date] || 1,
91+
parts[DatePart.Hours] || 0,
92+
parts[DatePart.Minutes] || 0,
93+
parts[DatePart.Seconds] || 0
94+
)
95+
};
96+
}
97+
98+
public static parseDateTimeFormat(mask: string, locale: string = DatePickerUtil.DEFAULT_LOCALE): DatePartInfo[] {
99+
let format = DatePickerUtil.setInputFormat(mask);
100+
let dateTimeData: DatePartInfo[] = [];
101+
if (!format && !isIE()) {
102+
dateTimeData = DatePickerUtil.getDefaultLocaleMask(locale);
103+
} else {
104+
format = (format) ? format : DatePickerUtil.SHORT_DATE_MASK;
105+
const formatArray = Array.from(format);
106+
for (let i = 0; i < formatArray.length; i++) {
107+
const datePartRange = this.getDatePartInfoRange(formatArray[i], format, i);
108+
const dateTimeInfo = {
109+
type: DatePickerUtil.determineDatePart(formatArray[i]),
110+
start: datePartRange.start,
111+
end: datePartRange.end,
112+
format: mask.match(new RegExp(`${format[i]}+`, 'g'))[0],
113+
};
114+
while (DatePickerUtil.isDateOrTimeChar(formatArray[i])) {
115+
if (dateTimeData.indexOf(dateTimeInfo) === -1) {
116+
dateTimeData.push(dateTimeInfo);
117+
}
118+
i++;
119+
}
120+
}
121+
}
122+
123+
return dateTimeData;
124+
}
125+
126+
public static setInputFormat(format: string): string {
127+
if (!format) { return ''; }
128+
let chars = '';
129+
let newFormat = '';
130+
for (let i = 0; ; i++) {
131+
while (DatePickerUtil.isDateOrTimeChar(format[i])) {
132+
chars += format[i];
133+
i++;
134+
}
135+
const datePartType = DatePickerUtil.determineDatePart(chars[0]);
136+
if (datePartType !== DatePart.Year) {
137+
newFormat += chars[0].repeat(2);
138+
} else {
139+
newFormat += chars;
140+
}
141+
142+
if (i >= format.length) { break; }
143+
144+
if (!DatePickerUtil.isDateOrTimeChar(format[i])) {
145+
newFormat += format[i];
146+
}
147+
chars = '';
148+
}
149+
150+
return newFormat;
151+
}
152+
153+
public static isDateOrTimeChar(char: string): boolean {
154+
return TimeCharsArr.indexOf(char) !== -1 || DateCharsArr.indexOf(char) !== -1;
155+
}
156+
157+
public static calculateDateOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
158+
if (isSpinLoop) {
159+
const maxDate = DatePickerUtil.daysInMonth(currentDate.getFullYear(), currentDate.getMonth() + 1);
160+
const deltaSign = delta > 1 ? delta % (Math.abs(delta) - 1) : delta;
161+
let date = currentDate.getDate();
162+
for (let i = Math.abs(delta); i > 0; i--) {
163+
if (deltaSign > 0) {
164+
date = date < maxDate ? date + deltaSign : 1;
165+
} else {
166+
date = date > 1 ? date + deltaSign : maxDate;
167+
}
168+
}
169+
170+
return new Date(currentDate.setDate(date));
171+
}
172+
newDate = new Date(newDate.setDate(newDate.getDate() + delta));
173+
if (currentDate.getMonth() === newDate.getMonth()) {
174+
return newDate;
175+
}
176+
177+
return currentDate;
178+
}
179+
180+
public static calculateMonthOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
181+
const maxDate = DatePickerUtil.daysInMonth(currentDate.getFullYear(), currentDate.getMonth() + 1 + delta);
182+
if (newDate.getDate() > maxDate) {
183+
newDate.setDate(maxDate);
184+
}
185+
if (isSpinLoop) {
186+
const deltaSign = delta > 1 ? delta % (Math.abs(delta) - 1) : delta;
187+
let month = currentDate.getMonth();
188+
for (let i = Math.abs(delta); i > 0; i--) {
189+
if (deltaSign > 0) {
190+
month = month < 11 ? month + deltaSign : 0;
191+
} else {
192+
month = month > 0 ? month + deltaSign : 11;
193+
}
194+
}
195+
196+
return new Date(newDate.setMonth(month));
197+
}
198+
newDate = new Date(newDate.setMonth(newDate.getMonth() + delta));
199+
if (currentDate.getFullYear() === newDate.getFullYear()) {
200+
return newDate;
201+
}
202+
203+
return currentDate;
204+
}
205+
206+
public static calculateYearOnSpin(delta: number, newDate: Date, currentDate: Date): Date {
207+
const maxDate = DatePickerUtil.daysInMonth(currentDate.getFullYear() + delta, currentDate.getMonth() + 1);
208+
if (newDate.getDate() > maxDate) {
209+
newDate.setDate(maxDate);
210+
}
211+
return new Date(newDate.setFullYear(newDate.getFullYear() + delta));
212+
}
213+
214+
public static calculateHoursOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
215+
if (isSpinLoop) {
216+
const deltaSign = delta > 1 ? delta % (Math.abs(delta) - 1) : delta;
217+
let hours = currentDate.getHours();
218+
for (let i = Math.abs(delta); i > 0; i--) {
219+
if (deltaSign > 0) {
220+
hours = hours < 23 ? hours + deltaSign : 0;
221+
} else {
222+
hours = hours > 0 ? hours + deltaSign : 23;
223+
}
224+
}
225+
226+
return new Date(currentDate.setHours(hours));
227+
}
228+
newDate = new Date(newDate.setHours(newDate.getHours() + delta));
229+
if (currentDate.getDate() === newDate.getDate()) {
230+
return newDate;
231+
}
232+
233+
return currentDate;
234+
}
235+
236+
public static calculateMinutesOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
237+
if (isSpinLoop) {
238+
const deltaSign = delta > 1 ? delta % (Math.abs(delta) - 1) : delta;
239+
let minutes = currentDate.getMinutes();
240+
for (let i = Math.abs(delta); i > 0; i--) {
241+
if (deltaSign > 0) {
242+
minutes = minutes < 59 ? minutes + deltaSign : 0;
243+
} else {
244+
minutes = minutes > 0 ? minutes + deltaSign : 59;
245+
}
246+
}
247+
248+
return new Date(currentDate.setMinutes(minutes));
249+
}
250+
newDate = new Date(newDate.setMinutes(newDate.getMinutes() + delta));
251+
if (currentDate.getHours() === newDate.getHours()) {
252+
return newDate;
253+
}
254+
255+
return currentDate;
256+
}
257+
258+
public static calculateSecondsOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
259+
if (isSpinLoop) {
260+
const deltaSign = delta > 1 ? delta % (Math.abs(delta) - 1) : delta;
261+
let seconds = currentDate.getSeconds();
262+
for (let i = Math.abs(delta); i > 0; i--) {
263+
if (deltaSign > 0) {
264+
seconds = seconds < 59 ? seconds + deltaSign : 0;
265+
} else {
266+
seconds = seconds > 0 ? seconds + deltaSign : 59;
267+
}
268+
}
269+
270+
return new Date(currentDate.setSeconds(seconds));
271+
}
272+
newDate = new Date(newDate.setSeconds(newDate.getSeconds() + delta));
273+
if (currentDate.getMinutes() === newDate.getMinutes()) {
274+
return newDate;
275+
}
276+
277+
return currentDate;
278+
}
279+
280+
public static calculateAmPmOnSpin(newDate: Date, currentDate: Date, amPmFromMask: string) {
281+
switch (amPmFromMask) {
282+
case 'AM':
283+
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * 1));
284+
break;
285+
case 'PM':
286+
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * -1));
287+
break;
288+
}
289+
if (newDate.getDate() !== currentDate.getDate()) {
290+
return currentDate;
291+
}
292+
293+
return newDate;
294+
}
295+
296+
private static getCleanVal(inputData: string, datePart: DatePartInfo): string {
297+
return DatePickerUtil.trimUnderlines(inputData.substring(datePart.start, datePart.end));
298+
}
299+
300+
private static getDatePartInfoRange(datePartChars: string, mask: string, index: number): any {
301+
const start = mask.indexOf(datePartChars, index);
302+
let end = start;
303+
while (this.isDateOrTimeChar(mask[end])) {
304+
end++;
305+
}
306+
307+
return { start, end };
308+
}
309+
310+
private static determineDatePart(char: string): DatePart {
311+
switch (char) {
312+
case 'd':
313+
case 'D':
314+
return DatePart.Date;
315+
case 'M':
316+
return DatePart.Month;
317+
case 'y':
318+
case 'Y':
319+
return DatePart.Year;
320+
case 'h':
321+
case 'H':
322+
return DatePart.Hours;
323+
case 'm':
324+
return DatePart.Minutes;
325+
case 's':
326+
case 'S':
327+
return DatePart.Seconds;
328+
case 't':
329+
case 'T':
330+
return DatePart.AmPm;
331+
}
332+
}
333+
49334
/**
50335
* This method generates date parts structure based on editor mask and locale.
51336
* @param maskValue: string
@@ -169,12 +454,12 @@ export abstract class DatePickerUtil {
169454
return mask.join('');
170455
}
171456
/**
172-
* This method parses an input string base on date parts and returns a date and its validation state.
173-
* @param dateFormatParts
174-
* @param prevDateValue
175-
* @param inputValue
176-
* @returns object containing a date and its validation state
177-
*/
457+
* This method parses an input string base on date parts and returns a date and its validation state.
458+
* @param dateFormatParts
459+
* @param prevDateValue
460+
* @param inputValue
461+
* @returns object containing a date and its validation state
462+
*/
178463
public static parseDateArray(dateFormatParts: any[], prevDateValue: Date, inputValue: string): any {
179464
const dayStr = DatePickerUtil.getDayValueFromInput(dateFormatParts, inputValue);
180465
const monthStr = DatePickerUtil.getMonthValueFromInput(dateFormatParts, inputValue);
@@ -339,6 +624,10 @@ export abstract class DatePickerUtil {
339624
return '';
340625
}
341626

627+
public static daysInMonth(fullYear: number, month: number): number {
628+
return new Date(fullYear, month, 0).getDate();
629+
}
630+
342631
private static getYearFormatType(format: string): string {
343632
switch (format.match(new RegExp(DateChars.YearChar, 'g')).length) {
344633
case 1: {
@@ -464,10 +753,6 @@ export abstract class DatePickerUtil {
464753
return { min: minValue, max: maxValue };
465754
}
466755

467-
private static daysInMonth(fullYear: number, month: number): number {
468-
return new Date(fullYear, month, 0).getDate();
469-
}
470-
471756
private static getDateValueFromInput(dateFormatParts: any[], type: DateParts, inputValue: string, trim: boolean = true): string {
472757
const partPosition = DatePickerUtil.getDateFormatPart(dateFormatParts, type).position;
473758
const result = inputValue.substring(partPosition[0], partPosition[1]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export interface IgxDateTimeEditorEventArgs {
2+
oldValue: Date | string;
3+
newValue: Date | string;
4+
}
5+
6+
export enum DatePart {
7+
Date = 'date',
8+
Month = 'month',
9+
Year = 'year',
10+
Hours = 'hours',
11+
Minutes = 'minutes',
12+
Seconds = 'seconds',
13+
AmPm = 'ampm'
14+
}
15+
16+
export interface DatePartInfo {
17+
type: DatePart;
18+
start: number;
19+
end: number;
20+
format: string;
21+
}

0 commit comments

Comments
 (0)