import { millisecondsInHour , millisecondsInMinute } from '../constants/index' ; import type { AdditionalDigitsOptions } from '../types' ; import requiredArgs from '../_lib/requiredArgs/index' ; import toInteger from '../_lib/toInteger/index' ; name category summary description param { String } argument param options param { 0 | 1 | 2 } options . additionalDigits returns { Date } throws throws example example export default function parseISO ( argument , dirtyOptions ? ) { requiredArgs ( 1 , arguments ) ; const options = dirtyOptions || { } ; const additionalDigits = options . additionalDigits == null ? 2 : toInteger ( options . additionalDigits ) ; if ( additionalDigits !== 2 && additionalDigits !== 1 && additionalDigits !== 0 ) { throw new RangeError ( 'additionalDigits must be 0, 1 or 2' ) ; } if ( ! ( typeof argument === 'string' || Object . prototype . toString . call ( argument ) === '[object String]' ) ) { return new Date ( NaN ) ; } const dateStrings = splitDateString ( argument ) ; let date ; if ( dateStrings . date ) { const parseYearResult = parseYear ( dateStrings . date , additionalDigits ) ; date = parseDate ( parseYearResult . restDateString , parseYearResult . year ) ; } if ( ! date || isNaN ( date . getTime ( ) ) ) { return new Date ( NaN ) ; } const timestamp = date . getTime ( ) ; let time = 0 ; let offset ; if ( dateStrings . time ) { time = parseTime ( dateStrings . time ) ; if ( isNaN ( time ) ) { return new Date ( NaN ) ; } } if ( dateStrings . timezone ) { offset = parseTimezone ( dateStrings . timezone ) ; if ( isNaN ( offset ) ) { return new Date ( NaN ) ; } } else { const dirtyDate = new Date ( timestamp + time ) ; const result = new Date ( 0 ) ; result . setFullYear ( dirtyDate . getUTCFullYear ( ) , dirtyDate . getUTCMonth ( ) , dirtyDate . getUTCDate ( ) ) ; result . setHours ( dirtyDate . getUTCHours ( ) , dirtyDate . getUTCMinutes ( ) , dirtyDate . getUTCSeconds ( ) , dirtyDate . getUTCMilliseconds ( ) ) ; return result ; } return new Date ( timestamp + time + offset ) ; } interface DateString { date ? : string ; time ? : string ; timezone ? : string ; } interface ParsedYear { year : number ; restDateString : string ; } const patterns = { dateTimeDelimiter : /[T ]/ , timeZoneDelimiter : /[Z ]/i , timezone : /([Z+-].*)$/ , } ; const dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/ ; const timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/ ; const timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/ ; function splitDateString ( dateString ) { const dateStrings = { } ; const array = dateString . split ( patterns . dateTimeDelimiter ) ; let timeString ; if ( array . length > 2 ) { return dateStrings ; } if ( /:/ . test ( array [ 0 ] ) ) { timeString = array [ 0 ] ; } else { dateStrings . date = array [ 0 ] ; timeString = array [ 1 ] ; if ( patterns . timeZoneDelimiter . test ( dateStrings . date ) ) { dateStrings . date = dateString . split ( patterns . timeZoneDelimiter ) [ 0 ] ; timeString = dateString . substr ( dateStrings . date . length , dateString . length ) ; } } if ( timeString ) { const token = patterns . timezone . exec ( timeString ) ; if ( token ) { dateStrings . time = timeString . replace ( token [ 1 ] , '' ) ; dateStrings . timezone = token [ 1 ] ; } else { dateStrings . time = timeString ; } } return dateStrings ; } function parseYear ( dateString , additionalDigits ) { const regex = new RegExp ( '^(?:(\\d{4}|[+-]\\d{' + ( 4 + additionalDigits ) + '})|(\\d{2}|[+-]\\d{' + ( 2 + additionalDigits ) + '})$)' ) ; const captures = dateString . match ( regex ) ; if ( ! captures ) return { year : NaN , restDateString : '' } ; const year = captures [ 1 ] ? parseInt ( captures [ 1 ] ) : null ; const century = captures [ 2 ] ? parseInt ( captures [ 2 ] ) : null ; return { year : century === null ? ( year as number ) : century * 100 , restDateString : dateString . slice ( ( captures [ 1 ] || captures [ 2 ] ) . length ) , } ; } function parseDate ( dateString , year ) { if ( year === null ) return new Date ( NaN ) ; const captures = dateString . match ( dateRegex ) ; if ( ! captures ) return new Date ( NaN ) ; const isWeekDate = ! ! captures [ 4 ] ; const dayOfYear = parseDateUnit ( captures [ 1 ] ) ; const month = parseDateUnit ( captures [ 2 ] ) - 1 ; const day = parseDateUnit ( captures [ 3 ] ) ; const week = parseDateUnit ( captures [ 4 ] ) ; const dayOfWeek = parseDateUnit ( captures [ 5 ] ) - 1 ; if ( isWeekDate ) { if ( ! validateWeekDate ( year , week , dayOfWeek ) ) { return new Date ( NaN ) ; } return dayOfISOWeekYear ( year , week , dayOfWeek ) ; } else { const date = new Date ( 0 ) ; if ( ! validateDate ( year , month , day ) || ! validateDayOfYearDate ( year , dayOfYear ) ) { return new Date ( NaN ) ; } date . setUTCFullYear ( year , month , Math . max ( dayOfYear , day ) ) ; return date ; } } function parseDateUnit ( value ) { return value ? parseInt ( value ) : 1 ; } function parseTime ( timeString ) { const captures = timeString . match ( timeRegex ) ; if ( ! captures ) return NaN ; const hours = parseTimeUnit ( captures [ 1 ] ) ; const minutes = parseTimeUnit ( captures [ 2 ] ) ; const seconds = parseTimeUnit ( captures [ 3 ] ) ; if ( ! validateTime ( hours , minutes , seconds ) ) { return NaN ; } return ( hours * millisecondsInHour + minutes * millisecondsInMinute + seconds * 1000 ) ; } function parseTimeUnit ( value ) { return ( value && parseFloat ( value . replace ( ',' , '.' ) ) ) || 0 ; } function parseTimezone ( timezoneString ) { if ( timezoneString === 'Z' ) return 0 ; const captures = timezoneString . match ( timezoneRegex ) ; if ( ! captures ) return 0 ; const sign = captures [ 1 ] === '+' ? - 1 : 1 ; const hours = parseInt ( captures [ 2 ] ) ; const minutes = ( captures [ 3 ] && parseInt ( captures [ 3 ] ) ) || 0 ; if ( ! validateTimezone ( hours , minutes ) ) { return NaN ; } return sign * ( hours * millisecondsInHour + minutes * millisecondsInMinute ) ; } function dayOfISOWeekYear ( isoWeekYear , week , day ) { const date = new Date ( 0 ) ; date . setUTCFullYear ( isoWeekYear , 0 , 4 ) ; const fourthOfJanuaryDay = date . getUTCDay ( ) || 7 ; const diff = ( week - 1 ) * 7 + day + 1 - fourthOfJanuaryDay ; date . setUTCDate ( date . getUTCDate ( ) + diff ) ; return date ; } const daysInMonths = [ 31 , null , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ] ; function isLeapYearIndex ( year ) { return year % 400 === 0 || ( year % 4 === 0 && year % 100 !== 0 ) ; } function validateDate ( year , month , date ) { return ( month >= 0 && month <= 11 && date >= 1 && date <= ( daysInMonths [ month ] || ( isLeapYearIndex ( year ) ? 29 : 28 ) ) ) ; } function validateDayOfYearDate ( year , dayOfYear ) { return dayOfYear >= 1 && dayOfYear <= ( isLeapYearIndex ( year ) ? 366 : 365 ) ; } function validateWeekDate ( _year , week , day ) { return week >= 1 && week <= 53 && day >= 0 && day <= 6 ; } function validateTime ( hours , minutes , seconds ) { if ( hours === 24 ) { return minutes === 0 && seconds === 0 ; } return ( seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25 ) ; } function validateTimezone ( _hours , minutes ) { return minutes >= 0 && minutes <= 59 ; }