Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DatePicker: Support literal strings #15525

Merged
merged 1 commit into from
May 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/docs/en-US/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/docs/es/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/docs/fr-FR/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/docs/zh-CN/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
84 changes: 54 additions & 30 deletions src/utils/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}],
Expand All @@ -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) {
Expand All @@ -211,18 +219,18 @@
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);
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
}
}]
};
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;
Expand All @@ -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',
Expand Down Expand Up @@ -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();
});
};

/**
Expand All @@ -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();
Expand Down
59 changes: 59 additions & 0 deletions test/unit/specs/date-picker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,37 @@ describe('DatePicker', () => {
}, DELAY);
});

it('with literal string', done => {
vm = createVue({
template: `
<el-date-picker
ref="compo"
v-model="value"
type="date"
value-format="dd/MM yyyy [Element]" />`,
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: `
Expand Down Expand Up @@ -549,6 +580,34 @@ describe('DatePicker', () => {
}, DELAY);
});

it('translates format to value-format with literal string', done => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我看log前面挂了几个TypeError?几个table的date和value必须是Date对象或者null,不能是数字或者字符串

这个测试应该是keyDown Enter没效果。测试里面可以直接调handleInputChange

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wacky6 问题修复了,改的 fecha 的问题,parse 方法不支持 literal string,再 review 下。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok。

估计下周我才能看。电脑炸机了,暂时没法测

vm = createVue({
template: `
<el-date-picker
ref="compo"
v-model="value"
type="date"
format="[Element] yyyy-MM-dd"
value-format="dd/MM yyyy [UI]" />`,
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: `
Expand Down