Skip to content
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ chrono.parseDate('Friday', referenceDate, { forwardDate: true });
// Fri Aug 31 2012 12:00:00 GMT+0900 (JST) -- The following Friday
```

`useShorts` (boolean) parses short-hands (currently only `en` locale) such as s, m, d, etc.

```javascript
const referenceDate = new Date(2012, 7, 25);
// Sat Aug 25 2012 00:00:00 GMT+0200 (CEST)

chrono.parseDate('in 25 minutes', referenceDate);
// Sat Aug 25 2012 00:25:00 GMT+0200 (CEST) -- adds 25 minutes

chrono.parseDate('in 25m', referenceDate);
// null -- does not understand 25m

chrono.parseDate('in 25m', referenceDate, { useShorts: true });
// Sat Aug 25 2012 00:25:00 GMT+0200 (CEST) -- allows 25m to parse as 25 minutes
```

### Parsed Results and Components

#### ParsedResult
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function parseDate(text: string, ref?: Date, option?: ParsingOption): Dat

export interface ParsingOption {
forwardDate?: boolean;
useShorts?: boolean;
debug?: DebugHandler | DebugConsume;
timezones?: { string: number };
}
Expand Down
47 changes: 39 additions & 8 deletions src/locales/en/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { OpUnitType } from "dayjs";
import { matchAnyPattern } from "../../utils/pattern";
import { findMostLikelyADYear } from "../../calculation/years";
import { TimeUnits } from "../../utils/timeunits";
import { DictionaryLike } from "../../utils/pattern";

export const WEEKDAY_DICTIONARY: { [word: string]: number } = {
sunday: 0,
Expand Down Expand Up @@ -158,6 +159,17 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType } = {
years: "year",
};

export const SHORT_TIME_UNIT_DICTIONARY: { [letter:string]: OpUnitType } = {
ms: "millisecond",
s: "second",
m: "minute",
d: "d",
w: "week",
wk: "week",
}

export const FULL_TIME_UNIT_DICTIONARY = { ...TIME_UNIT_DICTIONARY, ...SHORT_TIME_UNIT_DICTIONARY }

//-----------------------------

export const NUMBER_PATTERN = `(?:${matchAnyPattern(
Expand Down Expand Up @@ -223,29 +235,48 @@ export function parseYear(match: string): number {

//-----------------------------

const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s*`;
const SINGLE_TIME_PATTERN = matchAnyPattern(TIME_UNIT_DICTIONARY);
const SINGLE_TIME_UNIT_PATTERN = createUnitPattern(SINGLE_TIME_PATTERN);
const SINGLE_TIME_UNIT_REGEX = new RegExp(SINGLE_TIME_UNIT_PATTERN, "i");

const SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE = SINGLE_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");


const FULL_UNIT_PATTERN = matchAnyPattern(FULL_TIME_UNIT_DICTIONARY);
const FULL_TIME_UNIT_PATTERN = createUnitPattern(FULL_UNIT_PATTERN);
const FULL_TIME_UNIT_REGEX = new RegExp(FULL_TIME_UNIT_PATTERN, "i");
const FULL_TIME_UNIT_PATTERN_NO_CAPTURE = FULL_TIME_UNIT_PATTERN.replace(/\((?!\?)/g, "(?:");



export const TIME_UNITS_PATTERN =
`(?:(?:about|around)\\s*)?` +
`${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE}\\s*(?:,?\\s*${SINGLE_TIME_UNIT_PATTERN_NO_CAPTURE})*`;

export function parseTimeUnits(timeunitText): TimeUnits {
export const FULL_TIME_UNITS_PATTERN =
`(?:(?:about|around)\\s*)?` +
`${FULL_TIME_UNIT_PATTERN_NO_CAPTURE}\\s*(?:,?\\s*${FULL_TIME_UNIT_PATTERN_NO_CAPTURE})*`;

export function parseTimeUnits(timeunitText, useShorts = false): TimeUnits {
const fragments = {};
let remainingText = timeunitText;
let match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
const REGEX = useShorts ? FULL_TIME_UNIT_REGEX : SINGLE_TIME_UNIT_REGEX;
const DICT = useShorts ? FULL_TIME_UNIT_DICTIONARY : TIME_UNIT_DICTIONARY;

let match = REGEX.exec(remainingText);
while (match) {
collectDateTimeFragment(fragments, match);
collectDateTimeFragment(fragments, match, DICT);
remainingText = remainingText.substring(match[0].length);
match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
match = REGEX.exec(remainingText);
}
return fragments;
}

function collectDateTimeFragment(fragments, match) {
function collectDateTimeFragment(fragments, match, DICT: DictionaryLike) {
const num = parseNumberPattern(match[1]);
const unit = TIME_UNIT_DICTIONARY[match[2].toLowerCase()];
const unit = DICT[match[2].toLowerCase()];
fragments[unit] = num;
}

function createUnitPattern(pattern: string): string {
return `(${NUMBER_PATTERN})\\s*(${pattern})\\s*`;
}
16 changes: 11 additions & 5 deletions src/locales/en/parsers/ENTimeUnitAgoFormatParser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { ParsingContext } from "../../../chrono";
import { parseTimeUnits, TIME_UNITS_PATTERN } from "../constants";
import { FULL_TIME_UNITS_PATTERN, parseTimeUnits, 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 + ")" + "(?:ago|before|earlier)(?=(?:\\W|$))", "i");
function createRegExp(pattern: string): RegExp {
return new RegExp("" + "(" + pattern + ")" + "(?:ago|before|earlier)(?=(?:\\W|$))", "i");
}

const PATTERN = createRegExp(TIME_UNITS_PATTERN);
const FULL_PATTERN = createRegExp(FULL_TIME_UNITS_PATTERN);

const STRICT_PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "ago(?=(?:\\W|$))", "i");

Expand All @@ -13,12 +18,13 @@ export default class ENTimeUnitAgoFormatParser extends AbstractParserWithWordBou
super();
}

innerPattern(): RegExp {
return this.strictMode ? STRICT_PATTERN : PATTERN;
innerPattern(context: ParsingContext): RegExp {
return this.strictMode ? STRICT_PATTERN :
context.option.useShorts ? FULL_PATTERN : PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray) {
const timeUnits = parseTimeUnits(match[1]);
const timeUnits = parseTimeUnits(match[1], context.option.useShorts);
const outputTimeUnits = reverseTimeUnits(timeUnits);

return ParsingComponents.createRelativeFromRefDate(context.refDate, outputTimeUnits);
Expand Down
15 changes: 11 additions & 4 deletions src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { TIME_UNITS_PATTERN, parseTimeUnits } from "../constants";
import { TIME_UNITS_PATTERN, parseTimeUnits, FULL_TIME_UNITS_PATTERN } from "../constants";
import { ParsingContext } from "../../../chrono";
import { ParsingComponents } from "../../../results";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
import { reverseTimeUnits } from "../../../utils/timeunits";

const PATTERN = createRegExp(TIME_UNITS_PATTERN);
const FULL_PATTERN = createRegExp(FULL_TIME_UNITS_PATTERN);

function createRegExp(pattern: string): RegExp {
return new RegExp(`(this|last|past|next|\\+|-)\\s*(${pattern})(?=\\W|$)`, "i");
}

export default class ENTimeUnitCasualRelativeFormatParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(): RegExp {
return new RegExp(`(this|last|past|next|\\+|-)\\s*(${TIME_UNITS_PATTERN})(?=\\W|$)`, "i");
innerPattern(context: ParsingContext): RegExp {
return context.option.useShorts ? FULL_PATTERN : PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
const prefix = match[1].toLowerCase();
let timeUnits = parseTimeUnits(match[2]);
let timeUnits = parseTimeUnits(match[2], context.option.useShorts);
switch (prefix) {
case "last":
case "past":
Expand Down
22 changes: 14 additions & 8 deletions src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ParsingContext } from "../../../chrono";
import { parseTimeUnits, TIME_UNITS_PATTERN } from "../constants";
import { FULL_TIME_UNITS_PATTERN, parseTimeUnits, TIME_UNITS_PATTERN } from "../constants";
import { ParsingComponents } from "../../../results";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";

const PATTERN = new RegExp(
"" + "(" + TIME_UNITS_PATTERN + ")" + "(later|after|from now|henceforth|forward|out)" + "(?=(?:\\W|$))",
"i"
);
const PATTERN = createRegExp(TIME_UNITS_PATTERN);
const FULL_PATTERN = createRegExp(FULL_TIME_UNITS_PATTERN);

function createRegExp(pattern: string) {
return new RegExp(
"" + "(" + pattern + ")" + "(later|after|from now|henceforth|forward|out)" + "(?=(?:\\W|$))",
"i"
);
}

const STRICT_PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "(later|from now)" + "(?=(?:\\W|$))", "i");
const GROUP_NUM_TIMEUNITS = 1;
Expand All @@ -16,12 +21,13 @@ export default class ENTimeUnitLaterFormatParser extends AbstractParserWithWordB
super();
}

innerPattern(): RegExp {
return this.strictMode ? STRICT_PATTERN : PATTERN;
innerPattern(context: ParsingContext): RegExp {
return this.strictMode ? STRICT_PATTERN :
context.option.useShorts ? FULL_PATTERN : PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray) {
const fragments = parseTimeUnits(match[GROUP_NUM_TIMEUNITS]);
const fragments = parseTimeUnits(match[GROUP_NUM_TIMEUNITS], context.option.useShorts);
return ParsingComponents.createRelativeFromRefDate(context.refDate, fragments);
}
}
29 changes: 18 additions & 11 deletions src/locales/en/parsers/ENTimeUnitWithinFormatParser.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { TIME_UNITS_PATTERN, parseTimeUnits } from "../constants";
import { TIME_UNITS_PATTERN, parseTimeUnits, FULL_TIME_UNITS_PATTERN } from "../constants";
import { ParsingContext } from "../../../chrono";
import { ParsingComponents } from "../../../results";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";

const PATTERN = createRegExp(TIME_UNITS_PATTERN);
const FULL_PATTERN = createRegExp(FULL_TIME_UNITS_PATTERN);

function createRegExp(pattern: string): RegExp {
return new RegExp(
`(?:within|in|for)\\s*(?:(?:about|around|roughly|approximately|just)\\s*(?:~\\s*)?)?` +
"(" +
pattern +
")" +
`(?=\\W|$)`,
"i"
);
}

export default class ENTimeUnitWithinFormatParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(): RegExp {
return new RegExp(
`(?:within|in|for)\\s*(?:(?:about|around|roughly|approximately|just)\\s*(?:~\\s*)?)?` +
"(" +
TIME_UNITS_PATTERN +
")" +
`(?=\\W|$)`,
"i"
);
innerPattern(context: ParsingContext): RegExp {
return context.option.useShorts ? FULL_PATTERN : PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
const timeUnits = parseTimeUnits(match[1]);
const timeUnits = parseTimeUnits(match[1], context.option.useShorts);
return ParsingComponents.createRelativeFromRefDate(context.refDate, timeUnits);
}
}
14 changes: 10 additions & 4 deletions src/locales/nl/parsers/NLTimeUnitWithinFormatParser.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { TIME_UNITS_PATTERN, parseTimeUnits } from "../../en/constants";
import { TIME_UNITS_PATTERN, parseTimeUnits, FULL_TIME_UNITS_PATTERN } from "../../en/constants";
import { ParsingContext } from "../../../chrono";
import { ParsingComponents } from "../../../results";
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";

const PATTERN = createRegExp(TIME_UNITS_PATTERN);
const FULL_PATTERN = createRegExp(FULL_TIME_UNITS_PATTERN);

function createRegExp(pattern: string): RegExp {
return new RegExp(`(?:binnen|in)\\s*` + "(" + pattern + ")" + `(?=\\W|$)`, "i");
}
export default class ENTimeUnitWithinFormatParser extends AbstractParserWithWordBoundaryChecking {
innerPattern(): RegExp {
return new RegExp(`(?:binnen|in)\\s*` + "(" + TIME_UNITS_PATTERN + ")" + `(?=\\W|$)`, "i");
innerPattern(context: ParsingContext): RegExp {
return context.option.useShorts ? FULL_PATTERN : PATTERN;
}

innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
const timeUnits = parseTimeUnits(match[1]);
const timeUnits = parseTimeUnits(match[1], context.option.useShorts);
return ParsingComponents.createRelativeFromRefDate(context.refDate, timeUnits);
}
}
2 changes: 1 addition & 1 deletion src/utils/pattern.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type DictionaryLike = string[] | { [word: string]: unknown } | Map<string, unknown>;
export type DictionaryLike = string[] | { [word: string]: unknown } | Map<string, unknown>;

export function patternWithWordBreak(regExp: RegExp): RegExp {
return RegExp("" + regExp.source);
Expand Down