@@ -17,6 +17,8 @@ import {DateAdapter, MAT_DATE_LOCALE} from './date-adapter';
17
17
const ISO_8601_REGEX =
18
18
/ ^ \d { 4 } - \d { 2 } - \d { 2 } (?: T \d { 2 } : \d { 2 } : \d { 2 } (?: \. \d + ) ? (?: Z | (?: (?: \+ | - ) \d { 2 } : \d { 2 } ) ) ? ) ? $ / ;
19
19
20
+ const DATE_COMPONENT_SEPARATOR_REGEX = / [ \/ . : , ' " | \\ _ - ] + / ;
21
+
20
22
/** Creates an array and fills it with values. */
21
23
function range < T > ( length : number , valueFunction : ( index : number ) => T ) : T [ ] {
22
24
const valuesArray = Array ( length ) ;
@@ -118,7 +120,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
118
120
119
121
let result = this . _createDateWithOverflow ( year , month , date ) ;
120
122
// Check that the date wasn't above the upper bound for the month, causing the month to overflow
121
- if ( result . getMonth ( ) != month && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
123
+ if ( result . getMonth ( ) !== month && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
122
124
throw Error ( `Invalid date "${ date } " for month with index "${ month } ".` ) ;
123
125
}
124
126
@@ -135,7 +137,65 @@ export class NativeDateAdapter extends DateAdapter<Date> {
135
137
if ( typeof value == 'number' ) {
136
138
return new Date ( value ) ;
137
139
}
138
- return value ? new Date ( Date . parse ( value ) ) : null ;
140
+
141
+ if ( ! value ) {
142
+ return null ;
143
+ }
144
+
145
+ if ( typeof value !== 'string' ) {
146
+ return new Date ( value ) ;
147
+ }
148
+
149
+ const dateParts = ( value as string )
150
+ . trim ( )
151
+ . split ( DATE_COMPONENT_SEPARATOR_REGEX )
152
+ . map ( part => parseInt ( part , 10 ) )
153
+ . filter ( part => ! isNaN ( part ) ) ;
154
+
155
+ if ( dateParts . length < 2 ) {
156
+ return this . invalid ( ) ;
157
+ }
158
+
159
+ const localeFormatParts = Intl . DateTimeFormat ( this . locale , {
160
+ year : 'numeric' ,
161
+ month : '2-digit' ,
162
+ day : '2-digit' ,
163
+ } ) . formatToParts ( ) ;
164
+
165
+ let year : number | null = null ;
166
+ let month : number | null = null ;
167
+ let day : number | null = null ;
168
+
169
+ const valueHasYear = dateParts . length > 2 ;
170
+
171
+ if ( ! valueHasYear ) {
172
+ // Year is implied to be current year if only 2 date components are given.
173
+ year = new Date ( ) . getFullYear ( ) ;
174
+ }
175
+
176
+ let parsedPartIndex = 0 ;
177
+
178
+ for ( const part of localeFormatParts ) {
179
+ switch ( part . type ) {
180
+ case 'year' :
181
+ if ( valueHasYear ) {
182
+ year = dateParts [ parsedPartIndex ++ ] ;
183
+ }
184
+ break ;
185
+ case 'month' :
186
+ month = dateParts [ parsedPartIndex ++ ] - 1 ;
187
+ break ;
188
+ case 'day' :
189
+ day = dateParts [ parsedPartIndex ++ ] ;
190
+ break ;
191
+ }
192
+ }
193
+
194
+ if ( year !== null && month !== null && day !== null && this . _dateComponentsAreValid ( year , month , day ) ) {
195
+ return this . createDate ( year , month , day ) ;
196
+ }
197
+
198
+ return this . _nativeParseFallback ( value ) ;
139
199
}
140
200
141
201
format ( date : Date , displayFormat : Object ) : string {
@@ -257,4 +317,47 @@ export class NativeDateAdapter extends DateAdapter<Date> {
257
317
d . setUTCHours ( date . getHours ( ) , date . getMinutes ( ) , date . getSeconds ( ) , date . getMilliseconds ( ) ) ;
258
318
return dtf . format ( d ) ;
259
319
}
320
+
321
+ private _nativeParseFallback ( value : string ) : Date {
322
+ const date = new Date ( Date . parse ( value ) ) ;
323
+ if ( ! this . isValid ( date ) ) {
324
+ return date ;
325
+ }
326
+
327
+ // Native parsing sometimes assumes UTC, sometimes does not.
328
+ // We have to remove the difference between the two in order to get the date as a local date.
329
+
330
+ const compareDate = new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) , 0 , 0 , 0 ) ;
331
+ const difference = date . getTime ( ) - compareDate . getTime ( ) ;
332
+ if ( difference === 0 ) {
333
+ return date ;
334
+ }
335
+
336
+ return new Date (
337
+ date . getUTCFullYear ( ) ,
338
+ date . getUTCMonth ( ) ,
339
+ date . getUTCDate ( ) ,
340
+ date . getUTCHours ( ) ,
341
+ date . getUTCMinutes ( ) ,
342
+ date . getUTCSeconds ( ) ,
343
+ date . getUTCMilliseconds ( ) ,
344
+ ) ;
345
+ }
346
+
347
+ private _dateComponentsAreValid ( year : number , month : number , day : number ) {
348
+ if ( year < 0 || year > 9999 || month < 0 || month > 11 || day < 1 || day > 31 ) {
349
+ return false ;
350
+ }
351
+
352
+ if ( month === 1 ) {
353
+ const isLeapYear = ( year % 4 === 0 && year % 100 !== 0 ) || year % 400 === 0 ;
354
+ return isLeapYear ? day <= 29 : day <= 28 ;
355
+ }
356
+
357
+ if ( month === 3 || month === 5 || month === 8 || month === 10 ) {
358
+ return day <= 30 ;
359
+ }
360
+
361
+ return true ;
362
+ }
260
363
}
0 commit comments