inputmask.date.extensions.js 31 KB

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