@@ -18,6 +18,28 @@ const enum FormatDesc {
18
18
TwoDigits = '2-digit'
19
19
}
20
20
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
+
21
43
/**
22
44
*@hidden
23
45
*/
@@ -27,6 +49,9 @@ const enum DateChars {
27
49
DayChar = 'd'
28
50
}
29
51
52
+ const TimeCharsArr = [ 'h' , 'H' , 'm' , 's' , 'S' , 't' , 'T' ] ;
53
+ const DateCharsArr = [ 'd' , 'D' , 'M' , 'y' , 'Y' ] ;
54
+
30
55
/**
31
56
*@hidden
32
57
*/
@@ -36,8 +61,16 @@ const enum DateParts {
36
61
Year = 'year'
37
62
}
38
63
64
+ /** @hidden */
65
+ const enum TimeParts {
66
+ Hour = 'hour' ,
67
+ Minute = 'minute' ,
68
+ Second = 'second' ,
69
+ AmPm = 'ampm'
70
+ }
71
+
39
72
/**
40
- *@hidden
73
+ * @hidden 1
41
74
*/
42
75
export abstract class DatePickerUtil {
43
76
private static readonly SHORT_DATE_MASK = 'MM/dd/yy' ;
@@ -46,6 +79,226 @@ export abstract class DatePickerUtil {
46
79
private static readonly PROMPT_CHAR = '_' ;
47
80
private static readonly DEFAULT_LOCALE = 'en' ;
48
81
82
+ public static parseDateTimeArray ( dateTimeParts : DatePartInfo [ ] , inputData : string ) : DateTimeValue {
83
+ const parts : { [ key in DatePart ] : number } = { } as any ;
84
+ dateTimeParts . forEach ( dp => {
85
+ let value = parseInt ( this . getCleanVal ( inputData , dp ) , 10 ) ;
86
+ if ( ! value ) {
87
+ value = dp . type === DatePart . Date || dp . type === DatePart . Month ? 1 : 0 ;
88
+ }
89
+ parts [ dp . type ] = value ;
90
+ } ) ;
91
+
92
+ if ( parts [ DatePart . Month ] < 1 || 12 < parts [ DatePart . Month ] ) {
93
+ return { state : DateState . Invalid , value : null } ;
94
+ }
95
+
96
+ // TODO: Century threshold
97
+ if ( parts [ DatePart . Year ] < 50 ) {
98
+ parts [ DatePart . Year ] += 2000 ;
99
+ }
100
+
101
+ if ( parts [ DatePart . Date ] > DatePickerUtil . daysInMonth ( parts [ DatePart . Year ] , parts [ DatePart . Month ] ) ) {
102
+ return { state : DateState . Invalid , value : null } ;
103
+ }
104
+
105
+ if ( parts [ DatePart . Hours ] > 23 || parts [ DatePart . Minutes ] > 59 || parts [ DatePart . Seconds ] > 59 ) {
106
+ return { state : DateState . Invalid , value : null } ;
107
+ }
108
+
109
+ return {
110
+ state : DateState . Valid ,
111
+ value : new Date (
112
+ parts [ DatePart . Year ] ,
113
+ parts [ DatePart . Month ] - 1 ,
114
+ parts [ DatePart . Date ] ,
115
+ parts [ DatePart . Hours ] ,
116
+ parts [ DatePart . Minutes ] ,
117
+ parts [ DatePart . Seconds ]
118
+ )
119
+ } ;
120
+ }
121
+
122
+ public static parseDateTimeFormat ( mask : string , locale : string = DatePickerUtil . DEFAULT_LOCALE ) : DatePartInfo [ ] {
123
+ let dateTimeData : DatePartInfo [ ] = [ ] ;
124
+ if ( ( mask === undefined || mask === '' ) && ! isIE ( ) ) {
125
+ dateTimeData = DatePickerUtil . getDefaultLocaleMask ( locale ) ;
126
+ } else {
127
+ const format = ( mask ) ? mask : DatePickerUtil . SHORT_DATE_MASK ;
128
+ const formatArray = Array . from ( format ) ;
129
+ for ( let i = 0 ; i < formatArray . length ; i ++ ) {
130
+ const datePartRange = this . getDatePartInfoRange ( formatArray [ i ] , format , i ) ;
131
+ const dateTimeInfo = {
132
+ type : DatePickerUtil . determineDatePart ( formatArray [ i ] ) ,
133
+ start : datePartRange . start ,
134
+ end : datePartRange . end ,
135
+ format : formatArray [ i ] ,
136
+ } ;
137
+ while ( DatePickerUtil . isDateOrTimeChar ( formatArray [ i ] ) ) {
138
+ if ( dateTimeData . indexOf ( dateTimeInfo ) === - 1 ) {
139
+ dateTimeData . push ( dateTimeInfo ) ;
140
+ }
141
+ i ++ ;
142
+ }
143
+ }
144
+ }
145
+
146
+ return dateTimeData ;
147
+ }
148
+
149
+ public static setInputFormat ( format : string ) {
150
+ let chars = '' ;
151
+ let newFormat = '' ;
152
+ for ( let i = 0 ; ; i ++ ) {
153
+ while ( DatePickerUtil . isDateOrTimeChar ( format [ i ] ) ) {
154
+ chars += format [ i ] ;
155
+ i ++ ;
156
+ }
157
+
158
+ if ( chars . length === 1 || chars . length === 3 ) {
159
+ newFormat += chars [ 0 ] . repeat ( 2 ) ;
160
+ } else {
161
+ newFormat += chars ;
162
+ }
163
+
164
+ if ( i >= format . length ) { break ; }
165
+
166
+ if ( ! DatePickerUtil . isDateOrTimeChar ( format [ i ] ) ) {
167
+ newFormat += format [ i ] ;
168
+ }
169
+ chars = '' ;
170
+ }
171
+
172
+ return newFormat ;
173
+ }
174
+
175
+ public static isDateOrTimeChar ( char : string ) : boolean {
176
+ return TimeCharsArr . includes ( char ) || DateCharsArr . includes ( char ) ;
177
+ }
178
+
179
+ public static calculateDateOnSpin ( delta : number , newDate : Date , currentDate : Date , isSpinLoop : boolean ) : Date {
180
+ newDate = new Date ( newDate . setDate ( newDate . getDate ( ) + delta ) ) ;
181
+ if ( isSpinLoop ) {
182
+ if ( currentDate . getMonth ( ) > newDate . getMonth ( ) ) {
183
+ return new Date ( currentDate . getFullYear ( ) , currentDate . getMonth ( ) + 1 , 0 ) ;
184
+ } else if ( currentDate . getMonth ( ) < newDate . getMonth ( ) ) {
185
+ return new Date ( currentDate . setDate ( 1 ) ) ;
186
+ }
187
+ }
188
+ if ( currentDate . getMonth ( ) === newDate . getMonth ( ) ) {
189
+ return newDate ;
190
+ }
191
+
192
+ return currentDate ;
193
+ }
194
+
195
+ public static calculateMonthOnSpin ( delta : number , newDate : Date , currentDate : Date , isSpinLoop : boolean ) : Date {
196
+ const maxDate = DatePickerUtil . daysInMonth ( currentDate . getFullYear ( ) , newDate . getMonth ( ) + 1 + delta ) ;
197
+ if ( newDate . getDate ( ) > maxDate ) {
198
+ newDate . setDate ( maxDate ) ;
199
+ }
200
+ newDate = new Date ( newDate . setMonth ( newDate . getMonth ( ) + delta ) ) ;
201
+ if ( isSpinLoop ) {
202
+ if ( currentDate . getFullYear ( ) < newDate . getFullYear ( ) ) {
203
+ return new Date ( currentDate . setMonth ( 0 ) ) ;
204
+ } else if ( currentDate . getFullYear ( ) > newDate . getFullYear ( ) ) {
205
+ return new Date ( currentDate . setMonth ( 11 ) ) ;
206
+ }
207
+ }
208
+ if ( currentDate . getFullYear ( ) === newDate . getFullYear ( ) ) {
209
+ return newDate ;
210
+ }
211
+
212
+ return currentDate ;
213
+ }
214
+
215
+ public static calculateHoursOnSpin ( delta : number , newDate : Date , currentDate : Date , isSpinLoop : boolean ) : Date {
216
+ newDate = new Date ( newDate . setHours ( newDate . getHours ( ) + delta ) ) ;
217
+ if ( isSpinLoop ) {
218
+ if ( newDate . getDate ( ) > currentDate . getDate ( ) ) {
219
+ return new Date ( currentDate . setHours ( 0 ) ) ;
220
+ } else if ( newDate . getDate ( ) < currentDate . getDate ( ) ) {
221
+ return new Date ( currentDate . setHours ( 23 ) ) ;
222
+ }
223
+ }
224
+ if ( currentDate . getDate ( ) === newDate . getDate ( ) ) {
225
+ return newDate ;
226
+ }
227
+
228
+ return currentDate ;
229
+ }
230
+
231
+ public static calculateMinutesOnSpin ( delta : number , newDate : Date , currentDate : Date , isSpinLoop : boolean ) : Date {
232
+ newDate = new Date ( newDate . setMinutes ( newDate . getMinutes ( ) + delta ) ) ;
233
+ if ( isSpinLoop ) {
234
+ if ( newDate . getHours ( ) > currentDate . getHours ( ) ) {
235
+ return new Date ( currentDate . setMinutes ( 0 ) ) ;
236
+ } else if ( newDate . getHours ( ) < currentDate . getHours ( ) ) {
237
+ return new Date ( currentDate . setMinutes ( 59 ) ) ;
238
+ }
239
+ }
240
+
241
+ if ( currentDate . getHours ( ) === newDate . getHours ( ) ) {
242
+ return newDate ;
243
+ }
244
+
245
+ return currentDate ;
246
+ }
247
+
248
+ public static calculateSecondsOnSpin ( delta : number , newDate : Date , currentDate : Date , isSpinLoop : boolean ) : Date {
249
+ newDate = new Date ( newDate . setSeconds ( newDate . getSeconds ( ) + delta ) ) ;
250
+ if ( isSpinLoop ) {
251
+ if ( newDate . getMinutes ( ) > currentDate . getMinutes ( ) ) {
252
+ return new Date ( currentDate . setSeconds ( 0 ) ) ;
253
+ } else if ( newDate . getMinutes ( ) < currentDate . getMinutes ( ) ) {
254
+ return new Date ( currentDate . setSeconds ( 59 ) ) ;
255
+ }
256
+ }
257
+ if ( currentDate . getMinutes ( ) === newDate . getMinutes ( ) ) {
258
+ return newDate ;
259
+ }
260
+
261
+ return currentDate ;
262
+ }
263
+
264
+ private static getCleanVal ( inputData : string , datePart : DatePartInfo ) : string {
265
+ return DatePickerUtil . trimUnderlines ( inputData . substring ( datePart . start , datePart . end ) ) ;
266
+ }
267
+
268
+ private static getDatePartInfoRange ( datePartChars : string , mask : string , index : number ) : any {
269
+ const start = mask . indexOf ( datePartChars , index ) ;
270
+ let end = start ;
271
+ while ( this . isDateOrTimeChar ( mask [ end ] ) ) {
272
+ end ++ ;
273
+ }
274
+
275
+ return { start, end } ;
276
+ }
277
+
278
+ private static determineDatePart ( char : string ) : DatePart {
279
+ switch ( char ) {
280
+ case 'd' :
281
+ case 'D' :
282
+ return DatePart . Date ;
283
+ case 'M' :
284
+ return DatePart . Month ;
285
+ case 'y' :
286
+ case 'Y' :
287
+ return DatePart . Year ;
288
+ case 'h' :
289
+ case 'H' :
290
+ return DatePart . Hours ;
291
+ case 'm' :
292
+ return DatePart . Minutes ;
293
+ case 's' :
294
+ case 'S' :
295
+ return DatePart . Seconds ;
296
+ case 't' :
297
+ case 'T' :
298
+ return DatePart . AmPm ;
299
+ }
300
+ }
301
+
49
302
/**
50
303
* This method generates date parts structure based on editor mask and locale.
51
304
* @param maskValue: string
@@ -54,7 +307,7 @@ export abstract class DatePickerUtil {
54
307
*/
55
308
public static parseDateFormat ( maskValue : string , locale : string = DatePickerUtil . DEFAULT_LOCALE ) : any [ ] {
56
309
let dateStruct = [ ] ;
57
- if ( maskValue === undefined && ! isIE ( ) ) {
310
+ if ( ( maskValue === undefined || maskValue === '' ) && ! isIE ( ) ) {
58
311
dateStruct = DatePickerUtil . getDefaultLocaleMask ( locale ) ;
59
312
} else {
60
313
const mask = ( maskValue ) ? maskValue : DatePickerUtil . SHORT_DATE_MASK ;
@@ -88,7 +341,7 @@ export abstract class DatePickerUtil {
88
341
}
89
342
90
343
for ( let i = 0 ; i < maskArray . length ; i ++ ) {
91
- if ( ! DatePickerUtil . isDateChar ( maskArray [ i ] ) ) {
344
+ if ( ! DatePickerUtil . isDateTimeChar ( maskArray [ i ] ) ) {
92
345
dateStruct . push ( {
93
346
type : DatePickerUtil . SEPARATOR ,
94
347
initialPosition : i ,
@@ -180,8 +433,9 @@ export abstract class DatePickerUtil {
180
433
const monthStr = DatePickerUtil . getMonthValueFromInput ( dateFormatParts , inputValue ) ;
181
434
const yearStr = DatePickerUtil . getYearValueFromInput ( dateFormatParts , inputValue ) ;
182
435
const yearFormat = DatePickerUtil . getDateFormatPart ( dateFormatParts , DateParts . Year ) . formatType ;
183
- const day = ( dayStr !== '' ) ? parseInt ( dayStr , 10 ) : 1 ;
184
- const month = ( monthStr !== '' ) ? parseInt ( monthStr , 10 ) - 1 : 0 ;
436
+ const today = new Date ( ) ;
437
+ const day = ( dayStr !== '' ) ? parseInt ( dayStr , 10 ) : today . getDate ( ) ;
438
+ const month = ( monthStr !== '' ) ? parseInt ( monthStr , 10 ) - 1 : today . getMonth ( ) ;
185
439
186
440
let year ;
187
441
if ( yearStr === '' ) {
@@ -198,16 +452,21 @@ export abstract class DatePickerUtil {
198
452
} else {
199
453
yearPrefix = '20' ;
200
454
}
201
- const fullYear = ( yearFormat === FormatDesc . TwoDigits ) ? yearPrefix . concat ( year ) : year ;
202
455
203
456
if ( ( month < 0 ) || ( month > 11 ) || ( month === NaN ) ) {
204
457
return { state : DateState . Invalid , value : inputValue } ;
205
458
}
206
459
460
+ let fullYear = ( yearFormat === FormatDesc . TwoDigits ) ? yearPrefix . concat ( year ) : year ;
207
461
if ( ( day < 1 ) || ( day > DatePickerUtil . daysInMonth ( fullYear , month + 1 ) ) || ( day === NaN ) ) {
208
462
return { state : DateState . Invalid , value : inputValue } ;
209
463
}
210
464
465
+ if ( yearStr !== '' ) {
466
+ fullYear = parseInt ( fullYear , 10 ) ;
467
+ fullYear = fullYear < 50 ? fullYear + 2000 : fullYear + 1900 ;
468
+ }
469
+
211
470
return { state : DateState . Valid , date : new Date ( fullYear , month , day ) } ;
212
471
}
213
472
@@ -339,6 +598,20 @@ export abstract class DatePickerUtil {
339
598
return '' ;
340
599
}
341
600
601
+ public static daysInMonth ( fullYear : number , month : number ) : number {
602
+ return new Date ( fullYear , month , 0 ) . getDate ( ) ;
603
+ }
604
+
605
+ private static getFormatType ( format : string , targetChar : string ) {
606
+ switch ( format . match ( new RegExp ( targetChar , 'g' ) ) . length ) {
607
+ case 1 :
608
+ case 4 :
609
+ return FormatDesc . Numeric ;
610
+ case 2 :
611
+ return FormatDesc . TwoDigits ;
612
+ }
613
+ }
614
+
342
615
private static getYearFormatType ( format : string ) : string {
343
616
switch ( format . match ( new RegExp ( DateChars . YearChar , 'g' ) ) . length ) {
344
617
case 1 : {
@@ -419,8 +692,8 @@ export abstract class DatePickerUtil {
419
692
return dateStruct ;
420
693
}
421
694
422
- private static isDateChar ( char : string ) : boolean {
423
- return ( char === DateChars . YearChar || char === DateChars . MonthChar || char === DateChars . DayChar ) ;
695
+ private static isDateTimeChar ( char : string ) : boolean {
696
+ return ( char === DateChars . YearChar || char === DateChars . MonthChar || char === DateChars . DayChar || TimeCharsArr . includes ( char ) ) ;
424
697
}
425
698
426
699
private static getNumericFormatPrefix ( formatType : string ) : string {
@@ -464,10 +737,6 @@ export abstract class DatePickerUtil {
464
737
return { min : minValue , max : maxValue } ;
465
738
}
466
739
467
- private static daysInMonth ( fullYear : number , month : number ) : number {
468
- return new Date ( fullYear , month , 0 ) . getDate ( ) ;
469
- }
470
-
471
740
private static getDateValueFromInput ( dateFormatParts : any [ ] , type : DateParts , inputValue : string , trim : boolean = true ) : string {
472
741
const partPosition = DatePickerUtil . getDateFormatPart ( dateFormatParts , type ) . position ;
473
742
const result = inputValue . substring ( partPosition [ 0 ] , partPosition [ 1 ] ) ;
0 commit comments