inputmask.date.extensions.js 32 KB

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