index.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = parseISO;
  6. var _index = _interopRequireDefault(require("../_lib/toInteger/index.js"));
  7. var _index2 = _interopRequireDefault(require("../_lib/requiredArgs/index.js"));
  8. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  9. var MILLISECONDS_IN_HOUR = 3600000;
  10. var MILLISECONDS_IN_MINUTE = 60000;
  11. var DEFAULT_ADDITIONAL_DIGITS = 2;
  12. var patterns = {
  13. dateTimeDelimiter: /[T ]/,
  14. timeZoneDelimiter: /[Z ]/i,
  15. timezone: /([Z+-].*)$/
  16. };
  17. var dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/;
  18. var timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/;
  19. var timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/;
  20. /**
  21. * @name parseISO
  22. * @category Common Helpers
  23. * @summary Parse ISO string
  24. *
  25. * @description
  26. * Parse the given string in ISO 8601 format and return an instance of Date.
  27. *
  28. * Function accepts complete ISO 8601 formats as well as partial implementations.
  29. * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
  30. *
  31. * If the argument isn't a string, the function cannot parse the string or
  32. * the values are invalid, it returns Invalid Date.
  33. *
  34. * ### v2.0.0 breaking changes:
  35. *
  36. * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).
  37. *
  38. * - The previous `parse` implementation was renamed to `parseISO`.
  39. *
  40. * ```javascript
  41. * // Before v2.0.0
  42. * parse('2016-01-01')
  43. *
  44. * // v2.0.0 onward
  45. * parseISO('2016-01-01')
  46. * ```
  47. *
  48. * - `parseISO` now validates separate date and time values in ISO-8601 strings
  49. * and returns `Invalid Date` if the date is invalid.
  50. *
  51. * ```javascript
  52. * parseISO('2018-13-32')
  53. * //=> Invalid Date
  54. * ```
  55. *
  56. * - `parseISO` now doesn't fall back to `new Date` constructor
  57. * if it fails to parse a string argument. Instead, it returns `Invalid Date`.
  58. *
  59. * @param {String} argument - the value to convert
  60. * @param {Object} [options] - an object with options.
  61. * @param {0|1|2} [options.additionalDigits=2] - the additional number of digits in the extended year format
  62. * @returns {Date} the parsed date in the local time zone
  63. * @throws {TypeError} 1 argument required
  64. * @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
  65. *
  66. * @example
  67. * // Convert string '2014-02-11T11:30:30' to date:
  68. * var result = parseISO('2014-02-11T11:30:30')
  69. * //=> Tue Feb 11 2014 11:30:30
  70. *
  71. * @example
  72. * // Convert string '+02014101' to date,
  73. * // if the additional number of digits in the extended year format is 1:
  74. * var result = parseISO('+02014101', { additionalDigits: 1 })
  75. * //=> Fri Apr 11 2014 00:00:00
  76. */
  77. function parseISO(argument, dirtyOptions) {
  78. (0, _index2.default)(1, arguments);
  79. var options = dirtyOptions || {};
  80. var additionalDigits = options.additionalDigits == null ? DEFAULT_ADDITIONAL_DIGITS : (0, _index.default)(options.additionalDigits);
  81. if (additionalDigits !== 2 && additionalDigits !== 1 && additionalDigits !== 0) {
  82. throw new RangeError('additionalDigits must be 0, 1 or 2');
  83. }
  84. if (!(typeof argument === 'string' || Object.prototype.toString.call(argument) === '[object String]')) {
  85. return new Date(NaN);
  86. }
  87. var dateStrings = splitDateString(argument);
  88. var date;
  89. if (dateStrings.date) {
  90. var parseYearResult = parseYear(dateStrings.date, additionalDigits);
  91. date = parseDate(parseYearResult.restDateString, parseYearResult.year);
  92. }
  93. if (isNaN(date) || !date) {
  94. return new Date(NaN);
  95. }
  96. var timestamp = date.getTime();
  97. var time = 0;
  98. var offset;
  99. if (dateStrings.time) {
  100. time = parseTime(dateStrings.time);
  101. if (isNaN(time) || time === null) {
  102. return new Date(NaN);
  103. }
  104. }
  105. if (dateStrings.timezone) {
  106. offset = parseTimezone(dateStrings.timezone);
  107. if (isNaN(offset)) {
  108. return new Date(NaN);
  109. }
  110. } else {
  111. var dirtyDate = new Date(timestamp + time); // js parsed string assuming it's in UTC timezone
  112. // but we need it to be parsed in our timezone
  113. // so we use utc values to build date in our timezone.
  114. // Year values from 0 to 99 map to the years 1900 to 1999
  115. // so set year explicitly with setFullYear.
  116. var result = new Date(0);
  117. result.setFullYear(dirtyDate.getUTCFullYear(), dirtyDate.getUTCMonth(), dirtyDate.getUTCDate());
  118. result.setHours(dirtyDate.getUTCHours(), dirtyDate.getUTCMinutes(), dirtyDate.getUTCSeconds(), dirtyDate.getUTCMilliseconds());
  119. return result;
  120. }
  121. return new Date(timestamp + time + offset);
  122. }
  123. function splitDateString(dateString) {
  124. var dateStrings = {};
  125. var array = dateString.split(patterns.dateTimeDelimiter);
  126. var timeString; // The regex match should only return at maximum two array elements.
  127. // [date], [time], or [date, time].
  128. if (array.length > 2) {
  129. return dateStrings;
  130. }
  131. if (/:/.test(array[0])) {
  132. dateStrings.date = null;
  133. timeString = array[0];
  134. } else {
  135. dateStrings.date = array[0];
  136. timeString = array[1];
  137. if (patterns.timeZoneDelimiter.test(dateStrings.date)) {
  138. dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0];
  139. timeString = dateString.substr(dateStrings.date.length, dateString.length);
  140. }
  141. }
  142. if (timeString) {
  143. var token = patterns.timezone.exec(timeString);
  144. if (token) {
  145. dateStrings.time = timeString.replace(token[1], '');
  146. dateStrings.timezone = token[1];
  147. } else {
  148. dateStrings.time = timeString;
  149. }
  150. }
  151. return dateStrings;
  152. }
  153. function parseYear(dateString, additionalDigits) {
  154. var regex = new RegExp('^(?:(\\d{4}|[+-]\\d{' + (4 + additionalDigits) + '})|(\\d{2}|[+-]\\d{' + (2 + additionalDigits) + '})$)');
  155. var captures = dateString.match(regex); // Invalid ISO-formatted year
  156. if (!captures) return {
  157. year: null
  158. };
  159. var year = captures[1] && parseInt(captures[1]);
  160. var century = captures[2] && parseInt(captures[2]);
  161. return {
  162. year: century == null ? year : century * 100,
  163. restDateString: dateString.slice((captures[1] || captures[2]).length)
  164. };
  165. }
  166. function parseDate(dateString, year) {
  167. // Invalid ISO-formatted year
  168. if (year === null) return null;
  169. var captures = dateString.match(dateRegex); // Invalid ISO-formatted string
  170. if (!captures) return null;
  171. var isWeekDate = !!captures[4];
  172. var dayOfYear = parseDateUnit(captures[1]);
  173. var month = parseDateUnit(captures[2]) - 1;
  174. var day = parseDateUnit(captures[3]);
  175. var week = parseDateUnit(captures[4]);
  176. var dayOfWeek = parseDateUnit(captures[5]) - 1;
  177. if (isWeekDate) {
  178. if (!validateWeekDate(year, week, dayOfWeek)) {
  179. return new Date(NaN);
  180. }
  181. return dayOfISOWeekYear(year, week, dayOfWeek);
  182. } else {
  183. var date = new Date(0);
  184. if (!validateDate(year, month, day) || !validateDayOfYearDate(year, dayOfYear)) {
  185. return new Date(NaN);
  186. }
  187. date.setUTCFullYear(year, month, Math.max(dayOfYear, day));
  188. return date;
  189. }
  190. }
  191. function parseDateUnit(value) {
  192. return value ? parseInt(value) : 1;
  193. }
  194. function parseTime(timeString) {
  195. var captures = timeString.match(timeRegex);
  196. if (!captures) return null; // Invalid ISO-formatted time
  197. var hours = parseTimeUnit(captures[1]);
  198. var minutes = parseTimeUnit(captures[2]);
  199. var seconds = parseTimeUnit(captures[3]);
  200. if (!validateTime(hours, minutes, seconds)) {
  201. return NaN;
  202. }
  203. return hours * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE + seconds * 1000;
  204. }
  205. function parseTimeUnit(value) {
  206. return value && parseFloat(value.replace(',', '.')) || 0;
  207. }
  208. function parseTimezone(timezoneString) {
  209. if (timezoneString === 'Z') return 0;
  210. var captures = timezoneString.match(timezoneRegex);
  211. if (!captures) return 0;
  212. var sign = captures[1] === '+' ? -1 : 1;
  213. var hours = parseInt(captures[2]);
  214. var minutes = captures[3] && parseInt(captures[3]) || 0;
  215. if (!validateTimezone(hours, minutes)) {
  216. return NaN;
  217. }
  218. return sign * (hours * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE);
  219. }
  220. function dayOfISOWeekYear(isoWeekYear, week, day) {
  221. var date = new Date(0);
  222. date.setUTCFullYear(isoWeekYear, 0, 4);
  223. var fourthOfJanuaryDay = date.getUTCDay() || 7;
  224. var diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay;
  225. date.setUTCDate(date.getUTCDate() + diff);
  226. return date;
  227. } // Validation functions
  228. // February is null to handle the leap year (using ||)
  229. var daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  230. function isLeapYearIndex(year) {
  231. return year % 400 === 0 || year % 4 === 0 && year % 100;
  232. }
  233. function validateDate(year, month, date) {
  234. return month >= 0 && month <= 11 && date >= 1 && date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28));
  235. }
  236. function validateDayOfYearDate(year, dayOfYear) {
  237. return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365);
  238. }
  239. function validateWeekDate(_year, week, day) {
  240. return week >= 1 && week <= 53 && day >= 0 && day <= 6;
  241. }
  242. function validateTime(hours, minutes, seconds) {
  243. if (hours === 24) {
  244. return minutes === 0 && seconds === 0;
  245. }
  246. return seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25;
  247. }
  248. function validateTimezone(_hours, minutes) {
  249. return minutes >= 0 && minutes <= 59;
  250. }
  251. module.exports = exports.default;