Skip to content
Open
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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"build": "vite build",
"analyze": "vite build --config analyze.config.ts",
"test": "yarn jest",
"lint": "eslint . --ext .js,.vue"
"lint": "eslint . --ext .js,.vue",
"vi": "vitest run"
},
"dependencies": {
"@popperjs/core": "2.4.0",
Expand Down Expand Up @@ -81,6 +82,7 @@
"ts-jest": "^27.1.3",
"typescript": "3.9.3",
"vite": "^2.4.3",
"vitest": "^4.0.16",
"vue": "^3.1.0",
"vue3-jest": "^27.0.0-alpha.1"
},
Expand Down
143 changes: 70 additions & 73 deletions src/utils/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,85 +361,82 @@ export default class Locale {

parse(dateString, mask) {
const masks = this.normalizeMasks(mask);
return (
masks
.map(m => {
if (typeof m !== 'string') {
throw new Error('Invalid mask in fecha.parse');
}
// Reset string value
let str = dateString;
// 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 (str.length > 1000) {
return false;
}
return masks
.map(m => {
if (typeof m !== 'string') {
throw new Error('Invalid mask in fecha.parse');
}
// Reset string value
let str = dateString;
// 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 (str.length > 1000) {
return false;
}

let isValid = true;
const dateInfo = {};
m.replace(token, $0 => {
if (parseFlags[$0]) {
const info = parseFlags[$0];
const index = str.search(info[0]);
if (!~index) {
isValid = false;
} else {
str.replace(info[0], result => {
info[1](dateInfo, result, this);
str = str.substring(index + result.length);
return result;
});
}
let isValid = true;
const dateInfo = {};
m.replace(token, $0 => {
if (parseFlags[$0]) {
const info = parseFlags[$0];
const index = str.search(info[0]);
if (!~index) {
isValid = false;
} else {
str.replace(info[0], result => {
info[1](dateInfo, result, this);
str = str.substring(index + result.length);
return result;
});
}
}

return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});
return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
});

if (!isValid) {
return false;
}
if (!isValid) {
return false;
}

const today = new Date();
if (
dateInfo.isPm === true &&
dateInfo.hour != null &&
+dateInfo.hour !== 12
) {
dateInfo.hour = +dateInfo.hour + 12;
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0;
}
const today = new Date();
if (
dateInfo.isPm === true &&
dateInfo.hour != null &&
+dateInfo.hour !== 12
) {
dateInfo.hour = +dateInfo.hour + 12;
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
dateInfo.hour = 0;
}

let date;
if (dateInfo.timezoneOffset != null) {
dateInfo.minute =
+(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
date = new Date(
Date.UTC(
dateInfo.year || today.getFullYear(),
dateInfo.month || 0,
dateInfo.day || 1,
dateInfo.hour || 0,
dateInfo.minute || 0,
dateInfo.second || 0,
dateInfo.millisecond || 0,
),
);
} else {
date = this.getDateFromParts({
year: dateInfo.year || today.getFullYear(),
month: (dateInfo.month || 0) + 1,
day: dateInfo.day || 1,
hours: dateInfo.hour || 0,
minutes: dateInfo.minute || 0,
seconds: dateInfo.second || 0,
milliseconds: dateInfo.millisecond || 0,
});
}
return date;
})
.find(d => d) || new Date(dateString)
);
let date;
if (dateInfo.timezoneOffset != null) {
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
date = new Date(
Date.UTC(
dateInfo.year || today.getFullYear(),
dateInfo.month || 0,
dateInfo.day || 1,
dateInfo.hour || 0,
dateInfo.minute || 0,
dateInfo.second || 0,
dateInfo.millisecond || 0,
),
);
} else {
date = this.getDateFromParts({
year: dateInfo.year || today.getFullYear(),
month: (dateInfo.month || 0) + 1,
day: dateInfo.day || 1,
hours: dateInfo.hour || 0,
minutes: dateInfo.minute || 0,
seconds: dateInfo.second || 0,
milliseconds: dateInfo.millisecond || 0,
});
}
return date;
})
.find(d => d);
}

// Normalizes mask(s) as an array with replaced mask macros
Expand Down
29 changes: 29 additions & 0 deletions src/utils/locale.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Locale from './locale';

describe('Locale', () => {
describe('parse', () => {
let locale;

beforeEach(() => {
locale = new Locale('fr');
});

it('should parse date matching the given format', () => {
const result = locale.parse('24/12/2024 10:00', 'DD/MM/YYYY HH:mm');

expect(result).toStrictEqual(new Date(2024, 11, 24, 10, 0));
});

it('should fail to parse date not matching the format', () => {
const result = locale.parse('24/12/2024', 'DD/MM/YYYY HH:mm');

expect(result).toBe(undefined);
});

it('should fail to parse date not matching the format but in a format supported by native Date.parse', () => {
const result = locale.parse('12/24/2024', 'DD/MM/YYYY HH:mm');

expect(result).toBe(undefined);
});
});
});
11 changes: 11 additions & 0 deletions vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { coverageConfigDefaults, defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";

export default mergeConfig(viteConfig, defineConfig({
test: {
environment: "jsdom",
globals: true,
include: ["**/*.spec.ts"],
restoreMocks: true
}
}));
Loading