@@ -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 */
4267export 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