inputmask.date.extensions.js 30 KB

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