index.js 8.1 KB

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