chrono-node 2.9.1 → 2.9.2

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.
Files changed (81) hide show
  1. package/dist/cjs/locales/de/parsers/DECasualTimeParser.js +7 -0
  2. package/dist/cjs/locales/de/parsers/DECasualTimeParser.js.map +1 -1
  3. package/dist/cjs/locales/en/configuration.js +2 -0
  4. package/dist/cjs/locales/en/configuration.js.map +1 -1
  5. package/dist/cjs/locales/en/parsers/ENMonthNameLittleEndianParser.js +1 -1
  6. package/dist/cjs/locales/en/parsers/ENMonthNameLittleEndianParser.js.map +1 -1
  7. package/dist/cjs/locales/en/parsers/ENYearMonthNameParser.d.ts +6 -0
  8. package/dist/cjs/locales/en/parsers/ENYearMonthNameParser.js +31 -0
  9. package/dist/cjs/locales/en/parsers/ENYearMonthNameParser.js.map +1 -0
  10. package/dist/cjs/locales/fi/parsers/FICasualTimeParser.js +7 -0
  11. package/dist/cjs/locales/fi/parsers/FICasualTimeParser.js.map +1 -1
  12. package/dist/cjs/locales/fr/parsers/FRCasualTimeParser.js +5 -0
  13. package/dist/cjs/locales/fr/parsers/FRCasualTimeParser.js.map +1 -1
  14. package/dist/cjs/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js +2 -1
  15. package/dist/cjs/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js.map +1 -1
  16. package/dist/cjs/locales/it/parsers/ITCasualTimeParser.js +5 -0
  17. package/dist/cjs/locales/it/parsers/ITCasualTimeParser.js.map +1 -1
  18. package/dist/cjs/locales/uk/parsers/UKTimeUnitWithinFormatParser.js +1 -1
  19. package/dist/cjs/locales/uk/parsers/UKTimeUnitWithinFormatParser.js.map +1 -1
  20. package/dist/cjs/locales/zh/hans/index.js +2 -0
  21. package/dist/cjs/locales/zh/hans/index.js.map +1 -1
  22. package/dist/cjs/locales/zh/hans/parsers/ZHHansAgoFormatParser.d.ts +6 -0
  23. package/dist/cjs/locales/zh/hans/parsers/ZHHansAgoFormatParser.js +80 -0
  24. package/dist/cjs/locales/zh/hans/parsers/ZHHansAgoFormatParser.js.map +1 -0
  25. package/dist/cjs/locales/zh/hant/index.js +2 -0
  26. package/dist/cjs/locales/zh/hant/index.js.map +1 -1
  27. package/dist/cjs/locales/zh/hant/parsers/ZHHantAgoFormatParser.d.ts +6 -0
  28. package/dist/cjs/locales/zh/hant/parsers/ZHHantAgoFormatParser.js +80 -0
  29. package/dist/cjs/locales/zh/hant/parsers/ZHHantAgoFormatParser.js.map +1 -0
  30. package/dist/esm/locales/de/parsers/DECasualTimeParser.js +7 -0
  31. package/dist/esm/locales/de/parsers/DECasualTimeParser.js.map +1 -1
  32. package/dist/esm/locales/en/configuration.js +2 -0
  33. package/dist/esm/locales/en/configuration.js.map +1 -1
  34. package/dist/esm/locales/en/parsers/ENMonthNameLittleEndianParser.js +1 -1
  35. package/dist/esm/locales/en/parsers/ENMonthNameLittleEndianParser.js.map +1 -1
  36. package/dist/esm/locales/en/parsers/ENYearMonthNameParser.d.ts +6 -0
  37. package/dist/esm/locales/en/parsers/ENYearMonthNameParser.js +28 -0
  38. package/dist/esm/locales/en/parsers/ENYearMonthNameParser.js.map +1 -0
  39. package/dist/esm/locales/fi/parsers/FICasualTimeParser.js +7 -0
  40. package/dist/esm/locales/fi/parsers/FICasualTimeParser.js.map +1 -1
  41. package/dist/esm/locales/fr/parsers/FRCasualTimeParser.js +5 -0
  42. package/dist/esm/locales/fr/parsers/FRCasualTimeParser.js.map +1 -1
  43. package/dist/esm/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js +2 -1
  44. package/dist/esm/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js.map +1 -1
  45. package/dist/esm/locales/it/parsers/ITCasualTimeParser.js +5 -0
  46. package/dist/esm/locales/it/parsers/ITCasualTimeParser.js.map +1 -1
  47. package/dist/esm/locales/uk/parsers/UKTimeUnitWithinFormatParser.js +1 -1
  48. package/dist/esm/locales/uk/parsers/UKTimeUnitWithinFormatParser.js.map +1 -1
  49. package/dist/esm/locales/zh/hans/index.js +2 -0
  50. package/dist/esm/locales/zh/hans/index.js.map +1 -1
  51. package/dist/esm/locales/zh/hans/parsers/ZHHansAgoFormatParser.d.ts +6 -0
  52. package/dist/esm/locales/zh/hans/parsers/ZHHansAgoFormatParser.js +77 -0
  53. package/dist/esm/locales/zh/hans/parsers/ZHHansAgoFormatParser.js.map +1 -0
  54. package/dist/esm/locales/zh/hant/index.js +2 -0
  55. package/dist/esm/locales/zh/hant/index.js.map +1 -1
  56. package/dist/esm/locales/zh/hant/parsers/ZHHantAgoFormatParser.d.ts +6 -0
  57. package/dist/esm/locales/zh/hant/parsers/ZHHantAgoFormatParser.js +77 -0
  58. package/dist/esm/locales/zh/hant/parsers/ZHHantAgoFormatParser.js.map +1 -0
  59. package/package.json +1 -1
  60. package/src/locales/de/parsers/DECasualTimeParser.ts +7 -0
  61. package/src/locales/en/configuration.ts +2 -0
  62. package/src/locales/en/parsers/ENMonthNameLittleEndianParser.ts +1 -1
  63. package/src/locales/en/parsers/ENYearMonthNameParser.ts +38 -0
  64. package/src/locales/fi/parsers/FICasualTimeParser.ts +7 -0
  65. package/src/locales/fr/parsers/FRCasualTimeParser.ts +5 -0
  66. package/src/locales/fr/parsers/FRTimeUnitRelativeFormatParser.ts +2 -1
  67. package/src/locales/it/parsers/ITCasualTimeParser.ts +5 -0
  68. package/src/locales/uk/parsers/UKTimeUnitWithinFormatParser.ts +1 -1
  69. package/src/locales/zh/hans/index.ts +2 -0
  70. package/src/locales/zh/hans/parsers/ZHHansAgoFormatParser.ts +87 -0
  71. package/src/locales/zh/hant/index.ts +2 -0
  72. package/src/locales/zh/hant/parsers/ZHHantAgoFormatParser.ts +87 -0
  73. package/test/de/de_casual.test.ts +14 -0
  74. package/test/en/en_month.test.ts +44 -0
  75. package/test/fi/fi_casual.test.ts +14 -0
  76. package/test/fr/fr_casual.test.ts +14 -0
  77. package/test/fr/fr_time_units_casual_relative.test.ts +6 -0
  78. package/test/it/it_casual.test.ts +37 -0
  79. package/test/uk/uk_time_units_within.test.ts +8 -1
  80. package/test/zh/hans/zh_hans_ago.test.ts +58 -0
  81. package/test/zh/hant/zh_hant_ago.test.ts +58 -0
@@ -24,6 +24,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
24
24
  component.imply("minute", 0);
25
25
  component.imply("second", 0);
26
26
  component.imply("meridiem", Meridiem.AM);
27
+ component.addTag("casualReference/morning");
27
28
  break;
28
29
 
29
30
  case "vormittag":
@@ -31,6 +32,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
31
32
  component.imply("minute", 0);
32
33
  component.imply("second", 0);
33
34
  component.imply("meridiem", Meridiem.AM);
35
+ component.addTag("casualReference/morning");
34
36
  break;
35
37
 
36
38
  case "mittag":
@@ -39,6 +41,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
39
41
  component.imply("minute", 0);
40
42
  component.imply("second", 0);
41
43
  component.imply("meridiem", Meridiem.AM);
44
+ component.addTag("casualReference/noon");
42
45
  break;
43
46
 
44
47
  case "nachmittag":
@@ -46,6 +49,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
46
49
  component.imply("minute", 0);
47
50
  component.imply("second", 0);
48
51
  component.imply("meridiem", Meridiem.PM);
52
+ component.addTag("casualReference/afternoon");
49
53
  break;
50
54
 
51
55
  case "abend":
@@ -53,6 +57,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
53
57
  component.imply("minute", 0);
54
58
  component.imply("second", 0);
55
59
  component.imply("meridiem", Meridiem.PM);
60
+ component.addTag("casualReference/evening");
56
61
  break;
57
62
 
58
63
  case "nacht":
@@ -60,6 +65,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
60
65
  component.imply("minute", 0);
61
66
  component.imply("second", 0);
62
67
  component.imply("meridiem", Meridiem.PM);
68
+ component.addTag("casualReference/evening");
63
69
  break;
64
70
 
65
71
  case "mitternacht":
@@ -71,6 +77,7 @@ export default class DECasualTimeParser extends AbstractParserWithWordBoundaryCh
71
77
  component.imply("minute", 0);
72
78
  component.imply("second", 0);
73
79
  component.imply("meridiem", Meridiem.AM);
80
+ component.addTag("casualReference/midnight");
74
81
  break;
75
82
  }
76
83
  return component;
@@ -5,6 +5,7 @@ import ENMonthNameLittleEndianParser from "./parsers/ENMonthNameLittleEndianPars
5
5
  import ENMonthNameMiddleEndianParser from "./parsers/ENMonthNameMiddleEndianParser";
6
6
  import ENMonthNameParser from "./parsers/ENMonthNameParser";
7
7
  import ENYearMonthDayParser from "./parsers/ENYearMonthDayParser";
8
+ import ENYearMonthNameParser from "./parsers/ENYearMonthNameParser";
8
9
  import ENSlashMonthFormatParser from "./parsers/ENSlashMonthFormatParser";
9
10
  import ENTimeExpressionParser from "./parsers/ENTimeExpressionParser";
10
11
  import ENTimeUnitAgoFormatParser from "./parsers/ENTimeUnitAgoFormatParser";
@@ -61,6 +62,7 @@ export default class ENDefaultConfiguration {
61
62
  new ENTimeExpressionParser(strictMode),
62
63
  new ENTimeUnitAgoFormatParser(strictMode),
63
64
  new ENTimeUnitLaterFormatParser(strictMode),
65
+ new ENYearMonthNameParser(),
64
66
  ],
65
67
  refiners: [new ENMergeDateTimeRefiner()],
66
68
  },
@@ -12,7 +12,7 @@ const PATTERN = new RegExp(
12
12
  `(?:on\\s{0,3})?` +
13
13
  `(${ORDINAL_NUMBER_PATTERN})` +
14
14
  `(?:` +
15
- `\\s{0,3}(?:to|\\-|\\–|until|through|till)?\\s{0,3}` +
15
+ `\\s{0,3}(?:to|\\-|\\–|until|through|till)\\s{0,3}` +
16
16
  `(${ORDINAL_NUMBER_PATTERN})` +
17
17
  ")?" +
18
18
  `(?:-|/|\\s{0,3}(?:of)?\\s{0,3})` +
@@ -0,0 +1,38 @@
1
+ import { MONTH_DICTIONARY } from "../constants";
2
+ import { ParsingContext } from "../../../chrono";
3
+ import { matchAnyPattern } from "../../../utils/pattern";
4
+ import { parseYear } from "../constants";
5
+ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
6
+
7
+ const YEAR_PATTERN = `(?:[1-9][0-9]{0,3}\\s{0,2}(?:BE|AD|BC|BCE|CE)|[1-9][0-9]{3})`;
8
+
9
+ const PATTERN = new RegExp(
10
+ `(${YEAR_PATTERN})` +
11
+ `(?:\\s*[-.\\/,]?\\s*|\\s+of\\s+)` +
12
+ `(${matchAnyPattern(MONTH_DICTIONARY)})` +
13
+ `(?=[^\\s\\w]|\\s+[^0-9]|\\s+$|$)`,
14
+ "i"
15
+ );
16
+
17
+ const YEAR_GROUP = 1;
18
+ const MONTH_NAME_GROUP = 2;
19
+
20
+ export default class ENYearMonthNameParser extends AbstractParserWithWordBoundaryChecking {
21
+ innerPattern(): RegExp {
22
+ return PATTERN;
23
+ }
24
+
25
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
26
+ const year = parseYear(match[YEAR_GROUP]);
27
+ const monthName = match[MONTH_NAME_GROUP].toLowerCase();
28
+ const month = MONTH_DICTIONARY[monthName];
29
+
30
+ const result = context.createParsingResult(match.index, match[0]);
31
+ result.start.imply("day", 1);
32
+ result.start.assign("month", month);
33
+ result.start.assign("year", year);
34
+ result.start.addTag("parser/ENYearMonthNameParser");
35
+
36
+ return result;
37
+ }
38
+ }
@@ -25,6 +25,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
25
25
  component.imply("minute", 0);
26
26
  component.imply("second", 0);
27
27
  component.imply("meridiem", Meridiem.AM);
28
+ component.addTag("casualReference/morning");
28
29
  break;
29
30
 
30
31
  case "aamupäivällä":
@@ -32,6 +33,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
32
33
  component.imply("minute", 0);
33
34
  component.imply("second", 0);
34
35
  component.imply("meridiem", Meridiem.AM);
36
+ component.addTag("casualReference/morning");
35
37
  break;
36
38
 
37
39
  case "päivällä":
@@ -39,6 +41,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
39
41
  component.imply("minute", 0);
40
42
  component.imply("second", 0);
41
43
  component.imply("meridiem", Meridiem.AM);
44
+ component.addTag("casualReference/noon");
42
45
  break;
43
46
 
44
47
  case "iltapäivällä":
@@ -46,6 +49,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
46
49
  component.imply("minute", 0);
47
50
  component.imply("second", 0);
48
51
  component.imply("meridiem", Meridiem.PM);
52
+ component.addTag("casualReference/afternoon");
49
53
  break;
50
54
 
51
55
  case "illalla":
@@ -53,6 +57,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
53
57
  component.imply("minute", 0);
54
58
  component.imply("second", 0);
55
59
  component.imply("meridiem", Meridiem.PM);
60
+ component.addTag("casualReference/evening");
56
61
  break;
57
62
 
58
63
  case "yöllä":
@@ -60,6 +65,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
60
65
  component.imply("minute", 0);
61
66
  component.imply("second", 0);
62
67
  component.imply("meridiem", Meridiem.PM);
68
+ component.addTag("casualReference/evening");
63
69
  break;
64
70
 
65
71
  case "keskiyöllä":
@@ -71,6 +77,7 @@ export default class FICasualTimeParser extends AbstractParserWithWordBoundaryCh
71
77
  component.imply("minute", 0);
72
78
  component.imply("second", 0);
73
79
  component.imply("meridiem", Meridiem.AM);
80
+ component.addTag("casualReference/midnight");
74
81
  break;
75
82
  }
76
83
  return component;
@@ -18,29 +18,34 @@ export default class FRCasualTimeParser extends AbstractParserWithWordBoundaryCh
18
18
  component.imply("hour", 14);
19
19
  component.imply("minute", 0);
20
20
  component.imply("meridiem", Meridiem.PM);
21
+ component.addTag("casualReference/afternoon");
21
22
  break;
22
23
 
23
24
  case "soir":
24
25
  component.imply("hour", 18);
25
26
  component.imply("minute", 0);
26
27
  component.imply("meridiem", Meridiem.PM);
28
+ component.addTag("casualReference/evening");
27
29
  break;
28
30
 
29
31
  case "matin":
30
32
  component.imply("hour", 8);
31
33
  component.imply("minute", 0);
32
34
  component.imply("meridiem", Meridiem.AM);
35
+ component.addTag("casualReference/morning");
33
36
  break;
34
37
 
35
38
  case "a midi":
36
39
  component.imply("hour", 12);
37
40
  component.imply("minute", 0);
38
41
  component.imply("meridiem", Meridiem.AM);
42
+ component.addTag("casualReference/noon");
39
43
  break;
40
44
 
41
45
  case "à minuit":
42
46
  component.imply("hour", 0);
43
47
  component.imply("meridiem", Meridiem.AM);
48
+ component.addTag("casualReference/midnight");
44
49
  break;
45
50
  }
46
51
 
@@ -16,7 +16,8 @@ export default class FRTimeUnitAgoFormatParser extends AbstractParserWithWordBou
16
16
  `(${NUMBER_PATTERN})?` +
17
17
  `(?:\\s*(prochaine?s?|derni[eè]re?s?|pass[ée]e?s?|pr[ée]c[ée]dents?|suivante?s?))?` +
18
18
  `\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})` +
19
- `(?:\\s*(prochaine?s?|derni[eè]re?s?|pass[ée]e?s?|pr[ée]c[ée]dents?|suivante?s?))?`,
19
+ `(?:\\s*(prochaine?s?|derni[eè]re?s?|pass[ée]e?s?|pr[ée]c[ée]dents?|suivante?s?))?` +
20
+ `(?=\\W|$)`,
20
21
  "i"
21
22
  );
22
23
  }
@@ -18,12 +18,14 @@ export default class ITCasualTimeParser extends AbstractParserWithWordBoundaryCh
18
18
  case "pomeriggio":
19
19
  component.imply("meridiem", Meridiem.PM);
20
20
  component.imply("hour", 15);
21
+ component.addTag("casualReference/afternoon");
21
22
  break;
22
23
 
23
24
  case "sera":
24
25
  case "notte":
25
26
  component.imply("meridiem", Meridiem.PM);
26
27
  component.imply("hour", 20);
28
+ component.addTag("casualReference/evening");
27
29
  break;
28
30
 
29
31
  case "mezzanotte":
@@ -34,16 +36,19 @@ export default class ITCasualTimeParser extends AbstractParserWithWordBoundaryCh
34
36
  component.imply("hour", 0);
35
37
  component.imply("minute", 0);
36
38
  component.imply("second", 0);
39
+ component.addTag("casualReference/midnight");
37
40
  break;
38
41
 
39
42
  case "mattina":
40
43
  component.imply("meridiem", Meridiem.AM);
41
44
  component.imply("hour", 6);
45
+ component.addTag("casualReference/morning");
42
46
  break;
43
47
 
44
48
  case "mezzogiorno":
45
49
  component.imply("meridiem", Meridiem.AM);
46
50
  component.imply("hour", 12);
51
+ component.addTag("casualReference/noon");
47
52
  break;
48
53
  }
49
54
 
@@ -12,7 +12,7 @@ export default class UKTimeUnitWithinFormatParser extends AbstractParserWithWord
12
12
 
13
13
  innerPattern(context: ParsingContext): RegExp {
14
14
  return context.option.forwardDate
15
- ? new RegExp(PATTERN, "i")
15
+ ? new RegExp(PATTERN, REGEX_PARTS.flags)
16
16
  : new RegExp(`(?:протягом|на протязі|протягом|упродовж|впродовж)\\s*${PATTERN}`, REGEX_PARTS.flags);
17
17
  }
18
18
 
@@ -10,6 +10,7 @@ import { Component, ParsedResult, ParsingOption, ParsingReference, Meridiem, Wee
10
10
  import ZHHansCasualDateParser from "./parsers/ZHHansCasualDateParser";
11
11
  import ZHHansDateParser from "./parsers/ZHHansDateParser";
12
12
  import ZHHansDeadlineFormatParser from "./parsers/ZHHansDeadlineFormatParser";
13
+ import ZHHansAgoFormatParser from "./parsers/ZHHansAgoFormatParser";
13
14
  import ZHHansRelationWeekdayParser from "./parsers/ZHHansRelationWeekdayParser";
14
15
  import ZHHansTimeExpressionParser from "./parsers/ZHHansTimeExpressionParser";
15
16
  import ZHHansWeekdayParser from "./parsers/ZHHansWeekdayParser";
@@ -53,6 +54,7 @@ export function createConfiguration(): Configuration {
53
54
  new ZHHansWeekdayParser(),
54
55
  new ZHHansTimeExpressionParser(),
55
56
  new ZHHansDeadlineFormatParser(),
57
+ new ZHHansAgoFormatParser(),
56
58
  ],
57
59
  refiners: [new ZHHansMergeDateRangeRefiner(), new ZHHansMergeDateTimeRefiner()],
58
60
  });
@@ -0,0 +1,87 @@
1
+ import { ParsingContext } from "../../../../chrono";
2
+ import { AbstractParserWithWordBoundaryChecking } from "../../../../common/parsers/AbstractParserWithWordBoundary";
3
+ import { addDuration, Duration, reverseDuration } from "../../../../calculation/duration";
4
+ import { NUMBER, zhStringToNumber } from "../constants";
5
+
6
+ const PATTERN = new RegExp(
7
+ "(\\d+|[" +
8
+ Object.keys(NUMBER).join("") +
9
+ "]+|半|几)(?:\\s*)" +
10
+ "(?:个)?" +
11
+ "(秒(?:钟)?|分钟|小时|钟|日|天|星期|礼拜|月|年)" +
12
+ "(?:之)?前",
13
+ "i"
14
+ );
15
+
16
+ const NUMBER_GROUP = 1;
17
+ const UNIT_GROUP = 2;
18
+
19
+ export default class ZHHansAgoFormatParser extends AbstractParserWithWordBoundaryChecking {
20
+ innerPattern(): RegExp {
21
+ return PATTERN;
22
+ }
23
+
24
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
25
+ const result = context.createParsingResult(match.index, match[0]);
26
+
27
+ let number = parseInt(match[NUMBER_GROUP]);
28
+ if (isNaN(number)) {
29
+ number = zhStringToNumber(match[NUMBER_GROUP]);
30
+ }
31
+
32
+ if (isNaN(number)) {
33
+ const string = match[NUMBER_GROUP];
34
+ if (string === "几") {
35
+ number = 3;
36
+ } else if (string === "半") {
37
+ number = 0.5;
38
+ } else {
39
+ //just in case
40
+ return null;
41
+ }
42
+ }
43
+
44
+ let duration: Duration = {};
45
+ const unit = match[UNIT_GROUP];
46
+ const unitAbbr = unit[0];
47
+
48
+ if (unitAbbr.match(/[日天星礼月年]/)) {
49
+ if (unitAbbr == "日" || unitAbbr == "天") {
50
+ duration.day = number;
51
+ } else if (unitAbbr == "星" || unitAbbr == "礼") {
52
+ duration.week = number;
53
+ } else if (unitAbbr == "月") {
54
+ duration.month = number;
55
+ } else if (unitAbbr == "年") {
56
+ duration.year = number;
57
+ }
58
+
59
+ duration = reverseDuration(duration);
60
+
61
+ const date = addDuration(context.refDate, duration);
62
+ result.start.assign("year", date.getFullYear());
63
+ result.start.assign("month", date.getMonth() + 1);
64
+ result.start.assign("day", date.getDate());
65
+ return result;
66
+ }
67
+
68
+ if (unitAbbr == "秒") {
69
+ duration.second = number;
70
+ } else if (unitAbbr == "分") {
71
+ duration.minute = number;
72
+ } else if (unitAbbr == "小" || unitAbbr == "钟") {
73
+ duration.hour = number;
74
+ }
75
+
76
+ duration = reverseDuration(duration);
77
+
78
+ const date = addDuration(context.refDate, duration);
79
+ result.start.imply("year", date.getFullYear());
80
+ result.start.imply("month", date.getMonth() + 1);
81
+ result.start.imply("day", date.getDate());
82
+ result.start.assign("hour", date.getHours());
83
+ result.start.assign("minute", date.getMinutes());
84
+ result.start.assign("second", date.getSeconds());
85
+ return result;
86
+ }
87
+ }
@@ -11,6 +11,7 @@ import { Component, ParsedResult, ParsingOption, ParsingReference, Meridiem, Wee
11
11
  import ZHHantCasualDateParser from "./parsers/ZHHantCasualDateParser";
12
12
  import ZHHantDateParser from "./parsers/ZHHantDateParser";
13
13
  import ZHHantDeadlineFormatParser from "./parsers/ZHHantDeadlineFormatParser";
14
+ import ZHHantAgoFormatParser from "./parsers/ZHHantAgoFormatParser";
14
15
  import ZHHantRelationWeekdayParser from "./parsers/ZHHantRelationWeekdayParser";
15
16
  import ZHHantTimeExpressionParser from "./parsers/ZHHantTimeExpressionParser";
16
17
  import ZHHantWeekdayParser from "./parsers/ZHHantWeekdayParser";
@@ -54,6 +55,7 @@ export function createConfiguration(): Configuration {
54
55
  new ZHHantWeekdayParser(),
55
56
  new ZHHantTimeExpressionParser(),
56
57
  new ZHHantDeadlineFormatParser(),
58
+ new ZHHantAgoFormatParser(),
57
59
  ],
58
60
  refiners: [new ZHHantMergeDateRangeRefiner(), new ZHHantMergeDateTimeRefiner()],
59
61
  });
@@ -0,0 +1,87 @@
1
+ import { ParsingContext } from "../../../../chrono";
2
+ import { AbstractParserWithWordBoundaryChecking } from "../../../../common/parsers/AbstractParserWithWordBoundary";
3
+ import { addDuration, Duration, reverseDuration } from "../../../../calculation/duration";
4
+ import { NUMBER, zhStringToNumber } from "../constants";
5
+
6
+ const PATTERN = new RegExp(
7
+ "(\\d+|[" +
8
+ Object.keys(NUMBER).join("") +
9
+ "]+|半|幾)(?:\\s*)" +
10
+ "(?:個)?" +
11
+ "(秒(?:鐘)?|分鐘|小時|鐘|日|天|星期|禮拜|月|年)" +
12
+ "(?:之)?前",
13
+ "i"
14
+ );
15
+
16
+ const NUMBER_GROUP = 1;
17
+ const UNIT_GROUP = 2;
18
+
19
+ export default class ZHHantAgoFormatParser extends AbstractParserWithWordBoundaryChecking {
20
+ innerPattern(): RegExp {
21
+ return PATTERN;
22
+ }
23
+
24
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
25
+ const result = context.createParsingResult(match.index, match[0]);
26
+
27
+ let number = parseInt(match[NUMBER_GROUP]);
28
+ if (isNaN(number)) {
29
+ number = zhStringToNumber(match[NUMBER_GROUP]);
30
+ }
31
+
32
+ if (isNaN(number)) {
33
+ const string = match[NUMBER_GROUP];
34
+ if (string === "幾") {
35
+ number = 3;
36
+ } else if (string === "半") {
37
+ number = 0.5;
38
+ } else {
39
+ //just in case
40
+ return null;
41
+ }
42
+ }
43
+
44
+ let duration: Duration = {};
45
+ const unit = match[UNIT_GROUP];
46
+ const unitAbbr = unit[0];
47
+
48
+ if (unitAbbr.match(/[日天星禮月年]/)) {
49
+ if (unitAbbr == "日" || unitAbbr == "天") {
50
+ duration.day = number;
51
+ } else if (unitAbbr == "星" || unitAbbr == "禮") {
52
+ duration.week = number;
53
+ } else if (unitAbbr == "月") {
54
+ duration.month = number;
55
+ } else if (unitAbbr == "年") {
56
+ duration.year = number;
57
+ }
58
+
59
+ duration = reverseDuration(duration);
60
+
61
+ const date = addDuration(context.refDate, duration);
62
+ result.start.assign("year", date.getFullYear());
63
+ result.start.assign("month", date.getMonth() + 1);
64
+ result.start.assign("day", date.getDate());
65
+ return result;
66
+ }
67
+
68
+ if (unitAbbr == "秒") {
69
+ duration.second = number;
70
+ } else if (unitAbbr == "分") {
71
+ duration.minute = number;
72
+ } else if (unitAbbr == "小" || unitAbbr == "鐘") {
73
+ duration.hour = number;
74
+ }
75
+
76
+ duration = reverseDuration(duration);
77
+
78
+ const date = addDuration(context.refDate, duration);
79
+ result.start.imply("year", date.getFullYear());
80
+ result.start.imply("month", date.getMonth() + 1);
81
+ result.start.imply("day", date.getDate());
82
+ result.start.assign("hour", date.getHours());
83
+ result.start.assign("minute", date.getMinutes());
84
+ result.start.assign("second", date.getSeconds());
85
+ return result;
86
+ }
87
+ }
@@ -320,3 +320,17 @@ test("Test - Random negative text", function () {
320
320
 
321
321
  testUnexpectedResult(chrono.de, "ljetztlich");
322
322
  });
323
+
324
+ test("Test - Casual time keeps its meridiem when merged with an explicit time", () => {
325
+ testSingleCase(chrono.de, "nachmittag um 5", new Date(2016, 7, 10, 12), (result, text) => {
326
+ expect(result.text).toBe(text);
327
+ expect(result.start.get("hour")).toBe(17);
328
+ expect(result.start.get("meridiem")).toBe(1);
329
+ });
330
+
331
+ testSingleCase(chrono.de, "abend um 5", new Date(2016, 7, 10, 12), (result, text) => {
332
+ expect(result.text).toBe(text);
333
+ expect(result.start.get("hour")).toBe(17);
334
+ expect(result.start.get("meridiem")).toBe(1);
335
+ });
336
+ });
@@ -293,3 +293,47 @@ test("Test - Month only in different context", () => {
293
293
  testUnexpectedResult(chrono.casual, "The mountain may not move");
294
294
  testUnexpectedResult(chrono.casual, "May not be correct");
295
295
  });
296
+
297
+ test("Test - Year Month expression", () => {
298
+ testSingleCase(chrono, "2024 Aug", (result) => {
299
+ expect(result.text).toBe("2024 Aug");
300
+ expect(result.start.get("year")).toBe(2024);
301
+ expect(result.start.get("month")).toBe(8);
302
+ expect(result.start.get("day")).toBe(1);
303
+ expect(result.start.isCertain("year")).toBe(true);
304
+ expect(result.start.isCertain("month")).toBe(true);
305
+ expect(result.start.isCertain("day")).toBe(false);
306
+ });
307
+
308
+ testSingleCase(chrono, "2024 August", (result) => {
309
+ expect(result.text).toBe("2024 August");
310
+ expect(result.start.get("year")).toBe(2024);
311
+ expect(result.start.get("month")).toBe(8);
312
+ expect(result.start.get("day")).toBe(1);
313
+ });
314
+
315
+ testSingleCase(chrono, "2023 Sept", (result) => {
316
+ expect(result.text).toBe("2023 Sept");
317
+ expect(result.start.get("year")).toBe(2023);
318
+ expect(result.start.get("month")).toBe(9);
319
+ expect(result.start.get("day")).toBe(1);
320
+ });
321
+
322
+ testSingleCase(chrono, "2024-August", (result) => {
323
+ expect(result.text).toBe("2024-August");
324
+ expect(result.start.get("year")).toBe(2024);
325
+ expect(result.start.get("month")).toBe(8);
326
+ });
327
+
328
+ testSingleCase(chrono, "2024/August", (result) => {
329
+ expect(result.text).toBe("2024/August");
330
+ expect(result.start.get("year")).toBe(2024);
331
+ expect(result.start.get("month")).toBe(8);
332
+ });
333
+
334
+ testSingleCase(chrono, "2024 AD August", (result) => {
335
+ expect(result.text).toBe("2024 AD August");
336
+ expect(result.start.get("year")).toBe(2024);
337
+ expect(result.start.get("month")).toBe(8);
338
+ });
339
+ });
@@ -83,3 +83,17 @@ test("Test - Combined Expression", function () {
83
83
  expect(result.start.get("hour")).toBe(0);
84
84
  });
85
85
  });
86
+
87
+ test("Test - Casual time keeps its meridiem when merged with an explicit time", () => {
88
+ testSingleCase(chrono, "iltapäivällä klo 5", new Date(2016, 7, 10, 12), (result, text) => {
89
+ expect(result.text).toBe(text);
90
+ expect(result.start.get("hour")).toBe(17);
91
+ expect(result.start.get("meridiem")).toBe(1);
92
+ });
93
+
94
+ testSingleCase(chrono, "illalla klo 8", new Date(2016, 7, 10, 12), (result, text) => {
95
+ expect(result.text).toBe(text);
96
+ expect(result.start.get("hour")).toBe(20);
97
+ expect(result.start.get("meridiem")).toBe(1);
98
+ });
99
+ });
@@ -298,3 +298,17 @@ test("Test - Combined Expression", function () {
298
298
  // expect(results.length).toBe(0);
299
299
  //
300
300
  // });
301
+
302
+ test("Test - Casual time keeps its meridiem when merged with an explicit time", () => {
303
+ testSingleCase(chrono.fr, "après-midi à 5", new Date(2016, 7, 10, 12), (result, text) => {
304
+ expect(result.text).toBe(text);
305
+ expect(result.start.get("hour")).toBe(17);
306
+ expect(result.start.get("meridiem")).toBe(1);
307
+ });
308
+
309
+ testSingleCase(chrono.fr, "soir à 8", new Date(2016, 7, 10, 12), (result, text) => {
310
+ expect(result.text).toBe(text);
311
+ expect(result.start.get("hour")).toBe(20);
312
+ expect(result.start.get("meridiem")).toBe(1);
313
+ });
314
+ });
@@ -106,3 +106,9 @@ test("Test - fr - relative date", function () {
106
106
  expect(result.start.isCertain("second")).toBeFalsy();
107
107
  });
108
108
  });
109
+
110
+ test("Test - fr - false positive prevention for single-letter time units", function () {
111
+ // Test that "la derniere h" in "la derniere homme" doesn't match as "la derniere h" (last hour)
112
+ // The word boundary check should prevent "h" from matching the time unit "h"
113
+ testUnexpectedResult(chrono.fr, "la derniere homme");
114
+ });
@@ -42,3 +42,40 @@ test("Test - Single Expression", () => {
42
42
  expect(result.start).toBeDate(new Date(2012, 7, 11, 17, 10));
43
43
  });
44
44
  });
45
+
46
+ test("Test - Casual time keeps its meridiem when merged with an explicit time", () => {
47
+ testSingleCase(chrono.it, "pomeriggio alle 5", new Date(2016, 7, 10, 12), (result, text) => {
48
+ expect(result.text).toBe(text);
49
+ expect(result.start.get("hour")).toBe(17);
50
+ expect(result.start.get("meridiem")).toBe(1);
51
+ });
52
+
53
+ testSingleCase(chrono.it, "sera alle 8", new Date(2016, 7, 10, 12), (result, text) => {
54
+ expect(result.text).toBe(text);
55
+ expect(result.start.get("hour")).toBe(20);
56
+ expect(result.start.get("meridiem")).toBe(1);
57
+ });
58
+ });
59
+
60
+ test("Test - Casual time midnight and noon", () => {
61
+ testSingleCase(chrono.it.casual, "La scadenza è mezzanotte", new Date(2012, 7, 10, 12), (result) => {
62
+ expect(result.index).toBe(13);
63
+ expect(result.text).toBe(" mezzanotte");
64
+ expect(result.start.get("year")).toBe(2012);
65
+ expect(result.start.get("month")).toBe(8);
66
+ expect(result.start.get("day")).toBe(11);
67
+ expect(result.start.get("hour")).toBe(0);
68
+ expect(result.start.get("minute")).toBe(0);
69
+ expect(result.start.get("second")).toBe(0);
70
+ });
71
+
72
+ testSingleCase(chrono.it.casual, "La scadenza è mezzogiorno", new Date(2012, 7, 10, 12), (result) => {
73
+ expect(result.index).toBe(13);
74
+ expect(result.text).toBe(" mezzogiorno");
75
+ expect(result.start.get("year")).toBe(2012);
76
+ expect(result.start.get("month")).toBe(8);
77
+ expect(result.start.get("day")).toBe(10);
78
+ expect(result.start.get("hour")).toBe(12);
79
+ expect(result.start.get("meridiem")).toBe(0);
80
+ });
81
+ });
@@ -1,5 +1,5 @@
1
1
  import * as chrono from "../../src";
2
- import { testSingleCase } from "../test_util";
2
+ import { testSingleCase, testUnexpectedResult } from "../test_util";
3
3
 
4
4
  test("Test - The normal within expression", () => {
5
5
  testSingleCase(chrono.uk, "буде зроблено протягом хвилини", new Date(2012, 7, 10), (result) => {
@@ -14,3 +14,10 @@ test("Test - The normal within expression", () => {
14
14
  expect(result.start).toBeDate(new Date(2012, 7, 10, 2));
15
15
  });
16
16
  });
17
+
18
+ test("Test - Within expression respects the word boundary", () => {
19
+ // The forwardDate branch builds a bare time-unit pattern whose right boundary uses the
20
+ // \p{L}\p{N} escapes, so a unit glued to a following letter must not match. "годин" (hours)
21
+ // is a prefix of "годинників" (watches), so this sentence must not parse as "5 hours".
22
+ testUnexpectedResult(chrono.uk, "купив 5 годинників", new Date(2012, 7, 10), { forwardDate: true });
23
+ });