index.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. var getTimezoneOffsetInMilliseconds = require('../_lib/getTimezoneOffsetInMilliseconds/index.js')
  2. var isDate = require('../is_date/index.js')
  3. var MILLISECONDS_IN_HOUR = 3600000
  4. var MILLISECONDS_IN_MINUTE = 60000
  5. var DEFAULT_ADDITIONAL_DIGITS = 2
  6. var parseTokenDateTimeDelimeter = /[T ]/
  7. var parseTokenPlainTime = /:/
  8. // year tokens
  9. var parseTokenYY = /^(\d{2})$/
  10. var parseTokensYYY = [
  11. /^([+-]\d{2})$/, // 0 additional digits
  12. /^([+-]\d{3})$/, // 1 additional digit
  13. /^([+-]\d{4})$/ // 2 additional digits
  14. ]
  15. var parseTokenYYYY = /^(\d{4})/
  16. var parseTokensYYYYY = [
  17. /^([+-]\d{4})/, // 0 additional digits
  18. /^([+-]\d{5})/, // 1 additional digit
  19. /^([+-]\d{6})/ // 2 additional digits
  20. ]
  21. // date tokens
  22. var parseTokenMM = /^-(\d{2})$/
  23. var parseTokenDDD = /^-?(\d{3})$/
  24. var parseTokenMMDD = /^-?(\d{2})-?(\d{2})$/
  25. var parseTokenWww = /^-?W(\d{2})$/
  26. var parseTokenWwwD = /^-?W(\d{2})-?(\d{1})$/
  27. // time tokens
  28. var parseTokenHH = /^(\d{2}([.,]\d*)?)$/
  29. var parseTokenHHMM = /^(\d{2}):?(\d{2}([.,]\d*)?)$/
  30. var parseTokenHHMMSS = /^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/
  31. // timezone tokens
  32. var parseTokenTimezone = /([Z+-].*)$/
  33. var parseTokenTimezoneZ = /^(Z)$/
  34. var parseTokenTimezoneHH = /^([+-])(\d{2})$/
  35. var parseTokenTimezoneHHMM = /^([+-])(\d{2}):?(\d{2})$/
  36. /**
  37. * @category Common Helpers
  38. * @summary Convert the given argument to an instance of Date.
  39. *
  40. * @description
  41. * Convert the given argument to an instance of Date.
  42. *
  43. * If the argument is an instance of Date, the function returns its clone.
  44. *
  45. * If the argument is a number, it is treated as a timestamp.
  46. *
  47. * If an argument is a string, the function tries to parse it.
  48. * Function accepts complete ISO 8601 formats as well as partial implementations.
  49. * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
  50. *
  51. * If all above fails, the function passes the given argument to Date constructor.
  52. *
  53. * @param {Date|String|Number} argument - the value to convert
  54. * @param {Object} [options] - the object with options
  55. * @param {0 | 1 | 2} [options.additionalDigits=2] - the additional number of digits in the extended year format
  56. * @returns {Date} the parsed date in the local time zone
  57. *
  58. * @example
  59. * // Convert string '2014-02-11T11:30:30' to date:
  60. * var result = parse('2014-02-11T11:30:30')
  61. * //=> Tue Feb 11 2014 11:30:30
  62. *
  63. * @example
  64. * // Parse string '+02014101',
  65. * // if the additional number of digits in the extended year format is 1:
  66. * var result = parse('+02014101', {additionalDigits: 1})
  67. * //=> Fri Apr 11 2014 00:00:00
  68. */
  69. function parse (argument, dirtyOptions) {
  70. if (isDate(argument)) {
  71. // Prevent the date to lose the milliseconds when passed to new Date() in IE10
  72. return new Date(argument.getTime())
  73. } else if (typeof argument !== 'string') {
  74. return new Date(argument)
  75. }
  76. var options = dirtyOptions || {}
  77. var additionalDigits = options.additionalDigits
  78. if (additionalDigits == null) {
  79. additionalDigits = DEFAULT_ADDITIONAL_DIGITS
  80. } else {
  81. additionalDigits = Number(additionalDigits)
  82. }
  83. var dateStrings = splitDateString(argument)
  84. var parseYearResult = parseYear(dateStrings.date, additionalDigits)
  85. var year = parseYearResult.year
  86. var restDateString = parseYearResult.restDateString
  87. var date = parseDate(restDateString, year)
  88. if (date) {
  89. var timestamp = date.getTime()
  90. var time = 0
  91. var offset
  92. if (dateStrings.time) {
  93. time = parseTime(dateStrings.time)
  94. }
  95. if (dateStrings.timezone) {
  96. offset = parseTimezone(dateStrings.timezone) * MILLISECONDS_IN_MINUTE
  97. } else {
  98. var fullTime = timestamp + time
  99. var fullTimeDate = new Date(fullTime)
  100. offset = getTimezoneOffsetInMilliseconds(fullTimeDate)
  101. // Adjust time when it's coming from DST
  102. var fullTimeDateNextDay = new Date(fullTime)
  103. fullTimeDateNextDay.setDate(fullTimeDate.getDate() + 1)
  104. var offsetDiff =
  105. getTimezoneOffsetInMilliseconds(fullTimeDateNextDay) -
  106. getTimezoneOffsetInMilliseconds(fullTimeDate)
  107. if (offsetDiff > 0) {
  108. offset += offsetDiff
  109. }
  110. }
  111. return new Date(timestamp + time + offset)
  112. } else {
  113. return new Date(argument)
  114. }
  115. }
  116. function splitDateString (dateString) {
  117. var dateStrings = {}
  118. var array = dateString.split(parseTokenDateTimeDelimeter)
  119. var timeString
  120. if (parseTokenPlainTime.test(array[0])) {
  121. dateStrings.date = null
  122. timeString = array[0]
  123. } else {
  124. dateStrings.date = array[0]
  125. timeString = array[1]
  126. }
  127. if (timeString) {
  128. var token = parseTokenTimezone.exec(timeString)
  129. if (token) {
  130. dateStrings.time = timeString.replace(token[1], '')
  131. dateStrings.timezone = token[1]
  132. } else {
  133. dateStrings.time = timeString
  134. }
  135. }
  136. return dateStrings
  137. }
  138. function parseYear (dateString, additionalDigits) {
  139. var parseTokenYYY = parseTokensYYY[additionalDigits]
  140. var parseTokenYYYYY = parseTokensYYYYY[additionalDigits]
  141. var token
  142. // YYYY or ±YYYYY
  143. token = parseTokenYYYY.exec(dateString) || parseTokenYYYYY.exec(dateString)
  144. if (token) {
  145. var yearString = token[1]
  146. return {
  147. year: parseInt(yearString, 10),
  148. restDateString: dateString.slice(yearString.length)
  149. }
  150. }
  151. // YY or ±YYY
  152. token = parseTokenYY.exec(dateString) || parseTokenYYY.exec(dateString)
  153. if (token) {
  154. var centuryString = token[1]
  155. return {
  156. year: parseInt(centuryString, 10) * 100,
  157. restDateString: dateString.slice(centuryString.length)
  158. }
  159. }
  160. // Invalid ISO-formatted year
  161. return {
  162. year: null
  163. }
  164. }
  165. function parseDate (dateString, year) {
  166. // Invalid ISO-formatted year
  167. if (year === null) {
  168. return null
  169. }
  170. var token
  171. var date
  172. var month
  173. var week
  174. // YYYY
  175. if (dateString.length === 0) {
  176. date = new Date(0)
  177. date.setUTCFullYear(year)
  178. return date
  179. }
  180. // YYYY-MM
  181. token = parseTokenMM.exec(dateString)
  182. if (token) {
  183. date = new Date(0)
  184. month = parseInt(token[1], 10) - 1
  185. date.setUTCFullYear(year, month)
  186. return date
  187. }
  188. // YYYY-DDD or YYYYDDD
  189. token = parseTokenDDD.exec(dateString)
  190. if (token) {
  191. date = new Date(0)
  192. var dayOfYear = parseInt(token[1], 10)
  193. date.setUTCFullYear(year, 0, dayOfYear)
  194. return date
  195. }
  196. // YYYY-MM-DD or YYYYMMDD
  197. token = parseTokenMMDD.exec(dateString)
  198. if (token) {
  199. date = new Date(0)
  200. month = parseInt(token[1], 10) - 1
  201. var day = parseInt(token[2], 10)
  202. date.setUTCFullYear(year, month, day)
  203. return date
  204. }
  205. // YYYY-Www or YYYYWww
  206. token = parseTokenWww.exec(dateString)
  207. if (token) {
  208. week = parseInt(token[1], 10) - 1
  209. return dayOfISOYear(year, week)
  210. }
  211. // YYYY-Www-D or YYYYWwwD
  212. token = parseTokenWwwD.exec(dateString)
  213. if (token) {
  214. week = parseInt(token[1], 10) - 1
  215. var dayOfWeek = parseInt(token[2], 10) - 1
  216. return dayOfISOYear(year, week, dayOfWeek)
  217. }
  218. // Invalid ISO-formatted date
  219. return null
  220. }
  221. function parseTime (timeString) {
  222. var token
  223. var hours
  224. var minutes
  225. // hh
  226. token = parseTokenHH.exec(timeString)
  227. if (token) {
  228. hours = parseFloat(token[1].replace(',', '.'))
  229. return (hours % 24) * MILLISECONDS_IN_HOUR
  230. }
  231. // hh:mm or hhmm
  232. token = parseTokenHHMM.exec(timeString)
  233. if (token) {
  234. hours = parseInt(token[1], 10)
  235. minutes = parseFloat(token[2].replace(',', '.'))
  236. return (hours % 24) * MILLISECONDS_IN_HOUR +
  237. minutes * MILLISECONDS_IN_MINUTE
  238. }
  239. // hh:mm:ss or hhmmss
  240. token = parseTokenHHMMSS.exec(timeString)
  241. if (token) {
  242. hours = parseInt(token[1], 10)
  243. minutes = parseInt(token[2], 10)
  244. var seconds = parseFloat(token[3].replace(',', '.'))
  245. return (hours % 24) * MILLISECONDS_IN_HOUR +
  246. minutes * MILLISECONDS_IN_MINUTE +
  247. seconds * 1000
  248. }
  249. // Invalid ISO-formatted time
  250. return null
  251. }
  252. function parseTimezone (timezoneString) {
  253. var token
  254. var absoluteOffset
  255. // Z
  256. token = parseTokenTimezoneZ.exec(timezoneString)
  257. if (token) {
  258. return 0
  259. }
  260. // ±hh
  261. token = parseTokenTimezoneHH.exec(timezoneString)
  262. if (token) {
  263. absoluteOffset = parseInt(token[2], 10) * 60
  264. return (token[1] === '+') ? -absoluteOffset : absoluteOffset
  265. }
  266. // ±hh:mm or ±hhmm
  267. token = parseTokenTimezoneHHMM.exec(timezoneString)
  268. if (token) {
  269. absoluteOffset = parseInt(token[2], 10) * 60 + parseInt(token[3], 10)
  270. return (token[1] === '+') ? -absoluteOffset : absoluteOffset
  271. }
  272. return 0
  273. }
  274. function dayOfISOYear (isoYear, week, day) {
  275. week = week || 0
  276. day = day || 0
  277. var date = new Date(0)
  278. date.setUTCFullYear(isoYear, 0, 4)
  279. var fourthOfJanuaryDay = date.getUTCDay() || 7
  280. var diff = week * 7 + day + 1 - fourthOfJanuaryDay
  281. date.setUTCDate(date.getUTCDate() + diff)
  282. return date
  283. }
  284. module.exports = parse