@@ -28,7 +28,7 @@ const ISO_8601_REGEX =
28
28
* - {{hours}}.{{minutes}} AM/PM
29
29
* - {{hours}}.{{minutes}}.{{seconds}} AM/PM
30
30
*/
31
- const TIME_REGEX = / ( \d ? \d ) [: .] ( \d ? \d ) (?: [: .] ( \d ? \d ) ) ? \s * ( A M | P M ) ? / i;
31
+ const TIME_REGEX = / ^ ( \d ? \d ) [: .] ( \d ? \d ) (?: [: .] ( \d ? \d ) ) ? \s * ( A M | P M ) ? $ / i;
32
32
33
33
/** Creates an array and fills it with values. */
34
34
function range < T > ( length : number , valueFunction : ( index : number ) => T ) : T [ ] {
@@ -292,67 +292,20 @@ export class NativeDateAdapter extends DateAdapter<Date> {
292
292
return null ;
293
293
}
294
294
295
- const today = this . today ( ) ;
296
- const base = this . toIso8601 ( today ) ;
295
+ // Attempt to parse the value directly.
296
+ let result = this . _parseTimeString ( value ) ;
297
297
298
- // JS is able to parse colon-separated times (including AM/PM) by
299
- // appending it to a valid date string. Generate one from today's date.
300
- let result = Date . parse ( `${ base } ${ value } ` ) ;
301
-
302
- // Some locales use a dot instead of a colon as a separator, try replacing it before parsing.
303
- if ( ! result && value . includes ( '.' ) ) {
304
- result = Date . parse ( `${ base } ${ value . replace ( / \. / g, ':' ) } ` ) ;
305
- }
306
-
307
- // Other locales add extra characters around the time, but are otherwise parseable
298
+ // Some locales add extra characters around the time, but are otherwise parseable
308
299
// (e.g. `00:05 ч.` in bg-BG). Try replacing all non-number and non-colon characters.
309
- if ( ! result ) {
300
+ if ( result === null ) {
310
301
const withoutExtras = value . replace ( / [ ^ 0 - 9 : ( A M | P M ) ] / gi, '' ) . trim ( ) ;
311
302
312
303
if ( withoutExtras . length > 0 ) {
313
- result = Date . parse ( `${ base } ${ withoutExtras } ` ) ;
314
- }
315
- }
316
-
317
- // Some browser implementations of Date aren't very flexible with the time formats.
318
- // E.g. Safari doesn't support AM/PM or padded numbers. As a final resort, we try
319
- // parsing some of the more common time formats ourselves.
320
- if ( ! result ) {
321
- const parsed = value . toUpperCase ( ) . match ( TIME_REGEX ) ;
322
-
323
- if ( parsed ) {
324
- let hours = parseInt ( parsed [ 1 ] ) ;
325
- const minutes = parseInt ( parsed [ 2 ] ) ;
326
- let seconds : number | undefined = parsed [ 3 ] == null ? undefined : parseInt ( parsed [ 3 ] ) ;
327
- const amPm = parsed [ 4 ] as 'AM' | 'PM' | undefined ;
328
-
329
- if ( hours === 12 ) {
330
- hours = amPm === 'AM' ? 0 : hours ;
331
- } else if ( amPm === 'PM' ) {
332
- hours += 12 ;
333
- }
334
-
335
- if (
336
- inRange ( hours , 0 , 23 ) &&
337
- inRange ( minutes , 0 , 59 ) &&
338
- ( seconds == null || inRange ( seconds , 0 , 59 ) )
339
- ) {
340
- return this . setTime ( today , hours , minutes , seconds || 0 ) ;
341
- }
342
- }
343
- }
344
-
345
- if ( result ) {
346
- const date = new Date ( result ) ;
347
-
348
- // Firefox allows overflows in the time string, e.g. 25:00 gets parsed as the next day.
349
- // Other browsers return invalid date objects in such cases so try to normalize it.
350
- if ( this . sameDate ( today , date ) ) {
351
- return date ;
304
+ result = this . _parseTimeString ( withoutExtras ) ;
352
305
}
353
306
}
354
307
355
- return this . invalid ( ) ;
308
+ return result || this . invalid ( ) ;
356
309
}
357
310
358
311
override addSeconds ( date : Date , amount : number ) : Date {
@@ -397,6 +350,44 @@ export class NativeDateAdapter extends DateAdapter<Date> {
397
350
d . setUTCHours ( date . getHours ( ) , date . getMinutes ( ) , date . getSeconds ( ) , date . getMilliseconds ( ) ) ;
398
351
return dtf . format ( d ) ;
399
352
}
353
+
354
+ /**
355
+ * Attempts to parse a time string into a date object. Returns null if it cannot be parsed.
356
+ * @param value Time string to parse.
357
+ */
358
+ private _parseTimeString ( value : string ) : Date | null {
359
+ // Note: we can technically rely on the browser for the time parsing by generating
360
+ // an ISO string and appending the string to the end of it. We don't do it, because
361
+ // browsers aren't consistent in what they support. Some examples:
362
+ // - Safari doesn't support AM/PM.
363
+ // - Firefox produces a valid date object if the time string has overflows (e.g. 12:75) while
364
+ // other browsers produce an invalid date.
365
+ // - Safari doesn't allow padded numbers.
366
+ const parsed = value . toUpperCase ( ) . match ( TIME_REGEX ) ;
367
+
368
+ if ( parsed ) {
369
+ let hours = parseInt ( parsed [ 1 ] ) ;
370
+ const minutes = parseInt ( parsed [ 2 ] ) ;
371
+ let seconds : number | undefined = parsed [ 3 ] == null ? undefined : parseInt ( parsed [ 3 ] ) ;
372
+ const amPm = parsed [ 4 ] as 'AM' | 'PM' | undefined ;
373
+
374
+ if ( hours === 12 ) {
375
+ hours = amPm === 'AM' ? 0 : hours ;
376
+ } else if ( amPm === 'PM' ) {
377
+ hours += 12 ;
378
+ }
379
+
380
+ if (
381
+ inRange ( hours , 0 , 23 ) &&
382
+ inRange ( minutes , 0 , 59 ) &&
383
+ ( seconds == null || inRange ( seconds , 0 , 59 ) )
384
+ ) {
385
+ return this . setTime ( this . today ( ) , hours , minutes , seconds || 0 ) ;
386
+ }
387
+ }
388
+
389
+ return null ;
390
+ }
400
391
}
401
392
402
393
/** Checks whether a number is within a certain range. */
0 commit comments