Skip to content

Commit

Permalink
Merge pull request #485 from S4M3R/master
Browse files Browse the repository at this point in the history
Basic spanish support
  • Loading branch information
wanasit committed Dec 25, 2022
2 parents c1c7824 + c84d3b7 commit 4a1a434
Show file tree
Hide file tree
Showing 14 changed files with 970 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ import * as pt from "./locales/pt";
import * as nl from "./locales/nl";
import * as zh from "./locales/zh";
import * as ru from "./locales/ru";
export { de, fr, ja, pt, nl, zh, ru };
import * as es from "./locales/es";
export { de, fr, ja, pt, nl, zh, ru, es };

/**
* A shortcut for {@link en | chrono.en.strict}
Expand Down
85 changes: 85 additions & 0 deletions src/locales/es/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export const WEEKDAY_DICTIONARY: { [word: string]: number } = {
"domingo": 0,
"dom": 0,
"lunes": 1,
"lun": 1,
"martes": 2,
"mar": 2,
"miércoles": 3,
"miercoles": 3,
"mié": 3,
"mie": 3,
"jueves": 4,
"jue": 4,
"viernes": 5,
"vie": 5,
"sábado": 6,
"sabado": 6,
"sáb": 6,
"sab": 6,
};

export const MONTH_DICTIONARY: { [word: string]: number } = {
"enero": 1,
"ene": 1,
"ene.": 1,
"febrero": 2,
"feb": 2,
"feb.": 2,
"marzo": 3,
"mar": 3,
"mar.": 3,
"abril": 4,
"abr": 4,
"abr.": 4,
"mayo": 5,
"may": 5,
"may.": 5,
"junio": 6,
"jun": 6,
"jun.": 6,
"julio": 7,
"jul": 7,
"jul.": 7,
"agosto": 8,
"ago": 8,
"ago.": 8,
"septiembre": 9,
"setiembre": 9,
"sep": 9,
"sep.": 9,
"octubre": 10,
"oct": 10,
"oct.": 10,
"noviembre": 11,
"nov": 11,
"nov.": 11,
"diciembre": 12,
"dic": 12,
"dic.": 12,
};

//-----------------------------
// 88 p. Chr. n.
// 234 AC
export const YEAR_PATTERN = "[0-9]{1,4}(?![^\\s]\\d)(?:\\s*[a|d]\\.?\\s*c\\.?|\\s*a\\.?\\s*d\\.?)?";
export function parseYear(match: string): number {
if (match.match(/^[0-9]{1,4}$/)) {
let yearNumber = parseInt(match);
if (yearNumber < 100) {
if (yearNumber > 50) {
yearNumber = yearNumber + 1900;
} else {
yearNumber = yearNumber + 2000;
}
}
return yearNumber;
}

if (match.match(/a\.?\s*c\.?/i)) {
match = match.replace(/a\.?\s*c\.?/i, "");
return -parseInt(match);
}

return parseInt(match);
}
57 changes: 57 additions & 0 deletions src/locales/es/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Chrono components for Portuguese support (*parsers*, *refiners*, and *configuration*)
*
* @module
*/

import { includeCommonConfiguration } from "../../configurations";
import { ParsedResult, ParsingOption } from "../../index";
import { Chrono, Configuration } from "../../chrono";
import SlashDateFormatParser from "../../common/parsers/SlashDateFormatParser";
import ESWeekdayParser from "./parsers/ESWeekdayParser";
import ESTimeExpressionParser from "./parsers/ESTimeExpressionParser";
import ESMergeDateTimeRefiner from "./refiners/ESMergeDateTimeRefiner";
import ESMergeDateRangeRefiner from "./refiners/ESMergeDateRangeRefiner";
import ESMonthNameLittleEndianParser from "./parsers/ESMonthNameLittleEndianParser";
import ESCasualDateParser from "./parsers/ESCasualDateParser";
import ESCasualTimeParser from "./parsers/ESCasualTimeParser";

// Shortcuts
export const casual = new Chrono(createCasualConfiguration());
export const strict = new Chrono(createConfiguration(true));

export function parse(text: string, ref?: Date, option?: ParsingOption): ParsedResult[] {
return casual.parse(text, ref, option);
}

export function parseDate(text: string, ref?: Date, option?: ParsingOption): Date {
return casual.parseDate(text, ref, option);
}

/**
* @ignore (to be documented later)
*/
export function createCasualConfiguration(littleEndian = true): Configuration {
const option = createConfiguration(false, littleEndian);
option.parsers.push(new ESCasualDateParser());
option.parsers.push(new ESCasualTimeParser());
return option;
}

/**
* @ignore (to be documented later)
*/
export function createConfiguration(strictMode = true, littleEndian = true): Configuration {
return includeCommonConfiguration(
{
parsers: [
new SlashDateFormatParser(littleEndian),
new ESWeekdayParser(),
new ESTimeExpressionParser(),
new ESMonthNameLittleEndianParser(),
],
refiners: [new ESMergeDateTimeRefiner(), new ESMergeDateRangeRefiner()],
},
strictMode
);
}
31 changes: 31 additions & 0 deletions src/locales/es/parsers/ESCasualDateParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ParsingContext } from "../../../chrono";
import { ParsingComponents, ParsingResult } from "../../../results";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
import * as references from "../../../common/casualReferences";

export default class ESCasualDateParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(context: ParsingContext): RegExp {
return /(ahora|hoy|mañana|ayer)(?=\W|$)/i;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents | ParsingResult {
const lowerText = match[0].toLowerCase();
const component = context.createParsingComponents();

switch (lowerText) {
case "ahora":
return references.now(context.reference);

case "hoy":
return references.today(context.reference);

case "mañana":
return references.tomorrow(context.reference);

case "ayer":
return references.yesterday(context.reference);
}

return component;
}
}
47 changes: 47 additions & 0 deletions src/locales/es/parsers/ESCasualTimeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ParsingContext } from "../../../chrono";
import { Meridiem } from "../../../index";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
import { assignTheNextDay } from "../../../utils/dayjs";
import dayjs from "dayjs";

export default class ESCasualTimeParser extends AbstractParserWithWordBoundaryChecking {
innerPattern() {
return /(?:esta\s*)?(mañana|tarde|medianoche|mediodia|mediodía|noche)(?=\W|$)/i;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray) {
const targetDate = dayjs(context.refDate);
const component = context.createParsingComponents();
switch (match[1].toLowerCase()) {
case "tarde":
component.imply("meridiem", Meridiem.PM);
component.imply("hour", 15);
break;

case "noche":
component.imply("meridiem", Meridiem.PM);
component.imply("hour", 22);
break;

case "mañana":
component.imply("meridiem", Meridiem.AM);
component.imply("hour", 6);
break;

case "medianoche":
assignTheNextDay(component, targetDate);
component.imply("hour", 0);
component.imply("minute", 0);
component.imply("second", 0);
break;

case "mediodia":
case "mediodía":
component.imply("meridiem", Meridiem.AM);
component.imply("hour", 12);
break;
}

return component;
}
}
60 changes: 60 additions & 0 deletions src/locales/es/parsers/ESMonthNameLittleEndianParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ParsingContext } from "../../../chrono";
import { ParsingResult } from "../../../results";
import { findYearClosestToRef } from "../../../calculation/years";
import { MONTH_DICTIONARY } from "../constants";
import { YEAR_PATTERN, parseYear } from "../constants";
import { matchAnyPattern } from "../../../utils/pattern";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";

const PATTERN = new RegExp(
`([0-9]{1,2})(?:º|ª|°)?` +
"(?:\\s*(?:desde|de|\\-|\\–|ao?|\\s)\\s*([0-9]{1,2})(?:º|ª|°)?)?\\s*(?:de)?\\s*" +
`(?:-|/|\\s*(?:de|,)?\\s*)` +
`(${matchAnyPattern(MONTH_DICTIONARY)})` +
`(?:\\s*(?:de|,)?\\s*(${YEAR_PATTERN}))?` +
`(?=\\W|$)`,
"i"
);

const DATE_GROUP = 1;
const DATE_TO_GROUP = 2;
const MONTH_NAME_GROUP = 3;
const YEAR_GROUP = 4;

export default class ESMonthNameLittleEndianParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(): RegExp {
return PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingResult {
const result = context.createParsingResult(match.index, match[0]);

const month = MONTH_DICTIONARY[match[MONTH_NAME_GROUP].toLowerCase()];
const day = parseInt(match[DATE_GROUP]);
if (day > 31) {
// e.g. "[96 Aug]" => "9[6 Aug]", we need to shift away from the next number
match.index = match.index + match[DATE_GROUP].length;
return null;
}

result.start.assign("month", month);
result.start.assign("day", day);

if (match[YEAR_GROUP]) {
const yearNumber = parseYear(match[YEAR_GROUP]);
result.start.assign("year", yearNumber);
} else {
const year = findYearClosestToRef(context.refDate, day, month);
result.start.imply("year", year);
}

if (match[DATE_TO_GROUP]) {
const endDate = parseInt(match[DATE_TO_GROUP]);

result.end = result.start.clone();
result.end.assign("day", endDate);
}

return result;
}
}
20 changes: 20 additions & 0 deletions src/locales/es/parsers/ESTimeExpressionParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AbstractTimeExpressionParser } from "../../../common/parsers/AbstractTimeExpressionParser";

export default class ESTimeExpressionParser extends AbstractTimeExpressionParser {
primaryPrefix(): string {
return "(?:(?:aslas|deslas|las?|al?|de|del)\\s*)?";
}

followingPhase(): string {
return "\\s*(?:\\-|\\–|\\~|\\〜|a(?:l)?|\\?)\\s*";
}

// extractPrimaryTimeComponents(context: ParsingContext, match: RegExpMatchArray): ParsingComponents | null {
// // This looks more like a year e.g. 2020
// // if (match[0].match(/^\s*\d{4}\s*$/)) {
// // return null;
// // }
//
// return super.extractPrimaryTimeComponents(context, match);
// }
}
50 changes: 50 additions & 0 deletions src/locales/es/parsers/ESWeekdayParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ParsingContext } from "../../../chrono";
import { ParsingComponents } from "../../../results";
import { WEEKDAY_DICTIONARY } from "../constants";
import { matchAnyPattern } from "../../../utils/pattern";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
import { createParsingComponentsAtWeekday } from "../../../common/calculation/weekdays";

const PATTERN = new RegExp(
"(?:(?:\\,|\\(|\\()\\s*)?" +
"(?:(este|esta|pasado|pr[oó]ximo)\\s*)?" +
`(${matchAnyPattern(WEEKDAY_DICTIONARY)})` +
"(?:\\s*(?:\\,|\\)|\\)))?" +
"(?:\\s*(este|esta|pasado|pr[óo]ximo)\\s*semana)?" +
"(?=\\W|\\d|$)",
"i"
);

const PREFIX_GROUP = 1;
const WEEKDAY_GROUP = 2;
const POSTFIX_GROUP = 3;

export default class ESWeekdayParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(): RegExp {
return PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
const dayOfWeek = match[WEEKDAY_GROUP].toLowerCase();
const weekday = WEEKDAY_DICTIONARY[dayOfWeek];
if (weekday === undefined) {
return null;
}

const prefix = match[PREFIX_GROUP];
const postfix = match[POSTFIX_GROUP];
let norm = prefix || postfix || "";
norm = norm.toLowerCase();

let modifier = null;
if (norm == "pasado") {
modifier = "this";
} else if (norm == "próximo" || norm == "proximo") {
modifier = "next";
} else if (norm == "este") {
modifier = "this";
}

return createParsingComponentsAtWeekday(context.reference, weekday, modifier);
}
}
15 changes: 15 additions & 0 deletions src/locales/es/refiners/ESMergeDateRangeRefiner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
*/

import AbstractMergeDateRangeRefiner from "../../../common/refiners/AbstractMergeDateRangeRefiner";

/**
* Merging before and after results (see. AbstractMergeDateRangeRefiner)
*
*/
export default class ESMergeDateRangeRefiner extends AbstractMergeDateRangeRefiner {
patternBetween(): RegExp {
return /^\s*(?:-)\s*$/i;
}
}
10 changes: 10 additions & 0 deletions src/locales/es/refiners/ESMergeDateTimeRefiner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import AbstractMergeDateTimeRefiner from "../../../common/refiners/AbstractMergeDateTimeRefiner";

/**
* Merging date-only result and time-only result (see. AbstractMergeDateTimeRefiner).
*/
export default class ESMergeDateTimeRefiner extends AbstractMergeDateTimeRefiner {
patternBetween(): RegExp {
return new RegExp("^\\s*(?:,|de|aslas|a)?\\s*$");
}
}
Loading

0 comments on commit 4a1a434

Please sign in to comment.