@@ -30,6 +30,8 @@ const ISO_8601_REGEX =
30
30
*/
31
31
const TIME_REGEX = / ^ ( \d ? \d ) [: .] ( \d ? \d ) (?: [: .] ( \d ? \d ) ) ? \s * ( A M | P M ) ? $ / i;
32
32
33
+ const DATE_COMPONENT_SEPARATOR_REGEX = / [ \/ . : , ' " | \\ _ - ] + / ;
34
+
33
35
/** Creates an array and fills it with values. */
34
36
function range < T > ( length : number , valueFunction : ( index : number ) => T ) : T [ ] {
35
37
const valuesArray = Array ( length ) ;
@@ -148,7 +150,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
148
150
149
151
let result = this . _createDateWithOverflow ( year , month , date ) ;
150
152
// Check that the date wasn't above the upper bound for the month, causing the month to overflow
151
- if ( result . getMonth ( ) != month && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
153
+ if ( result . getMonth ( ) !== month && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
152
154
throw Error ( `Invalid date "${ date } " for month with index "${ month } ".` ) ;
153
155
}
154
156
@@ -165,7 +167,65 @@ export class NativeDateAdapter extends DateAdapter<Date> {
165
167
if ( typeof value == 'number' ) {
166
168
return new Date ( value ) ;
167
169
}
168
- return value ? new Date ( Date . parse ( value ) ) : null ;
170
+
171
+ if ( ! value ) {
172
+ return null ;
173
+ }
174
+
175
+ if ( typeof value !== 'string' ) {
176
+ return new Date ( value ) ;
177
+ }
178
+
179
+ const dateParts = ( value as string )
180
+ . trim ( )
181
+ . split ( DATE_COMPONENT_SEPARATOR_REGEX )
182
+ . map ( part => parseInt ( part , 10 ) )
183
+ . filter ( part => ! isNaN ( part ) ) ;
184
+
185
+ if ( dateParts . length < 2 ) {
186
+ return this . invalid ( ) ;
187
+ }
188
+
189
+ const localeFormatParts = Intl . DateTimeFormat ( this . locale , {
190
+ year : 'numeric' ,
191
+ month : '2-digit' ,
192
+ day : '2-digit' ,
193
+ } ) . formatToParts ( ) ;
194
+
195
+ let year : number | null = null ;
196
+ let month : number | null = null ;
197
+ let day : number | null = null ;
198
+
199
+ const valueHasYear = dateParts . length > 2 ;
200
+
201
+ if ( ! valueHasYear ) {
202
+ // Year is implied to be current year if only 2 date components are given.
203
+ year = new Date ( ) . getFullYear ( ) ;
204
+ }
205
+
206
+ let parsedPartIndex = 0 ;
207
+
208
+ for ( const part of localeFormatParts ) {
209
+ switch ( part . type ) {
210
+ case 'year' :
211
+ if ( valueHasYear ) {
212
+ year = dateParts [ parsedPartIndex ++ ] ;
213
+ }
214
+ break ;
215
+ case 'month' :
216
+ month = dateParts [ parsedPartIndex ++ ] - 1 ;
217
+ break ;
218
+ case 'day' :
219
+ day = dateParts [ parsedPartIndex ++ ] ;
220
+ break ;
221
+ }
222
+ }
223
+
224
+ if ( year !== null && month !== null && day !== null && this . _dateComponentsAreValid ( year , month , day ) ) {
225
+ return this . createDate ( year , month , day ) ;
226
+ }
227
+
228
+ return this . _nativeParseFallback ( value ) ;
169
229
}
170
230
171
231
format ( date : Date , displayFormat : Object ) : string {
@@ -351,6 +411,49 @@ export class NativeDateAdapter extends DateAdapter<Date> {
351
411
return dtf . format ( d ) ;
352
412
}
353
413
414
+ private _nativeParseFallback ( value : string ) : Date {
415
+ const date = new Date ( Date . parse ( value ) ) ;
416
+ if ( ! this . isValid ( date ) ) {
417
+ return date ;
418
+ }
419
+
420
+ // Native parsing sometimes assumes UTC, sometimes does not.
421
+ // We have to remove the difference between the two in order to get the date as a local date.
422
+
423
+ const compareDate = new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) , 0 , 0 , 0 ) ;
424
+ const difference = date . getTime ( ) - compareDate . getTime ( ) ;
425
+ if ( difference === 0 ) {
426
+ return date ;
427
+ }
428
+
429
+ return new Date (
430
+ date . getUTCFullYear ( ) ,
431
+ date . getUTCMonth ( ) ,
432
+ date . getUTCDate ( ) ,
433
+ date . getUTCHours ( ) ,
434
+ date . getUTCMinutes ( ) ,
435
+ date . getUTCSeconds ( ) ,
436
+ date . getUTCMilliseconds ( ) ,
437
+ ) ;
438
+ }
439
+
440
+ private _dateComponentsAreValid ( year : number , month : number , day : number ) {
441
+ if ( year < 0 || year > 9999 || month < 0 || month > 11 || day < 1 || day > 31 ) {
442
+ return false ;
443
+ }
444
+
445
+ if ( month === 1 ) {
446
+ const isLeapYear = ( year % 4 === 0 && year % 100 !== 0 ) || year % 400 === 0 ;
447
+ return isLeapYear ? day <= 29 : day <= 28 ;
448
+ }
449
+
450
+ if ( month === 3 || month === 5 || month === 8 || month === 10 ) {
451
+ return day <= 30 ;
452
+ }
453
+
454
+ return true ;
455
+ }
456
+
354
457
/**
355
458
* Attempts to parse a time string into a date object. Returns null if it cannot be parsed.
356
459
* @param value Time string to parse.
0 commit comments