333 lines
8.5 KiB
JavaScript
333 lines
8.5 KiB
JavaScript
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
|