diff --git a/src/locales/en/constants.ts b/src/locales/en/constants.ts index 09c0b1a7..298b04ab 100644 --- a/src/locales/en/constants.ts +++ b/src/locales/en/constants.ts @@ -264,10 +264,17 @@ const SINGLE_TIME_UNIT_NO_ABBR_PATTERN = `(${NUMBER_PATTERN})\\s{0,3}(${matchAny TIME_UNIT_DICTIONARY_NO_ABBR )})`; -export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(`(?:(?:about|around)\\s{0,3})?`, SINGLE_TIME_UNIT_PATTERN); +const TIME_UNIT_CONNECTOR_PATTERN = `\\s{0,5},?(?:\\s*and)?\\s{0,5}`; + +export const TIME_UNITS_PATTERN = repeatedTimeunitPattern( + `(?:(?:about|around)\\s{0,3})?`, + SINGLE_TIME_UNIT_PATTERN, + TIME_UNIT_CONNECTOR_PATTERN +); export const TIME_UNITS_NO_ABBR_PATTERN = repeatedTimeunitPattern( `(?:(?:about|around)\\s{0,3})?`, - SINGLE_TIME_UNIT_NO_ABBR_PATTERN + SINGLE_TIME_UNIT_NO_ABBR_PATTERN, + TIME_UNIT_CONNECTOR_PATTERN ); export function parseTimeUnits(timeunitText): TimeUnits { diff --git a/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts b/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts index 69fe4942..91286951 100644 --- a/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts +++ b/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts @@ -8,10 +8,7 @@ const PATTERN = new RegExp( "i" ); -const STRICT_PATTERN = new RegExp( - "" + "(" + TIME_UNITS_NO_ABBR_PATTERN + ")" + "(later|from now)" + "(?=(?:\\W|$))", - "i" -); +const STRICT_PATTERN = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(later|after|from now)(?=\\W|$)`, "i"); const GROUP_NUM_TIMEUNITS = 1; export default class ENTimeUnitLaterFormatParser extends AbstractParserWithWordBoundaryChecking { diff --git a/src/utils/pattern.ts b/src/utils/pattern.ts index bc7ca8bc..4a1e587f 100644 --- a/src/utils/pattern.ts +++ b/src/utils/pattern.ts @@ -1,8 +1,12 @@ type DictionaryLike = string[] | { [word: string]: unknown } | Map; -export function repeatedTimeunitPattern(prefix: string, singleTimeunitPattern: string): string { +export function repeatedTimeunitPattern( + prefix: string, + singleTimeunitPattern: string, + connectorPattern = "\\s{0,5},?\\s{0,5}" +): string { const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:"); - return `${prefix}${singleTimeunitPatternNoCapture}\\s{0,5}(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`; + return `${prefix}${singleTimeunitPatternNoCapture}(?:${connectorPattern}${singleTimeunitPatternNoCapture}){0,10}`; } export function extractTerms(dictionary: DictionaryLike): string[] { diff --git a/test/en/en_time_units_later.test.ts b/test/en/en_time_units_later.test.ts index 125de010..31e4cf1a 100644 --- a/test/en/en_time_units_later.test.ts +++ b/test/en/en_time_units_later.test.ts @@ -249,7 +249,9 @@ test("Test - From now Expression", () => { expect(result.start).toBeDate(new Date(2012, 7, 10, 14, 10)); }); +}); +test("Test - The later expression with multiple time units", function () { testSingleCase(chrono, "in 1d 2hr 5min", new Date(2012, 7, 10, 12, 40), (result) => { expect(result.index).toBe(0); expect(result.text).toBe("in 1d 2hr 5min"); @@ -259,6 +261,16 @@ test("Test - From now Expression", () => { expect(result.start).toBeDate(new Date(2012, 7, 11, 14, 45)); }); + + testSingleCase(chrono, "in 1d, 2hr, and 5min", new Date(2012, 7, 10, 12, 40), (result) => { + expect(result.index).toBe(0); + expect(result.text).toBe("in 1d, 2hr, and 5min"); + expect(result.start.get("day")).toBe(11); + expect(result.start.get("hour")).toBe(14); + expect(result.start.get("minute")).toBe(45); + + expect(result.start).toBeDate(new Date(2012, 7, 11, 14, 45)); + }); }); test("Test - Strict mode", function () { diff --git a/test/en/en_time_units_within.test.ts b/test/en/en_time_units_within.test.ts index 3897a67c..d610b59f 100644 --- a/test/en/en_time_units_within.test.ts +++ b/test/en/en_time_units_within.test.ts @@ -174,6 +174,48 @@ test("Test - The normal within expression", () => { }); }); +test("Test - The within expression with multiple time units", function () { + testSingleCase(chrono, "set a timer for 5 minutes 30 seconds", new Date(2012, 7, 10, 12, 14), (result) => { + expect(result.index).toBe(12); + expect(result.text).toBe("for 5 minutes 30 seconds"); + + expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 19, 30)); + }); + + testSingleCase(chrono, "set a timer for 5 minutes, 30 seconds", new Date(2012, 7, 10, 12, 14), (result) => { + expect(result.index).toBe(12); + expect(result.text).toBe("for 5 minutes, 30 seconds"); + + expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 19, 30)); + }); + + testSingleCase(chrono, "set a timer for 1 hour, 5 minutes, 30 seconds", new Date(2012, 7, 10, 12, 14), (result) => { + expect(result.index).toBe(12); + expect(result.text).toBe("for 1 hour, 5 minutes, 30 seconds"); + + expect(result.start).toBeDate(new Date(2012, 7, 10, 13, 19, 30)); + }); + + testSingleCase(chrono, "set a timer for 5 minutes and 30 seconds", new Date(2012, 7, 10, 12, 14), (result) => { + expect(result.index).toBe(12); + expect(result.text).toBe("for 5 minutes and 30 seconds"); + + expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 19, 30)); + }); + + testSingleCase( + chrono, + "set a timer for 1 hour, 5 minutes, and 30 seconds", + new Date(2012, 7, 10, 12, 14), + (result) => { + expect(result.index).toBe(12); + expect(result.text).toBe("for 1 hour, 5 minutes, and 30 seconds"); + + expect(result.start).toBeDate(new Date(2012, 7, 10, 13, 19, 30)); + } + ); +}); + test("Test - The within expression with certain keywords", () => { testSingleCase(chrono, "In about 5 hours", new Date(2012, 8 - 1, 10, 12, 49), (result, text) => { expect(result.text).toBe(text);