diff --git a/examples/docs/en-US/date-picker.md b/examples/docs/en-US/date-picker.md index 2d6ddeb134b..83afac5a84f 100644 --- a/examples/docs/en-US/date-picker.md +++ b/examples/docs/en-US/date-picker.md @@ -346,6 +346,7 @@ Pay attention to capitalization | `A` | AM/PM | only for `format`, uppercased | AM | | `a` | am/pm | only for `format`, lowercased | am | | `timestamp` | JS timestamp | only for `value-format`; binding value will be a `number` | 1483326245000 | +| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM | :::demo ```html diff --git a/examples/docs/es/date-picker.md b/examples/docs/es/date-picker.md index 5ad473c3798..a87af0d17bb 100644 --- a/examples/docs/es/date-picker.md +++ b/examples/docs/es/date-picker.md @@ -346,6 +346,7 @@ Preste atención a la capitalización | `A` | AM/PM | solamente para `format`, mayusculas | AM | | `a` | am/pm | solamente para `format`, minúsculas | am | | `timestamp` | JS timestamp | solamente para `value-format`; valor vinculado debe ser un `number` | 1483326245000 | +| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM | :::demo ```html diff --git a/examples/docs/fr-FR/date-picker.md b/examples/docs/fr-FR/date-picker.md index f55f24c07d4..30b9aa79325 100644 --- a/examples/docs/fr-FR/date-picker.md +++ b/examples/docs/fr-FR/date-picker.md @@ -347,6 +347,7 @@ Attention à la capitalisation ! | `A` | AM/PM | uniquement pour `format`, majuscules | AM | | `a` | am/pm | uniquement pour `format`, minuscules | am | | `timestamp` | timestamp JS | uniquement pour `value-format`; la variable stockée sera un `number` | 1483326245000 | +| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM | :::demo ```html diff --git a/examples/docs/zh-CN/date-picker.md b/examples/docs/zh-CN/date-picker.md index c9bf17863b1..6c17d29523e 100644 --- a/examples/docs/zh-CN/date-picker.md +++ b/examples/docs/zh-CN/date-picker.md @@ -299,6 +299,7 @@ | `A` | AM/PM | 仅 `format` 可用,大写 | AM | | `a` | am/pm | 仅 `format` 可用,小写 | am | | `timestamp` | JS时间戳 | 仅 `value-format` 可用;组件绑定值为`number`类型 | 1483326245000 | +| `[MM]` | 不需要格式化字符 | 使用方括号标识不需要格式化的字符 (如 [A] [MM]) | MM | :::demo ```html diff --git a/src/utils/date.js b/src/utils/date.js index 42db03067fb..001692c4536 100644 --- a/src/utils/date.js +++ b/src/utils/date.js @@ -34,13 +34,18 @@ */ var fecha = {}; var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g; - var twoDigits = /\d\d?/; - var threeDigits = /\d{3}/; - var fourDigits = /\d{4}/; - var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + var twoDigits = '\\d\\d?'; + var threeDigits = '\\d{3}'; + var fourDigits = '\\d{4}'; + var word = '[^\\s]+'; + var literal = /\[([^]*?)\]/gm; var noop = function () { }; + function regexEscape(str) { + return str.replace( /[|\\{()[^$+*?.-]/g, '\\$&'); + } + function shorten(arr, sLen) { var newArr = []; for (var i = 0, len = arr.length; i < len; i++) { @@ -117,10 +122,10 @@ return i18n.monthNames[dateObj.getMonth()]; }, yy: function(dateObj) { - return String(dateObj.getFullYear()).substr(2); + return pad(String(dateObj.getFullYear()), 4).substr(2); }, yyyy: function(dateObj) { - return dateObj.getFullYear(); + return pad(dateObj.getFullYear(), 4); }, h: function(dateObj) { return dateObj.getHours() % 12 || 12; @@ -171,6 +176,9 @@ d: [twoDigits, function (d, v) { d.day = v; }], + Do: [twoDigits + word, function (d, v) { + d.day = parseInt(v, 10); + }], M: [twoDigits, function (d, v) { d.month = v - 1; }], @@ -190,10 +198,10 @@ yyyy: [fourDigits, function (d, v) { d.year = v; }], - S: [/\d/, function (d, v) { + S: ['\\d', function (d, v) { d.millisecond = v * 100; }], - SS: [/\d{2}/, function (d, v) { + SS: ['\\d{2}', function (d, v) { d.millisecond = v * 10; }], SSS: [threeDigits, function (d, v) { @@ -211,8 +219,8 @@ d.isPm = true; } }], - ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) { - var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes; + ZZ: ['[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z', function (d, v) { + var parts = (v + '').match(/([+-]|\d\d)/gi), minutes; if (parts) { minutes = +(parts[1] * 60) + parseInt(parts[2], 10); @@ -220,9 +228,9 @@ } }] }; - parseFlags.DD = parseFlags.D; + parseFlags.dd = parseFlags.d; parseFlags.dddd = parseFlags.ddd; - parseFlags.Do = parseFlags.dd = parseFlags.d; + parseFlags.DD = parseFlags.D; parseFlags.mm = parseFlags.m; parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h; parseFlags.MM = parseFlags.M; @@ -232,7 +240,7 @@ // Some common format strings fecha.masks = { - 'default': 'ddd MMM dd yyyy HH:mm:ss', + default: 'ddd MMM dd yyyy HH:mm:ss', shortDate: 'M/D/yy', mediumDate: 'MMM d, yyyy', longDate: 'MMMM d, yyyy', @@ -261,9 +269,21 @@ mask = fecha.masks[mask] || mask || fecha.masks['default']; - return mask.replace(token, function ($0) { + var literals = []; + + // Make literals inactive by replacing them with ?? + mask = mask.replace(literal, function($0, $1) { + literals.push($1); + return '@@@'; + }); + // Apply formatting rules + mask = mask.replace(token, function ($0) { return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1); }); + // Inline literal values back into the formatted value + return mask.replace(/@@@/g, function() { + return literals.shift(); + }); }; /** @@ -285,31 +305,35 @@ // Avoid regular expression denial of service, fail early for really long strings // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS if (dateStr.length > 1000) { - return false; + return null; } - var isValid = true; var dateInfo = {}; - format.replace(token, function ($0) { + var parseInfo = []; + var literals = []; + format = format.replace(literal, function($0, $1) { + literals.push($1); + return '@@@'; + }); + var newFormat = regexEscape(format).replace(token, function ($0) { if (parseFlags[$0]) { var info = parseFlags[$0]; - var index = dateStr.search(info[0]); - if (!~index) { - isValid = false; - } else { - dateStr.replace(info[0], function (result) { - info[1](dateInfo, result, i18n); - dateStr = dateStr.substr(index + result.length); - return result; - }); - } + parseInfo.push(info[1]); + return '(' + info[0] + ')'; } - return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1); + return $0; }); + newFormat = newFormat.replace(/@@@/g, function() { + return literals.shift(); + }); + var matches = dateStr.match(new RegExp(newFormat, 'i')); + if (!matches) { + return null; + } - if (!isValid) { - return false; + for (var i = 1; i < matches.length; i++) { + parseInfo[i - 1](dateInfo, matches[i], i18n); } var today = new Date(); diff --git a/test/unit/specs/date-picker.spec.js b/test/unit/specs/date-picker.spec.js index 3b818e5c5cd..54180cc64f7 100644 --- a/test/unit/specs/date-picker.spec.js +++ b/test/unit/specs/date-picker.spec.js @@ -489,6 +489,37 @@ describe('DatePicker', () => { }, DELAY); }); + it('with literal string', done => { + vm = createVue({ + template: ` + `, + data() { + return { + value: '' + }; + } + }, true); + + vm.$refs.compo.$el.querySelector('input').focus(); + + setTimeout(_ => { + vm.$refs.compo.picker.$el.querySelector('.el-date-table td.available').click(); + setTimeout(_ => { + const today = new Date(); + const yyyy = today.getFullYear(); + const MM = ('0' + (today.getMonth() + 1)).slice(-2); + const dd = '01'; // first available one should be first day of month + const expectValue = `${dd}/${MM} ${yyyy} Element`; + expect(vm.value).to.equal(expectValue); + done(); + }, DELAY); + }, DELAY); + }); + it('accepts', done => { vm = createVue({ template: ` @@ -549,6 +580,34 @@ describe('DatePicker', () => { }, DELAY); }); + it('translates format to value-format with literal string', done => { + vm = createVue({ + template: ` + `, + data() { + return { + value: '' + }; + } + }, true); + const input = vm.$refs.compo.$el.querySelector('input'); + input.focus(); + setTimeout(_ => { + input.value = 'Element 2000-10-01'; + triggerEvent(input, 'input'); + keyDown(input, ENTER); + setTimeout(_ => { + expect(vm.value).to.equal('01/10 2000 UI'); + done(); + }, DELAY); + }, DELAY); + }); + it('works for daterange', done => { vm = createVue({ template: `