var getTimezoneOffsetInMilliseconds = require('../_lib/getTimezoneOffsetInMilliseconds/index.js') var isDate = require('../is_date/index.js') var MILLISECONDS_IN_HOUR = 3600000 var MILLISECONDS_IN_MINUTE = 60000 var DEFAULT_ADDITIONAL_DIGITS = 2 var parseTokenDateTimeDelimeter = /[T ]/ var parseTokenPlainTime = /:/ // year tokens var parseTokenYY = /^(\d{2})$/ var parseTokensYYY = [ /^([+-]\d{2})$/, // 0 additional digits /^([+-]\d{3})$/, // 1 additional digit /^([+-]\d{4})$/ // 2 additional digits ] var parseTokenYYYY = /^(\d{4})/ var parseTokensYYYYY = [ /^([+-]\d{4})/, // 0 additional digits /^([+-]\d{5})/, // 1 additional digit /^([+-]\d{6})/ // 2 additional digits ] // date tokens var parseTokenMM = /^-(\d{2})$/ var parseTokenDDD = /^-?(\d{3})$/ var parseTokenMMDD = /^-?(\d{2})-?(\d{2})$/ var parseTokenWww = /^-?W(\d{2})$/ var parseTokenWwwD = /^-?W(\d{2})-?(\d{1})$/ // time tokens var parseTokenHH = /^(\d{2}([.,]\d*)?)$/ var parseTokenHHMM = /^(\d{2}):?(\d{2}([.,]\d*)?)$/ var parseTokenHHMMSS = /^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/ // timezone tokens var parseTokenTimezone = /([Z+-].*)$/ var parseTokenTimezoneZ = /^(Z)$/ var parseTokenTimezoneHH = /^([+-])(\d{2})$/ var parseTokenTimezoneHHMM = /^([+-])(\d{2}):?(\d{2})$/ /** * @category Common Helpers * @summary Convert the given argument to an instance of Date. * * @description * Convert the given argument to an instance of Date. * * If the argument is an instance of Date, the function returns its clone. * * If the argument is a number, it is treated as a timestamp. * * If an argument is a string, the function tries to parse it. * Function accepts complete ISO 8601 formats as well as partial implementations. * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 * * If all above fails, the function passes the given argument to Date constructor. * * @param {Date|String|Number} argument - the value to convert * @param {Object} [options] - the object with options * @param {0 | 1 | 2} [options.additionalDigits=2] - the additional number of digits in the extended year format * @returns {Date} the parsed date in the local time zone * * @example * // Convert string '2014-02-11T11:30:30' to date: * var result = parse('2014-02-11T11:30:30') * //=> Tue Feb 11 2014 11:30:30 * * @example * // Parse string '+02014101', * // if the additional number of digits in the extended year format is 1: * var result = parse('+02014101', {additionalDigits: 1}) * //=> Fri Apr 11 2014 00:00:00 */ function parse (argument, dirtyOptions) { if (isDate(argument)) { // Prevent the date to lose the milliseconds when passed to new Date() in IE10 return new Date(argument.getTime()) } else if (typeof argument !== 'string') { return new Date(argument) } var options = dirtyOptions || {} var additionalDigits = options.additionalDigits if (additionalDigits == null) { additionalDigits = DEFAULT_ADDITIONAL_DIGITS } else { additionalDigits = Number(additionalDigits) } var dateStrings = splitDateString(argument) var parseYearResult = parseYear(dateStrings.date, additionalDigits) var year = parseYearResult.year var restDateString = parseYearResult.restDateString var date = parseDate(restDateString, year) if (date) { var timestamp = date.getTime() var time = 0 var offset if (dateStrings.time) { time = parseTime(dateStrings.time) } if (dateStrings.timezone) { offset = parseTimezone(dateStrings.timezone) * MILLISECONDS_IN_MINUTE } else { var fullTime = timestamp + time var fullTimeDate = new Date(fullTime) offset = getTimezoneOffsetInMilliseconds(fullTimeDate) // Adjust time when it's coming from DST var fullTimeDateNextDay = new Date(fullTime) fullTimeDateNextDay.setDate(fullTimeDate.getDate() + 1) var offsetDiff = getTimezoneOffsetInMilliseconds(fullTimeDateNextDay) - getTimezoneOffsetInMilliseconds(fullTimeDate) if (offsetDiff > 0) { offset += offsetDiff } } return new Date(timestamp + time + offset) } else { return new Date(argument) } } function splitDateString (dateString) { var dateStrings = {} var array = dateString.split(parseTokenDateTimeDelimeter) var timeString if (parseTokenPlainTime.test(array[0])) { dateStrings.date = null timeString = array[0] } else { dateStrings.date = array[0] timeString = array[1] } if (timeString) { var token = parseTokenTimezone.exec(timeString) if (token) { dateStrings.time = timeString.replace(token[1], '') dateStrings.timezone = token[1] } else { dateStrings.time = timeString } } return dateStrings } function parseYear (dateString, additionalDigits) { var parseTokenYYY = parseTokensYYY[additionalDigits] var parseTokenYYYYY = parseTokensYYYYY[additionalDigits] var token // YYYY or ±YYYYY token = parseTokenYYYY.exec(dateString) || parseTokenYYYYY.exec(dateString) if (token) { var yearString = token[1] return { year: parseInt(yearString, 10), restDateString: dateString.slice(yearString.length) } } // YY or ±YYY token = parseTokenYY.exec(dateString) || parseTokenYYY.exec(dateString) if (token) { var centuryString = token[1] return { year: parseInt(centuryString, 10) * 100, restDateString: dateString.slice(centuryString.length) } } // Invalid ISO-formatted year return { year: null } } function parseDate (dateString, year) { // Invalid ISO-formatted year if (year === null) { return null } var token var date var month var week // YYYY if (dateString.length === 0) { date = new Date(0) date.setUTCFullYear(year) return date } // YYYY-MM token = parseTokenMM.exec(dateString) if (token) { date = new Date(0) month = parseInt(token[1], 10) - 1 date.setUTCFullYear(year, month) return date } // YYYY-DDD or YYYYDDD token = parseTokenDDD.exec(dateString) if (token) { date = new Date(0) var dayOfYear = parseInt(token[1], 10) date.setUTCFullYear(year, 0, dayOfYear) return date } // YYYY-MM-DD or YYYYMMDD token = parseTokenMMDD.exec(dateString) if (token) { date = new Date(0) month = parseInt(token[1], 10) - 1 var day = parseInt(token[2], 10) date.setUTCFullYear(year, month, day) return date } // YYYY-Www or YYYYWww token = parseTokenWww.exec(dateString) if (token) { week = parseInt(token[1], 10) - 1 return dayOfISOYear(year, week) } // YYYY-Www-D or YYYYWwwD token = parseTokenWwwD.exec(dateString) if (token) { week = parseInt(token[1], 10) - 1 var dayOfWeek = parseInt(token[2], 10) - 1 return dayOfISOYear(year, week, dayOfWeek) } // Invalid ISO-formatted date return null } function parseTime (timeString) { var token var hours var minutes // hh token = parseTokenHH.exec(timeString) if (token) { hours = parseFloat(token[1].replace(',', '.')) return (hours % 24) * MILLISECONDS_IN_HOUR } // hh:mm or hhmm token = parseTokenHHMM.exec(timeString) if (token) { hours = parseInt(token[1], 10) minutes = parseFloat(token[2].replace(',', '.')) return (hours % 24) * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE } // hh:mm:ss or hhmmss token = parseTokenHHMMSS.exec(timeString) if (token) { hours = parseInt(token[1], 10) minutes = parseInt(token[2], 10) var seconds = parseFloat(token[3].replace(',', '.')) return (hours % 24) * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE + seconds * 1000 } // Invalid ISO-formatted time return null } function parseTimezone (timezoneString) { var token var absoluteOffset // Z token = parseTokenTimezoneZ.exec(timezoneString) if (token) { return 0 } // ±hh token = parseTokenTimezoneHH.exec(timezoneString) if (token) { absoluteOffset = parseInt(token[2], 10) * 60 return (token[1] === '+') ? -absoluteOffset : absoluteOffset } // ±hh:mm or ±hhmm token = parseTokenTimezoneHHMM.exec(timezoneString) if (token) { absoluteOffset = parseInt(token[2], 10) * 60 + parseInt(token[3], 10) return (token[1] === '+') ? -absoluteOffset : absoluteOffset } return 0 } function dayOfISOYear (isoYear, week, day) { week = week || 0 day = day || 0 var date = new Date(0) date.setUTCFullYear(isoYear, 0, 4) var fourthOfJanuaryDay = date.getUTCDay() || 7 var diff = week * 7 + day + 1 - fourthOfJanuaryDay date.setUTCDate(date.getUTCDate() + diff) return date } module.exports = parse