chrono-node
Version:
A natural language date parser in Javascript
381 lines (354 loc) • 8.46 kB
text/typescript
import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern";
import { findMostLikelyADYear } from "../../calculation/years";
import { Duration } from "../../calculation/duration";
import { Timeunit, Weekday } from "../../types";
export const WEEKDAY_DICTIONARY: { [word: string]: Weekday } = {
domenica: 0,
dom: 0,
"dom.": 0,
lunedì: 1,
lunedi: 1,
lun: 1,
"lun.": 1,
martedì: 2,
martedi: 2,
mar: 2,
"mar.": 2,
mercoledì: 3,
mercoledi: 3,
mer: 3,
"mer.": 3,
giovedì: 4,
giovedi: 4,
gio: 4,
"gio.": 4,
venerdì: 5,
venerdi: 5,
ven: 5,
"ven.": 5,
sabato: 6,
sab: 6,
"sab.": 6,
};
export const FULL_MONTH_NAME_DICTIONARY: { [word: string]: number } = {
gennaio: 1,
febbraio: 2,
marzo: 3,
aprile: 4,
maggio: 5,
giugno: 6,
luglio: 7,
agosto: 8,
settembre: 9,
ottobre: 10,
novembre: 11,
dicembre: 12,
};
export const MONTH_DICTIONARY: { [word: string]: number } = {
...FULL_MONTH_NAME_DICTIONARY,
gen: 1,
"gen.": 1,
feb: 2,
"feb.": 2,
mar: 3,
"mar.": 3,
apr: 4,
"apr.": 4,
mag: 5,
"mag.": 5,
giu: 6,
"giu.": 6,
lug: 7,
"lug.": 7,
ago: 8,
"ago.": 8,
set: 9,
"set.": 9,
sett: 9,
"sett.": 9,
ott: 10,
"ott.": 10,
nov: 11,
"nov.": 11,
dic: 12,
"dic.": 12,
};
export const INTEGER_WORD_DICTIONARY: { [word: string]: number } = {
uno: 1,
una: 1,
un: 1,
due: 2,
tre: 3,
quattro: 4,
cinque: 5,
sei: 6,
sette: 7,
otto: 8,
nove: 9,
dieci: 10,
undici: 11,
dodici: 12,
};
export const ORDINAL_WORD_DICTIONARY: { [word: string]: number } = {
primo: 1,
prima: 1,
"1°": 1,
"1ª": 1,
secondo: 2,
seconda: 2,
"2°": 2,
"2ª": 2,
terzo: 3,
terza: 3,
"3°": 3,
"3ª": 3,
quarto: 4,
quarta: 4,
"4°": 4,
"4ª": 4,
quinto: 5,
quinta: 5,
"5°": 5,
"5ª": 5,
sesto: 6,
sesta: 6,
"6°": 6,
"6ª": 6,
settimo: 7,
settima: 7,
"7°": 7,
"7ª": 7,
ottavo: 8,
ottava: 8,
"8°": 8,
"8ª": 8,
nono: 9,
nona: 9,
"9°": 9,
"9ª": 9,
decimo: 10,
decima: 10,
"10°": 10,
"10ª": 10,
undicesimo: 11,
undicesima: 11,
"11°": 11,
"11ª": 11,
dodicesimo: 12,
dodicesima: 12,
"12°": 12,
"12ª": 12,
tredicesimo: 13,
tredicesima: 13,
"13°": 13,
"13ª": 13,
quattordicesimo: 14,
quattordicesima: 14,
"14°": 14,
"14ª": 14,
quindicesimo: 15,
quindicesima: 15,
"15°": 15,
"15ª": 15,
sedicesimo: 16,
sedicesima: 16,
"16°": 16,
"16ª": 16,
diciassettesimo: 17,
diciassettesima: 17,
"17°": 17,
"17ª": 17,
diciottesimo: 18,
diciottesima: 18,
"18°": 18,
"18ª": 18,
diciannovesimo: 19,
diciannovesima: 19,
"19°": 19,
"19ª": 19,
ventesimo: 20,
ventesima: 20,
"20°": 20,
"20ª": 20,
ventunesimo: 21,
ventunesima: 21,
"21°": 21,
"21ª": 21,
ventiduesimo: 22,
ventiduesima: 22,
"22°": 22,
"22ª": 22,
ventitreesimo: 23,
ventitreesima: 23,
"23°": 23,
"23ª": 23,
ventiquattresimo: 24,
ventiquattresima: 24,
"24°": 24,
"24ª": 24,
venticinquesimo: 25,
venticinquesima: 25,
"25°": 25,
"25ª": 25,
ventiseiesimo: 26,
ventiseiesima: 26,
"26°": 26,
"26ª": 26,
ventisettesimo: 27,
ventisettesima: 27,
"27°": 27,
"27ª": 27,
ventottesimo: 28,
ventottesima: 28,
"28°": 28,
"28ª": 28,
ventinovesimo: 29,
ventinovesima: 29,
"29°": 29,
"29ª": 29,
trentesimo: 30,
trentesima: 30,
"30°": 30,
"30ª": 30,
trentunesimo: 31,
trentunesima: 31,
"31°": 31,
"31ª": 31,
};
export const TIME_UNIT_DICTIONARY_NO_ABBR: { [word: string]: Timeunit } = {
secondo: "second",
secondi: "second",
minuto: "minute",
minuti: "minute",
ora: "hour",
ore: "hour",
giorno: "day",
giorni: "day",
settimana: "week",
settimane: "week",
mese: "month",
mesi: "month",
trimestre: "quarter",
trimestri: "quarter",
anno: "year",
anni: "year",
};
export const TIME_UNIT_DICTIONARY: { [word: string]: Timeunit } = {
s: "second",
sec: "second",
secondo: "second",
secondi: "second",
m: "minute",
min: "minute",
minuto: "minute",
minuti: "minute",
h: "hour",
ora: "hour",
ore: "hour",
g: "day",
gg: "day",
giorno: "day",
giorni: "day",
sett: "week",
settimana: "week",
settimane: "week",
mese: "month",
mesi: "month",
trim: "quarter",
trimestre: "quarter",
trimestri: "quarter",
anno: "year",
anni: "year",
...TIME_UNIT_DICTIONARY_NO_ABBR,
};
//-----------------------------
export const NUMBER_PATTERN = `(?:${matchAnyPattern(
INTEGER_WORD_DICTIONARY
)}|[0-9]+|[0-9]+\\.[0-9]+|mezz[oa]?(?:\\s{0,2})?|un(?:'|\\s{0,2})?|qualche|alcuni|paio\\s{0,2}(?:di)?)`;
export function parseNumberPattern(match: string): number {
const num = match.toLowerCase();
if (INTEGER_WORD_DICTIONARY[num] !== undefined) {
return INTEGER_WORD_DICTIONARY[num];
} else if (num === "un" || num === "un'" || num === "una" || num === "uno") {
return 1;
} else if (num.match(/qualche/)) {
return 3;
} else if (num.match(/mezz/)) {
return 0.5;
} else if (num.match(/paio/)) {
return 2;
} else if (num.match(/alcuni/)) {
return 7;
}
return parseFloat(num);
}
//-----------------------------
export const ORDINAL_NUMBER_PATTERN = `(?:${matchAnyPattern(ORDINAL_WORD_DICTIONARY)}|[0-9]{1,2}(?:°|ª|º)?)`;
export function parseOrdinalNumberPattern(match: string): number {
let num = match.toLowerCase();
if (ORDINAL_WORD_DICTIONARY[num] !== undefined) {
return ORDINAL_WORD_DICTIONARY[num];
}
num = num.replace(/(?:°|ª|º)$/i, "");
return parseInt(num);
}
//-----------------------------
export const YEAR_PATTERN = `(?:[1-9][0-9]{0,3}\\s{0,2}(?:BE|AD|BC|BCE|CE)|[1-2][0-9]{3}|[5-9][0-9]|2[0-5])`;
export function parseYear(match: string): number {
if (/BE/i.test(match)) {
// Buddhist Era
match = match.replace(/BE/i, "");
return parseInt(match) - 543;
}
if (/BCE?/i.test(match)) {
// Before Christ, Before Common Era
match = match.replace(/BCE?/i, "");
return -parseInt(match);
}
if (/(AD|CE)/i.test(match)) {
// Anno Domini, Common Era
match = match.replace(/(AD|CE)/i, "");
return parseInt(match);
}
const rawYearNumber = parseInt(match);
return findMostLikelyADYear(rawYearNumber);
}
//-----------------------------
const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,3}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})`;
const SINGLE_TIME_UNIT_REGEX = new RegExp(SINGLE_TIME_UNIT_PATTERN, "i");
const SINGLE_TIME_UNIT_NO_ABBR_PATTERN = `(${NUMBER_PATTERN})\\s{0,3}(${matchAnyPattern(
TIME_UNIT_DICTIONARY_NO_ABBR
)})`;
const TIME_UNIT_CONNECTOR_PATTERN = `\\s{0,5},?(?:\\s*e)?\\s{0,5}`;
export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(
`(?:(?:circa|approssimativamente)\\s{0,3})?`,
SINGLE_TIME_UNIT_PATTERN,
TIME_UNIT_CONNECTOR_PATTERN
);
export const TIME_UNITS_NO_ABBR_PATTERN = repeatedTimeunitPattern(
`(?:(?:circa|approssimativamente)\\s{0,3})?`,
SINGLE_TIME_UNIT_NO_ABBR_PATTERN,
TIME_UNIT_CONNECTOR_PATTERN
);
export function parseDuration(timeunitText): null | Duration {
const fragments = {};
let remainingText = timeunitText;
let match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
while (match) {
collectDateTimeFragment(fragments, match);
remainingText = remainingText.substring(match[0].length).trim();
match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
}
if (Object.keys(fragments).length == 0) {
return null;
}
return fragments as Duration;
}
function collectDateTimeFragment(fragments, match) {
if (match[0].match(/^[a-zA-Z]+$/)) {
return;
}
const num = parseNumberPattern(match[1]);
const unit = TIME_UNIT_DICTIONARY[match[2].toLowerCase()];
fragments[unit] = num;
}