chrono-node 2.7.6 → 2.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/common/parsers/ISOFormatParser.d.ts +1 -12
- package/dist/cjs/common/parsers/ISOFormatParser.js +30 -28
- package/dist/cjs/common/parsers/ISOFormatParser.js.map +1 -1
- package/dist/cjs/common/parsers/SlashDateFormatParser.js +14 -7
- package/dist/cjs/common/parsers/SlashDateFormatParser.js.map +1 -1
- package/dist/cjs/common/refiners/ForwardDateRefiner.js +8 -5
- package/dist/cjs/common/refiners/ForwardDateRefiner.js.map +1 -1
- package/dist/cjs/locales/en/configuration.js +6 -2
- package/dist/cjs/locales/en/configuration.js.map +1 -1
- package/dist/cjs/locales/en/constants.d.ts +1 -1
- package/dist/cjs/locales/en/constants.js +6 -0
- package/dist/cjs/locales/en/constants.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENMonthNameParser.js +1 -1
- package/dist/cjs/locales/en/parsers/ENMonthNameParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENTimeExpressionParser.d.ts +2 -1
- package/dist/cjs/locales/en/parsers/ENTimeExpressionParser.js +7 -0
- package/dist/cjs/locales/en/parsers/ENTimeExpressionParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENTimeUnitAgoFormatParser.js +3 -0
- package/dist/cjs/locales/en/parsers/ENTimeUnitAgoFormatParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +3 -0
- package/dist/cjs/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENTimeUnitLaterFormatParser.js +5 -2
- package/dist/cjs/locales/en/parsers/ENTimeUnitLaterFormatParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENTimeUnitWithinFormatParser.js +3 -0
- package/dist/cjs/locales/en/parsers/ENTimeUnitWithinFormatParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/ENWeekdayParser.d.ts +1 -1
- package/dist/cjs/locales/en/parsers/ENWeekdayParser.js +24 -3
- package/dist/cjs/locales/en/parsers/ENWeekdayParser.js.map +1 -1
- package/dist/cjs/locales/en/parsers/{ENCasualYearMonthDayParser.d.ts → ENYearMonthDayParser.d.ts} +3 -1
- package/dist/cjs/locales/en/parsers/{ENCasualYearMonthDayParser.js → ENYearMonthDayParser.js} +20 -8
- package/dist/cjs/locales/en/parsers/ENYearMonthDayParser.js.map +1 -0
- package/dist/cjs/locales/en/refiners/ENExtractYearSuffixRefiner.d.ts +5 -0
- package/dist/cjs/locales/en/refiners/ENExtractYearSuffixRefiner.js +31 -0
- package/dist/cjs/locales/en/refiners/ENExtractYearSuffixRefiner.js.map +1 -0
- package/dist/cjs/locales/en/refiners/ENMergeDateTimeRefiner.js +1 -1
- package/dist/cjs/locales/en/refiners/ENMergeDateTimeRefiner.js.map +1 -1
- package/dist/cjs/locales/en/refiners/ENUnlikelyFormatFilter.d.ts +6 -0
- package/dist/cjs/locales/en/refiners/ENUnlikelyFormatFilter.js +35 -0
- package/dist/cjs/locales/en/refiners/ENUnlikelyFormatFilter.js.map +1 -0
- package/dist/cjs/results.d.ts +2 -0
- package/dist/cjs/results.js +23 -4
- package/dist/cjs/results.js.map +1 -1
- package/dist/esm/common/parsers/ISOFormatParser.d.ts +1 -12
- package/dist/esm/common/parsers/ISOFormatParser.js +30 -28
- package/dist/esm/common/parsers/ISOFormatParser.js.map +1 -1
- package/dist/esm/common/parsers/SlashDateFormatParser.js +14 -7
- package/dist/esm/common/parsers/SlashDateFormatParser.js.map +1 -1
- package/dist/esm/common/refiners/ForwardDateRefiner.js +8 -5
- package/dist/esm/common/refiners/ForwardDateRefiner.js.map +1 -1
- package/dist/esm/locales/en/configuration.js +6 -2
- package/dist/esm/locales/en/configuration.js.map +1 -1
- package/dist/esm/locales/en/constants.d.ts +1 -1
- package/dist/esm/locales/en/constants.js +6 -0
- package/dist/esm/locales/en/constants.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENMonthNameParser.js +1 -1
- package/dist/esm/locales/en/parsers/ENMonthNameParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENTimeExpressionParser.d.ts +2 -1
- package/dist/esm/locales/en/parsers/ENTimeExpressionParser.js +7 -0
- package/dist/esm/locales/en/parsers/ENTimeExpressionParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js +3 -0
- package/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +3 -0
- package/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js +5 -2
- package/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js +3 -0
- package/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/ENWeekdayParser.d.ts +1 -1
- package/dist/esm/locales/en/parsers/ENWeekdayParser.js +24 -3
- package/dist/esm/locales/en/parsers/ENWeekdayParser.js.map +1 -1
- package/dist/esm/locales/en/parsers/{ENCasualYearMonthDayParser.d.ts → ENYearMonthDayParser.d.ts} +3 -1
- package/dist/esm/locales/en/parsers/{ENCasualYearMonthDayParser.js → ENYearMonthDayParser.js} +19 -7
- package/dist/esm/locales/en/parsers/ENYearMonthDayParser.js.map +1 -0
- package/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.d.ts +5 -0
- package/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.js +28 -0
- package/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.js.map +1 -0
- package/dist/esm/locales/en/refiners/ENMergeDateTimeRefiner.js +1 -1
- package/dist/esm/locales/en/refiners/ENMergeDateTimeRefiner.js.map +1 -1
- package/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.d.ts +6 -0
- package/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.js +32 -0
- package/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.js.map +1 -0
- package/dist/esm/results.d.ts +2 -0
- package/dist/esm/results.js +23 -4
- package/dist/esm/results.js.map +1 -1
- package/package.json +1 -1
- package/src/common/parsers/ISOFormatParser.ts +30 -32
- package/src/common/parsers/SlashDateFormatParser.ts +14 -11
- package/src/common/refiners/ForwardDateRefiner.ts +8 -5
- package/src/locales/en/configuration.ts +10 -2
- package/src/locales/en/constants.ts +7 -1
- package/src/locales/en/parsers/ENMonthNameParser.ts +1 -1
- package/src/locales/en/parsers/ENTimeExpressionParser.ts +13 -1
- package/src/locales/en/parsers/ENTimeUnitAgoFormatParser.ts +3 -1
- package/src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts +4 -2
- package/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts +5 -2
- package/src/locales/en/parsers/ENTimeUnitWithinFormatParser.ts +4 -2
- package/src/locales/en/parsers/ENWeekdayParser.ts +28 -4
- package/src/locales/en/parsers/{ENCasualYearMonthDayParser.ts → ENYearMonthDayParser.ts} +18 -7
- package/src/locales/en/refiners/ENExtractYearSuffixRefiner.ts +34 -0
- package/src/locales/en/refiners/ENMergeDateTimeRefiner.ts +1 -1
- package/src/locales/en/refiners/ENUnlikelyFormatFilter.ts +44 -0
- package/src/results.ts +26 -5
- package/test/en/en_casual.test.ts +4 -0
- package/test/en/en_inter_std.test.ts +30 -28
- package/test/en/en_merging_relative_dates.test.ts +3 -4
- package/test/en/en_month.test.ts +22 -1
- package/test/en/en_slash.test.ts +51 -42
- package/test/en/en_time_exp.test.ts +67 -0
- package/test/en/en_time_units_ago.test.ts +38 -0
- package/test/en/en_time_units_casual_relative.test.ts +8 -0
- package/test/en/en_time_units_later.test.ts +5 -0
- package/test/en/en_time_units_within.test.ts +7 -0
- package/test/en/en_weekday.test.ts +43 -0
- package/test/en/en_year.test.ts +26 -1
- package/test/en/{en_casual_year_month_day.test.ts → en_year_month_day.test.ts} +25 -4
- package/test/system.test.ts +2 -0
- package/dist/cjs/locales/en/parsers/ENCasualYearMonthDayParser.js.map +0 -1
- package/dist/esm/locales/en/parsers/ENCasualYearMonthDayParser.js.map +0 -1
|
@@ -18,7 +18,7 @@ const PATTERN = new RegExp(
|
|
|
18
18
|
"(?:" +
|
|
19
19
|
":([0-9]{1,2})(?:\\.(\\d{1,4}))?" +
|
|
20
20
|
")?" + // :ss.s
|
|
21
|
-
"(
|
|
21
|
+
"(" +
|
|
22
22
|
"Z|([+-]\\d{2}):?(\\d{2})?" +
|
|
23
23
|
")?" + // TZD (Z or ±hh:mm or ±hhmm or ±hh)
|
|
24
24
|
")?" +
|
|
@@ -33,8 +33,9 @@ const HOUR_NUMBER_GROUP = 4;
|
|
|
33
33
|
const MINUTE_NUMBER_GROUP = 5;
|
|
34
34
|
const SECOND_NUMBER_GROUP = 6;
|
|
35
35
|
const MILLISECOND_NUMBER_GROUP = 7;
|
|
36
|
-
const
|
|
37
|
-
const
|
|
36
|
+
const TZD_GROUP = 8;
|
|
37
|
+
const TZD_HOUR_OFFSET_GROUP = 9;
|
|
38
|
+
const TZD_MINUTE_OFFSET_GROUP = 10;
|
|
38
39
|
|
|
39
40
|
export default class ISOFormatParser extends AbstractParserWithWordBoundaryChecking {
|
|
40
41
|
innerPattern(): RegExp {
|
|
@@ -42,44 +43,41 @@ export default class ISOFormatParser extends AbstractParserWithWordBoundaryCheck
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
45
|
-
const components
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const components = context.createParsingComponents({
|
|
47
|
+
"year": parseInt(match[YEAR_NUMBER_GROUP]),
|
|
48
|
+
"month": parseInt(match[MONTH_NUMBER_GROUP]),
|
|
49
|
+
"day": parseInt(match[DATE_NUMBER_GROUP]),
|
|
50
|
+
});
|
|
50
51
|
if (match[HOUR_NUMBER_GROUP] != null) {
|
|
51
|
-
components
|
|
52
|
-
components
|
|
52
|
+
components.assign("hour", parseInt(match[HOUR_NUMBER_GROUP]));
|
|
53
|
+
components.assign("minute", parseInt(match[MINUTE_NUMBER_GROUP]));
|
|
53
54
|
|
|
54
55
|
if (match[SECOND_NUMBER_GROUP] != null) {
|
|
55
|
-
components
|
|
56
|
+
components.assign("second", parseInt(match[SECOND_NUMBER_GROUP]));
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (match[MILLISECOND_NUMBER_GROUP] != null) {
|
|
59
|
-
components
|
|
60
|
+
components.assign("millisecond", parseInt(match[MILLISECOND_NUMBER_GROUP]));
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
if (match[TZD_GROUP] != null) {
|
|
63
|
+
// The Zulu time zone (Z) is equivalent to UTC
|
|
64
|
+
let offset = 0;
|
|
65
|
+
if (match[TZD_HOUR_OFFSET_GROUP]) {
|
|
66
|
+
const hourOffset = parseInt(match[TZD_HOUR_OFFSET_GROUP]);
|
|
67
|
+
let minuteOffset = 0;
|
|
68
|
+
if (match[TZD_MINUTE_OFFSET_GROUP] != null) {
|
|
69
|
+
minuteOffset = parseInt(match[TZD_MINUTE_OFFSET_GROUP]);
|
|
70
|
+
}
|
|
71
|
+
offset = hourOffset * 60;
|
|
72
|
+
if (offset < 0) {
|
|
73
|
+
offset -= minuteOffset;
|
|
74
|
+
} else {
|
|
75
|
+
offset += minuteOffset;
|
|
76
|
+
}
|
|
70
77
|
}
|
|
71
|
-
|
|
72
|
-
let offset = hourOffset * 60;
|
|
73
|
-
if (offset < 0) {
|
|
74
|
-
offset -= minuteOffset;
|
|
75
|
-
} else {
|
|
76
|
-
offset += minuteOffset;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
components["timezoneOffset"] = offset;
|
|
78
|
+
components.assign("timezoneOffset", offset);
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
|
-
|
|
83
|
-
return components;
|
|
81
|
+
return components.addTag("parser/ISOFormatParser");
|
|
84
82
|
}
|
|
85
83
|
}
|
|
@@ -41,18 +41,22 @@ export default class SlashDateFormatParser implements Parser {
|
|
|
41
41
|
extract(context: ParsingContext, match: RegExpMatchArray): ParsingResult {
|
|
42
42
|
// Because of how pattern is executed on remaining text in `chrono.ts`, the character before the match could
|
|
43
43
|
// still be a number (e.g. X[X/YY/ZZ] or XX[/YY/ZZ] or [XX/YY/]ZZ). We want to check and skip them.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const index = match.index + match[OPENING_GROUP].length;
|
|
45
|
+
const indexEnd = match.index + match[0].length - match[ENDING_GROUP].length;
|
|
46
|
+
if (index > 0) {
|
|
47
|
+
const textBefore = context.text.substring(0, index);
|
|
48
|
+
if (textBefore.match("\\d/?$")) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (indexEnd < context.text.length) {
|
|
53
|
+
const textAfter = context.text.substring(indexEnd);
|
|
54
|
+
if (textAfter.match("^/?\\d")) {
|
|
47
55
|
return;
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const
|
|
52
|
-
const text = match[0].substr(
|
|
53
|
-
match[OPENING_GROUP].length,
|
|
54
|
-
match[0].length - match[OPENING_GROUP].length - match[ENDING_GROUP].length
|
|
55
|
-
);
|
|
59
|
+
const text = context.text.substring(index, indexEnd);
|
|
56
60
|
|
|
57
61
|
// '1.12', '1.12.12' is more like a version numbers
|
|
58
62
|
if (text.match(/^\d\.\d$/) || text.match(/^\d\.\d{1,2}\.\d{1,2}\s*$/)) {
|
|
@@ -61,14 +65,13 @@ export default class SlashDateFormatParser implements Parser {
|
|
|
61
65
|
|
|
62
66
|
// MM/dd -> OK
|
|
63
67
|
// MM.dd -> NG
|
|
64
|
-
if (!match[YEAR_GROUP] &&
|
|
68
|
+
if (!match[YEAR_GROUP] && text.indexOf("/") < 0) {
|
|
65
69
|
return;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
const result = context.createParsingResult(index, text);
|
|
69
73
|
let month = parseInt(match[this.groupNumberMonth]);
|
|
70
74
|
let day = parseInt(match[this.groupNumberDay]);
|
|
71
|
-
|
|
72
75
|
if (month < 1 || month > 12) {
|
|
73
76
|
if (month > 12) {
|
|
74
77
|
if (day >= 1 && day <= 12 && month <= 31) {
|
|
@@ -95,6 +98,6 @@ export default class SlashDateFormatParser implements Parser {
|
|
|
95
98
|
result.start.imply("year", year);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
return result;
|
|
101
|
+
return result.addTag("parser/SlashDateFormatParser");
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -15,7 +15,7 @@ export default class ForwardDateRefiner implements Refiner {
|
|
|
15
15
|
return results;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
results.forEach(
|
|
18
|
+
results.forEach((result) => {
|
|
19
19
|
let refMoment = dayjs(context.refDate);
|
|
20
20
|
|
|
21
21
|
if (result.start.isOnlyTime() && refMoment.isAfter(result.start.dayjs())) {
|
|
@@ -28,6 +28,9 @@ export default class ForwardDateRefiner implements Refiner {
|
|
|
28
28
|
implySimilarDate(result.end, refMoment);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
context.debug(() => {
|
|
32
|
+
console.log(`${this.constructor.name} adjusted ${result} time result (${result.start})`);
|
|
33
|
+
});
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
if (result.start.isOnlyWeekdayComponent() && refMoment.isAfter(result.start.dayjs())) {
|
|
@@ -41,7 +44,7 @@ export default class ForwardDateRefiner implements Refiner {
|
|
|
41
44
|
result.start.imply("month", refMoment.month() + 1);
|
|
42
45
|
result.start.imply("year", refMoment.year());
|
|
43
46
|
context.debug(() => {
|
|
44
|
-
console.log(
|
|
47
|
+
console.log(`${this.constructor.name} adjusted ${result} weekday (${result.start})`);
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
if (result.end && result.end.isOnlyWeekdayComponent()) {
|
|
@@ -56,7 +59,7 @@ export default class ForwardDateRefiner implements Refiner {
|
|
|
56
59
|
result.end.imply("month", refMoment.month() + 1);
|
|
57
60
|
result.end.imply("year", refMoment.year());
|
|
58
61
|
context.debug(() => {
|
|
59
|
-
console.log(
|
|
62
|
+
console.log(`${this.constructor.name} adjusted ${result} weekday (${result.end})`);
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -67,13 +70,13 @@ export default class ForwardDateRefiner implements Refiner {
|
|
|
67
70
|
for (let i = 0; i < 3 && refMoment.isAfter(result.start.dayjs()); i++) {
|
|
68
71
|
result.start.imply("year", result.start.get("year") + 1);
|
|
69
72
|
context.debug(() => {
|
|
70
|
-
console.log(
|
|
73
|
+
console.log(`${this.constructor.name} adjusted ${result} year (${result.start})`);
|
|
71
74
|
});
|
|
72
75
|
|
|
73
76
|
if (result.end && !result.end.isCertain("year")) {
|
|
74
77
|
result.end.imply("year", result.end.get("year") + 1);
|
|
75
78
|
context.debug(() => {
|
|
76
|
-
console.log(
|
|
79
|
+
console.log(`${this.constructor.name} adjusted ${result} month (${result.start})`);
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
}
|
|
@@ -4,7 +4,7 @@ import ENTimeUnitWithinFormatParser from "./parsers/ENTimeUnitWithinFormatParser
|
|
|
4
4
|
import ENMonthNameLittleEndianParser from "./parsers/ENMonthNameLittleEndianParser";
|
|
5
5
|
import ENMonthNameMiddleEndianParser from "./parsers/ENMonthNameMiddleEndianParser";
|
|
6
6
|
import ENMonthNameParser from "./parsers/ENMonthNameParser";
|
|
7
|
-
import
|
|
7
|
+
import ENYearMonthDayParser from "./parsers/ENYearMonthDayParser";
|
|
8
8
|
import ENSlashMonthFormatParser from "./parsers/ENSlashMonthFormatParser";
|
|
9
9
|
import ENTimeExpressionParser from "./parsers/ENTimeExpressionParser";
|
|
10
10
|
import ENTimeUnitAgoFormatParser from "./parsers/ENTimeUnitAgoFormatParser";
|
|
@@ -23,6 +23,8 @@ import ENTimeUnitCasualRelativeFormatParser from "./parsers/ENTimeUnitCasualRela
|
|
|
23
23
|
import ENMergeRelativeAfterDateRefiner from "./refiners/ENMergeRelativeAfterDateRefiner";
|
|
24
24
|
import ENMergeRelativeFollowByDateRefiner from "./refiners/ENMergeRelativeFollowByDateRefiner";
|
|
25
25
|
import OverlapRemovalRefiner from "../../common/refiners/OverlapRemovalRefiner";
|
|
26
|
+
import ENExtractYearSuffixRefiner from "./refiners/ENExtractYearSuffixRefiner";
|
|
27
|
+
import ENUnlikelyFormatFilter from "./refiners/ENUnlikelyFormatFilter";
|
|
26
28
|
|
|
27
29
|
export default class ENDefaultConfiguration {
|
|
28
30
|
/**
|
|
@@ -36,6 +38,7 @@ export default class ENDefaultConfiguration {
|
|
|
36
38
|
option.parsers.push(new ENMonthNameParser());
|
|
37
39
|
option.parsers.push(new ENRelativeDateFormatParser());
|
|
38
40
|
option.parsers.push(new ENTimeUnitCasualRelativeFormatParser());
|
|
41
|
+
option.refiners.push(new ENUnlikelyFormatFilter());
|
|
39
42
|
return option;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -54,7 +57,6 @@ export default class ENDefaultConfiguration {
|
|
|
54
57
|
new ENMonthNameLittleEndianParser(),
|
|
55
58
|
new ENMonthNameMiddleEndianParser(/*shouldSkipYearLikeDate=*/ littleEndian),
|
|
56
59
|
new ENWeekdayParser(),
|
|
57
|
-
new ENCasualYearMonthDayParser(),
|
|
58
60
|
new ENSlashMonthFormatParser(),
|
|
59
61
|
new ENTimeExpressionParser(strictMode),
|
|
60
62
|
new ENTimeUnitAgoFormatParser(strictMode),
|
|
@@ -64,6 +66,8 @@ export default class ENDefaultConfiguration {
|
|
|
64
66
|
},
|
|
65
67
|
strictMode
|
|
66
68
|
);
|
|
69
|
+
options.parsers.unshift(new ENYearMonthDayParser(/*strictMonthDateOrder=*/ strictMode));
|
|
70
|
+
|
|
67
71
|
// These relative-dates consideration should be done before other common refiners.
|
|
68
72
|
options.refiners.unshift(new ENMergeRelativeFollowByDateRefiner());
|
|
69
73
|
options.refiners.unshift(new ENMergeRelativeAfterDateRefiner());
|
|
@@ -71,6 +75,10 @@ export default class ENDefaultConfiguration {
|
|
|
71
75
|
|
|
72
76
|
// Re-apply the date time refiner again after the timezone refinement and exclusion in common refiners.
|
|
73
77
|
options.refiners.push(new ENMergeDateTimeRefiner());
|
|
78
|
+
|
|
79
|
+
// Extract year after merging date and time
|
|
80
|
+
options.refiners.push(new ENExtractYearSuffixRefiner());
|
|
81
|
+
|
|
74
82
|
// Keep the date range refiner at the end (after all other refinements).
|
|
75
83
|
options.refiners.push(new ENMergeDateRangeRefiner());
|
|
76
84
|
return options;
|
|
@@ -277,7 +277,7 @@ export const TIME_UNITS_NO_ABBR_PATTERN = repeatedTimeunitPattern(
|
|
|
277
277
|
TIME_UNIT_CONNECTOR_PATTERN
|
|
278
278
|
);
|
|
279
279
|
|
|
280
|
-
export function parseTimeUnits(timeunitText): TimeUnits {
|
|
280
|
+
export function parseTimeUnits(timeunitText): null | TimeUnits {
|
|
281
281
|
const fragments = {};
|
|
282
282
|
let remainingText = timeunitText;
|
|
283
283
|
let match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
|
|
@@ -286,10 +286,16 @@ export function parseTimeUnits(timeunitText): TimeUnits {
|
|
|
286
286
|
remainingText = remainingText.substring(match[0].length).trim();
|
|
287
287
|
match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
|
|
288
288
|
}
|
|
289
|
+
if (Object.keys(fragments).length == 0) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
289
292
|
return fragments;
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
function collectDateTimeFragment(fragments, match) {
|
|
296
|
+
if (match[0].match(/^[a-zA-Z]+$/)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
293
299
|
const num = parseNumberPattern(match[1]);
|
|
294
300
|
const unit = TIME_UNIT_DICTIONARY[match[2].toLowerCase()];
|
|
295
301
|
fragments[unit] = num;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParsingContext } from "../../../chrono";
|
|
2
|
-
import { ParsingComponents } from "../../../results";
|
|
2
|
+
import { ParsingComponents, ParsingResult } from "../../../results";
|
|
3
3
|
import { Meridiem } from "../../../types";
|
|
4
4
|
import { AbstractTimeExpressionParser } from "../../../common/parsers/AbstractTimeExpressionParser";
|
|
5
5
|
|
|
@@ -54,4 +54,16 @@ export default class ENTimeExpressionParser extends AbstractTimeExpressionParser
|
|
|
54
54
|
|
|
55
55
|
return components.addTag("parser/ENTimeExpressionParser");
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
extractFollowingTimeComponents(
|
|
59
|
+
context: ParsingContext,
|
|
60
|
+
match: RegExpMatchArray,
|
|
61
|
+
result: ParsingResult
|
|
62
|
+
): ParsingComponents | null {
|
|
63
|
+
const followingComponents = super.extractFollowingTimeComponents(context, match, result);
|
|
64
|
+
if (followingComponents) {
|
|
65
|
+
followingComponents.addTag("parser/ENTimeExpressionParser");
|
|
66
|
+
}
|
|
67
|
+
return followingComponents;
|
|
68
|
+
}
|
|
57
69
|
}
|
|
@@ -18,8 +18,10 @@ export default class ENTimeUnitAgoFormatParser extends AbstractParserWithWordBou
|
|
|
18
18
|
|
|
19
19
|
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
20
20
|
const timeUnits = parseTimeUnits(match[1]);
|
|
21
|
+
if (!timeUnits) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
21
24
|
const outputTimeUnits = reverseTimeUnits(timeUnits);
|
|
22
|
-
|
|
23
25
|
return ParsingComponents.createRelativeFromReference(context.reference, outputTimeUnits);
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -19,9 +19,12 @@ export default class ENTimeUnitCasualRelativeFormatParser extends AbstractParser
|
|
|
19
19
|
return this.allowAbbreviations ? PATTERN : PATTERN_NO_ABBR;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
innerExtract(context: ParsingContext, match: RegExpMatchArray)
|
|
22
|
+
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
23
23
|
const prefix = match[1].toLowerCase();
|
|
24
24
|
let timeUnits = parseTimeUnits(match[2]);
|
|
25
|
+
if (!timeUnits) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
25
28
|
switch (prefix) {
|
|
26
29
|
case "last":
|
|
27
30
|
case "past":
|
|
@@ -29,7 +32,6 @@ export default class ENTimeUnitCasualRelativeFormatParser extends AbstractParser
|
|
|
29
32
|
timeUnits = reverseTimeUnits(timeUnits);
|
|
30
33
|
break;
|
|
31
34
|
}
|
|
32
|
-
|
|
33
35
|
return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -21,7 +21,10 @@ export default class ENTimeUnitLaterFormatParser extends AbstractParserWithWordB
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const timeUnits = parseTimeUnits(match[GROUP_NUM_TIMEUNITS]);
|
|
25
|
+
if (!timeUnits) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -33,13 +33,15 @@ export default class ENTimeUnitWithinFormatParser extends AbstractParserWithWord
|
|
|
33
33
|
return context.option.forwardDate ? PATTERN_WITH_OPTIONAL_PREFIX : PATTERN_WITH_PREFIX;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
innerExtract(context: ParsingContext, match: RegExpMatchArray)
|
|
36
|
+
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
37
37
|
// Exclude "for the unit" phases, e.g. "for the year"
|
|
38
38
|
if (match[0].match(/^for\s*the\s*\w+/)) {
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
41
|
const timeUnits = parseTimeUnits(match[1]);
|
|
42
|
+
if (!timeUnits) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
43
45
|
return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
|
|
44
46
|
}
|
|
45
47
|
}
|
|
@@ -4,12 +4,13 @@ import { WEEKDAY_DICTIONARY } from "../constants";
|
|
|
4
4
|
import { matchAnyPattern } from "../../../utils/pattern";
|
|
5
5
|
import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
|
|
6
6
|
import { createParsingComponentsAtWeekday } from "../../../common/calculation/weekdays";
|
|
7
|
+
import { Weekday } from "../../../types";
|
|
7
8
|
|
|
8
9
|
const PATTERN = new RegExp(
|
|
9
10
|
"(?:(?:\\,|\\(|\\()\\s*)?" +
|
|
10
11
|
"(?:on\\s*?)?" +
|
|
11
12
|
"(?:(this|last|past|next)\\s*)?" +
|
|
12
|
-
`(${matchAnyPattern(WEEKDAY_DICTIONARY)})` +
|
|
13
|
+
`(${matchAnyPattern(WEEKDAY_DICTIONARY)}|weekend|weekday)` +
|
|
13
14
|
"(?:\\s*(?:\\,|\\)|\\)))?" +
|
|
14
15
|
"(?:\\s*(this|last|past|next)\\s*week)?" +
|
|
15
16
|
"(?=\\W|$)",
|
|
@@ -25,9 +26,7 @@ export default class ENWeekdayParser extends AbstractParserWithWordBoundaryCheck
|
|
|
25
26
|
return PATTERN;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
|
|
29
|
-
const dayOfWeek = match[WEEKDAY_GROUP].toLowerCase();
|
|
30
|
-
const weekday = WEEKDAY_DICTIONARY[dayOfWeek];
|
|
29
|
+
innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents | null {
|
|
31
30
|
const prefix = match[PREFIX_GROUP];
|
|
32
31
|
const postfix = match[POSTFIX_GROUP];
|
|
33
32
|
let modifierWord = prefix || postfix;
|
|
@@ -43,6 +42,31 @@ export default class ENWeekdayParser extends AbstractParserWithWordBoundaryCheck
|
|
|
43
42
|
modifier = "this";
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
const weekday_word = match[WEEKDAY_GROUP].toLowerCase();
|
|
46
|
+
let weekday;
|
|
47
|
+
if (WEEKDAY_DICTIONARY[weekday_word] !== undefined) {
|
|
48
|
+
weekday = WEEKDAY_DICTIONARY[weekday_word];
|
|
49
|
+
} else if (weekday_word == "weekend") {
|
|
50
|
+
// This depends on what days are weekend setting, but typically:
|
|
51
|
+
// 'This/next weekend' means the coming Saturday, 'last weekend' means last Sunday.
|
|
52
|
+
weekday = modifier == "last" ? Weekday.SUNDAY : Weekday.SATURDAY;
|
|
53
|
+
} else if (weekday_word == "weekday") {
|
|
54
|
+
// In English, the "weekday" means any day of the week except weekend.
|
|
55
|
+
// This also depends on what days are weekend setting, but typically:
|
|
56
|
+
// - On weekend ref, this means the coming Monday or last Friday.
|
|
57
|
+
// - On weekday ref, this means the next/last working day.
|
|
58
|
+
const refWeekday = context.reference.getDateWithAdjustedTimezone().getDay();
|
|
59
|
+
if (refWeekday == Weekday.SUNDAY || refWeekday == Weekday.SATURDAY) {
|
|
60
|
+
weekday = modifier == "last" ? Weekday.FRIDAY : Weekday.MONDAY;
|
|
61
|
+
} else {
|
|
62
|
+
weekday = refWeekday - 1;
|
|
63
|
+
weekday = modifier == "last" ? weekday - 1 : weekday + 1;
|
|
64
|
+
weekday = (weekday % 5) + 1;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
return createParsingComponentsAtWeekday(context.reference, weekday, modifier);
|
|
47
71
|
}
|
|
48
72
|
}
|
|
@@ -11,8 +11,8 @@ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/
|
|
|
11
11
|
- YYYY.MM.DD
|
|
12
12
|
*/
|
|
13
13
|
const PATTERN = new RegExp(
|
|
14
|
-
`([0-9]{4})[
|
|
15
|
-
`(?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[
|
|
14
|
+
`([0-9]{4})[-\\.\\/\\s]` +
|
|
15
|
+
`(?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[-\\.\\/\\s]` +
|
|
16
16
|
`([0-9]{1,2})` +
|
|
17
17
|
"(?=\\W|$)",
|
|
18
18
|
"i"
|
|
@@ -23,23 +23,34 @@ const MONTH_NAME_GROUP = 2;
|
|
|
23
23
|
const MONTH_NUMBER_GROUP = 3;
|
|
24
24
|
const DATE_NUMBER_GROUP = 4;
|
|
25
25
|
|
|
26
|
-
export default class
|
|
26
|
+
export default class ENYearMonthDayParser extends AbstractParserWithWordBoundaryChecking {
|
|
27
|
+
constructor(private strictMonthDateOrder: boolean) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
innerPattern(): RegExp {
|
|
28
32
|
return PATTERN;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
|
|
32
|
-
const
|
|
36
|
+
const year = parseInt(match[YEAR_NUMBER_GROUP]);
|
|
37
|
+
let day = parseInt(match[DATE_NUMBER_GROUP]);
|
|
38
|
+
let month = match[MONTH_NUMBER_GROUP]
|
|
33
39
|
? parseInt(match[MONTH_NUMBER_GROUP])
|
|
34
40
|
: MONTH_DICTIONARY[match[MONTH_NAME_GROUP].toLowerCase()];
|
|
35
41
|
|
|
36
42
|
if (month < 1 || month > 12) {
|
|
43
|
+
if (this.strictMonthDateOrder) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (day >= 1 && day <= 12) {
|
|
47
|
+
[month, day] = [day, month];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (day < 1 || day > 31) {
|
|
37
51
|
return null;
|
|
38
52
|
}
|
|
39
53
|
|
|
40
|
-
const year = parseInt(match[YEAR_NUMBER_GROUP]);
|
|
41
|
-
const day = parseInt(match[DATE_NUMBER_GROUP]);
|
|
42
|
-
|
|
43
54
|
return {
|
|
44
55
|
day: day,
|
|
45
56
|
month: month,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ParsingContext, Refiner } from "../../../chrono";
|
|
2
|
+
import { ParsingResult } from "../../../results";
|
|
3
|
+
import { YEAR_PATTERN, parseYear } from "../constants";
|
|
4
|
+
|
|
5
|
+
const YEAR_SUFFIX_PATTERN = new RegExp(`^\\s*(${YEAR_PATTERN})`, "i");
|
|
6
|
+
const YEAR_GROUP = 1;
|
|
7
|
+
export default class ENExtractYearSuffixRefiner implements Refiner {
|
|
8
|
+
refine(context: ParsingContext, results: ParsingResult[]): ParsingResult[] {
|
|
9
|
+
results.forEach(function (result) {
|
|
10
|
+
if (!result.start.isDateWithUnknownYear()) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const suffix = context.text.substring(result.index + result.text.length);
|
|
15
|
+
const match = YEAR_SUFFIX_PATTERN.exec(suffix);
|
|
16
|
+
if (!match) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
context.debug(() => {
|
|
21
|
+
console.log(`Extracting year: '${match[0]}' into : ${result}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const year = parseYear(match[YEAR_GROUP]);
|
|
25
|
+
if (result.end != null) {
|
|
26
|
+
result.end.assign("year", year);
|
|
27
|
+
}
|
|
28
|
+
result.start.assign("year", year);
|
|
29
|
+
result.text += match[0];
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -8,6 +8,6 @@ import AbstractMergeDateTimeRefiner from "../../../common/refiners/AbstractMerge
|
|
|
8
8
|
*/
|
|
9
9
|
export default class ENMergeDateTimeRefiner extends AbstractMergeDateTimeRefiner {
|
|
10
10
|
patternBetween(): RegExp {
|
|
11
|
-
return new RegExp("^\\s*(T|at|after|before|on|of
|
|
11
|
+
return new RegExp("^\\s*(T|at|after|before|on|of|,|-|\\.|∙|:)?\\s*$");
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Filter } from "../../../common/abstractRefiners";
|
|
2
|
+
import { ParsingResult } from "../../../results";
|
|
3
|
+
|
|
4
|
+
export default class ENUnlikelyFormatFilter extends Filter {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
isValid(context, result: ParsingResult): boolean {
|
|
10
|
+
const text = result.text.trim();
|
|
11
|
+
|
|
12
|
+
// If the result is consists of the whole text (e.g. "2024", "May", etc),
|
|
13
|
+
// then it is unlikely to be a date.
|
|
14
|
+
if (text === context.text.trim()) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// In English, the word "may" is a month name, but it is also a modal verb.
|
|
19
|
+
// Check if the text before "may" follows some allowed patterns.
|
|
20
|
+
if (text.toLowerCase() === "may") {
|
|
21
|
+
const textBefore = context.text.substring(0, result.index).trim();
|
|
22
|
+
if (!textBefore.match(/\b(in)$/i)) {
|
|
23
|
+
context.debug(() => {
|
|
24
|
+
console.log(`Removing unlikely result: ${result}`);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// In English, "the second" could refer to the ordinal number or timeunit.
|
|
32
|
+
if (text.toLowerCase().endsWith("the second")) {
|
|
33
|
+
const textAfter = context.text.substring(result.index + result.text.length).trim();
|
|
34
|
+
if (textAfter.length > 0) {
|
|
35
|
+
context.debug(() => {
|
|
36
|
+
console.log(`Removing unlikely result: ${result}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/results.ts
CHANGED
|
@@ -132,7 +132,9 @@ export class ParsingComponents implements ParsedComponents {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
isOnlyTime(): boolean {
|
|
135
|
-
return
|
|
135
|
+
return (
|
|
136
|
+
!this.isCertain("weekday") && !this.isCertain("day") && !this.isCertain("month") && !this.isCertain("year")
|
|
137
|
+
);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
isOnlyWeekdayComponent(): boolean {
|
|
@@ -214,7 +216,9 @@ export class ParsingComponents implements ParsedComponents {
|
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
const components = new ParsingComponents(reference);
|
|
219
|
+
components.addTag("result/relativeDate");
|
|
217
220
|
if (fragments["hour"] || fragments["minute"] || fragments["second"]) {
|
|
221
|
+
components.addTag("result/relativeDateAndTime");
|
|
218
222
|
assignSimilarTime(components, date);
|
|
219
223
|
assignSimilarDate(components, date);
|
|
220
224
|
if (reference.timezoneOffset !== null) {
|
|
@@ -230,11 +234,12 @@ export class ParsingComponents implements ParsedComponents {
|
|
|
230
234
|
components.assign("day", date.date());
|
|
231
235
|
components.assign("month", date.month() + 1);
|
|
232
236
|
components.assign("year", date.year());
|
|
237
|
+
} else if (fragments["week"]) {
|
|
238
|
+
components.assign("day", date.date());
|
|
239
|
+
components.assign("month", date.month() + 1);
|
|
240
|
+
components.assign("year", date.year());
|
|
241
|
+
components.imply("weekday", date.day());
|
|
233
242
|
} else {
|
|
234
|
-
if (fragments["week"]) {
|
|
235
|
-
components.imply("weekday", date.day());
|
|
236
|
-
}
|
|
237
|
-
|
|
238
243
|
components.imply("day", date.date());
|
|
239
244
|
if (fragments["month"]) {
|
|
240
245
|
components.assign("month", date.month() + 1);
|
|
@@ -290,6 +295,22 @@ export class ParsingResult implements ParsedResult {
|
|
|
290
295
|
return this.start.date();
|
|
291
296
|
}
|
|
292
297
|
|
|
298
|
+
addTag(tag: string): ParsingResult {
|
|
299
|
+
this.start.addTag(tag);
|
|
300
|
+
if (this.end) {
|
|
301
|
+
this.end.addTag(tag);
|
|
302
|
+
}
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addTags(tags: string[] | Set<string>): ParsingResult {
|
|
307
|
+
this.start.addTags(tags);
|
|
308
|
+
if (this.end) {
|
|
309
|
+
this.end.addTags(tags);
|
|
310
|
+
}
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
|
|
293
314
|
tags(): Set<string> {
|
|
294
315
|
const combinedTags: Set<string> = new Set(this.start.tags());
|
|
295
316
|
if (this.end) {
|
|
@@ -412,6 +412,10 @@ test("Test - Random negative text", () => {
|
|
|
412
412
|
|
|
413
413
|
testUnexpectedResult(chrono, "do I have the money");
|
|
414
414
|
|
|
415
|
+
testUnexpectedResult(chrono, "I may by here. May the force be with you. Theresa may become PM soon.");
|
|
416
|
+
|
|
417
|
+
testUnexpectedResult(chrono, "XXX is set to be released in the second half of 2025");
|
|
418
|
+
|
|
415
419
|
testUnexpectedResult(chrono.casual, "do I have the money");
|
|
416
420
|
|
|
417
421
|
testUnexpectedResult(chrono.en.GB, "do I have the money");
|