1
1
import { isIE } from '../core/utils' ;
2
+ import { DatePart , DatePartInfo } from '../directives/date-time-editor/date-time-editor.common' ;
2
3
3
4
/**
4
5
* This enum is used to keep the date validation result.
@@ -18,6 +19,11 @@ const enum FormatDesc {
18
19
TwoDigits = '2-digit'
19
20
}
20
21
22
+ export interface DateTimeValue {
23
+ state : DateState ;
24
+ value : Date ;
25
+ }
26
+
21
27
/**
22
28
*@hidden
23
29
*/
@@ -27,6 +33,9 @@ const enum DateChars {
27
33
DayChar = 'd'
28
34
}
29
35
36
+ const TimeCharsArr = [ 'h' , 'H' , 'm' , 's' , 'S' , 't' , 'T' ] ;
37
+ const DateCharsArr = [ 'd' , 'D' , 'M' , 'y' , 'Y' ] ;
38
+
30
39
/**
31
40
*@hidden
32
41
*/
@@ -37,7 +46,7 @@ const enum DateParts {
37
46
}
38
47
39
48
/**
40
- *@hidden
49
+ * @hidden
41
50
*/
42
51
export abstract class DatePickerUtil {
43
52
private static readonly SHORT_DATE_MASK = 'MM/dd/yy' ;
@@ -46,6 +55,282 @@ export abstract class DatePickerUtil {
46
55
private static readonly PROMPT_CHAR = '_' ;
47
56
private static readonly DEFAULT_LOCALE = 'en' ;
48
57
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
+
49
334
/**
50
335
* This method generates date parts structure based on editor mask and locale.
51
336
* @param maskValue: string
@@ -169,12 +454,12 @@ export abstract class DatePickerUtil {
169
454
return mask . join ( '' ) ;
170
455
}
171
456
/**
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
+ */
178
463
public static parseDateArray ( dateFormatParts : any [ ] , prevDateValue : Date , inputValue : string ) : any {
179
464
const dayStr = DatePickerUtil . getDayValueFromInput ( dateFormatParts , inputValue ) ;
180
465
const monthStr = DatePickerUtil . getMonthValueFromInput ( dateFormatParts , inputValue ) ;
@@ -339,6 +624,10 @@ export abstract class DatePickerUtil {
339
624
return '' ;
340
625
}
341
626
627
+ public static daysInMonth ( fullYear : number , month : number ) : number {
628
+ return new Date ( fullYear , month , 0 ) . getDate ( ) ;
629
+ }
630
+
342
631
private static getYearFormatType ( format : string ) : string {
343
632
switch ( format . match ( new RegExp ( DateChars . YearChar , 'g' ) ) . length ) {
344
633
case 1 : {
@@ -464,10 +753,6 @@ export abstract class DatePickerUtil {
464
753
return { min : minValue , max : maxValue } ;
465
754
}
466
755
467
- private static daysInMonth ( fullYear : number , month : number ) : number {
468
- return new Date ( fullYear , month , 0 ) . getDate ( ) ;
469
- }
470
-
471
756
private static getDateValueFromInput ( dateFormatParts : any [ ] , type : DateParts , inputValue : string , trim : boolean = true ) : string {
472
757
const partPosition = DatePickerUtil . getDateFormatPart ( dateFormatParts , type ) . position ;
473
758
const result = inputValue . substring ( partPosition [ 0 ] , partPosition [ 1 ] ) ;
0 commit comments