@@ -30,6 +30,8 @@ const ISO_8601_REGEX =
3030 */
3131const TIME_REGEX = / ^ ( \d ? \d ) [: .] ( \d ? \d ) (?: [: .] ( \d ? \d ) ) ? \s * ( A M | P M ) ? $ / i;
3232
33+ const DATE_COMPONENT_SEPARATOR_REGEX = / [ \/ . : , ' " | \\ _ - ] + / ;
34+
3335/** Creates an array and fills it with values. */
3436function range < T > ( length : number , valueFunction : ( index : number ) => T ) : T [ ] {
3537 const valuesArray = Array ( length ) ;
@@ -148,7 +150,7 @@ export class NativeDateAdapter extends DateAdapter<Date> {
148150
149151 let result = this . _createDateWithOverflow ( year , month , date ) ;
150152 // 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 ) ) {
152154 throw Error ( `Invalid date "${ date } " for month with index "${ month } ".` ) ;
153155 }
154156
@@ -165,7 +167,65 @@ export class NativeDateAdapter extends DateAdapter<Date> {
165167 if ( typeof value == 'number' ) {
166168 return new Date ( value ) ;
167169 }
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 ) ;
169229 }
170230
171231 format ( date : Date , displayFormat : Object ) : string {
@@ -351,6 +411,49 @@ export class NativeDateAdapter extends DateAdapter<Date> {
351411 return dtf . format ( d ) ;
352412 }
353413
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+
354457 /**
355458 * Attempts to parse a time string into a date object. Returns null if it cannot be parsed.
356459 * @param value Time string to parse.
0 commit comments