fecha.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. var token = /d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|Z|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
  2. var twoDigitsOptional = "\\d\\d?";
  3. var twoDigits = "\\d\\d";
  4. var threeDigits = "\\d{3}";
  5. var fourDigits = "\\d{4}";
  6. var word = "[^\\s]+";
  7. var literal = /\[([^]*?)\]/gm;
  8. function shorten(arr, sLen) {
  9. var newArr = [];
  10. for (var i = 0, len = arr.length; i < len; i++) {
  11. newArr.push(arr[i].substr(0, sLen));
  12. }
  13. return newArr;
  14. }
  15. var monthUpdate = function (arrName) { return function (v, i18n) {
  16. var lowerCaseArr = i18n[arrName].map(function (v) { return v.toLowerCase(); });
  17. var index = lowerCaseArr.indexOf(v.toLowerCase());
  18. if (index > -1) {
  19. return index;
  20. }
  21. return null;
  22. }; };
  23. function assign(origObj) {
  24. var args = [];
  25. for (var _i = 1; _i < arguments.length; _i++) {
  26. args[_i - 1] = arguments[_i];
  27. }
  28. for (var _a = 0, args_1 = args; _a < args_1.length; _a++) {
  29. var obj = args_1[_a];
  30. for (var key in obj) {
  31. // @ts-ignore ex
  32. origObj[key] = obj[key];
  33. }
  34. }
  35. return origObj;
  36. }
  37. var dayNames = [
  38. "Sunday",
  39. "Monday",
  40. "Tuesday",
  41. "Wednesday",
  42. "Thursday",
  43. "Friday",
  44. "Saturday"
  45. ];
  46. var monthNames = [
  47. "January",
  48. "February",
  49. "March",
  50. "April",
  51. "May",
  52. "June",
  53. "July",
  54. "August",
  55. "September",
  56. "October",
  57. "November",
  58. "December"
  59. ];
  60. var monthNamesShort = shorten(monthNames, 3);
  61. var dayNamesShort = shorten(dayNames, 3);
  62. var defaultI18n = {
  63. dayNamesShort: dayNamesShort,
  64. dayNames: dayNames,
  65. monthNamesShort: monthNamesShort,
  66. monthNames: monthNames,
  67. amPm: ["am", "pm"],
  68. DoFn: function (dayOfMonth) {
  69. return (dayOfMonth +
  70. ["th", "st", "nd", "rd"][dayOfMonth % 10 > 3
  71. ? 0
  72. : ((dayOfMonth - (dayOfMonth % 10) !== 10 ? 1 : 0) * dayOfMonth) % 10]);
  73. }
  74. };
  75. var globalI18n = assign({}, defaultI18n);
  76. var setGlobalDateI18n = function (i18n) {
  77. return (globalI18n = assign(globalI18n, i18n));
  78. };
  79. var regexEscape = function (str) {
  80. return str.replace(/[|\\{()[^$+*?.-]/g, "\\$&");
  81. };
  82. var pad = function (val, len) {
  83. if (len === void 0) { len = 2; }
  84. val = String(val);
  85. while (val.length < len) {
  86. val = "0" + val;
  87. }
  88. return val;
  89. };
  90. var formatFlags = {
  91. D: function (dateObj) { return String(dateObj.getDate()); },
  92. DD: function (dateObj) { return pad(dateObj.getDate()); },
  93. Do: function (dateObj, i18n) {
  94. return i18n.DoFn(dateObj.getDate());
  95. },
  96. d: function (dateObj) { return String(dateObj.getDay()); },
  97. dd: function (dateObj) { return pad(dateObj.getDay()); },
  98. ddd: function (dateObj, i18n) {
  99. return i18n.dayNamesShort[dateObj.getDay()];
  100. },
  101. dddd: function (dateObj, i18n) {
  102. return i18n.dayNames[dateObj.getDay()];
  103. },
  104. M: function (dateObj) { return String(dateObj.getMonth() + 1); },
  105. MM: function (dateObj) { return pad(dateObj.getMonth() + 1); },
  106. MMM: function (dateObj, i18n) {
  107. return i18n.monthNamesShort[dateObj.getMonth()];
  108. },
  109. MMMM: function (dateObj, i18n) {
  110. return i18n.monthNames[dateObj.getMonth()];
  111. },
  112. YY: function (dateObj) {
  113. return pad(String(dateObj.getFullYear()), 4).substr(2);
  114. },
  115. YYYY: function (dateObj) { return pad(dateObj.getFullYear(), 4); },
  116. h: function (dateObj) { return String(dateObj.getHours() % 12 || 12); },
  117. hh: function (dateObj) { return pad(dateObj.getHours() % 12 || 12); },
  118. H: function (dateObj) { return String(dateObj.getHours()); },
  119. HH: function (dateObj) { return pad(dateObj.getHours()); },
  120. m: function (dateObj) { return String(dateObj.getMinutes()); },
  121. mm: function (dateObj) { return pad(dateObj.getMinutes()); },
  122. s: function (dateObj) { return String(dateObj.getSeconds()); },
  123. ss: function (dateObj) { return pad(dateObj.getSeconds()); },
  124. S: function (dateObj) {
  125. return String(Math.round(dateObj.getMilliseconds() / 100));
  126. },
  127. SS: function (dateObj) {
  128. return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
  129. },
  130. SSS: function (dateObj) { return pad(dateObj.getMilliseconds(), 3); },
  131. a: function (dateObj, i18n) {
  132. return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
  133. },
  134. A: function (dateObj, i18n) {
  135. return dateObj.getHours() < 12
  136. ? i18n.amPm[0].toUpperCase()
  137. : i18n.amPm[1].toUpperCase();
  138. },
  139. ZZ: function (dateObj) {
  140. var offset = dateObj.getTimezoneOffset();
  141. return ((offset > 0 ? "-" : "+") +
  142. pad(Math.floor(Math.abs(offset) / 60) * 100 + (Math.abs(offset) % 60), 4));
  143. },
  144. Z: function (dateObj) {
  145. var offset = dateObj.getTimezoneOffset();
  146. return ((offset > 0 ? "-" : "+") +
  147. pad(Math.floor(Math.abs(offset) / 60), 2) +
  148. ":" +
  149. pad(Math.abs(offset) % 60, 2));
  150. }
  151. };
  152. var monthParse = function (v) { return +v - 1; };
  153. var emptyDigits = [null, twoDigitsOptional];
  154. var emptyWord = [null, word];
  155. var amPm = [
  156. "isPm",
  157. word,
  158. function (v, i18n) {
  159. var val = v.toLowerCase();
  160. if (val === i18n.amPm[0]) {
  161. return 0;
  162. }
  163. else if (val === i18n.amPm[1]) {
  164. return 1;
  165. }
  166. return null;
  167. }
  168. ];
  169. var timezoneOffset = [
  170. "timezoneOffset",
  171. "[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z?",
  172. function (v) {
  173. var parts = (v + "").match(/([+-]|\d\d)/gi);
  174. if (parts) {
  175. var minutes = +parts[1] * 60 + parseInt(parts[2], 10);
  176. return parts[0] === "+" ? minutes : -minutes;
  177. }
  178. return 0;
  179. }
  180. ];
  181. var parseFlags = {
  182. D: ["day", twoDigitsOptional],
  183. DD: ["day", twoDigits],
  184. Do: ["day", twoDigitsOptional + word, function (v) { return parseInt(v, 10); }],
  185. M: ["month", twoDigitsOptional, monthParse],
  186. MM: ["month", twoDigits, monthParse],
  187. YY: [
  188. "year",
  189. twoDigits,
  190. function (v) {
  191. var now = new Date();
  192. var cent = +("" + now.getFullYear()).substr(0, 2);
  193. return +("" + (+v > 68 ? cent - 1 : cent) + v);
  194. }
  195. ],
  196. h: ["hour", twoDigitsOptional, undefined, "isPm"],
  197. hh: ["hour", twoDigits, undefined, "isPm"],
  198. H: ["hour", twoDigitsOptional],
  199. HH: ["hour", twoDigits],
  200. m: ["minute", twoDigitsOptional],
  201. mm: ["minute", twoDigits],
  202. s: ["second", twoDigitsOptional],
  203. ss: ["second", twoDigits],
  204. YYYY: ["year", fourDigits],
  205. S: ["millisecond", "\\d", function (v) { return +v * 100; }],
  206. SS: ["millisecond", twoDigits, function (v) { return +v * 10; }],
  207. SSS: ["millisecond", threeDigits],
  208. d: emptyDigits,
  209. dd: emptyDigits,
  210. ddd: emptyWord,
  211. dddd: emptyWord,
  212. MMM: ["month", word, monthUpdate("monthNamesShort")],
  213. MMMM: ["month", word, monthUpdate("monthNames")],
  214. a: amPm,
  215. A: amPm,
  216. ZZ: timezoneOffset,
  217. Z: timezoneOffset
  218. };
  219. // Some common format strings
  220. var globalMasks = {
  221. default: "ddd MMM DD YYYY HH:mm:ss",
  222. shortDate: "M/D/YY",
  223. mediumDate: "MMM D, YYYY",
  224. longDate: "MMMM D, YYYY",
  225. fullDate: "dddd, MMMM D, YYYY",
  226. isoDate: "YYYY-MM-DD",
  227. isoDateTime: "YYYY-MM-DDTHH:mm:ssZ",
  228. shortTime: "HH:mm",
  229. mediumTime: "HH:mm:ss",
  230. longTime: "HH:mm:ss.SSS"
  231. };
  232. var setGlobalDateMasks = function (masks) { return assign(globalMasks, masks); };
  233. /***
  234. * Format a date
  235. * @method format
  236. * @param {Date|number} dateObj
  237. * @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
  238. * @returns {string} Formatted date string
  239. */
  240. var format = function (dateObj, mask, i18n) {
  241. if (mask === void 0) { mask = globalMasks["default"]; }
  242. if (i18n === void 0) { i18n = {}; }
  243. if (typeof dateObj === "number") {
  244. dateObj = new Date(dateObj);
  245. }
  246. if (Object.prototype.toString.call(dateObj) !== "[object Date]" ||
  247. isNaN(dateObj.getTime())) {
  248. throw new Error("Invalid Date pass to format");
  249. }
  250. mask = globalMasks[mask] || mask;
  251. var literals = [];
  252. // Make literals inactive by replacing them with @@@
  253. mask = mask.replace(literal, function ($0, $1) {
  254. literals.push($1);
  255. return "@@@";
  256. });
  257. var combinedI18nSettings = assign(assign({}, globalI18n), i18n);
  258. // Apply formatting rules
  259. mask = mask.replace(token, function ($0) {
  260. return formatFlags[$0](dateObj, combinedI18nSettings);
  261. });
  262. // Inline literal values back into the formatted value
  263. return mask.replace(/@@@/g, function () { return literals.shift(); });
  264. };
  265. /**
  266. * Parse a date string into a Javascript Date object /
  267. * @method parse
  268. * @param {string} dateStr Date string
  269. * @param {string} format Date parse format
  270. * @param {i18n} I18nSettingsOptional Full or subset of I18N settings
  271. * @returns {Date|null} Returns Date object. Returns null what date string is invalid or doesn't match format
  272. */
  273. function parse(dateStr, format, i18n) {
  274. if (i18n === void 0) { i18n = {}; }
  275. if (typeof format !== "string") {
  276. throw new Error("Invalid format in fecha parse");
  277. }
  278. // Check to see if the format is actually a mask
  279. format = globalMasks[format] || format;
  280. // Avoid regular expression denial of service, fail early for really long strings
  281. // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
  282. if (dateStr.length > 1000) {
  283. return null;
  284. }
  285. // Default to the beginning of the year.
  286. var today = new Date();
  287. var dateInfo = {
  288. year: today.getFullYear(),
  289. month: 0,
  290. day: 1,
  291. hour: 0,
  292. minute: 0,
  293. second: 0,
  294. millisecond: 0,
  295. isPm: null,
  296. timezoneOffset: null
  297. };
  298. var parseInfo = [];
  299. var literals = [];
  300. // Replace all the literals with @@@. Hopefully a string that won't exist in the format
  301. var newFormat = format.replace(literal, function ($0, $1) {
  302. literals.push(regexEscape($1));
  303. return "@@@";
  304. });
  305. var specifiedFields = {};
  306. var requiredFields = {};
  307. // Change every token that we find into the correct regex
  308. newFormat = regexEscape(newFormat).replace(token, function ($0) {
  309. var info = parseFlags[$0];
  310. var field = info[0], regex = info[1], requiredField = info[3];
  311. // Check if the person has specified the same field twice. This will lead to confusing results.
  312. if (specifiedFields[field]) {
  313. throw new Error("Invalid format. " + field + " specified twice in format");
  314. }
  315. specifiedFields[field] = true;
  316. // Check if there are any required fields. For instance, 12 hour time requires AM/PM specified
  317. if (requiredField) {
  318. requiredFields[requiredField] = true;
  319. }
  320. parseInfo.push(info);
  321. return "(" + regex + ")";
  322. });
  323. // Check all the required fields are present
  324. Object.keys(requiredFields).forEach(function (field) {
  325. if (!specifiedFields[field]) {
  326. throw new Error("Invalid format. " + field + " is required in specified format");
  327. }
  328. });
  329. // Add back all the literals after
  330. newFormat = newFormat.replace(/@@@/g, function () { return literals.shift(); });
  331. // Check if the date string matches the format. If it doesn't return null
  332. var matches = dateStr.match(new RegExp(newFormat, "i"));
  333. if (!matches) {
  334. return null;
  335. }
  336. var combinedI18nSettings = assign(assign({}, globalI18n), i18n);
  337. // For each match, call the parser function for that date part
  338. for (var i = 1; i < matches.length; i++) {
  339. var _a = parseInfo[i - 1], field = _a[0], parser = _a[2];
  340. var value = parser
  341. ? parser(matches[i], combinedI18nSettings)
  342. : +matches[i];
  343. // If the parser can't make sense of the value, return null
  344. if (value == null) {
  345. return null;
  346. }
  347. dateInfo[field] = value;
  348. }
  349. if (dateInfo.isPm === 1 && dateInfo.hour != null && +dateInfo.hour !== 12) {
  350. dateInfo.hour = +dateInfo.hour + 12;
  351. }
  352. else if (dateInfo.isPm === 0 && +dateInfo.hour === 12) {
  353. dateInfo.hour = 0;
  354. }
  355. var dateTZ;
  356. if (dateInfo.timezoneOffset == null) {
  357. dateTZ = new Date(dateInfo.year, dateInfo.month, dateInfo.day, dateInfo.hour, dateInfo.minute, dateInfo.second, dateInfo.millisecond);
  358. var validateFields = [
  359. ["month", "getMonth"],
  360. ["day", "getDate"],
  361. ["hour", "getHours"],
  362. ["minute", "getMinutes"],
  363. ["second", "getSeconds"]
  364. ];
  365. for (var i = 0, len = validateFields.length; i < len; i++) {
  366. // Check to make sure the date field is within the allowed range. Javascript dates allows values
  367. // outside the allowed range. If the values don't match the value was invalid
  368. if (specifiedFields[validateFields[i][0]] &&
  369. dateInfo[validateFields[i][0]] !== dateTZ[validateFields[i][1]]()) {
  370. return null;
  371. }
  372. }
  373. }
  374. else {
  375. dateTZ = new Date(Date.UTC(dateInfo.year, dateInfo.month, dateInfo.day, dateInfo.hour, dateInfo.minute - dateInfo.timezoneOffset, dateInfo.second, dateInfo.millisecond));
  376. // We can't validate dates in another timezone unfortunately. Do a basic check instead
  377. if (dateInfo.month > 11 ||
  378. dateInfo.month < 0 ||
  379. dateInfo.day > 31 ||
  380. dateInfo.day < 1 ||
  381. dateInfo.hour > 23 ||
  382. dateInfo.hour < 0 ||
  383. dateInfo.minute > 59 ||
  384. dateInfo.minute < 0 ||
  385. dateInfo.second > 59 ||
  386. dateInfo.second < 0) {
  387. return null;
  388. }
  389. }
  390. // Don't allow invalid dates
  391. return dateTZ;
  392. }
  393. var fecha = {
  394. format: format,
  395. parse: parse,
  396. defaultI18n: defaultI18n,
  397. setGlobalDateI18n: setGlobalDateI18n,
  398. setGlobalDateMasks: setGlobalDateMasks
  399. };
  400. export default fecha;
  401. export { assign, format, parse, defaultI18n, setGlobalDateI18n, setGlobalDateMasks };
  402. //# sourceMappingURL=fecha.js.map