Skip to content

Commit 1553c9a

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

14 files changed

+1143
-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

+274-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ const enum FormatDesc {
1818
TwoDigits = '2-digit'
1919
}
2020

21+
export interface DateTimeValue {
22+
state: DateState;
23+
value: Date;
24+
}
25+
26+
export enum DatePart {
27+
Date = 'date',
28+
Month = 'month',
29+
Year = 'year',
30+
Hours = 'hours',
31+
Minutes = 'minutes',
32+
Seconds = 'seconds',
33+
AmPm = 'ampm'
34+
}
35+
36+
export interface DatePartInfo {
37+
type: DatePart;
38+
start: number;
39+
end: number;
40+
format: string;
41+
}
42+
2143
/**
2244
*@hidden
2345
*/
@@ -27,6 +49,9 @@ const enum DateChars {
2749
DayChar = 'd'
2850
}
2951

52+
const TimeCharsArr = ['h', 'H', 'm', 's', 'S', 't', 'T'];
53+
const DateCharsArr = ['d', 'D', 'M', 'y', 'Y'];
54+
3055
/**
3156
*@hidden
3257
*/
@@ -37,7 +62,7 @@ const enum DateParts {
3762
}
3863

3964
/**
40-
*@hidden
65+
* @hidden
4166
*/
4267
export abstract class DatePickerUtil {
4368
private static readonly SHORT_DATE_MASK = 'MM/dd/yy';
@@ -46,6 +71,244 @@ export abstract class DatePickerUtil {
4671
private static readonly PROMPT_CHAR = '_';
4772
private static readonly DEFAULT_LOCALE = 'en';
4873

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

605+
public static daysInMonth(fullYear: number, month: number): number {
606+
return new Date(fullYear, month, 0).getDate();
607+
}
608+
342609
private static getYearFormatType(format: string): string {
343610
switch (format.match(new RegExp(DateChars.YearChar, 'g')).length) {
344611
case 1: {
@@ -464,10 +731,6 @@ export abstract class DatePickerUtil {
464731
return { min: minValue, max: maxValue };
465732
}
466733

467-
private static daysInMonth(fullYear: number, month: number): number {
468-
return new Date(fullYear, month, 0).getDate();
469-
}
470-
471734
private static getDateValueFromInput(dateFormatParts: any[], type: DateParts, inputValue: string, trim: boolean = true): string {
472735
const partPosition = DatePickerUtil.getDateFormatPart(dateFormatParts, type).position;
473736
const result = inputValue.substring(partPosition[0], partPosition[1]);

0 commit comments

Comments
 (0)