quival 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/.eslintrc.cjs +13 -0
- package/.prettierrc.json +5 -0
- package/LICENSE.md +21 -0
- package/README.md +157 -0
- package/package.json +27 -0
- package/src/Checkers.js +876 -0
- package/src/ErrorBag.js +68 -0
- package/src/Lang.js +30 -0
- package/src/Replacers.js +260 -0
- package/src/Validator.js +446 -0
- package/src/helpers.js +229 -0
- package/src/index.js +7 -0
- package/src/locales/en.js +161 -0
- package/src/locales/ms.js +161 -0
package/src/Validator.js
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import Checkers from './Checkers.js';
|
|
2
|
+
import ErrorBag from './ErrorBag.js';
|
|
3
|
+
import Lang from './Lang.js';
|
|
4
|
+
import Replacers from './Replacers.js';
|
|
5
|
+
import { flattenObject, getByPath, isEmpty, isNumeric, isPlainObject, toCamelCase, toSnakeCase } from './helpers.js';
|
|
6
|
+
|
|
7
|
+
export default class Validator {
|
|
8
|
+
static #customCheckers = {};
|
|
9
|
+
static #customReplacers = {};
|
|
10
|
+
|
|
11
|
+
static #dummyRules = [
|
|
12
|
+
'active_url',
|
|
13
|
+
'bail',
|
|
14
|
+
'current_password',
|
|
15
|
+
'enum',
|
|
16
|
+
'exclude',
|
|
17
|
+
'exclude_if',
|
|
18
|
+
'exclude_unless',
|
|
19
|
+
'exclude_with',
|
|
20
|
+
'exclude_without',
|
|
21
|
+
'exists',
|
|
22
|
+
'nullable',
|
|
23
|
+
'sometimes',
|
|
24
|
+
'unique',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
static #implicitRules = [
|
|
28
|
+
'accepted',
|
|
29
|
+
'accepted_if',
|
|
30
|
+
'declined',
|
|
31
|
+
'declined_if',
|
|
32
|
+
'filled',
|
|
33
|
+
'missing',
|
|
34
|
+
'missing_if',
|
|
35
|
+
'missing_unless',
|
|
36
|
+
'missing_with',
|
|
37
|
+
'missing_with_all',
|
|
38
|
+
'present',
|
|
39
|
+
'required',
|
|
40
|
+
'required_if',
|
|
41
|
+
'required_if_accepted',
|
|
42
|
+
'required_unless',
|
|
43
|
+
'required_with',
|
|
44
|
+
'required_with_all',
|
|
45
|
+
'required_without',
|
|
46
|
+
'required_without_all',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
#data;
|
|
50
|
+
#rules;
|
|
51
|
+
#customMessages;
|
|
52
|
+
#customAttributes;
|
|
53
|
+
#customValues;
|
|
54
|
+
|
|
55
|
+
#checkers;
|
|
56
|
+
#replacers;
|
|
57
|
+
#errors;
|
|
58
|
+
|
|
59
|
+
#implicitAttributes = {};
|
|
60
|
+
#stopOnFirstFailure = false;
|
|
61
|
+
#alwaysBail = false;
|
|
62
|
+
|
|
63
|
+
fileRules = ['file', 'image', 'mimetypes', 'mimes'];
|
|
64
|
+
numericRules = ['decimal', 'numeric', 'integer'];
|
|
65
|
+
sizeRules = ['size', 'between', 'min', 'max', 'gt', 'lt', 'gte', 'lte'];
|
|
66
|
+
|
|
67
|
+
static setLocale(locale) {
|
|
68
|
+
Lang.locale(locale);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static setMessages(locale, messages) {
|
|
72
|
+
Lang.setMessages(locale, messages);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static addChecker(rule, method, message) {
|
|
76
|
+
Validator.#customCheckers[rule] = method;
|
|
77
|
+
|
|
78
|
+
if (message) {
|
|
79
|
+
Lang.set(rule, message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static addImplicitChecker(rule, method, message) {
|
|
84
|
+
Validator.addChecker(rule, method, message);
|
|
85
|
+
Validator.#implicitRules.push(rule);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static addReplacer(rule, method) {
|
|
89
|
+
Validator.#customReplacers[rule] = method;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static addDummyRule(rule) {
|
|
93
|
+
Validator.#dummyRules.push(rule);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
constructor(data = {}, rules = {}, messages = {}, attributes = {}, values = {}) {
|
|
97
|
+
this.setProperties(data, rules, messages, attributes, values);
|
|
98
|
+
|
|
99
|
+
this.#checkers = new Checkers(this);
|
|
100
|
+
this.#replacers = new Replacers(this);
|
|
101
|
+
|
|
102
|
+
for (const [rule, checker] of Object.entries(Validator.#customCheckers)) {
|
|
103
|
+
this.#checkers[toCamelCase('check_' + rule)] = checker;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [rule, replacer] of Object.entries(Validator.#customReplacers)) {
|
|
107
|
+
this.#replacers[toCamelCase('replace_' + rule)] = replacer;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setProperties(data = {}, rules = {}, messages = {}, attributes = {}, values = {}) {
|
|
112
|
+
this.#data = data;
|
|
113
|
+
this.#rules = this.parseRules(rules);
|
|
114
|
+
this.#customMessages = messages;
|
|
115
|
+
this.#customAttributes = attributes;
|
|
116
|
+
this.#customValues = flattenObject(values);
|
|
117
|
+
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setData(data) {
|
|
122
|
+
this.#data = data;
|
|
123
|
+
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setRules(rules) {
|
|
128
|
+
this.#rules = this.parseRules(rules);
|
|
129
|
+
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setCustomMessages(messages) {
|
|
134
|
+
this.#customMessages = messages;
|
|
135
|
+
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setCustomAttributes(attributes) {
|
|
140
|
+
this.#customAttributes = attributes;
|
|
141
|
+
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setCustomValues(values) {
|
|
146
|
+
this.#customValues = flattenObject(values);
|
|
147
|
+
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
addImplicitAttribute(implicitAttribute, attribute) {
|
|
152
|
+
this.#implicitAttributes[implicitAttribute] = attribute;
|
|
153
|
+
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stopOnFirstFailure(flag = true) {
|
|
158
|
+
this.#stopOnFirstFailure = flag;
|
|
159
|
+
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
alwaysBail(flag = true) {
|
|
164
|
+
this.#alwaysBail = flag;
|
|
165
|
+
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
parseRules(rules) {
|
|
170
|
+
const parsedRules = {};
|
|
171
|
+
|
|
172
|
+
const parseAttributeRules = (rules) => {
|
|
173
|
+
if (Array.isArray(rules)) {
|
|
174
|
+
return rules;
|
|
175
|
+
} else {
|
|
176
|
+
return String(rules).split('|');
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
for (const [attribute, attributeRules] of Object.entries(rules)) {
|
|
181
|
+
const attributes = attribute.includes('*') ? this.parseWildcardAttribute(attribute) : [attribute];
|
|
182
|
+
|
|
183
|
+
for (const attribute of attributes) {
|
|
184
|
+
const parsedAttributeRules = {};
|
|
185
|
+
|
|
186
|
+
for (const attributeRule of parseAttributeRules(attributeRules)) {
|
|
187
|
+
const parts = attributeRule.split(':');
|
|
188
|
+
const rule = parts[0];
|
|
189
|
+
const parameters = parts
|
|
190
|
+
.slice(1)
|
|
191
|
+
.join(':')
|
|
192
|
+
.split(',')
|
|
193
|
+
.filter((part) => part !== '');
|
|
194
|
+
|
|
195
|
+
parsedAttributeRules[rule] = parameters;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
parsedRules[attribute] = parsedAttributeRules;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return parsedRules;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
parseWildcardAttribute(attribute) {
|
|
206
|
+
const attributes = [];
|
|
207
|
+
const index = attribute.indexOf('*');
|
|
208
|
+
const parentPath = attribute.substring(0, index - 1);
|
|
209
|
+
const childPath = attribute.substring(index + 2);
|
|
210
|
+
const data = this.getValue(parentPath);
|
|
211
|
+
|
|
212
|
+
if (!(Array.isArray(data) || isPlainObject(data))) {
|
|
213
|
+
return [attribute];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
217
|
+
const implicitAttribute = `${parentPath}.${key}.${childPath}`.replace(/\.$/, '');
|
|
218
|
+
const implicitAttributes = implicitAttribute.includes('*') ? this.parseWildcardAttribute(implicitAttribute) : [implicitAttribute];
|
|
219
|
+
|
|
220
|
+
attributes.push(...implicitAttributes);
|
|
221
|
+
implicitAttributes.forEach((value) => (this.#implicitAttributes[value] = attribute));
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return attributes;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async validate() {
|
|
228
|
+
this.#checkers.clearCaches();
|
|
229
|
+
this.#errors = new ErrorBag();
|
|
230
|
+
|
|
231
|
+
for (const [attribute, rules] of Object.entries(this.#rules)) {
|
|
232
|
+
let value = this.getValue(attribute);
|
|
233
|
+
|
|
234
|
+
const doBail = this.#alwaysBail || rules.hasOwnProperty('bail');
|
|
235
|
+
const isNullable = rules.hasOwnProperty('nullable');
|
|
236
|
+
|
|
237
|
+
if (rules.hasOwnProperty('sometimes') && typeof value === 'undefined') {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const [rule, parameters] of Object.entries(rules)) {
|
|
242
|
+
if (
|
|
243
|
+
!Validator.#implicitRules.includes(rule) &&
|
|
244
|
+
(typeof value == 'undefined' || (typeof value === 'string' && value.trim() === '') || (isNullable && value === null))
|
|
245
|
+
) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let result, status, message;
|
|
250
|
+
const camelRule = toCamelCase('check_' + rule);
|
|
251
|
+
|
|
252
|
+
if (typeof this.#checkers[camelRule] === 'function') {
|
|
253
|
+
result = await this.#checkers[camelRule](attribute, value, parameters);
|
|
254
|
+
} else if (Validator.#dummyRules.includes(rule)) {
|
|
255
|
+
result = true;
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error(`Invalid validation rule: ${rule}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (typeof result === 'boolean') {
|
|
261
|
+
status = result;
|
|
262
|
+
} else {
|
|
263
|
+
({ status, message } = result);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!status) {
|
|
267
|
+
message = isEmpty(message) ? this.getMessage(attribute, rule) : message;
|
|
268
|
+
message = this.makeReplacements(message, attribute, rule, parameters);
|
|
269
|
+
|
|
270
|
+
this.#errors.add(attribute, message);
|
|
271
|
+
|
|
272
|
+
if (doBail || Validator.#implicitRules.includes(rule)) {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (this.#stopOnFirstFailure && this.#errors.isNotEmpty()) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const messages = this.#errors.messages();
|
|
284
|
+
|
|
285
|
+
if (Object.keys(messages).length > 0) {
|
|
286
|
+
throw messages;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
getMessage(attribute, rule) {
|
|
291
|
+
const value = this.getValue(attribute);
|
|
292
|
+
attribute = this.getPrimaryAttribute(attribute);
|
|
293
|
+
|
|
294
|
+
let message;
|
|
295
|
+
|
|
296
|
+
for (const key of [`${attribute}.${rule}`, rule]) {
|
|
297
|
+
if (this.#customMessages.hasOwnProperty(key)) {
|
|
298
|
+
message = this.#customMessages[key];
|
|
299
|
+
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!message) {
|
|
305
|
+
let key = rule;
|
|
306
|
+
|
|
307
|
+
if (this.sizeRules.includes(key)) {
|
|
308
|
+
if (Array.isArray(value) || isPlainObject(value) || this.hasRule(attribute, 'array')) {
|
|
309
|
+
key += '.array';
|
|
310
|
+
} else if (value instanceof File || this.hasRule(attribute, this.fileRules)) {
|
|
311
|
+
key += '.file';
|
|
312
|
+
} else if (typeof value === 'number' || this.hasRule(attribute, this.numericRules)) {
|
|
313
|
+
key += '.numeric';
|
|
314
|
+
} else {
|
|
315
|
+
key += '.string';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
message = Lang.get(key);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return message ?? `validation.${rule}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
makeReplacements(message, attribute, rule, parameters) {
|
|
326
|
+
const attributeName = this.getDisplayableAttribute(attribute);
|
|
327
|
+
const value = this.getValue(attribute);
|
|
328
|
+
|
|
329
|
+
const data = {
|
|
330
|
+
attribute: attributeName,
|
|
331
|
+
ATTRIBUTE: attributeName.toLocaleUpperCase(),
|
|
332
|
+
Attribute: attributeName.charAt(0).toLocaleUpperCase() + attributeName.substring(1),
|
|
333
|
+
input: this.getDisplayableValue(attribute, value),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
for (const [key, value] of Object.entries(data)) {
|
|
337
|
+
message = message.replaceAll(':' + key, value);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const match = attribute.match(/\.(\d+)\.?/);
|
|
341
|
+
const index = match === null ? -1 : parseInt(match[1], 10);
|
|
342
|
+
|
|
343
|
+
if (index !== -1) {
|
|
344
|
+
message = message.replaceAll(':index', index).replaceAll(':position', index + 1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const camelRule = toCamelCase('replace_' + rule);
|
|
348
|
+
|
|
349
|
+
if (typeof this.#replacers[camelRule] === 'function') {
|
|
350
|
+
message = this.#replacers[camelRule](message, attribute, rule, parameters);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return message;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getDisplayableAttribute(attribute) {
|
|
357
|
+
const unparsed = this.getPrimaryAttribute(attribute);
|
|
358
|
+
|
|
359
|
+
for (const name of [attribute, unparsed]) {
|
|
360
|
+
if (this.#customAttributes.hasOwnProperty(name)) {
|
|
361
|
+
return this.#customAttributes[name];
|
|
362
|
+
} else if (Lang.has(`attributes.${name}`)) {
|
|
363
|
+
return Lang.get(`attributes.${name}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (this.#implicitAttributes.hasOwnProperty(attribute)) {
|
|
368
|
+
return attribute;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return toSnakeCase(attribute).replaceAll('_', ' ');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
getDisplayableValue(attribute, value) {
|
|
375
|
+
attribute = this.getPrimaryAttribute(attribute);
|
|
376
|
+
|
|
377
|
+
const path = `${attribute}.${value}`;
|
|
378
|
+
|
|
379
|
+
if (isEmpty(value)) {
|
|
380
|
+
return 'empty';
|
|
381
|
+
} else if (typeof value === 'boolean' || this.hasRule(attribute, 'boolean')) {
|
|
382
|
+
return Number(value) ? 'true' : 'false';
|
|
383
|
+
} else if (this.#customValues.hasOwnProperty(path)) {
|
|
384
|
+
return this.#customValues[path];
|
|
385
|
+
} else if (Lang.has(`values.${path}`)) {
|
|
386
|
+
return Lang.get(`values.${path}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
getSize(attribute, value) {
|
|
393
|
+
if (isEmpty(value)) {
|
|
394
|
+
return 0;
|
|
395
|
+
} else if (isNumeric(value) && this.hasRule(attribute, this.numericRules)) {
|
|
396
|
+
return parseFloat(typeof value === 'string' ? value.trim() : value, 10);
|
|
397
|
+
} else if (value instanceof File) {
|
|
398
|
+
return value.size / 1024;
|
|
399
|
+
} else if (isPlainObject(value)) {
|
|
400
|
+
return Object.keys(value).length;
|
|
401
|
+
} else if (value.hasOwnProperty('length')) {
|
|
402
|
+
return value.length;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return value;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
getRule(attribute) {
|
|
409
|
+
attribute = this.getPrimaryAttribute(attribute);
|
|
410
|
+
|
|
411
|
+
return this.#rules[attribute];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
hasRule(attribute, rules) {
|
|
415
|
+
attribute = this.getPrimaryAttribute(attribute);
|
|
416
|
+
rules = typeof rules === 'string' ? [rules] : rules;
|
|
417
|
+
|
|
418
|
+
if (!this.#rules.hasOwnProperty(attribute)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (const rule of rules) {
|
|
423
|
+
if (this.#rules[attribute].hasOwnProperty(rule)) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
getPrimaryAttribute(attribute) {
|
|
432
|
+
return this.#implicitAttributes.hasOwnProperty(attribute) ? this.#implicitAttributes[attribute] : attribute;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
hasAttribute(attribute) {
|
|
436
|
+
return typeof this.getValue(attribute) !== 'undefined';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getValue(attribute) {
|
|
440
|
+
return getByPath(this.#data, attribute);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
errors() {
|
|
444
|
+
return this.#errors;
|
|
445
|
+
}
|
|
446
|
+
}
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export function toCamelCase(string) {
|
|
2
|
+
return string.replace(/(_\w)/g, (match) => match[1].toUpperCase());
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function toSnakeCase(string) {
|
|
6
|
+
return string.replace(/[\w]([A-Z])/g, (match) => match[0] + '_' + match[1].toLowerCase()).toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function escapeRegExp(string) {
|
|
10
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getByPath(obj, path, defaultValue) {
|
|
14
|
+
const keys = path.split('.');
|
|
15
|
+
let current = obj;
|
|
16
|
+
|
|
17
|
+
for (const key of keys) {
|
|
18
|
+
if (!current.hasOwnProperty(key)) {
|
|
19
|
+
return defaultValue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
current = current[key];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function setByPath(obj, path, value) {
|
|
29
|
+
const keys = path.split('.');
|
|
30
|
+
let current = obj;
|
|
31
|
+
|
|
32
|
+
for (const key of keys.slice(0, -1)) {
|
|
33
|
+
if (!current.hasOwnProperty(key)) {
|
|
34
|
+
current[key] = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
current = current[key];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
current[keys.slice(-1)] = value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function flattenObject(obj, prefix = '') {
|
|
44
|
+
return Object.keys(obj).reduce((accumulator, key) => {
|
|
45
|
+
const prefixedKey = prefix ? `${prefix}.${key}` : key;
|
|
46
|
+
|
|
47
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
48
|
+
Object.assign(accumulator, flattenObject(obj[key], prefixedKey));
|
|
49
|
+
} else {
|
|
50
|
+
accumulator[prefixedKey] = obj[key];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return accumulator;
|
|
54
|
+
}, {});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseDate(value) {
|
|
58
|
+
if (isEmpty(value)) {
|
|
59
|
+
return new Date('');
|
|
60
|
+
} else if (value instanceof Date) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let match, years, months, days, hours, minutes, seconds, meridiem;
|
|
65
|
+
|
|
66
|
+
const castToIntegers = (value) => (value && /^\d*$/.test(value) ? parseInt(value) : value);
|
|
67
|
+
|
|
68
|
+
if ((match = value.match(/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{2,4})\s?((\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s?(am|pm)?)?/i)) !== null) {
|
|
69
|
+
[, days, months, years, , hours = 0, minutes = 0, , seconds = 0, meridiem = 'am'] = match.map(castToIntegers);
|
|
70
|
+
} else if (
|
|
71
|
+
(match = value.match(/^(\d{2,4})[.\/-](\d{1,2})[.\/-](\d{1,2})\s?((\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s?(am|pm)?)?/i)) !== null ||
|
|
72
|
+
(match = value.match(/^(\d{4})(\d{2})(\d{2})\s?((\d{2})(\d{2})((\d{2}))?\s?(am|pm)?)?/i)) !== null
|
|
73
|
+
) {
|
|
74
|
+
[, years, months, days, , hours = 0, minutes = 0, , seconds = 0, meridiem = 'am'] = match.map(castToIntegers);
|
|
75
|
+
} else if ((match = value.match(/(\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s?(am|pm)?\s?(\d{4})[.\/-](\d{2})[.\/-](\d{2})/i))) {
|
|
76
|
+
[, hours, minutes, , seconds, meridiem = 'am', years, months, days] = match.map(castToIntegers);
|
|
77
|
+
} else if ((match = value.match(/(\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s?(am|pm)?\s?(\d{2})[.\/-](\d{2})[.\/-](\d{4})/i))) {
|
|
78
|
+
[, hours, minutes, , seconds, meridiem = 'am', days, months, years] = match.map(castToIntegers);
|
|
79
|
+
} else if ((match = value.match(/(\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s?(am|pm)?/i))) {
|
|
80
|
+
const current = new Date();
|
|
81
|
+
|
|
82
|
+
years = current.getFullYear();
|
|
83
|
+
months = current.getMonth() + 1;
|
|
84
|
+
days = current.getDate();
|
|
85
|
+
|
|
86
|
+
[, hours = 0, minutes = 0, , seconds = 0, meridiem = 'am'] = match.map(castToIntegers);
|
|
87
|
+
} else {
|
|
88
|
+
return new Date(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (years >= 10 && years < 100) {
|
|
92
|
+
years += 2000;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (meridiem.toLowerCase() === 'pm' && hours < 12) {
|
|
96
|
+
hours += 12;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Date(`${years}-${months}-${days} ${hours}:${minutes}:${seconds}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function parseDateByFormat(value, format) {
|
|
103
|
+
if (isEmpty(value)) {
|
|
104
|
+
return new Date('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
format = format.split('');
|
|
108
|
+
|
|
109
|
+
const formats = {
|
|
110
|
+
Y: '(\\d{4})',
|
|
111
|
+
y: '(\\d{2})',
|
|
112
|
+
m: '(\\d{2})',
|
|
113
|
+
n: '([1-9]\\d?)',
|
|
114
|
+
d: '(\\d{2})',
|
|
115
|
+
j: '([1-9]\\d?)',
|
|
116
|
+
G: '([1-9]\\d?)',
|
|
117
|
+
g: '([1-9]\\d?)',
|
|
118
|
+
H: '(\\d{2})',
|
|
119
|
+
h: '(\\d{2})',
|
|
120
|
+
i: '(\\d{2})',
|
|
121
|
+
s: '(\\d{2})',
|
|
122
|
+
A: '(AM|PM)',
|
|
123
|
+
a: '(am|pm)',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
let pattern = '^';
|
|
127
|
+
let indices = {
|
|
128
|
+
years: -1,
|
|
129
|
+
months: -1,
|
|
130
|
+
days: -1,
|
|
131
|
+
hours: -1,
|
|
132
|
+
minutes: -1,
|
|
133
|
+
seconds: -1,
|
|
134
|
+
meridiem: -1,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let index = 1;
|
|
138
|
+
|
|
139
|
+
for (const char of format) {
|
|
140
|
+
if (formats.hasOwnProperty(char)) {
|
|
141
|
+
pattern += formats[char];
|
|
142
|
+
|
|
143
|
+
if (['Y', 'y'].indexOf(char) !== -1) {
|
|
144
|
+
indices.years = index++;
|
|
145
|
+
} else if (['m', 'n'].indexOf(char) !== -1) {
|
|
146
|
+
indices.months = index++;
|
|
147
|
+
} else if (['d', 'j'].indexOf(char) !== -1) {
|
|
148
|
+
indices.days = index++;
|
|
149
|
+
} else if (['G', 'g', 'H', 'h'].indexOf(char) !== -1) {
|
|
150
|
+
indices.hours = index++;
|
|
151
|
+
} else if (char === 'i') {
|
|
152
|
+
indices.minutes = index++;
|
|
153
|
+
} else if (char === 's') {
|
|
154
|
+
indices.seconds = index++;
|
|
155
|
+
} else if (['A', 'a'].indexOf(char) !== -1) {
|
|
156
|
+
indices.meridiem = index++;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
pattern += '\\' + char;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pattern += '$';
|
|
164
|
+
|
|
165
|
+
let match = value.match(new RegExp(pattern));
|
|
166
|
+
|
|
167
|
+
if (match === null) {
|
|
168
|
+
return new Date('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
match = match.map((value) => (value && /^\d*$/.test(value) ? parseInt(value) : value));
|
|
172
|
+
|
|
173
|
+
const current = new Date();
|
|
174
|
+
|
|
175
|
+
let years = match[indices.years];
|
|
176
|
+
let months = match[indices.months];
|
|
177
|
+
let days = match[indices.days];
|
|
178
|
+
let hours = match[indices.hours] ?? 0;
|
|
179
|
+
let minutes = match[indices.minutes] ?? 0;
|
|
180
|
+
let seconds = match[indices.seconds] ?? 0;
|
|
181
|
+
let meridiem = match[indices.meridiem] ?? 'am';
|
|
182
|
+
|
|
183
|
+
if (!years && !months && !days) {
|
|
184
|
+
years = current.getFullYear();
|
|
185
|
+
months = current.getMonth() + 1;
|
|
186
|
+
days = current.getDate();
|
|
187
|
+
} else if (years && !months && !days) {
|
|
188
|
+
months = 1;
|
|
189
|
+
days = 1;
|
|
190
|
+
} else if (!years && months && !days) {
|
|
191
|
+
years = current.getFullYear();
|
|
192
|
+
days = 1;
|
|
193
|
+
} else if (!years && !months && days) {
|
|
194
|
+
years = current.getFullYear();
|
|
195
|
+
months = current.getMonth() + 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (years >= 10 && years < 100) {
|
|
199
|
+
years = years + 2000;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (meridiem.toLowerCase() === 'pm' && hours < 12) {
|
|
203
|
+
hours += 12;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return new Date(`${years}-${months}-${days} ${hours}:${minutes}:${seconds}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function isDigits(value) {
|
|
210
|
+
return String(value).search(/[^0-9]/) === -1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function isEmpty(value) {
|
|
214
|
+
return value === '' || value === null || typeof value === 'undefined';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function isNumeric(value) {
|
|
218
|
+
const number = Number(value);
|
|
219
|
+
|
|
220
|
+
return value !== null && typeof value !== 'boolean' && typeof number === 'number' && !isNaN(number);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function isPlainObject(value) {
|
|
224
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function isValidDate(value) {
|
|
228
|
+
return value instanceof Date && value.toDateString() !== 'Invalid Date';
|
|
229
|
+
}
|
package/src/index.js
ADDED