11import { 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 */
4251export 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 ] ) ;
0 commit comments