Skip to content

Commit d61f933

Browse files
author
Wanasit Tanakitrungruang
committedAug 10, 2024·
Fix: Hyphanated (or slash) dates started by year intepretation
1 parent 23abdda commit d61f933

File tree

6 files changed

+64
-30
lines changed

6 files changed

+64
-30
lines changed
 

‎src/common/parsers/SlashDateFormatParser.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,22 @@ export default class SlashDateFormatParser implements Parser {
4141
extract(context: ParsingContext, match: RegExpMatchArray): ParsingResult {
4242
// Because of how pattern is executed on remaining text in `chrono.ts`, the character before the match could
4343
// 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-
if (match[OPENING_GROUP].length == 0 && match.index > 0 && match.index < context.text.length) {
45-
const previousChar = context.text[match.index - 1];
46-
if (previousChar >= "0" && previousChar <= "9") {
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")) {
4755
return;
4856
}
4957
}
5058

51-
const index = match.index + match[OPENING_GROUP].length;
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);
5660

5761
// '1.12', '1.12.12' is more like a version numbers
5862
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 {
6165

6266
// MM/dd -> OK
6367
// MM.dd -> NG
64-
if (!match[YEAR_GROUP] && match[0].indexOf("/") < 0) {
68+
if (!match[YEAR_GROUP] && text.indexOf("/") < 0) {
6569
return;
6670
}
6771

6872
const result = context.createParsingResult(index, text);
6973
let month = parseInt(match[this.groupNumberMonth]);
7074
let day = parseInt(match[this.groupNumberDay]);
71-
7275
if (month < 1 || month > 12) {
7376
if (month > 12) {
7477
if (day >= 1 && day <= 12 && month <= 31) {

‎src/locales/en/configuration.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ENTimeUnitWithinFormatParser from "./parsers/ENTimeUnitWithinFormatParser
44
import ENMonthNameLittleEndianParser from "./parsers/ENMonthNameLittleEndianParser";
55
import ENMonthNameMiddleEndianParser from "./parsers/ENMonthNameMiddleEndianParser";
66
import ENMonthNameParser from "./parsers/ENMonthNameParser";
7-
import ENCasualYearMonthDayParser from "./parsers/ENCasualYearMonthDayParser";
7+
import ENYearMonthDayParser from "./parsers/ENYearMonthDayParser";
88
import ENSlashMonthFormatParser from "./parsers/ENSlashMonthFormatParser";
99
import ENTimeExpressionParser from "./parsers/ENTimeExpressionParser";
1010
import ENTimeUnitAgoFormatParser from "./parsers/ENTimeUnitAgoFormatParser";
@@ -55,7 +55,6 @@ export default class ENDefaultConfiguration {
5555
new ENMonthNameLittleEndianParser(),
5656
new ENMonthNameMiddleEndianParser(/*shouldSkipYearLikeDate=*/ littleEndian),
5757
new ENWeekdayParser(),
58-
new ENCasualYearMonthDayParser(),
5958
new ENSlashMonthFormatParser(),
6059
new ENTimeExpressionParser(strictMode),
6160
new ENTimeUnitAgoFormatParser(strictMode),
@@ -65,6 +64,8 @@ export default class ENDefaultConfiguration {
6564
},
6665
strictMode
6766
);
67+
options.parsers.unshift(new ENYearMonthDayParser(/*strictMonthDateOrder=*/ strictMode));
68+
6869
// These relative-dates consideration should be done before other common refiners.
6970
options.refiners.unshift(new ENMergeRelativeFollowByDateRefiner());
7071
options.refiners.unshift(new ENMergeRelativeAfterDateRefiner());

‎src/locales/en/parsers/ENCasualYearMonthDayParser.ts ‎src/locales/en/parsers/ENYearMonthDayParser.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/
1111
- YYYY.MM.DD
1212
*/
1313
const PATTERN = new RegExp(
14-
`([0-9]{4})[\\.\\/\\s]` +
15-
`(?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[\\.\\/\\s]` +
14+
`([0-9]{4})[-\\.\\/\\s]` +
15+
`(?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[-\\.\\/\\s]` +
1616
`([0-9]{1,2})` +
1717
"(?=\\W|$)",
1818
"i"
@@ -23,23 +23,34 @@ const MONTH_NAME_GROUP = 2;
2323
const MONTH_NUMBER_GROUP = 3;
2424
const DATE_NUMBER_GROUP = 4;
2525

26-
export default class ENCasualYearMonthDayParser extends AbstractParserWithWordBoundaryChecking {
26+
export default class ENYearMonthDayParser extends AbstractParserWithWordBoundaryChecking {
27+
constructor(private strictMonthDateOrder: boolean) {
28+
super();
29+
}
30+
2731
innerPattern(): RegExp {
2832
return PATTERN;
2933
}
3034

3135
innerExtract(context: ParsingContext, match: RegExpMatchArray) {
32-
const month = match[MONTH_NUMBER_GROUP]
36+
const year = parseInt(match[YEAR_NUMBER_GROUP]);
37+
let day = parseInt(match[DATE_NUMBER_GROUP]);
38+
let month = match[MONTH_NUMBER_GROUP]
3339
? parseInt(match[MONTH_NUMBER_GROUP])
3440
: MONTH_DICTIONARY[match[MONTH_NAME_GROUP].toLowerCase()];
3541

3642
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) {
3751
return null;
3852
}
3953

40-
const year = parseInt(match[YEAR_NUMBER_GROUP]);
41-
const day = parseInt(match[DATE_NUMBER_GROUP]);
42-
4354
return {
4455
day: day,
4556
month: month,

‎src/locales/en/refiners/ENExtractYearSuffixRefiner.ts

-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ const YEAR_GROUP = 1;
77
export default class ENExtractYearSuffixRefiner implements Refiner {
88
refine(context: ParsingContext, results: ParsingResult[]): ParsingResult[] {
99
results.forEach(function (result) {
10-
context.debug(() => {
11-
console.log("ENExtractYearSuffixRefiner", result.text, result.start);
12-
});
1310
if (!result.start.isDateWithUnknownYear()) {
1411
return;
1512
}

‎test/en/en_slash.test.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test("Test - Parsing Offset Expression", function () {
88
});
99
});
1010

11-
test("Test - Single Expression", function () {
11+
test("Test - Single Expression (MM/dd/yyyy)", function () {
1212
testSingleCase(chrono, "8/10/2012", new Date(2012, 7, 10), (result) => {
1313
expect(result.start).not.toBeNull();
1414
expect(result.start.get("year")).toBe(2012);
@@ -63,7 +63,7 @@ test("Test - Single Expression", function () {
6363
});
6464
});
6565

66-
test("Test - Single Expression Little-Endian", function () {
66+
test("Test - Single Expression Little-Endian (dd/MM/yyyy)", function () {
6767
testSingleCase(chrono.en.GB, "8/10/2012", new Date(2012, 7, 10), (result) => {
6868
expect(result.start).not.toBeNull();
6969
expect(result.start.get("year")).toBe(2012);
@@ -113,7 +113,7 @@ test("Test - Single Expression Little-Endian with Month name", function () {
113113
});
114114
});
115115

116-
test("Test - Single Expression Shorten (month/year)", () => {
116+
test("Test - Single Expression Shorten (mm/yyyy)", () => {
117117
testSingleCase(chrono, "The event is going ahead (04/2016)", new Date(2012, 7, 10), (result) => {
118118
expect(result.start).not.toBeNull();
119119
expect(result.start.get("year")).toBe(2016);
@@ -139,7 +139,7 @@ test("Test - Single Expression Shorten (month/year)", () => {
139139
});
140140
});
141141

142-
test("Test - Single Expression Shorten (date/month)", () => {
142+
test("Test - Single Expression Shorten (dd/mm)", () => {
143143
testSingleCase(chrono, "8/10", new Date(2012, 7, 10), (result) => {
144144
expect(result.start).not.toBeNull();
145145
expect(result.start.get("year")).toBe(2012);
@@ -239,6 +239,7 @@ test("Test - Impossible Dates and Unexpected Results", function () {
239239
testUnexpectedResult(chrono, "06/-31/2022");
240240
testUnexpectedResult(chrono, "18/13/2022");
241241
testUnexpectedResult(chrono, "15/28/2022");
242+
testUnexpectedResult(chrono, "4/13/1");
242243
});
243244

244245
test("Test - forward dates only option", function () {

‎test/en/en_casual_year_month_day.test.ts ‎test/en/en_year_month_day.test.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { testSingleCase, testUnexpectedResult } from "../test_util";
22
import * as chrono from "../../src";
33

4-
test("Test - Single Expression Start with Year", function () {
4+
test("Test - Single Expression (yyyy/MM/dd)", function () {
55
testSingleCase(chrono, "2012/8/10", new Date(2012, 7, 10), (result) => {
66
expect(result.start).not.toBeNull();
77
expect(result.start.get("year")).toBe(2012);
@@ -41,7 +41,7 @@ test("Test - Single Expression Start with Year", function () {
4141
});
4242
});
4343

44-
test("Test - Single Expression Start with Year and Month Name", function () {
44+
test("Test - Single Expression with month name (yyyy/MMM/dd)", function () {
4545
testSingleCase(chrono, "2012/Aug/10", new Date(2012, 7, 10), (result) => {
4646
expect(result.start).not.toBeNull();
4747
expect(result.start.get("year")).toBe(2012);
@@ -73,8 +73,29 @@ test("Test - Single Expression Start with Year and Month Name", function () {
7373
});
7474
});
7575

76-
test("Test - Negative year-month-day like pattern", function () {
77-
testUnexpectedResult(chrono, "2012-80-10", new Date(2012, 7, 10));
76+
test("Test - Allow swap date/month order in casual mode", () => {
77+
testUnexpectedResult(chrono.strict, "2024/13/1");
78+
testUnexpectedResult(chrono.strict, "2024-13-01");
7879

80+
testSingleCase(chrono.casual, "2024/13/1", new Date(2012, 7, 10), (result) => {
81+
expect(result.start.get("year")).toBe(2024);
82+
expect(result.start.get("month")).toBe(1);
83+
expect(result.start.get("day")).toBe(13);
84+
});
85+
86+
testSingleCase(chrono.casual, "2024-13-01", new Date(2012, 7, 10), (result) => {
87+
expect(result.start.get("year")).toBe(2024);
88+
expect(result.start.get("month")).toBe(1);
89+
expect(result.start.get("day")).toBe(13);
90+
});
91+
});
92+
93+
test("Test - Not parse unlikely xxxx-xx-xx pattern", function () {
94+
testUnexpectedResult(chrono, "2012/80/10", new Date(2012, 7, 10));
7995
testUnexpectedResult(chrono, "2012 80 10", new Date(2012, 7, 10));
8096
});
97+
98+
test("Test - Not parse impossible dates", function () {
99+
testUnexpectedResult(chrono, "2014-08-32");
100+
testUnexpectedResult(chrono, "2014-02-30");
101+
});

0 commit comments

Comments
 (0)
Please sign in to comment.