inputmask.date.extensions.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. /*
  2. Input Mask plugin extensions
  3. http://github.com/RobinHerbots/inputmask
  4. Copyright (c) Robin Herbots
  5. Licensed under the MIT license
  6. */
  7. import { escapeRegex } from "../escapeRegex";
  8. import Inputmask from "../inputmask";
  9. import { keyCode, keys } from "../keycode.js";
  10. import { seekNext } from "../positioning";
  11. import { getMaskTemplate, getTest } from "../validation-tests";
  12. import "./inputmask.date.i18n";
  13. const $ = Inputmask.dependencyLib;
  14. class DateObject {
  15. constructor(mask, format, opts, inputmask) {
  16. this.mask = mask;
  17. this.format = format;
  18. this.opts = opts;
  19. this.inputmask = inputmask;
  20. this._date = new Date(1, 0, 1);
  21. this.initDateObject(mask, this.opts, this.inputmask);
  22. }
  23. get date() {
  24. if (this._date === undefined) {
  25. this._date = new Date(1, 0, 1);
  26. this.initDateObject(undefined, this.opts, this.inputmask);
  27. }
  28. return this._date;
  29. }
  30. initDateObject(mask, opts, inputmask) {
  31. let match,
  32. lastNdx = -1;
  33. getTokenizer(opts).lastIndex = 0;
  34. while ((match = getTokenizer(opts).exec(this.format))) {
  35. if (match.index >= lastNdx) {
  36. let dynMatches = /\d+$/.exec(match[0]),
  37. fcode = dynMatches ? match[0][0] + "x" : match[0],
  38. value;
  39. if (mask !== undefined) {
  40. // console.log("mask", mask);
  41. if (dynMatches) {
  42. const lastIndex = getTokenizer(opts).lastIndex,
  43. tokenMatch = getTokenMatch.call(
  44. inputmask,
  45. match.index,
  46. opts,
  47. inputmask && inputmask.maskset
  48. );
  49. getTokenizer(opts).lastIndex = lastIndex;
  50. value = mask.slice(0, mask.indexOf(tokenMatch.nextMatch[0]));
  51. } else {
  52. let targetSymbol = match[0][0],
  53. ndx = match.index;
  54. while (
  55. inputmask &&
  56. (opts.placeholder[
  57. `${match.index}'${
  58. getTest.call(inputmask, ndx).match.placeholder
  59. }`
  60. ] || getTest.call(inputmask, ndx).match.placeholder) ===
  61. targetSymbol
  62. ) {
  63. ndx++;
  64. }
  65. lastNdx = ndx;
  66. const targetMatchLength = ndx - match.index;
  67. value = mask.slice(
  68. 0,
  69. targetMatchLength ||
  70. (formatCode[fcode] && formatCode[fcode][4]) ||
  71. fcode.length
  72. );
  73. }
  74. mask = mask.slice(value.length);
  75. }
  76. if (Object.prototype.hasOwnProperty.call(formatCode, fcode)) {
  77. this.setValue(
  78. this,
  79. value,
  80. fcode,
  81. formatCode[fcode][2],
  82. formatCode[fcode][1]
  83. );
  84. }
  85. }
  86. }
  87. }
  88. setValue(dateObj, value, fcode, targetProp, dateOperation) {
  89. if (value !== undefined) {
  90. switch (targetProp) {
  91. case "ampm":
  92. dateObj[targetProp] = value;
  93. dateObj["raw" + targetProp] = value.replace(/\s/g, "_");
  94. break;
  95. case "month":
  96. if (fcode === "mmm" || fcode === "mmmm") {
  97. fcode === "mmm"
  98. ? (dateObj[targetProp] = pad(
  99. i18n.monthNames
  100. .slice(0, 12)
  101. .findIndex(
  102. (item) => value.toLowerCase() === item.toLowerCase()
  103. ) + 1,
  104. 2
  105. ))
  106. : (dateObj[targetProp] = pad(
  107. i18n.monthNames
  108. .slice(12, 24)
  109. .findIndex(
  110. (item) => value.toLowerCase() === item.toLowerCase()
  111. ) + 1,
  112. 2
  113. ));
  114. dateObj[targetProp] =
  115. dateObj[targetProp] === "00"
  116. ? ""
  117. : dateObj[targetProp].toString();
  118. dateObj["raw" + targetProp] = dateObj[targetProp];
  119. break;
  120. }
  121. // eslint-disable-next-line no-fallthrough
  122. default:
  123. dateObj[targetProp] = value.replace(/[^0-9]/g, "0");
  124. dateObj["raw" + targetProp] = value.replace(/\s/g, "_");
  125. }
  126. }
  127. if (dateOperation !== undefined) {
  128. let datavalue = dateObj[targetProp];
  129. if (
  130. (targetProp === "day" && parseInt(datavalue) === 29) ||
  131. (targetProp === "month" && parseInt(datavalue) === 2)
  132. ) {
  133. if (
  134. parseInt(dateObj.day) === 29 &&
  135. parseInt(dateObj.month) === 2 &&
  136. (dateObj.year === "" || dateObj.year === undefined)
  137. ) {
  138. // set temporary leap year in dateObj
  139. dateObj._date.setFullYear(2012, 1, 29);
  140. }
  141. }
  142. if (targetProp === "day") {
  143. useDateObject = true;
  144. if (parseInt(datavalue) === 0) datavalue = 1;
  145. }
  146. if (targetProp === "month") useDateObject = true;
  147. if (targetProp === "year") {
  148. useDateObject = true;
  149. if (datavalue.length < formatCode[fcode][4])
  150. datavalue = pad(datavalue, formatCode[fcode][4], true);
  151. }
  152. if ((datavalue !== "" && !isNaN(datavalue)) || targetProp === "ampm")
  153. dateOperation.call(dateObj._date, datavalue);
  154. }
  155. }
  156. reset() {
  157. this._date = new Date(1, 0, 1);
  158. }
  159. reInit() {
  160. this._date = undefined;
  161. // eslint-disable-next-line no-unused-expressions
  162. this.date;
  163. }
  164. }
  165. let currentYear = new Date().getFullYear(),
  166. i18n = Inputmask.prototype.i18n,
  167. useDateObject = false, // supported codes for formatting
  168. // http://blog.stevenlevithan.com/archives/date-time-format
  169. // https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings?view=netframework-4.7
  170. formatCode = {
  171. // regex, valueSetter, type, displayformatter, #entries (optional)
  172. d: [
  173. "[1-9]|[12][0-9]|3[01]",
  174. Date.prototype.setDate,
  175. "day",
  176. Date.prototype.getDate
  177. ], // Day of the month as digits; no leading zero for single-digit days.
  178. dd: [
  179. "0[1-9]|[12][0-9]|3[01]",
  180. Date.prototype.setDate,
  181. "day",
  182. function () {
  183. return pad(Date.prototype.getDate.call(this), 2);
  184. }
  185. ], // Day of the month as digits; leading zero for single-digit days.
  186. ddd: [""], // Day of the week as a three-letter abbreviation.
  187. dddd: [""], // Day of the week as its full name.
  188. m: [
  189. "[1-9]|1[012]",
  190. function (val) {
  191. let mval = val ? parseInt(val) : 0;
  192. if (mval > 0) mval--;
  193. return Date.prototype.setMonth.call(this, mval);
  194. },
  195. "month",
  196. function () {
  197. return Date.prototype.getMonth.call(this) + 1;
  198. }
  199. ], // Month as digits; no leading zero for single-digit months.
  200. mm: [
  201. "0[1-9]|1[012]",
  202. function (val) {
  203. let mval = val ? parseInt(val) : 0;
  204. if (mval > 0) mval--;
  205. return Date.prototype.setMonth.call(this, mval);
  206. },
  207. "month",
  208. function () {
  209. return pad(Date.prototype.getMonth.call(this) + 1, 2);
  210. }
  211. ], // Month as digits; leading zero for single-digit months.
  212. mmm: [
  213. i18n.monthNames.slice(0, 12).join("|"),
  214. function (val) {
  215. const mval = i18n.monthNames
  216. .slice(0, 12)
  217. .findIndex((item) => val.toLowerCase() === item.toLowerCase());
  218. return mval !== -1 ? Date.prototype.setMonth.call(this, mval) : false;
  219. },
  220. "month",
  221. function () {
  222. return i18n.monthNames.slice(0, 12)[Date.prototype.getMonth.call(this)];
  223. }
  224. ], // Month as a three-letter abbreviation.
  225. mmmm: [
  226. i18n.monthNames.slice(12, 24).join("|"),
  227. function (val) {
  228. const mval = i18n.monthNames
  229. .slice(12, 24)
  230. .findIndex((item) => val.toLowerCase() === item.toLowerCase());
  231. return mval !== -1 ? Date.prototype.setMonth.call(this, mval) : false;
  232. },
  233. "month",
  234. function () {
  235. return i18n.monthNames.slice(12, 24)[
  236. Date.prototype.getMonth.call(this)
  237. ];
  238. }
  239. ], // Month as its full name.
  240. yy: [
  241. "[0-9]{2}",
  242. function (val) {
  243. const centuryPart = new Date().getFullYear().toString().slice(0, 2);
  244. Date.prototype.setFullYear.call(this, `${centuryPart}${val}`);
  245. },
  246. "year",
  247. function () {
  248. return pad(Date.prototype.getFullYear.call(this), 2);
  249. },
  250. 2
  251. ], // Year as last two digits; leading zero for years less than 10.
  252. yyyy: [
  253. "[0-9]{4}",
  254. Date.prototype.setFullYear,
  255. "year",
  256. function () {
  257. return pad(Date.prototype.getFullYear.call(this), 4);
  258. },
  259. 4
  260. ],
  261. h: [
  262. "[1-9]|1[0-2]",
  263. Date.prototype.setHours,
  264. "hours",
  265. Date.prototype.getHours
  266. ], // Hours; no leading zero for single-digit hours (12-hour clock).
  267. hh: [
  268. "0[1-9]|1[0-2]",
  269. Date.prototype.setHours,
  270. "hours",
  271. function () {
  272. return pad(Date.prototype.getHours.call(this), 2);
  273. }
  274. ], // Hours; leading zero for single-digit hours (12-hour clock).
  275. hx: [
  276. function (x) {
  277. return `[0-9]{${x}}`;
  278. },
  279. Date.prototype.setHours,
  280. "hours",
  281. function (x) {
  282. return Date.prototype.getHours;
  283. }
  284. ], // Hours; no limit; set maximum digits
  285. H: [
  286. "1?[0-9]|2[0-3]",
  287. Date.prototype.setHours,
  288. "hours",
  289. Date.prototype.getHours
  290. ], // Hours; no leading zero for single-digit hours (24-hour clock).
  291. HH: [
  292. "0[0-9]|1[0-9]|2[0-3]",
  293. Date.prototype.setHours,
  294. "hours",
  295. function () {
  296. return pad(Date.prototype.getHours.call(this), 2);
  297. }
  298. ], // Hours; leading zero for single-digit hours (24-hour clock).
  299. Hx: [
  300. function (x) {
  301. return `[0-9]{${x}}`;
  302. },
  303. Date.prototype.setHours,
  304. "hours",
  305. function (x) {
  306. return function () {
  307. return pad(Date.prototype.getHours.call(this), x);
  308. };
  309. }
  310. ], // Hours; no limit; set maximum digits
  311. M: [
  312. "[1-5]?[0-9]",
  313. Date.prototype.setMinutes,
  314. "minutes",
  315. Date.prototype.getMinutes
  316. ], // Minutes; no leading zero for single-digit minutes. Uppercase M unlike CF timeFormat's m to avoid conflict with months.
  317. MM: [
  318. "0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]",
  319. Date.prototype.setMinutes,
  320. "minutes",
  321. function () {
  322. return pad(Date.prototype.getMinutes.call(this), 2);
  323. }
  324. ], // Minutes; leading zero for single-digit minutes. Uppercase MM unlike CF timeFormat's mm to avoid conflict with months.
  325. s: [
  326. "[1-5]?[0-9]",
  327. Date.prototype.setSeconds,
  328. "seconds",
  329. Date.prototype.getSeconds
  330. ], // Seconds; no leading zero for single-digit seconds.
  331. ss: [
  332. "0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]",
  333. Date.prototype.setSeconds,
  334. "seconds",
  335. function () {
  336. return pad(Date.prototype.getSeconds.call(this), 2);
  337. }
  338. ], // Seconds; leading zero for single-digit seconds.
  339. l: [
  340. "[0-9]{3}",
  341. Date.prototype.setMilliseconds,
  342. "milliseconds",
  343. function () {
  344. return pad(Date.prototype.getMilliseconds.call(this), 3);
  345. },
  346. 3
  347. ], // Milliseconds. 3 digits.
  348. L: [
  349. "[0-9]{2}",
  350. Date.prototype.setMilliseconds,
  351. "milliseconds",
  352. function () {
  353. return pad(Date.prototype.getMilliseconds.call(this), 2);
  354. },
  355. 2
  356. ], // Milliseconds. 2 digits.
  357. t: ["[ap]", setAMPM, "ampm", getAMPM, 1], // Lowercase, single-character time marker string: a or p.
  358. tt: ["[ap]m", setAMPM, "ampm", getAMPM, 2], // two-character time marker string: am or pm.
  359. T: ["[AP]", setAMPM, "ampm", getAMPM, 1], // single-character time marker string: A or P.
  360. TT: ["[AP]M", setAMPM, "ampm", getAMPM, 2], // two-character time marker string: AM or PM.
  361. Z: [".*", undefined, "Z", getTimeZoneAbbreviated], // US timezone abbreviation, e.g. EST or MDT. With non-US timezones or in the Opera browser, the GMT/UTC offset is returned, e.g. GMT-0500
  362. o: [""], // GMT/UTC timezone offset, e.g. -0500 or +0230.
  363. S: [""] // The date's ordinal suffix (st, nd, rd, or th).
  364. },
  365. formatAlias = {
  366. isoDate: "yyyy-mm-dd", // 2007-06-09
  367. isoTime: "HH:MM:ss", // 17:46:21
  368. isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", // 2007-06-09T17:46:21
  369. isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" // 2007-06-09T22:46:21Z
  370. };
  371. function setAMPM(value) {
  372. const hours = this.getHours();
  373. if (value.toLowerCase().includes("p")) {
  374. this.setHours(hours + 12);
  375. // console.log("setAMPM + 12");
  376. } else if (value.toLowerCase().includes("a") && hours >= 12) {
  377. this.setHours(hours - 12);
  378. }
  379. }
  380. function getAMPM() {
  381. let date = this,
  382. hours = date.getHours();
  383. hours = hours || 12;
  384. return hours >= 12 ? "PM" : "AM";
  385. }
  386. function getTimeZoneAbbreviated() {
  387. // not perfect, but ok for now
  388. let date = this,
  389. { 1: tz } = date.toString().match(/\((.+)\)/);
  390. if (tz.includes(" ")) {
  391. tz = tz.replace("-", " ").toUpperCase();
  392. tz = tz
  393. .split(" ")
  394. .map(([first]) => first)
  395. .join("");
  396. }
  397. return tz;
  398. }
  399. function formatcode(match) {
  400. const dynMatches = /\d+$/.exec(match[0]);
  401. if (dynMatches && dynMatches[0] !== undefined) {
  402. const fcode = formatCode[match[0][0] + "x"].slice("");
  403. fcode[0] = fcode[0](dynMatches[0]);
  404. fcode[3] = fcode[3](dynMatches[0]);
  405. return fcode;
  406. } else if (formatCode[match[0]]) {
  407. return formatCode[match[0]];
  408. }
  409. }
  410. function getTokenizer(opts) {
  411. if (!opts.tokenizer) {
  412. const tokens = [],
  413. dyntokens = [];
  414. for (const ndx in formatCode) {
  415. if (/\.*x$/.test(ndx)) {
  416. const dynToken = ndx[0] + "\\d+";
  417. if (dyntokens.indexOf(dynToken) === -1) {
  418. dyntokens.push(dynToken);
  419. }
  420. } else if (tokens.indexOf(ndx[0]) === -1) {
  421. tokens.push(ndx[0]);
  422. }
  423. }
  424. opts.tokenizer =
  425. "(" +
  426. (dyntokens.length > 0 ? dyntokens.join("|") + "|" : "") +
  427. tokens.join("+|") +
  428. ")+?|.";
  429. opts.tokenizer = new RegExp(opts.tokenizer, "g");
  430. }
  431. return opts.tokenizer;
  432. }
  433. function prefillYear(dateParts, currentResult, opts) {
  434. if (dateParts.year !== dateParts.rawyear) {
  435. const crrntyear = currentYear.toString(),
  436. enteredPart = dateParts.rawyear.replace(/[^0-9]/g, ""),
  437. currentYearPart = crrntyear.slice(0, enteredPart.length),
  438. currentYearNextPart = crrntyear.slice(enteredPart.length);
  439. if (enteredPart.length === 2 && enteredPart === currentYearPart) {
  440. const entryCurrentYear = new Date(
  441. currentYear,
  442. dateParts.month - 1,
  443. dateParts.day
  444. );
  445. if (
  446. dateParts.day == entryCurrentYear.getDate() &&
  447. (!opts.max || opts.max.date.getTime() >= entryCurrentYear.getTime())
  448. ) {
  449. // update dateParts
  450. dateParts.date.setFullYear(currentYear);
  451. dateParts.year = crrntyear;
  452. // update result
  453. currentResult.insert = [
  454. {
  455. pos: currentResult.pos + 1,
  456. c: currentYearNextPart[0]
  457. },
  458. {
  459. pos: currentResult.pos + 2,
  460. c: currentYearNextPart[1]
  461. }
  462. ];
  463. }
  464. }
  465. }
  466. return currentResult;
  467. }
  468. function isValidDate(dateParts, currentResult, opts) {
  469. const inputmask = this;
  470. if (!useDateObject) return true;
  471. if (
  472. dateParts.rawday === undefined ||
  473. (!isFinite(dateParts.rawday) &&
  474. new Date(
  475. dateParts.date.getFullYear(),
  476. isFinite(dateParts.rawmonth)
  477. ? dateParts.month
  478. : dateParts.date.getMonth() + 1,
  479. 0
  480. ).getDate() >= dateParts.day) ||
  481. (dateParts.day == "29" &&
  482. (!isFinite(dateParts.rawyear) ||
  483. dateParts.rawyear === undefined ||
  484. dateParts.rawyear === "")) ||
  485. new Date(
  486. dateParts.date.getFullYear(),
  487. isFinite(dateParts.rawmonth)
  488. ? dateParts.month
  489. : dateParts.date.getMonth() + 1,
  490. 0
  491. ).getDate() >= dateParts.day
  492. ) {
  493. return currentResult;
  494. } else {
  495. // take corrective action if possible
  496. if (dateParts.day == "29") {
  497. const tokenMatch = getTokenMatch.call(
  498. inputmask,
  499. currentResult.pos,
  500. opts,
  501. inputmask.maskset
  502. );
  503. if (
  504. tokenMatch.targetMatch &&
  505. tokenMatch.targetMatch[0] === "yyyy" &&
  506. currentResult.pos - tokenMatch.targetMatchIndex === 2
  507. ) {
  508. currentResult.remove = currentResult.pos + 1;
  509. return currentResult;
  510. }
  511. } else if (
  512. dateParts.date.getMonth() == 2 &&
  513. dateParts.day == "30" &&
  514. currentResult.c !== undefined
  515. ) {
  516. dateParts.day = "03";
  517. dateParts.date.setDate(3);
  518. dateParts.date.setMonth(1);
  519. currentResult.insert = [
  520. { pos: currentResult.pos, c: "0" },
  521. { pos: currentResult.pos + 1, c: currentResult.c }
  522. ];
  523. currentResult.caret = seekNext.call(this, currentResult.pos + 1);
  524. return currentResult;
  525. }
  526. return false;
  527. }
  528. }
  529. function isDateInRange(dateParts, result, opts, maskset, fromCheckval) {
  530. if (!result) return result;
  531. if (result && opts.min) {
  532. if (
  533. /* useDateObject && (dateParts["year"] === undefined || dateParts["yearSet"]) && */ !isNaN(
  534. opts.min.date.getTime()
  535. )
  536. ) {
  537. let match;
  538. dateParts.reset();
  539. getTokenizer(opts).lastIndex = 0;
  540. while ((match = getTokenizer(opts).exec(opts.inputFormat))) {
  541. var fcode;
  542. if ((fcode = formatcode(match))) {
  543. if (fcode[3]) {
  544. let setFn = fcode[1],
  545. current = dateParts[fcode[2]],
  546. minVal = opts.min[fcode[2]],
  547. maxVal = opts.max ? opts.max[fcode[2]] : minVal + 1,
  548. curVal = [],
  549. forceCurrentValue = false;
  550. for (let i = 0; i < minVal.length; i++) {
  551. if (
  552. maskset.validPositions[i + match.index] === undefined &&
  553. !forceCurrentValue
  554. ) {
  555. if (i + match.index == 0 && current[i] < minVal[i]) {
  556. curVal[i] = current[i];
  557. forceCurrentValue = true;
  558. } else {
  559. curVal[i] = minVal[i];
  560. }
  561. // ADD +1 to whole
  562. if (
  563. fcode[2] === "year" &&
  564. current.length - 1 == i &&
  565. minVal != maxVal
  566. )
  567. curVal = (parseInt(curVal.join("")) + 1).toString().split("");
  568. if (
  569. fcode[2] === "ampm" &&
  570. minVal != maxVal &&
  571. opts.min.date.getTime() > dateParts.date.getTime()
  572. )
  573. curVal[i] = maxVal[i];
  574. } else {
  575. curVal[i] = current[i];
  576. forceCurrentValue = forceCurrentValue || current[i] > minVal[i];
  577. }
  578. }
  579. setFn.call(dateParts._date, curVal.join(""));
  580. }
  581. }
  582. }
  583. result = opts.min.date.getTime() <= dateParts.date.getTime();
  584. dateParts.reInit();
  585. }
  586. }
  587. if (result && opts.max) {
  588. if (!isNaN(opts.max.date.getTime())) {
  589. result = opts.max.date.getTime() >= dateParts.date.getTime();
  590. }
  591. }
  592. return result;
  593. }
  594. // parse the given format and return a mask pattern
  595. // when a dateObjValue is passed a datestring in the requested format is returned
  596. function parse(format, dateObjValue, opts, raw) {
  597. // parse format to regex string
  598. let mask = "",
  599. match,
  600. fcode,
  601. ndx = 0,
  602. escaped = false;
  603. const placeHolder = {};
  604. getTokenizer(opts).lastIndex = 0;
  605. while ((match = getTokenizer(opts).exec(format))) {
  606. if (match[0] === "\\") {
  607. escaped = true;
  608. } else {
  609. if (dateObjValue === undefined) {
  610. if (!escaped && (fcode = formatcode(match))) {
  611. mask += "(" + fcode[0] + ")";
  612. // map placeholder to placeholder object and set placeholder mappings
  613. if (opts.placeholder && opts.placeholder !== "") {
  614. placeHolder[ndx] =
  615. opts.placeholder[match.index % opts.placeholder.length];
  616. // internal use of datetime alias
  617. placeHolder[
  618. `${match.index}'${
  619. opts.placeholder[match.index % opts.placeholder.length]
  620. }`
  621. ] = match[0].charAt(0);
  622. } else {
  623. placeHolder[ndx] = match[0].charAt(0);
  624. }
  625. } else {
  626. switch (match[0]) {
  627. case "[":
  628. mask += "(";
  629. break;
  630. case "]":
  631. mask += ")?";
  632. break;
  633. default:
  634. mask += escapeRegex(match[0]);
  635. placeHolder[ndx] = match[0].charAt(0);
  636. }
  637. }
  638. } else {
  639. if (!escaped && (fcode = formatcode(match))) {
  640. if (raw !== true && fcode[3]) {
  641. const getFn = fcode[3];
  642. mask += getFn.call(dateObjValue.date);
  643. } else if (fcode[2]) {
  644. mask += dateObjValue["raw" + fcode[2]];
  645. } else {
  646. mask += match[0];
  647. }
  648. } else {
  649. mask += `${escaped ? "\\" : ""}${match[0]}`;
  650. }
  651. }
  652. ndx++;
  653. escaped = false;
  654. }
  655. }
  656. if (dateObjValue === undefined) {
  657. // console.log(JSON.stringify(placeHolder));
  658. opts.placeholder = placeHolder;
  659. }
  660. return mask;
  661. }
  662. // padding function
  663. function pad(val, len, right) {
  664. val = String(val);
  665. len = len || 2;
  666. while (val.length < len) val = right ? val + "0" : "0" + val;
  667. return val;
  668. }
  669. function analyseMask(mask, format, opts) {
  670. const inputmask = this;
  671. if (typeof mask === "string") {
  672. return new DateObject(mask, format, opts, inputmask);
  673. } else if (
  674. mask &&
  675. typeof mask === "object" &&
  676. Object.prototype.hasOwnProperty.call(mask, "date")
  677. ) {
  678. return mask;
  679. }
  680. return undefined;
  681. }
  682. function importDate(dateObj, opts) {
  683. return parse(opts.inputFormat, { date: dateObj }, opts);
  684. }
  685. function getTokenMatch(pos, opts, maskset) {
  686. let inputmask = this,
  687. calcPos = 0,
  688. targetMatch,
  689. match,
  690. matchLength = 0;
  691. getTokenizer(opts).lastIndex = 0;
  692. while ((match = getTokenizer(opts).exec(opts.inputFormat))) {
  693. // console.log(`match.index ${match.index}`);
  694. const dynMatches = /\d+$/.exec(match[0]);
  695. if (dynMatches) {
  696. matchLength = parseInt(dynMatches[0]);
  697. } else {
  698. let targetSymbol = match[0][0],
  699. ndx = calcPos;
  700. while (
  701. inputmask &&
  702. (opts.placeholder[
  703. `${match.index}'${getTest.call(inputmask, ndx).match.placeholder}`
  704. ] || getTest.call(inputmask, ndx).match.placeholder) === targetSymbol
  705. ) {
  706. ndx++;
  707. }
  708. matchLength = ndx - calcPos;
  709. if (matchLength === 0) matchLength = match[0].length;
  710. }
  711. calcPos += matchLength;
  712. // console.log(`calcPos ${calcPos}`);
  713. if (calcPos >= pos + 1) {
  714. let masksetHint = "";
  715. if (maskset && maskset.tests[pos]) {
  716. const filteredPlaceholders = Object.keys(opts.placeholder).filter(
  717. (value) => {
  718. for (let i = match.index - 1; i < calcPos; i++) {
  719. if (value === `${i}'${maskset.tests[pos][0].match.placeholder}`) {
  720. return true;
  721. }
  722. }
  723. return false;
  724. }
  725. );
  726. masksetHint =
  727. filteredPlaceholders.length > 0
  728. ? opts.placeholder[filteredPlaceholders[0]]
  729. : maskset.tests[pos][0].match.placeholder;
  730. }
  731. // console.log(masksetHint);
  732. if (match[0].indexOf(masksetHint) !== -1) {
  733. // console.log(`match ${masksetHint} ${calcPos} >= ${pos + 1}`);
  734. targetMatch = match;
  735. match = getTokenizer(opts).exec(opts.inputFormat);
  736. break;
  737. } else {
  738. // console.log(`no match ${masksetHint} ${calcPos} >= ${pos + 1}`);
  739. }
  740. }
  741. }
  742. return {
  743. targetMatchIndex: calcPos - matchLength,
  744. nextMatch: match,
  745. targetMatch
  746. };
  747. }
  748. Inputmask.extendAliases({
  749. datetime: {
  750. mask: function (opts) {
  751. // do not allow numeric input in datetime alias
  752. opts.numericInput = false;
  753. // localize
  754. formatCode.S = i18n.ordinalSuffix.join("|");
  755. opts.inputFormat = formatAlias[opts.inputFormat] || opts.inputFormat; // resolve possible formatAlias
  756. if (opts.repeat) {
  757. opts.repeat = parseInt(opts.repeat.toString());
  758. if (opts.repeat > 0) {
  759. let inputFormat = "";
  760. for (let i = 0; i < opts.repeat; i++) {
  761. inputFormat = inputFormat + opts.inputFormat;
  762. }
  763. opts.inputFormat = inputFormat;
  764. opts.repeat = 0;
  765. }
  766. }
  767. opts.displayFormat =
  768. formatAlias[opts.displayFormat] ||
  769. opts.displayFormat ||
  770. opts.inputFormat; // resolve possible formatAlias
  771. opts.outputFormat =
  772. formatAlias[opts.outputFormat] || opts.outputFormat || opts.inputFormat; // resolve possible formatAlias
  773. // opts.placeholder = opts.placeholder !== "" ? opts.placeholder : opts.inputFormat.replace(/[[\]]/, "");
  774. opts.regex = parse(opts.inputFormat, undefined, opts);
  775. // console.log("inputFormat", opts.regex);
  776. opts.min = analyseMask(opts.min, opts.inputFormat, opts);
  777. opts.max = analyseMask(opts.max, opts.inputFormat, opts);
  778. return null; // migrate to regex mask
  779. },
  780. placeholder: "", // set default as none (~ auto); when a custom placeholder is passed it will be used
  781. inputFormat: "isoDateTime", // format used to input the date
  782. displayFormat: null, // visual format when the input looses focus
  783. outputFormat: null, // unmasking format
  784. min: null, // needs to be in the same format as the inputfornat
  785. max: null, // needs to be in the same format as the inputfornat,
  786. skipOptionalPartCharacter: "",
  787. preValidation: function (
  788. buffer,
  789. pos,
  790. c,
  791. isSelection,
  792. opts,
  793. maskset,
  794. caretPos,
  795. strict
  796. ) {
  797. const inputmask = this;
  798. if (strict) return true;
  799. if (isNaN(c) && buffer[pos] !== c) {
  800. const tokenMatch = getTokenMatch.call(inputmask, pos, opts, maskset);
  801. if (
  802. tokenMatch.nextMatch &&
  803. tokenMatch.nextMatch[0] === c &&
  804. tokenMatch.targetMatch[0].length > 1
  805. ) {
  806. const validator = formatcode(tokenMatch.targetMatch)[0];
  807. if (new RegExp(validator).test("0" + buffer[pos - 1])) {
  808. buffer[pos] = buffer[pos - 1];
  809. buffer[pos - 1] = "0";
  810. return {
  811. fuzzy: true,
  812. buffer,
  813. refreshFromBuffer: { start: pos - 1, end: pos + 1 },
  814. pos: pos + 1
  815. };
  816. }
  817. }
  818. }
  819. return true;
  820. },
  821. postValidation: function (
  822. buffer,
  823. pos,
  824. c,
  825. currentResult,
  826. opts,
  827. maskset,
  828. strict,
  829. fromCheckval
  830. ) {
  831. const inputmask = this;
  832. if (strict) return true;
  833. let tokenMatch, validator;
  834. if (currentResult === false) {
  835. // try some shifting
  836. tokenMatch = getTokenMatch.call(inputmask, pos + 1, opts, maskset);
  837. if (
  838. tokenMatch.targetMatch &&
  839. tokenMatch.targetMatchIndex === pos &&
  840. tokenMatch.targetMatch[0].length > 1 &&
  841. formatCode[tokenMatch.targetMatch[0]] !== undefined
  842. ) {
  843. validator = formatcode(tokenMatch.targetMatch)[0];
  844. } else {
  845. tokenMatch = getTokenMatch.call(inputmask, pos + 2, opts, maskset);
  846. if (
  847. tokenMatch.targetMatch &&
  848. tokenMatch.targetMatchIndex === pos + 1 &&
  849. tokenMatch.targetMatch[0].length > 1 &&
  850. formatCode[tokenMatch.targetMatch[0]] !== undefined
  851. ) {
  852. validator = formatcode(tokenMatch.targetMatch)[0];
  853. }
  854. }
  855. if (validator !== undefined) {
  856. if (
  857. maskset.validPositions[pos + 1] !== undefined &&
  858. new RegExp(validator).test(c + "0")
  859. ) {
  860. buffer[pos] = c;
  861. buffer[pos + 1] = "0";
  862. currentResult = {
  863. // insert: [{pos: pos, c: "0"}, {pos: pos + 1, c: c}],
  864. pos: pos + 2, // this will triggeer a refreshfrombuffer
  865. caret: pos
  866. };
  867. } else if (new RegExp(validator).test("0" + c)) {
  868. buffer[pos] = "0";
  869. buffer[pos + 1] = c;
  870. currentResult = {
  871. // insert: [{pos: pos, c: "0"}, {pos: pos + 1, c: c}],
  872. pos: pos + 2 // this will triggeer a refreshfrombuffer
  873. };
  874. }
  875. }
  876. if (currentResult === false) return currentResult;
  877. }
  878. if (currentResult.fuzzy) {
  879. buffer = currentResult.buffer;
  880. pos = currentResult.pos;
  881. }
  882. // full validate target
  883. tokenMatch = getTokenMatch.call(inputmask, pos, opts, maskset);
  884. if (
  885. tokenMatch.targetMatch &&
  886. tokenMatch.targetMatch[0] &&
  887. formatCode[tokenMatch.targetMatch[0]] !== undefined
  888. ) {
  889. const fcode = formatcode(tokenMatch.targetMatch);
  890. validator = fcode[0];
  891. const part = buffer.slice(
  892. tokenMatch.targetMatchIndex,
  893. tokenMatch.targetMatchIndex + tokenMatch.targetMatch[0].length
  894. );
  895. if (
  896. new RegExp(validator).test(part.join("")) === false &&
  897. tokenMatch.targetMatch[0].length === 2 &&
  898. maskset.validPositions[tokenMatch.targetMatchIndex] &&
  899. maskset.validPositions[tokenMatch.targetMatchIndex + 1]
  900. ) {
  901. maskset.validPositions[tokenMatch.targetMatchIndex + 1].input = "0";
  902. }
  903. if (fcode[2] == "year") {
  904. const _buffer = getMaskTemplate.call(
  905. inputmask,
  906. false,
  907. 1,
  908. undefined,
  909. true
  910. );
  911. for (let i = pos + 1; i < buffer.length; i++) {
  912. buffer[i] = _buffer[i];
  913. maskset.validPositions.splice(pos + 1, 1);
  914. }
  915. }
  916. }
  917. let result = currentResult,
  918. dateParts = analyseMask.call(
  919. inputmask,
  920. buffer.join(""),
  921. opts.inputFormat,
  922. opts
  923. );
  924. if (result && !isNaN(dateParts.date.getTime())) {
  925. // check for a valid date ~ an invalid date returns NaN which isn't equal
  926. if (opts.prefillYear) result = prefillYear(dateParts, result, opts);
  927. result = isValidDate.call(inputmask, dateParts, result, opts);
  928. result = isDateInRange(dateParts, result, opts, maskset, fromCheckval);
  929. }
  930. if (pos !== undefined && result && currentResult.pos !== pos) {
  931. return {
  932. buffer: parse(opts.inputFormat, dateParts, opts).split(""),
  933. refreshFromBuffer: { start: pos, end: currentResult.pos },
  934. pos: currentResult.caret || currentResult.pos // correct caret position
  935. };
  936. }
  937. return result;
  938. },
  939. onKeyDown: function (e, buffer, caretPos, opts) {
  940. const input = this;
  941. if (e.ctrlKey && e.key === keys.ArrowRight) {
  942. input.inputmask._valueSet(importDate(new Date(), opts));
  943. $(input).trigger("setvalue");
  944. }
  945. },
  946. onUnMask: function (maskedValue, unmaskedValue, opts) {
  947. const inputmask = this;
  948. return unmaskedValue
  949. ? parse(
  950. opts.outputFormat,
  951. analyseMask.call(inputmask, maskedValue, opts.inputFormat, opts),
  952. opts,
  953. true
  954. )
  955. : unmaskedValue;
  956. },
  957. casing: function (elem, test, pos, validPositions) {
  958. if (test.nativeDef.indexOf("[ap]") == 0) return elem.toLowerCase();
  959. if (test.nativeDef.indexOf("[AP]") == 0) return elem.toUpperCase();
  960. const posBefore = getTest.call(this, [pos - 1]);
  961. if (posBefore.match.def.indexOf("[AP]") == 0) return elem.toUpperCase();
  962. if (
  963. pos === 0 ||
  964. (posBefore && posBefore.input === String.fromCharCode(keyCode.Space)) ||
  965. (posBefore &&
  966. posBefore.match.def === String.fromCharCode(keyCode.Space))
  967. ) {
  968. return elem.toUpperCase();
  969. }
  970. return elem.toLowerCase();
  971. },
  972. onBeforeMask: function (initialValue, opts) {
  973. if (Object.prototype.toString.call(initialValue) === "[object Date]") {
  974. initialValue = importDate(initialValue, opts);
  975. }
  976. return initialValue;
  977. },
  978. insertMode: false,
  979. insertModeVisual: false,
  980. shiftPositions: false,
  981. keepStatic: false,
  982. inputmode: "numeric",
  983. prefillYear: true // Allows to disable prefill for datetime year.
  984. }
  985. });