diff --git a/README.md b/README.md index 5a77f694..2b8dc3a8 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ const chrono = require('chrono-node'); ### What's changed in the v2 For Users * Chrono’s default now handles only international English. While in the previous version, it tried to parse with all known languages. -* The current fully supported languages are `en`, `ja`, `fr`, `nl` and `ru` (`de`, `pt`, and `zh.hant` are partially supported). +* The current fully supported languages are `en`, `ja`, `fr`, `nl`, `ru` and `uk` (`de`, `pt`, and `zh.hant` are partially supported). For contributors and advanced users * The project is rewritten in TypeScript @@ -212,7 +212,7 @@ chrono.en.GB.parseDate('6/10/2018'); // October 6th, 2018 chrono.ja.parseDate('昭和64年1月7日'); ``` -Current supported locale options are: `en`, `ja`, `fr`, `nl` and `ru` (`de`, `pt`, and `zh.hant` are partially supported). +Current supported locale options are: `en`, `ja`, `fr`, `nl`, `ru` and `uk` (`de`, `pt`, and `zh.hant` are partially supported). #### Importing specific locales diff --git a/src/index.ts b/src/index.ts index 1a0db2e9..481de645 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,9 @@ import * as nl from "./locales/nl"; import * as zh from "./locales/zh"; import * as ru from "./locales/ru"; import * as es from "./locales/es"; -export { de, fr, ja, pt, nl, zh, ru, es }; +import * as uk from "./locales/uk"; + +export { de, fr, ja, pt, nl, zh, ru, es, uk }; /** * A shortcut for {@link en | chrono.en.strict} diff --git a/src/locales/uk/constants.ts b/src/locales/uk/constants.ts new file mode 100644 index 00000000..627e5ca9 --- /dev/null +++ b/src/locales/uk/constants.ts @@ -0,0 +1,349 @@ +import { OpUnitType, QUnitType } from "dayjs"; +import { matchAnyPattern, repeatedTimeunitPattern } from "../../utils/pattern"; +import { findMostLikelyADYear } from "../../calculation/years"; +import { TimeUnits } from "../../utils/timeunits"; + +export const REGEX_PARTS = { + leftBoundary: "([^\\p{L}\\p{N}_]|^)", + rightBoundary: "(?=[^\\p{L}\\p{N}_]|$)", + flags: "iu", +}; + +export const WEEKDAY_DICTIONARY: { [word: string]: number } = { + "неділя": 0, + "неділі": 0, + "неділю": 0, + "нд": 0, + "нд.": 0, + "понеділок": 1, + "понеділка": 1, + "пн": 1, + "пн.": 1, + "вівторок": 2, + "вівторка": 2, + "вт": 2, + "вт.": 2, + "середа": 3, + "середи": 3, + "середу": 3, + "ср": 3, + "ср.": 3, + "четвер": 4, + "четверга": 4, + "четвергу": 4, + "чт": 4, + "чт.": 4, + "п'ятниця": 5, + "п'ятниці": 5, + "п'ятницю": 5, + "пт": 5, + "пт.": 5, + "субота": 6, + "суботи": 6, + "суботу": 6, + "сб": 6, + "сб.": 6, +}; + +export const FULL_MONTH_NAME_DICTIONARY: { [word: string]: number } = { + "січень": 1, + "січня": 1, + "січні": 1, + "лютий": 2, + "лютого": 2, + "лютому": 2, + "березень": 3, + "березня": 3, + "березні": 3, + "квітень": 4, + "квітня": 4, + "квітні": 4, + "травень": 5, + "травня": 5, + "травні": 5, + "червень": 6, + "червня": 6, + "червні": 6, + "липень": 7, + "липня": 7, + "липні": 7, + "серпень": 8, + "серпня": 8, + "серпні": 8, + "вересень": 9, + "вересня": 9, + "вересні": 9, + "жовтень": 10, + "жовтня": 10, + "жовтні": 10, + "листопад": 11, + "листопада": 11, + "листопаду": 11, + "грудень": 12, + "грудня": 12, + "грудні": 12, +}; + +export const MONTH_DICTIONARY: { [word: string]: number } = { + ...FULL_MONTH_NAME_DICTIONARY, + "січ": 1, + "січ.": 1, + "лют": 2, + "лют.": 2, + "бер": 3, + "бер.": 3, + "квіт": 4, + "квіт.": 4, + "трав": 5, + "трав.": 5, + "черв": 6, + "черв.": 6, + "лип": 7, + "лип.": 7, + "серп": 8, + "серп.": 8, + "сер": 8, + "cер.": 8, + "вер": 9, + "вер.": 9, + "верес": 9, + "верес.": 9, + "жовт": 10, + "жовт.": 10, + "листоп": 11, + "листоп.": 11, + "груд": 12, + "груд.": 12, +}; + +export const INTEGER_WORD_DICTIONARY: { [word: string]: number } = { + "один": 1, + "одна": 1, + "одної": 1, + "одну": 1, + "дві": 2, + "два": 2, + "двох": 2, + "три": 3, + "трьох": 3, + "чотири": 4, + "чотирьох": 4, + "п'ять": 5, + "п'яти": 5, + "шість": 6, + "шести": 6, + "сім": 7, + "семи": 7, + "вісім": 8, + "восьми": 8, + "дев'ять": 9, + "дев'яти": 9, + "десять": 10, + "десяти": 10, + "одинадцять": 11, + "одинадцяти": 11, + "дванадцять": 12, + "дванадцяти": 12, +}; + +export const ORDINAL_WORD_DICTIONARY: { [word: string]: number } = { + "перше": 1, + "першого": 1, + "друге": 2, + "другого": 2, + "третє": 3, + "третього": 3, + "четверте": 4, + "четвертого": 4, + "п'яте": 5, + "п'ятого": 5, + "шосте": 6, + "шостого": 6, + "сьоме": 7, + "сьомого": 7, + "восьме": 8, + "восьмого": 8, + "дев'яте": 9, + "дев'ятого": 9, + "десяте": 10, + "десятого": 10, + "одинадцяте": 11, + "одинадцятого": 11, + "дванадцяте": 12, + "дванадцятого": 12, + "тринадцяте": 13, + "тринадцятого": 13, + "чотирнадцяте": 14, + "чотинрнадцятого": 14, + "п'ятнадцяте": 15, + "п'ятнадцятого": 15, + "шістнадцяте": 16, + "шістнадцятого": 16, + "сімнадцяте": 17, + "сімнадцятого": 17, + "вісімнадцяте": 18, + "вісімнадцятого": 18, + "дев'ятнадцяте": 19, + "дев'ятнадцятого": 19, + "двадцяте": 20, + "двадцятого": 20, + "двадцять перше": 21, + "двадцять першого": 21, + "двадцять друге": 22, + "двадцять другого": 22, + "двадцять третє": 23, + "двадцять третього": 23, + "двадцять четверте": 24, + "двадцять четвертого": 24, + "двадцять п'яте": 25, + "двадцять п'ятого": 25, + "двадцять шосте": 26, + "двадцять шостого": 26, + "двадцять сьоме": 27, + "двадцять сьомого": 27, + "двадцять восьме": 28, + "двадцять восьмого": 28, + "двадцять дев'яте": 29, + "двадцять дев'ятого": 29, + "тридцяте": 30, + "тридцятого": 30, + "тридцять перше": 31, + "тридцять першого": 31, +}; + +export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType | QUnitType } = { + сек: "second", + секунда: "second", + секунд: "second", + секунди: "second", + секунду: "second", + секундочок: "second", + секундочки: "second", + секундочку: "second", + хв: "minute", + хвилина: "minute", + хвилин: "minute", + хвилини: "minute", + хвилину: "minute", + хвилинок: "minute", + хвилинки: "minute", + хвилинку: "minute", + хвилиночок: "minute", + хвилиночки: "minute", + хвилиночку: "minute", + год: "hour", + година: "hour", + годин: "hour", + години: "hour", + годину: "hour", + годинка: "hour", + годинок: "hour", + годинки: "hour", + годинку: "hour", + день: "d", + дня: "d", + днів: "d", + дні: "d", + доба: "d", + добу: "d", + тиждень: "week", + тижню: "week", + тижня: "week", + тижні: "week", + тижнів: "week", + місяць: "month", + місяців: "month", + місяці: "month", + місяця: "month", + квартал: "quarter", + кварталу: "quarter", + квартала: "quarter", + кварталів: "quarter", + кварталі: "quarter", + рік: "year", + року: "year", + році: "year", + років: "year", + роки: "year", +}; + +//-------------------------------- + +export const NUMBER_PATTERN = `(?:${matchAnyPattern( + INTEGER_WORD_DICTIONARY +)}|[0-9]+|[0-9]+\\.[0-9]+|пів|декілька|пар(?:у)|\\s{0,3})`; + +export function parseNumberPattern(match: string): number { + const num = match.toLowerCase(); + if (INTEGER_WORD_DICTIONARY[num] !== undefined) { + return INTEGER_WORD_DICTIONARY[num]; + } + if (num.match(/декілька/)) { + return 2; + } else if (num.match(/пів/)) { + return 0.5; + } else if (num.match(/пар/)) { + return 2; + } else if (num === "") { + return 1; + } + return parseFloat(num); +} + +export const ORDINAL_NUMBER_PATTERN = `(?:${matchAnyPattern(ORDINAL_WORD_DICTIONARY)}|[0-9]{1,2}(?:го|ого|е)?)`; +export function parseOrdinalNumberPattern(match: string): number { + const num = match.toLowerCase(); + if (ORDINAL_WORD_DICTIONARY[num] !== undefined) { + return ORDINAL_WORD_DICTIONARY[num]; + } + return parseInt(num); +} + +const year = "(?:\\s+(?:року|рік|р|р.))?"; +export const YEAR_PATTERN = `(?:[1-9][0-9]{0,3}${year}\\s*(?:н.е.|до н.е.|н. е.|до н. е.)|[1-2][0-9]{3}${year}|[5-9][0-9]${year})`; +export function parseYearPattern(match: string): number { + if (/(рік|року|р|р.)/i.test(match)) { + match = match.replace(/(рік|року|р|р.)/i, ""); + } + + if (/(до н.е.|до н. е.)/i.test(match)) { + //Before Common Era + match = match.replace(/(до н.е.|до н. е.)/i, ""); + return -parseInt(match); + } + + if (/(н. е.|н.е.)/i.test(match)) { + //Common Era + match = match.replace(/(н. е.|н.е.)/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"); + +export const TIME_UNITS_PATTERN = repeatedTimeunitPattern( + `(?:(?:близько|приблизно)\\s{0,3})?`, + SINGLE_TIME_UNIT_PATTERN +); + +export function parseTimeUnits(timeunitText): TimeUnits { + 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); + } + return fragments; +} + +function collectDateTimeFragment(fragments, match) { + const num = parseNumberPattern(match[1]); + const unit = TIME_UNIT_DICTIONARY[match[2].toLowerCase()]; + fragments[unit] = num; +} diff --git a/src/locales/uk/index.ts b/src/locales/uk/index.ts new file mode 100644 index 00000000..03688b03 --- /dev/null +++ b/src/locales/uk/index.ts @@ -0,0 +1,90 @@ +/** + * Chrono components for Ukrainian support (*parsers*, *refiners*, and *configuration*) + * + * @module + */ + +import UKTimeUnitWithinFormatParser from "./parsers/UKTimeUnitWithinFormatParser"; +import UKMonthNameLittleEndianParser from "./parsers/UKMonthNameLittleEndianParser"; +import UkMonthNameParser from "./parsers/UKMonthNameParser"; +import UKTimeExpressionParser from "./parsers/UKTimeExpressionParser"; +import UKTimeUnitAgoFormatParser from "./parsers/UKTimeUnitAgoFormatParser"; +import UKMergeDateRangeRefiner from "./refiners/UKMergeDateRangeRefiner"; +import UKMergeDateTimeRefiner from "./refiners/UKMergeDateTimeRefiner"; + +import { includeCommonConfiguration } from "../../configurations"; +import UKCasualDateParser from "./parsers/UKCasualDateParser"; +import UKCasualTimeParser from "./parsers/UKCasualTimeParser"; +import UKWeekdayParser from "./parsers/UKWeekdayParser"; +import UKRelativeDateFormatParser from "./parsers/UKRelativeDateFormatParser"; + +import { Chrono, Configuration, Parser, Refiner } from "../../chrono"; +import { ParsingResult } from "../../results"; +import { Component, ParsedResult, ParsingOption, ParsingReference, Meridiem, Weekday } from "../../types"; +import SlashDateFormatParser from "../../common/parsers/SlashDateFormatParser"; +import UKTimeUnitCasualRelativeFormatParser from "./parsers/UKTimeUnitCasualRelativeFormatParser"; +import ISOFormatParser from "../../common/parsers/ISOFormatParser"; + +export { Chrono, Parser, Refiner, ParsingResult }; +export { Component, ParsedResult, ParsingOption, ParsingReference, Meridiem, Weekday }; + +/** + * Chrono object configured for parsing *casual* Ukrainian + */ +export const casual = new Chrono(createCasualConfiguration()); + +/** + * Chrono object configured for parsing *strict* Ukrainian + */ +export const strict = new Chrono(createConfiguration(true)); + +/** + * Create a default *casual* {@Link Configuration} for Ukrainian chrono. + * It calls {@Link createConfiguration} and includes additional parsers. + */ +export function createCasualConfiguration(): Configuration { + const option = createConfiguration(false); + option.parsers.unshift(new UKCasualDateParser()); + option.parsers.unshift(new UKCasualTimeParser()); + option.parsers.unshift(new UkMonthNameParser()); + option.parsers.unshift(new UKRelativeDateFormatParser()); + option.parsers.unshift(new UKTimeUnitCasualRelativeFormatParser()); + return option; +} + +/** + * Create a default {@Link Configuration} for Ukrainian chrono + * + * @param strictMode If the timeunit mentioning should be strict, not casual + */ +export function createConfiguration(strictMode: boolean): Configuration { + return includeCommonConfiguration( + { + parsers: [ + new ISOFormatParser(), + new SlashDateFormatParser(true), + new UKTimeUnitWithinFormatParser(), + new UKMonthNameLittleEndianParser(), + new UKWeekdayParser(), + new UKTimeExpressionParser(strictMode), + new UKTimeUnitAgoFormatParser(), + ], + refiners: [new UKMergeDateTimeRefiner(), new UKMergeDateRangeRefiner()], + }, + strictMode + ); +} + +/** + * A shortcut for uk.casual.parse() + */ +export function parse(text: string, ref?: Date, option?: ParsingOption): ParsedResult[] { + return casual.parse(text, ref, option); +} + +/** + * A shortcut for uk.casual.parseDate() + */ +export function parseDate(text: string, ref?: Date, option?: ParsingOption): Date { + return casual.parseDate(text, ref, option); +} diff --git a/src/locales/uk/parsers/UKCasualDateParser.ts b/src/locales/uk/parsers/UKCasualDateParser.ts new file mode 100644 index 00000000..11e6d487 --- /dev/null +++ b/src/locales/uk/parsers/UKCasualDateParser.ts @@ -0,0 +1,50 @@ +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents, ParsingResult } from "../../../results"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; +import * as references from "../../../common/casualReferences"; +import { REGEX_PARTS } from "../constants"; + +const PATTERN = new RegExp( + `(?:з|із|від)?\\s*(сьогодні|вчора|завтра|післязавтра|післяпіслязавтра|позапозавчора|позавчора)${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); + +export default class UKCasualDateParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(context: ParsingContext): RegExp { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents | ParsingResult { + const lowerText = match[1].toLowerCase(); + const component = context.createParsingComponents(); + + switch (lowerText) { + case "сьогодні": + return references.today(context.reference); + + case "вчора": + return references.yesterday(context.reference); + + case "завтра": + return references.tomorrow(context.reference); + + case "післязавтра": + return references.theDayAfter(context.reference, 2); + + case "післяпіслязавтра": + return references.theDayAfter(context.reference, 3); + + case "позавчора": + return references.theDayBefore(context.reference, 2); + + case "позапозавчора": + return references.theDayBefore(context.reference, 3); + } + + return component; + } +} diff --git a/src/locales/uk/parsers/UKCasualTimeParser.ts b/src/locales/uk/parsers/UKCasualTimeParser.ts new file mode 100644 index 00000000..87933196 --- /dev/null +++ b/src/locales/uk/parsers/UKCasualTimeParser.ts @@ -0,0 +1,59 @@ +import { ParsingContext } from "../../../chrono"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; +import * as references from "../../../common/casualReferences"; +import { assignSimilarDate } from "../../../utils/dayjs"; +import dayjs from "dayjs"; +import { REGEX_PARTS } from "../constants"; + +const PATTERN = new RegExp( + `(зараз|минулого\\s*вечора|минулої\\s*ночі|наступної\\s*ночі|сьогодні\\s*вночі|цієї\\s*ночі|цього ранку|вранці|ранку|зранку|опівдні|ввечері|вечора|опівночі|вночі)` + + `${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); +export default class UKCasualTimeParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern() { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray) { + let targetDate = dayjs(context.reference.instant); + const lowerText = match[0].toLowerCase(); + const component = context.createParsingComponents(); + + if (lowerText === "зараз") { + return references.now(context.reference); + } + if (lowerText === "ввечері" || lowerText === "вечора") { + return references.evening(context.reference); + } + if (lowerText.endsWith("вранці") || lowerText.endsWith("ранку") || lowerText.endsWith("зранку")) { + return references.morning(context.reference); + } + if (lowerText.endsWith("опівдні")) { + return references.noon(context.reference); + } + if (lowerText.match(/минулої\s*ночі/)) { + return references.lastNight(context.reference); + } + if (lowerText.match(/минулого\s*вечора/)) { + return references.yesterdayEvening(context.reference); + } + if (lowerText.match(/наступної\s*ночі/)) { + const daysToAdd = targetDate.hour() < 22 ? 1 : 2; + targetDate = targetDate.add(daysToAdd, "day"); + assignSimilarDate(component, targetDate); + component.imply("hour", 1); + } + if (lowerText.match(/цієї\s*ночі/)) { + return references.midnight(context.reference); + } + if (lowerText.endsWith("опівночі") || lowerText.endsWith("вночі")) { + return references.midnight(context.reference); + } + return component; + } +} diff --git a/src/locales/uk/parsers/UKMonthNameLittleEndianParser.ts b/src/locales/uk/parsers/UKMonthNameLittleEndianParser.ts new file mode 100644 index 00000000..bf28f3a2 --- /dev/null +++ b/src/locales/uk/parsers/UKMonthNameLittleEndianParser.ts @@ -0,0 +1,72 @@ +import { ParsingContext } from "../../../chrono"; +import { ParsingResult } from "../../../results"; +import { findYearClosestToRef } from "../../../calculation/years"; +import { MONTH_DICTIONARY, REGEX_PARTS } from "../constants"; +import { YEAR_PATTERN, parseYearPattern } from "../constants"; +import { ORDINAL_NUMBER_PATTERN, parseOrdinalNumberPattern } from "../constants"; +import { matchAnyPattern } from "../../../utils/pattern"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; + +// prettier-ignore +const PATTERN = new RegExp( + `(?:з|із)?\\s*(${ORDINAL_NUMBER_PATTERN})` + + `(?:` + + `\\s{0,3}(?:по|-|–|до)?\\s{0,3}` + + `(${ORDINAL_NUMBER_PATTERN})` + + `)?` + + `(?:-|\\/|\\s{0,3}(?:of)?\\s{0,3})` + + `(${matchAnyPattern(MONTH_DICTIONARY)})` + + `(?:` + + `(?:-|\\/|,?\\s{0,3})` + + `(${YEAR_PATTERN}(?![^\\s]\\d))` + + `)?` + + `${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); + +const DATE_GROUP = 1; +const DATE_TO_GROUP = 2; +const MONTH_NAME_GROUP = 3; +const YEAR_GROUP = 4; + +export default class UKMonthNameLittleEndianParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + 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 = parseOrdinalNumberPattern(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 = parseYearPattern(match[YEAR_GROUP]); + result.start.assign("year", yearNumber); + } else { + const year = findYearClosestToRef(context.reference.instant, day, month); + result.start.imply("year", year); + } + + if (match[DATE_TO_GROUP]) { + const endDate = parseOrdinalNumberPattern(match[DATE_TO_GROUP]); + + result.end = result.start.clone(); + result.end.assign("day", endDate); + } + + return result; + } +} diff --git a/src/locales/uk/parsers/UKMonthNameParser.ts b/src/locales/uk/parsers/UKMonthNameParser.ts new file mode 100644 index 00000000..c98bb8e4 --- /dev/null +++ b/src/locales/uk/parsers/UKMonthNameParser.ts @@ -0,0 +1,61 @@ +import { FULL_MONTH_NAME_DICTIONARY, MONTH_DICTIONARY, REGEX_PARTS } from "../constants"; +import { ParsingContext } from "../../../chrono"; +import { findYearClosestToRef } from "../../../calculation/years"; +import { matchAnyPattern } from "../../../utils/pattern"; +import { YEAR_PATTERN, parseYearPattern } from "../constants"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; + +const PATTERN = new RegExp( + `((?:в|у)\\s*)?` + + `(${matchAnyPattern(MONTH_DICTIONARY)})` + + `\\s*` + + `(?:` + + `[,-]?\\s*(${YEAR_PATTERN})?` + + `)?` + + `(?=[^\\s\\w]|\\s+[^0-9]|\\s+$|$)`, + REGEX_PARTS.flags +); + +const MONTH_NAME_GROUP = 2; +const YEAR_GROUP = 3; + +/** + * The parser for parsing month name and year. + * - Cічень, 2012 + * - Січень 2012 + * - Січень + */ +export default class UkMonthNameParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(): RegExp { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray) { + const monthName = match[MONTH_NAME_GROUP].toLowerCase(); + + // skip some unlikely words "січ", "лют", .. + if (match[0].length <= 3 && !FULL_MONTH_NAME_DICTIONARY[monthName]) { + return null; + } + + const result = context.createParsingResult(match.index, match.index + match[0].length); + result.start.imply("day", 1); + + const month = MONTH_DICTIONARY[monthName]; + result.start.assign("month", month); + + if (match[YEAR_GROUP]) { + const year = parseYearPattern(match[YEAR_GROUP]); + result.start.assign("year", year); + } else { + const year = findYearClosestToRef(context.reference.instant, 1, month); + result.start.imply("year", year); + } + + return result; + } +} diff --git a/src/locales/uk/parsers/UKRelativeDateFormatParser.ts b/src/locales/uk/parsers/UKRelativeDateFormatParser.ts new file mode 100644 index 00000000..0c7eb7be --- /dev/null +++ b/src/locales/uk/parsers/UKRelativeDateFormatParser.ts @@ -0,0 +1,85 @@ +import { REGEX_PARTS, TIME_UNIT_DICTIONARY } from "../constants"; +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents } from "../../../results"; +import dayjs from "dayjs"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; +import { matchAnyPattern } from "../../../utils/pattern"; + +const PATTERN = new RegExp( + `(в минулому|у минулому|на минулому|минулого|на наступному|в наступному|у наступному|наступного|на цьому|в цьому|у цьому|цього)\\s*(${matchAnyPattern( + TIME_UNIT_DICTIONARY + )})(?=\\s*)${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); + +const MODIFIER_WORD_GROUP = 1; +const RELATIVE_WORD_GROUP = 2; + +export default class UKRelativeDateFormatParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(): RegExp { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents { + const modifier = match[MODIFIER_WORD_GROUP].toLowerCase(); + const unitWord = match[RELATIVE_WORD_GROUP].toLowerCase(); + const timeunit = TIME_UNIT_DICTIONARY[unitWord]; + + if ( + modifier == "на наступному" || + modifier == "в наступному" || + modifier == "у наступному" || + modifier == "наступного" + ) { + const timeUnits = {}; + timeUnits[timeunit] = 1; + return ParsingComponents.createRelativeFromReference(context.reference, timeUnits); + } + + if ( + modifier == "на минулому" || + modifier == "в минулому" || + modifier == "у минулому" || + modifier == "минулого" + ) { + const timeUnits = {}; + timeUnits[timeunit] = -1; + return ParsingComponents.createRelativeFromReference(context.reference, timeUnits); + } + + const components = context.createParsingComponents(); + let date = dayjs(context.reference.instant); + + // This week + if (timeunit.match(/week/i)) { + date = date.add(-date.get("d"), "d"); + components.imply("day", date.date()); + components.imply("month", date.month() + 1); + components.imply("year", date.year()); + } + + // This month + else if (timeunit.match(/month/i)) { + date = date.add(-date.date() + 1, "d"); + components.imply("day", date.date()); + components.assign("year", date.year()); + components.assign("month", date.month() + 1); + } + + // This year + else if (timeunit.match(/year/i)) { + date = date.add(-date.date() + 1, "d"); + date = date.add(-date.month(), "month"); + + components.imply("day", date.date()); + components.imply("month", date.month() + 1); + components.assign("year", date.year()); + } + + return components; + } +} diff --git a/src/locales/uk/parsers/UKTimeExpressionParser.ts b/src/locales/uk/parsers/UKTimeExpressionParser.ts new file mode 100644 index 00000000..5dbed486 --- /dev/null +++ b/src/locales/uk/parsers/UKTimeExpressionParser.ts @@ -0,0 +1,64 @@ +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents } from "../../../results"; +import { Meridiem } from "../../../types"; +import { AbstractTimeExpressionParser } from "../../../common/parsers/AbstractTimeExpressionParser"; +import { REGEX_PARTS } from "../constants"; + +export default class UKTimeExpressionParser extends AbstractTimeExpressionParser { + constructor(strictMode) { + super(strictMode); + } + + patternFlags(): string { + return REGEX_PARTS.flags; + } + + primaryPatternLeftBoundary(): string { + return `(^|\\s|T|(?:[^\\p{L}\\p{N}_]))`; + } + + followingPhase(): string { + return `\\s*(?:\\-|\\–|\\~|\\〜|до|і|по|\\?)\\s*`; + } + + primaryPrefix(): string { + return `(?:(?:в|у|о|об|з|із|від)\\s*)??`; + } + + primarySuffix(): string { + return `(?:\\s*(?:ранку|вечора|по обіді|після обіду))?(?!\\/)${REGEX_PARTS.rightBoundary}`; + } + + extractPrimaryTimeComponents(context: ParsingContext, match: RegExpMatchArray): null | ParsingComponents { + const components = super.extractPrimaryTimeComponents(context, match); + if (components) { + if (match[0].endsWith("вечора")) { + const hour = components.get("hour"); + if (hour >= 6 && hour < 12) { + components.assign("hour", components.get("hour") + 12); + components.assign("meridiem", Meridiem.PM); + } else if (hour < 6) { + components.assign("meridiem", Meridiem.AM); + } + } + + if (match[0].endsWith("по обіді") || match[0].endsWith("після обіду")) { + components.assign("meridiem", Meridiem.PM); + const hour = components.get("hour"); + if (hour >= 0 && hour <= 6) { + components.assign("hour", components.get("hour") + 12); + } + } + + if (match[0].endsWith("ранку")) { + components.assign("meridiem", Meridiem.AM); + const hour = components.get("hour"); + if (hour < 12) { + components.assign("hour", components.get("hour")); + } + } + } + + return components; + } +} diff --git a/src/locales/uk/parsers/UKTimeUnitAgoFormatParser.ts b/src/locales/uk/parsers/UKTimeUnitAgoFormatParser.ts new file mode 100644 index 00000000..42a441e3 --- /dev/null +++ b/src/locales/uk/parsers/UKTimeUnitAgoFormatParser.ts @@ -0,0 +1,24 @@ +import { ParsingContext } from "../../../chrono"; +import { parseTimeUnits, REGEX_PARTS, TIME_UNITS_PATTERN } from "../constants"; +import { ParsingComponents } from "../../../results"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; +import { reverseTimeUnits } from "../../../utils/timeunits"; + +const PATTERN = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}тому(?=(?:\\W|$))`, REGEX_PARTS.flags); + +export default class UKTimeUnitAgoFormatParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(): RegExp { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray) { + const timeUnits = parseTimeUnits(match[1]); + const outputTimeUnits = reverseTimeUnits(timeUnits); + + return ParsingComponents.createRelativeFromReference(context.reference, outputTimeUnits); + } +} diff --git a/src/locales/uk/parsers/UKTimeUnitCasualRelativeFormatParser.ts b/src/locales/uk/parsers/UKTimeUnitCasualRelativeFormatParser.ts new file mode 100644 index 00000000..33b0b377 --- /dev/null +++ b/src/locales/uk/parsers/UKTimeUnitCasualRelativeFormatParser.ts @@ -0,0 +1,34 @@ +import { TIME_UNITS_PATTERN, parseTimeUnits, REGEX_PARTS } from "../constants"; +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents } from "../../../results"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; +import { reverseTimeUnits } from "../../../utils/timeunits"; + +const PATTERN = new RegExp( + `(ці|останні|минулі|майбутні|наступні|після|через|\\+|-)\\s*(${TIME_UNITS_PATTERN})${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); + +export default class UKTimeUnitCasualRelativeFormatParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(): RegExp { + return PATTERN; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents { + const prefix = match[1].toLowerCase(); + let timeUnits = parseTimeUnits(match[3]); + switch (prefix) { + case "останні": + case "минулі": + case "-": + timeUnits = reverseTimeUnits(timeUnits); + break; + } + + return ParsingComponents.createRelativeFromReference(context.reference, timeUnits); + } +} diff --git a/src/locales/uk/parsers/UKTimeUnitWithinFormatParser.ts b/src/locales/uk/parsers/UKTimeUnitWithinFormatParser.ts new file mode 100644 index 00000000..45ee0f01 --- /dev/null +++ b/src/locales/uk/parsers/UKTimeUnitWithinFormatParser.ts @@ -0,0 +1,27 @@ +import { TIME_UNITS_PATTERN, parseTimeUnits, REGEX_PARTS } from "../constants"; +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents } from "../../../results"; +import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary"; + +const PATTERN = `(?:(?:приблизно|орієнтовно)\\s*(?:~\\s*)?)?(${TIME_UNITS_PATTERN})${REGEX_PARTS.rightBoundary}`; +const PATTERN_WITH_PREFIX = new RegExp( + `(?:протягом|на протязі|протягом|упродовж|впродовж)\\s*${PATTERN}`, + REGEX_PARTS.flags +); + +const PATTERN_WITHOUT_PREFIX = new RegExp(PATTERN, "i"); + +export default class UKTimeUnitWithinFormatParser extends AbstractParserWithWordBoundaryChecking { + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerPattern(context: ParsingContext): RegExp { + return context.option.forwardDate ? PATTERN_WITHOUT_PREFIX : PATTERN_WITH_PREFIX; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents { + const timeUnits = parseTimeUnits(match[1]); + return ParsingComponents.createRelativeFromReference(context.reference, timeUnits); + } +} diff --git a/src/locales/uk/parsers/UKWeekdayParser.ts b/src/locales/uk/parsers/UKWeekdayParser.ts new file mode 100644 index 00000000..e92bf741 --- /dev/null +++ b/src/locales/uk/parsers/UKWeekdayParser.ts @@ -0,0 +1,59 @@ +import { ParsingContext } from "../../../chrono"; +import { ParsingComponents } from "../../../results"; +// TODO: ADD REGEX_PARTS below +import { REGEX_PARTS, 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*)?` + + `(?:в\\s*?)?` + + `(?:у\\s*?)?` + + `(?:(цей|минулого|минулий|попередній|попереднього|наступного|наступний|наступному)\\s*)?` + + `(${matchAnyPattern(WEEKDAY_DICTIONARY)})` + + `(?:\\s*(?:,|\\)|)))?` + + `(?:\\s*(на|у|в)\\s*(цьому|минулому|наступному)\\s*тижні)?` + + `${REGEX_PARTS.rightBoundary}`, + REGEX_PARTS.flags +); + +const PREFIX_GROUP = 1; +const WEEKDAY_GROUP = 2; +const POSTFIX_GROUP = 3; + +export default class UKWeekdayParser extends AbstractParserWithWordBoundaryChecking { + innerPattern(): RegExp { + return PATTERN; + } + + patternLeftBoundary(): string { + return REGEX_PARTS.leftBoundary; + } + + innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents { + const dayOfWeek = match[WEEKDAY_GROUP].toLocaleLowerCase(); + const weekday = WEEKDAY_DICTIONARY[dayOfWeek]; + const prefix = match[PREFIX_GROUP]; + const postfix = match[POSTFIX_GROUP]; + let modifierWord = prefix || postfix; + modifierWord = modifierWord || ""; + modifierWord = modifierWord.toLocaleLowerCase(); + + let modifier = null; + if ( + modifierWord == "минулого" || + modifierWord == "минулий" || + modifierWord == "попередній" || + modifierWord == "попереднього" + ) { + modifier = "last"; + } else if (modifierWord == "наступного" || modifierWord == "наступний") { + modifier = "next"; + } else if (modifierWord == "цей" || modifierWord == "цього" || modifierWord == "цьому") { + modifier = "this"; + } + + return createParsingComponentsAtWeekday(context.reference, weekday, modifier); + } +} diff --git a/src/locales/uk/refiners/UKMergeDateRangeRefiner.ts b/src/locales/uk/refiners/UKMergeDateRangeRefiner.ts new file mode 100644 index 00000000..4aeadb3c --- /dev/null +++ b/src/locales/uk/refiners/UKMergeDateRangeRefiner.ts @@ -0,0 +1,13 @@ +import AbstractMergeDateRangeRefiner from "../../../common/refiners/AbstractMergeDateRangeRefiner"; + +/** + * Merging before and after results (see. AbstractMergeDateRangeRefiner) + * This implementation should provide Russian connecting phases + * - [з|із] 06.09.1989 [до|по] 11.12.1996 + * - [з|із] п'ятниці і до середи + */ +export default class UKMergeDateRangeRefiner extends AbstractMergeDateRangeRefiner { + patternBetween(): RegExp { + return /^\s*(і до|і по|до|по|-)\s*$/i; + } +} diff --git a/src/locales/uk/refiners/UKMergeDateTimeRefiner.ts b/src/locales/uk/refiners/UKMergeDateTimeRefiner.ts new file mode 100644 index 00000000..0533e270 --- /dev/null +++ b/src/locales/uk/refiners/UKMergeDateTimeRefiner.ts @@ -0,0 +1,13 @@ +import AbstractMergeDateTimeRefiner from "../../../common/refiners/AbstractMergeDateTimeRefiner"; + +/** + * Merging date-only result and time-only result (see. AbstractMergeDateTimeRefiner). + * This implementation should provide Ukrainian connecting phases + * - 2020-02-13 [в/у/о] 6:00 + * - Завтра [,] 7:00 + */ +export default class UKMergeDateTimeRefiner extends AbstractMergeDateTimeRefiner { + patternBetween(): RegExp { + return new RegExp(`^\\s*(T|в|у|о|,|-)?\\s*$`); + } +} diff --git a/test/uk/uk_casual.test.ts b/test/uk/uk_casual.test.ts new file mode 100644 index 00000000..63fe11cb --- /dev/null +++ b/test/uk/uk_casual.test.ts @@ -0,0 +1,153 @@ +import * as chrono from "../../src/"; +import { testSingleCase, testUnexpectedResult } from "../test_util"; + +test("Test - Single Expression", () => { + testSingleCase(chrono.uk.casual, "Дедлайн сьогодні", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("сьогодні"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн завтра", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("завтра"); + expect(result.start).toBeDate(new Date(2012, 7, 11, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн післязавтра", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("післязавтра"); + expect(result.start).toBeDate(new Date(2012, 7, 12, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн післяпіслязавтра", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("післяпіслязавтра"); + expect(result.start).toBeDate(new Date(2012, 7, 13, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн вчора", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("вчора"); + expect(result.start).toBeDate(new Date(2012, 7, 9, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн позавчора", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("позавчора"); + expect(result.start).toBeDate(new Date(2012, 7, 8, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн позапозавчора", new Date(2012, 7, 10, 17, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("позапозавчора"); + expect(result.start).toBeDate(new Date(2012, 7, 7, 17, 10)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн зараз", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("зараз"); + expect(result.start).toBeDate(result.refDate); + expect(result.start).toBeDate(new Date(2012, 7, 10, 8, 9, 10, 11)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн вранці", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("вранці"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 6, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн цього ранку", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("цього ранку"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 6, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн опівдні", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("опівдні"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн минулого вечора", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("минулого вечора"); + expect(result.start).toBeDate(new Date(2012, 7, 9, 20, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн ввечері", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("ввечері"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 20, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн минулої ночі", new Date(2012, 7, 10, 8, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("минулої ночі"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн сьогодні вночі", new Date(2012, 7, 10, 2, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("сьогодні вночі"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн цієї ночі", new Date(2012, 7, 10, 2, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("цієї ночі"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн вночі", new Date(2012, 7, 10, 2, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("вночі"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 0, 0, 0)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн опівночі", new Date(2012, 7, 10, 2, 9, 10, 11), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("опівночі"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 0, 0, 0)); + }); +}); + +test("Test - Combined Expression", () => { + testSingleCase(chrono.uk.casual, "Дедлайн вчора ввечері", new Date(2012, 7, 10, 12), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("вчора ввечері"); + expect(result.start).toBeDate(new Date(2012, 7, 9, 20)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн завтра вранці", new Date(2012, 8, 10, 14), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("завтра вранці"); + expect(result.start).toBeDate(new Date(2012, 8, 11, 6)); + }); +}); + +test("Test - Casual date range", () => { + testSingleCase(chrono.uk.casual, "Подія від сьогодні і до післязавтра", new Date(2012, 7, 4, 12), (result) => { + expect(result.index).toBe(6); + expect(result.text).toBe("від сьогодні і до післязавтра"); + expect(result.start).toBeDate(new Date(2012, 7, 4, 12)); + expect(result.end).toBeDate(new Date(2012, 7, 6, 12)); + }); + + testSingleCase(chrono.uk.casual, "Подія сьогодні-завтра", new Date(2012, 7, 10, 12), (result) => { + expect(result.index).toBe(6); + expect(result.text).toBe("сьогодні-завтра"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 12)); + expect(result.end).toBeDate(new Date(2012, 7, 11, 12)); + }); +}); + +test("Test - Random negative text", () => { + testUnexpectedResult(chrono.uk, "несьогодні"); + + testUnexpectedResult(chrono.uk, "звтра"); + + testUnexpectedResult(chrono.uk, "ввчора"); + + testUnexpectedResult(chrono.uk, "січен"); +}); diff --git a/test/uk/uk_month.test.ts b/test/uk/uk_month.test.ts new file mode 100644 index 00000000..514ee2cf --- /dev/null +++ b/test/uk/uk_month.test.ts @@ -0,0 +1,70 @@ +import * as chrono from "../../src"; +import { testSingleCase } from "../test_util"; + +test("Test - Month-Year expression", function () { + testSingleCase(chrono.uk, "Вересень 2012", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("Вересень 2012"); + expect(result.start).toBeDate(new Date(2012, 9 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "верес 2012", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("верес 2012"); + expect(result.start).toBeDate(new Date(2012, 9 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "верес. 2012", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("верес. 2012"); + expect(result.start).toBeDate(new Date(2012, 9 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "верес-2012", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("верес-2012"); + expect(result.start).toBeDate(new Date(2012, 9 - 1, 1, 12)); + }); +}); + +test("Test - Month-Only expression", function () { + testSingleCase(chrono.uk, "в січні", new Date(2020, 11 - 1, 22), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("в січні"); + expect(result.start).toBeDate(new Date(2021, 1 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "в січ", new Date(2020, 11 - 1, 22), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("в січ"); + expect(result.start).toBeDate(new Date(2021, 1 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "травень", new Date(2020, 11 - 1, 22), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("травень"); + expect(result.start).toBeDate(new Date(2021, 5 - 1, 1, 12)); + }); +}); + +test("Test - Month expression in context", function () { + testSingleCase(chrono.uk, "Це було у вересні 2012 перед новим роком", (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("у вересні 2012"); + expect(result.start).toBeDate(new Date(2012, 9 - 1, 1, 12)); + }); +}); + +test("Test - year 90's parsing", () => { + testSingleCase(chrono.uk, "сер 96", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("сер 96"); + expect(result.start).toBeDate(new Date(1996, 8 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "96 сер 96", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(3); + expect(result.text).toBe("сер 96"); + expect(result.start).toBeDate(new Date(1996, 8 - 1, 1, 12)); + }); +}); diff --git a/test/uk/uk_month_name_little_endian.test.ts b/test/uk/uk_month_name_little_endian.test.ts new file mode 100644 index 00000000..13082edd --- /dev/null +++ b/test/uk/uk_month_name_little_endian.test.ts @@ -0,0 +1,148 @@ +import * as chrono from "../../src"; +import { testSingleCase, testUnexpectedResult } from "../test_util"; + +test("Test - Single expression", () => { + testSingleCase(chrono.uk, "10.08.2012", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("10.08.2012"); + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + }); + + testSingleCase(chrono.uk, "10 серпня 2012", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("10 серпня 2012"); + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + }); + + testSingleCase(chrono.uk, "3 лют 82", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("3 лют 82"); + expect(result.start).toBeDate(new Date(1982, 2 - 1, 3, 12)); + }); + + testSingleCase(chrono.uk, "Дедлайн 10 серпня", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("10 серпня"); + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + }); + + testSingleCase(chrono.uk, "Дедлайн Четвер, 10 січня", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("Четвер, 10 січня"); + expect(result.start).toBeDate(new Date(2013, 1 - 1, 10, 12)); + }); +}); + +test("Test - Single expression with separators", () => { + testSingleCase(chrono.uk, "10-серпня 2012", new Date(2012, 7, 8), (result, text) => { + expect(result.text).toBe(text); + expect(result).toBeDate(new Date(2012, 8 - 1, 10, 12, 0)); + }); + + testSingleCase(chrono.uk, "10-серпня-2012", new Date(2012, 7, 8), (result, text) => { + expect(result.text).toBe(text); + expect(result).toBeDate(new Date(2012, 8 - 1, 10, 12, 0)); + }); + + testSingleCase(chrono.uk, "10/серпня 2012", new Date(2012, 7, 8), (result, text) => { + expect(result.text).toBe(text); + expect(result).toBeDate(new Date(2012, 8 - 1, 10, 12, 0)); + }); + + testSingleCase(chrono.uk, "10/серпня/2012", new Date(2012, 7, 8), (result, text) => { + expect(result.text).toBe(text); + expect(result).toBeDate(new Date(2012, 8 - 1, 10, 12, 0)); + }); +}); + +test("Test - Range expression", () => { + testSingleCase(chrono.uk, "10 - 22 серпня 2012", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("10 - 22 серпня 2012"); + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + expect(result.end).toBeDate(new Date(2012, 8 - 1, 22, 12)); + }); + + testSingleCase(chrono.uk, "із 10 по 22 серпня 2012", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("із 10 по 22 серпня 2012"); + + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + expect(result.end).toBeDate(new Date(2012, 8 - 1, 22, 12)); + }); + + testSingleCase(chrono.uk, "10 серпня - 12 вересня", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("10 серпня - 12 вересня"); + + expect(result.start).toBeDate(new Date(2012, 8 - 1, 10, 12)); + expect(result.end).toBeDate(new Date(2012, 9 - 1, 12, 12)); + }); + + testSingleCase(chrono.uk, "10 серпня - 12 вересня 2013", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("10 серпня - 12 вересня 2013"); + + expect(result.start).toBeDate(new Date(2013, 8 - 1, 10, 12)); + expect(result.end).toBeDate(new Date(2013, 9 - 1, 12, 12)); + }); +}); + +test("Test - Combined expression", () => { + testSingleCase(chrono.uk, "5 травня 12:00", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("5 травня 12:00"); + expect(result.start).toBeDate(new Date(2012, 5 - 1, 5, 12, 0)); + }); +}); + +test("Test - Ordinal Words", () => { + testSingleCase(chrono.uk, "п'яте травня", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("п'яте травня"); + expect(result.start).toBeDate(new Date(2012, 5 - 1, 5, 12, 0)); + }); +}); + +test("Test - Ordinal Words", () => { + testSingleCase(chrono.uk, "двадцять п'яте травня", new Date(2012, 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("двадцять п'яте травня"); + expect(result.start).toBeDate(new Date(2012, 5 - 1, 25, 12, 0)); + }); +}); + +test("Test - little endian date followed by time", () => { + testSingleCase(chrono.uk, "24го жовтня, 9:00", new Date(2017, 7 - 1, 7, 15), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("24го жовтня, 9:00"); + expect(result.start).toBeDate(new Date(2017, 10 - 1, 24, 9)); + }); +}); + +test("Test - year 90's parsing", () => { + testSingleCase(chrono.uk, "03 сер 96", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("03 сер 96"); + expect(result.start).toBeDate(new Date(1996, 8 - 1, 3, 12)); + }); +}); + +test("Test - Forward Option", () => { + testSingleCase(chrono.uk.casual, "22-23 лют в 7", new Date(2016, 3 - 1, 15), { forwardDate: true }, (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("22-23 лют в 7"); + expect(result.start).toBeDate(new Date(2017, 2 - 1, 22, 7)); + expect(result.end).toBeDate(new Date(2017, 2 - 1, 23, 7)); + }); +}); + +test("Test - Impossible Dates (Strict Mode)", function () { + testUnexpectedResult(chrono.uk.strict, "32 серпня 2014", new Date(2012, 7, 10)); + + testUnexpectedResult(chrono.uk.strict, "29 лютого 2014", new Date(2012, 7, 10)); + + testUnexpectedResult(chrono.uk.strict, "32 серпня", new Date(2012, 7, 10)); + + testUnexpectedResult(chrono.uk.strict, "29 лютого", new Date(2013, 7, 10)); +}); diff --git a/test/uk/uk_relative.test.ts b/test/uk/uk_relative.test.ts new file mode 100644 index 00000000..4ae743c6 --- /dev/null +++ b/test/uk/uk_relative.test.ts @@ -0,0 +1,74 @@ +import * as chrono from "../../src"; +import { testSingleCase } from "../test_util"; + +test("Test - 'This' expressions", () => { + testSingleCase(chrono.uk, "на цьому тижні", new Date(2017, 11 - 1, 19, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 11 - 1, 19, 12)); + }); + + testSingleCase(chrono.uk, "у цьому місяці", new Date(2017, 11 - 1, 19, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 11 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "цього місяця", new Date(2017, 11 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 11 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "у цьому році", new Date(2017, 11 - 1, 19, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 1 - 1, 1, 12)); + }); +}); + +test("Test - Past relative expressions", () => { + testSingleCase(chrono.uk, "на минулому тижні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 9 - 1, 24, 12)); + }); + + testSingleCase(chrono.uk, "минулого місяця", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 9 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "у минулому році", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2015, 10 - 1, 1, 12)); + }); +}); + +test("Test - Future relative expressions", () => { + testSingleCase(chrono.uk, "на наступному тижні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 8, 12)); + }); + + testSingleCase(chrono.uk, "наступного місяця", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 11 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "в наступному кварталі", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 1 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "наступного року", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 10 - 1, 1, 12)); + }); +}); diff --git a/test/uk/uk_time_exp.test.ts b/test/uk/uk_time_exp.test.ts new file mode 100644 index 00000000..e44ab7c7 --- /dev/null +++ b/test/uk/uk_time_exp.test.ts @@ -0,0 +1,111 @@ +import * as chrono from "../../src"; +import { testSingleCase, testUnexpectedResult } from "../test_util"; + +test("Test - Time expression", function () { + testSingleCase(chrono.uk, "20:32:13", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 20, 32, 13)); + }); +}); + +test("Test - Time range expression", function () { + testSingleCase(chrono.uk, "10:00:00 - 21:45:01", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 10)); + expect(result.end).toBeDate(new Date(2016, 10 - 1, 1, 21, 45, 1)); + }); +}); + +test("Test - Casual time number expression", function () { + testSingleCase(chrono.uk, "об 11 ранку", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 11)); + }); + + testSingleCase(chrono.uk, "в 11 вечора", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 23)); + }); +}); + +test("Test - Time range's meridiem handling", function () { + testSingleCase(chrono.uk, "з 10 до 11 ранку", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 10)); + expect(result.end).toBeDate(new Date(2016, 10 - 1, 1, 11)); + }); + testSingleCase(chrono.uk, "із 10 до 11 вечора", new Date(2016, 10 - 1, 1, 8), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 22)); + expect(result.end).toBeDate(new Date(2016, 10 - 1, 1, 23)); + }); +}); + +test("Test - Parsing causal positive cases", function () { + testSingleCase(chrono.uk.casual, "в 1", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("в 1"); + expect(result.start.get("hour")).toBe(1); + }); + + testSingleCase(chrono.uk.casual, "о 12", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("о 12"); + expect(result.start.get("hour")).toBe(12); + }); + + testSingleCase(chrono.uk.casual, "в 12.30", (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("в 12.30"); + expect(result.start.get("hour")).toBe(12); + expect(result.start.get("minute")).toBe(30); + }); +}); + +test("Test - Parsing negative cases : [year-like] pattern", function () { + testUnexpectedResult(chrono.uk, "2020"); + + testUnexpectedResult(chrono.uk, "2020 "); +}); + +test("Test - Parsing negative cases : 'at [some numbers]'", function () { + testUnexpectedResult(chrono.uk, "Температура 101,194 градусів!"); + + testUnexpectedResult(chrono.uk, "Температура 101 градусів!"); + + testUnexpectedResult(chrono.uk, "Температура 10.1"); +}); + +test("Test - Parsing negative cases : 'at [some numbers] - [some numbers]'", function () { + testUnexpectedResult(chrono.uk, "Це в 10.1 - 10.12"); + + testUnexpectedResult(chrono.uk, "Це в 10 - 10.1"); +}); + +test("Test - Parsing negative cases (Strict)", function () { + testUnexpectedResult(chrono.uk.strict, "Це в 101,194 телефон!"); + + testUnexpectedResult(chrono.uk.strict, "Це в 101 стіл!"); + + testUnexpectedResult(chrono.uk.strict, "Це в 10.1"); + + testUnexpectedResult(chrono.uk.strict, "Це в 10"); + + testUnexpectedResult(chrono.uk.strict, "2020"); +}); + +test("Test - Parsing negative cases : 'at [some numbers] - [some numbers]' (Strict)", function () { + testUnexpectedResult(chrono.uk.strict, "Це в 10.1 - 10.12"); + + testUnexpectedResult(chrono.uk.strict, "Це в 10 - 10.1"); + + testUnexpectedResult(chrono.uk.strict, "Це в 10 - 20"); + + testUnexpectedResult(chrono.uk.strict, "7-730"); +}); diff --git a/test/uk/uk_time_units_ago.test.ts b/test/uk/uk_time_units_ago.test.ts new file mode 100644 index 00000000..3ee41424 --- /dev/null +++ b/test/uk/uk_time_units_ago.test.ts @@ -0,0 +1,48 @@ +import * as chrono from "../../src/"; +import { testSingleCase, testUnexpectedResult } from "../test_util"; + +test("Test - Single Expression", function () { + testSingleCase(chrono.uk, "5 днів тому щось відбулось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("5 днів тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 5)); + }); + + testSingleCase(chrono.uk, "5 хвилин тому щось відбулось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("5 хвилин тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 9, 23, 55)); + }); + + testSingleCase(chrono.uk, "півгодини тому щось відбулось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("півгодини тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 9, 23, 30)); + }); +}); + +test("Test - Nested time ago", function () { + testSingleCase(chrono.uk, "5 днів 2 години тому щось відбулось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("5 днів 2 години тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 4, 22)); + }); + + testSingleCase(chrono.uk, "5 хвилин 20 секунд тому щось сталось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("5 хвилин 20 секунд тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 9, 23, 54, 40)); + }); + + testSingleCase(chrono.uk, "2 години 5 хвилин тому щось сталось", new Date(2012, 7 - 1, 10), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("2 години 5 хвилин тому"); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 9, 21, 55)); + }); +}); + +test("Test - Negative cases", function () { + testUnexpectedResult(chrono.uk, "15 годин 29 хв."); + testUnexpectedResult(chrono.uk, "декілька годин"); + testUnexpectedResult(chrono.uk, "5 днів"); +}); diff --git a/test/uk/uk_time_units_casual_relative.test.ts b/test/uk/uk_time_units_casual_relative.test.ts new file mode 100644 index 00000000..209633cb --- /dev/null +++ b/test/uk/uk_time_units_casual_relative.test.ts @@ -0,0 +1,112 @@ +import * as chrono from "../../src"; +import { testSingleCase } from "../test_util"; + +test("Test - Positive time units", () => { + testSingleCase(chrono.uk, "наступні 2 тижні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 15, 12)); + }); + + testSingleCase(chrono.uk, "наступні 2 дні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 3, 12)); + }); + + testSingleCase(chrono.uk, "наступні два роки", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2018, 10 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "наступні 2 тижні 3 дні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 18, 12)); + }); + + testSingleCase(chrono.uk, "через декілька хвилин", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 12, 2)); + }); + + testSingleCase(chrono.uk, "через півгодини", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 12, 30)); + }); + + testSingleCase(chrono.uk, "через 2 години", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 1, 14)); + }); + + testSingleCase(chrono.uk, "через три місяці", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2017, 1 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "через тиждень", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 10 - 1, 8, 12)); + }); + + testSingleCase(chrono.uk, "через місяць", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 11 - 1, 1, 12)); + }); + + testSingleCase(chrono.uk, "через рік", new Date(2020, 11 - 1, 22, 12, 11, 32, 6), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2021, 11 - 1, 22, 12, 11, 32, 6)); + }); +}); + +test("Test - Negative time units", () => { + testSingleCase(chrono.uk, "минулі 2 тижні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 9 - 1, 17, 12)); + }); + + testSingleCase(chrono.uk, "минулі два дні", new Date(2016, 10 - 1, 1, 12), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2016, 9 - 1, 29, 12)); + }); +}); + +test("Test - Plus '+' sign", () => { + testSingleCase(chrono.uk.casual, "+15 хвилин", new Date(2012, 7 - 1, 10, 12, 14), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 10, 12, 29)); + }); + + testSingleCase(chrono.uk.casual, "+15хв", new Date(2012, 7 - 1, 10, 12, 14), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 10, 12, 29)); + }); + + testSingleCase(chrono.uk.casual, "+1 день 2 години", new Date(2012, 7 - 1, 10, 12, 14), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 11, 14, 14)); + }); +}); + +test("Test - Minus '-' sign", () => { + testSingleCase(chrono.uk.casual, "-3 роки", new Date(2015, 7 - 1, 10, 12, 14), (result, text) => { + expect(result.index).toBe(0); + expect(result.text).toBe(text); + expect(result.start).toBeDate(new Date(2012, 7 - 1, 10, 12, 14)); + }); +}); diff --git a/test/uk/uk_time_units_within.test.ts b/test/uk/uk_time_units_within.test.ts new file mode 100644 index 00000000..39166eac --- /dev/null +++ b/test/uk/uk_time_units_within.test.ts @@ -0,0 +1,16 @@ +import * as chrono from "../../src"; +import { testSingleCase } from "../test_util"; + +test("Test - The normal within expression", () => { + testSingleCase(chrono.uk, "буде зроблено протягом хвилини", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(14); + expect(result.text).toBe("протягом хвилини"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 0, 1)); + }); + + testSingleCase(chrono.uk, "буде виконано на протязі 2 годин.", new Date(2012, 7, 10), (result) => { + expect(result.index).toBe(14); + expect(result.text).toBe("на протязі 2 годин"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 2)); + }); +}); diff --git a/test/uk/uk_weekday.test.ts b/test/uk/uk_weekday.test.ts new file mode 100644 index 00000000..4e7dae25 --- /dev/null +++ b/test/uk/uk_weekday.test.ts @@ -0,0 +1,52 @@ +import * as chrono from "../../src"; +import { testSingleCase } from "../test_util"; + +test("Test - Single Expression", function () { + testSingleCase(chrono.uk.casual, "понеділок", new Date(2012, 7, 9), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("понеділок"); + expect(result.start).toBeDate(new Date(2012, 7, 6, 12)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн у п'ятницю...", new Date(2012, 7, 9), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("у п'ятницю"); + expect(result.start).toBeDate(new Date(2012, 7, 10, 12)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн в минулий четвер!", new Date(2012, 7, 9), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("в минулий четвер"); + expect(result.start).toBeDate(new Date(2012, 7, 2, 12)); + }); + + testSingleCase(chrono.uk.casual, "Дедлайн в наступний вівторок!", new Date(2015, 3, 18), (result) => { + expect(result.index).toBe(8); + expect(result.text).toBe("в наступний вівторок"); + expect(result.start).toBeDate(new Date(2015, 3, 21, 12)); + }); +}); + +test("Test - Weekday With Casual Time", function () { + testSingleCase(chrono.uk.casual, "Подзвони в середу вранці", new Date(2015, 3, 18), (result) => { + expect(result.index).toBe(9); + expect(result.text).toBe("в середу вранці"); + expect(result.start).toBeDate(new Date(2015, 3, 15, 6)); + }); +}); + +test("Test - Weekday Overlap", function () { + testSingleCase(chrono.uk.casual, "неділя, 7 грудня 2014", new Date(2012, 7, 9), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("неділя, 7 грудня 2014"); + expect(result.start).toBeDate(new Date(2014, 12 - 1, 7, 12)); + }); +}); + +test("Test - forward dates only option", () => { + testSingleCase(chrono.uk.casual, "У понеділок?", new Date(2012, 7, 9), { forwardDate: true }, (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("У понеділок"); + expect(result.start).toBeDate(new Date(2012, 7, 13, 12)); + }); +});