chrono-node 2.3.0 → 2.3.4

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 (74) hide show
  1. package/benchmark/benchmark.js +4 -0
  2. package/dist/common/casualReferences.js +3 -1
  3. package/dist/common/refiners/ForwardDateRefiner.js +1 -1
  4. package/dist/locales/de/constants.js +1 -1
  5. package/dist/locales/en/constants.d.ts +1 -1
  6. package/dist/locales/en/constants.js +5 -5
  7. package/dist/locales/en/parsers/ENCasualTimeParser.js +1 -1
  8. package/dist/locales/en/parsers/ENMonthNameLittleEndianParser.js +6 -8
  9. package/dist/locales/en/parsers/ENRelativeDateFormatParser.js +3 -3
  10. package/dist/locales/en/parsers/ENTimeUnitAgoFormatParser.js +3 -3
  11. package/dist/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +1 -1
  12. package/dist/locales/en/parsers/ENTimeUnitLaterFormatParser.js +2 -2
  13. package/dist/locales/en/parsers/ENTimeUnitWithinFormatParser.js +1 -1
  14. package/dist/locales/fr/constants.js +1 -1
  15. package/dist/locales/fr/parsers/FRTimeUnitAgoFormatParser.js +1 -1
  16. package/dist/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js +1 -1
  17. package/dist/locales/fr/parsers/FRTimeUnitWithinFormatParser.js +1 -1
  18. package/dist/locales/nl/constants.js +8 -5
  19. package/dist/locales/nl/index.js +12 -3
  20. package/dist/locales/nl/parsers/NLRelativeDateFormatParser.d.ts +7 -0
  21. package/dist/locales/nl/parsers/NLRelativeDateFormatParser.js +57 -0
  22. package/dist/locales/nl/parsers/NLTimeExpressionParser.d.ts +1 -0
  23. package/dist/locales/nl/parsers/NLTimeExpressionParser.js +3 -0
  24. package/dist/locales/nl/parsers/NLTimeUnitAgoFormatParser.d.ts +9 -0
  25. package/dist/locales/nl/parsers/NLTimeUnitAgoFormatParser.js +23 -0
  26. package/dist/locales/nl/parsers/NLTimeUnitCasualRelativeFormatParser.d.ts +7 -0
  27. package/dist/locales/nl/parsers/NLTimeUnitCasualRelativeFormatParser.js +25 -0
  28. package/dist/locales/nl/parsers/NLTimeUnitLaterFormatParser.d.ts +9 -0
  29. package/dist/locales/nl/parsers/NLTimeUnitLaterFormatParser.js +22 -0
  30. package/dist/locales/nl/parsers/NLTimeUnitWithinFormatParser.js +1 -1
  31. package/dist/results.d.ts +2 -2
  32. package/dist/results.js +18 -12
  33. package/dist/timezone.d.ts +1 -1
  34. package/dist/timezone.js +3 -0
  35. package/dist/utils/pattern.js +1 -1
  36. package/package.json +1 -1
  37. package/src/common/casualReferences.ts +3 -1
  38. package/src/common/refiners/ForwardDateRefiner.ts +1 -1
  39. package/src/locales/de/constants.ts +1 -1
  40. package/src/locales/en/constants.ts +5 -5
  41. package/src/locales/en/parsers/ENCasualTimeParser.ts +1 -1
  42. package/src/locales/en/parsers/ENMonthNameLittleEndianParser.ts +9 -10
  43. package/src/locales/en/parsers/ENRelativeDateFormatParser.ts +3 -3
  44. package/src/locales/en/parsers/ENTimeUnitAgoFormatParser.ts +3 -4
  45. package/src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts +1 -1
  46. package/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts +2 -2
  47. package/src/locales/en/parsers/ENTimeUnitWithinFormatParser.ts +1 -1
  48. package/src/locales/fr/constants.ts +1 -1
  49. package/src/locales/fr/parsers/FRTimeUnitAgoFormatParser.ts +1 -1
  50. package/src/locales/fr/parsers/FRTimeUnitRelativeFormatParser.ts +1 -1
  51. package/src/locales/fr/parsers/FRTimeUnitWithinFormatParser.ts +1 -1
  52. package/src/locales/nl/constants.ts +11 -5
  53. package/src/locales/nl/index.ts +12 -3
  54. package/src/locales/nl/parsers/NLRelativeDateFormatParser.ts +70 -0
  55. package/src/locales/nl/parsers/NLTimeExpressionParser.ts +4 -0
  56. package/src/locales/nl/parsers/NLTimeUnitAgoFormatParser.ts +26 -0
  57. package/src/locales/nl/parsers/NLTimeUnitCasualRelativeFormatParser.ts +27 -0
  58. package/src/locales/nl/parsers/NLTimeUnitLaterFormatParser.ts +27 -0
  59. package/src/locales/nl/parsers/NLTimeUnitWithinFormatParser.ts +1 -1
  60. package/src/results.ts +22 -12
  61. package/src/timezone.ts +5 -1
  62. package/src/utils/pattern.ts +1 -1
  63. package/test/en/en_casual.test.ts +11 -0
  64. package/test/en/en_month_name_little_endian.test.ts +10 -0
  65. package/test/en/en_relative.test.ts +8 -0
  66. package/test/en/en_slash.test.ts +11 -0
  67. package/test/en/en_timezone_exp.test.ts +85 -10
  68. package/test/en/en_weekday.test.ts +69 -25
  69. package/test/en/negative_cases.test.ts +2 -0
  70. package/test/nl/nl_relative.test.ts +212 -0
  71. package/test/nl/nl_time_units_ago.test.ts +243 -0
  72. package/test/nl/nl_time_units_casual_relative.test.ts +130 -0
  73. package/test/nl/nl_time_units_later.test.ts +250 -0
  74. package/test/test_util.ts +5 -2
package/dist/timezone.js CHANGED
@@ -196,6 +196,9 @@ exports.TIMEZONE_ABBR_MAP = {
196
196
  };
197
197
  function toTimezoneOffset(timezoneInput) {
198
198
  var _a;
199
+ if (timezoneInput === null) {
200
+ return null;
201
+ }
199
202
  if (typeof timezoneInput === "number") {
200
203
  return timezoneInput;
201
204
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.matchAnyPattern = exports.extractTerms = exports.repeatedTimeunitPattern = void 0;
4
4
  function repeatedTimeunitPattern(prefix, singleTimeunitPattern) {
5
5
  const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
6
- return `${prefix}${singleTimeunitPatternNoCapture}\\s*(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`;
6
+ return `${prefix}${singleTimeunitPatternNoCapture}\\s{0,5}(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`;
7
7
  }
8
8
  exports.repeatedTimeunitPattern = repeatedTimeunitPattern;
9
9
  function extractTerms(dictionary) {
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "preset": "ts-jest"
16
16
  },
17
17
  "license": "MIT",
18
- "version": "2.3.0",
18
+ "version": "2.3.4",
19
19
  "directories": {
20
20
  "source": "./src",
21
21
  "test": "./test"
@@ -8,7 +8,9 @@ export function now(reference: ReferenceWithTimezone): ParsingComponents {
8
8
  const component = new ParsingComponents(reference, {});
9
9
  assignSimilarDate(component, targetDate);
10
10
  assignSimilarTime(component, targetDate);
11
- component.assign("timezoneOffset", targetDate.utcOffset());
11
+ if (reference.timezoneOffset !== null) {
12
+ component.assign("timezoneOffset", targetDate.utcOffset());
13
+ }
12
14
  return component;
13
15
  }
14
16
 
@@ -34,7 +34,7 @@ export default class ForwardDateRefiner implements Refiner {
34
34
  }
35
35
 
36
36
  if (result.start.isOnlyWeekdayComponent() && refMoment.isAfter(result.start.dayjs())) {
37
- if (refMoment.day() > result.start.get("weekday")) {
37
+ if (refMoment.day() >= result.start.get("weekday")) {
38
38
  refMoment = refMoment.day(result.start.get("weekday") + 7);
39
39
  } else {
40
40
  refMoment = refMoment.day(<number>result.start.get("weekday"));
@@ -108,7 +108,7 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType } = {
108
108
 
109
109
  export const NUMBER_PATTERN = `(?:${matchAnyPattern(
110
110
  INTEGER_WORD_DICTIONARY
111
- )}|[0-9]+|[0-9]+\\.[0-9]+|half(?:\\s*an?)?|an?(?:\\s*few)?|few|several|a?\\s*couple\\s*(?:of)?)`;
111
+ )}|[0-9]+|[0-9]+\\.[0-9]+|half(?:\\s*an?)?|an?\\b(?:\\s*few)?|few|several|a?\\s*couple\\s*(?:of)?)`;
112
112
 
113
113
  export function parseNumberPattern(match: string): number {
114
114
  const num = match.toLowerCase();
@@ -162,7 +162,7 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType } = {
162
162
 
163
163
  export const NUMBER_PATTERN = `(?:${matchAnyPattern(
164
164
  INTEGER_WORD_DICTIONARY
165
- )}|[0-9]+|[0-9]+\\.[0-9]+|half(?:\\s*an?)?|an?(?:\\s*few)?|few|several|a?\\s*couple\\s*(?:of)?)`;
165
+ )}|[0-9]+|[0-9]+\\.[0-9]+|half(?:\\s{0,2}an?)?|an?\\b(?:\\s{0,2}few)?|few|several|a?\\s{0,2}couple\\s{0,2}(?:of)?)`;
166
166
 
167
167
  export function parseNumberPattern(match: string): number {
168
168
  const num = match.toLowerCase();
@@ -198,7 +198,7 @@ export function parseOrdinalNumberPattern(match: string): number {
198
198
 
199
199
  //-----------------------------
200
200
 
201
- export const YEAR_PATTERN = `(?:[1-9][0-9]{0,3}\\s*(?:BE|AD|BC|BCE|CE)|[1-2][0-9]{3}|[5-9][0-9])`;
201
+ export const YEAR_PATTERN = `(?:[1-9][0-9]{0,3}\\s{0,2}(?:BE|AD|BC|BCE|CE)|[1-2][0-9]{3}|[5-9][0-9])`;
202
202
  export function parseYear(match: string): number {
203
203
  if (/BE/i.test(match)) {
204
204
  // Buddhist Era
@@ -224,10 +224,10 @@ export function parseYear(match: string): number {
224
224
 
225
225
  //-----------------------------
226
226
 
227
- const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,5}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})\\s{0,5}`;
227
+ const SINGLE_TIME_UNIT_PATTERN = `(${NUMBER_PATTERN})\\s{0,3}(${matchAnyPattern(TIME_UNIT_DICTIONARY)})`;
228
228
  const SINGLE_TIME_UNIT_REGEX = new RegExp(SINGLE_TIME_UNIT_PATTERN, "i");
229
229
 
230
- export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(`(?:(?:about|around)\\s*)?`, SINGLE_TIME_UNIT_PATTERN);
230
+ export const TIME_UNITS_PATTERN = repeatedTimeunitPattern(`(?:(?:about|around)\\s{0,3})?`, SINGLE_TIME_UNIT_PATTERN);
231
231
 
232
232
  export function parseTimeUnits(timeunitText): TimeUnits {
233
233
  const fragments = {};
@@ -235,7 +235,7 @@ export function parseTimeUnits(timeunitText): TimeUnits {
235
235
  let match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
236
236
  while (match) {
237
237
  collectDateTimeFragment(fragments, match);
238
- remainingText = remainingText.substring(match[0].length);
238
+ remainingText = remainingText.substring(match[0].length).trim();
239
239
  match = SINGLE_TIME_UNIT_REGEX.exec(remainingText);
240
240
  }
241
241
  return fragments;
@@ -4,7 +4,7 @@ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/
4
4
  import dayjs from "dayjs";
5
5
  import { assignTheNextDay } from "../../../utils/dayjs";
6
6
 
7
- const PATTERN = /(?:this)?\s*(morning|afternoon|evening|night|midnight|noon)(?=\W|$)/i;
7
+ const PATTERN = /(?:this)?\s{0,3}(morning|afternoon|evening|night|midnight|noon)(?=\W|$)/i;
8
8
 
9
9
  export default class ENCasualTimeParser extends AbstractParserWithWordBoundaryChecking {
10
10
  innerPattern() {
@@ -7,20 +7,19 @@ import { ORDINAL_NUMBER_PATTERN, parseOrdinalNumberPattern } from "../constants"
7
7
  import { matchAnyPattern } from "../../../utils/pattern";
8
8
  import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
9
9
 
10
+ // prettier-ignore
10
11
  const PATTERN = new RegExp(
11
- "(?:on\\s*?)?" +
12
- `(${ORDINAL_NUMBER_PATTERN})` +
13
- "(?:\\s*" +
14
- "(?:to|\\-|\\–|until|through|till|\\s)\\s*" +
12
+ `(?:on\\s{0,3})?` +
15
13
  `(${ORDINAL_NUMBER_PATTERN})` +
14
+ `(?:` +
15
+ `\\s{0,3}(?:to|\\-|\\–|until|through|till)?\\s{0,3}` +
16
+ `(${ORDINAL_NUMBER_PATTERN})` +
16
17
  ")?" +
17
- "(?:-|/|\\s*(?:of)?\\s*)" +
18
- "(" +
19
- matchAnyPattern(MONTH_DICTIONARY) +
20
- ")" +
18
+ `(?:-|/|\\s{0,3}(?:of)?\\s{0,3})` +
19
+ `(${matchAnyPattern(MONTH_DICTIONARY)})` +
21
20
  "(?:" +
22
- "(?:-|/|,?\\s*)" +
23
- `(${YEAR_PATTERN}(?![^\\s]\\d))` +
21
+ `(?:-|/|,?\\s{0,3})` +
22
+ `(${YEAR_PATTERN}(?![^\\s]\\d))` +
24
23
  ")?" +
25
24
  "(?=\\W|$)",
26
25
  "i"
@@ -26,17 +26,17 @@ export default class ENRelativeDateFormatParser extends AbstractParserWithWordBo
26
26
  if (modifier == "next") {
27
27
  const timeUnits = {};
28
28
  timeUnits[timeunit] = 1;
29
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
29
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
30
30
  }
31
31
 
32
32
  if (modifier == "last" || modifier == "past") {
33
33
  const timeUnits = {};
34
34
  timeUnits[timeunit] = -1;
35
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
35
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
36
36
  }
37
37
 
38
38
  const components = context.createParsingComponents();
39
- let date = dayjs(context.refDate);
39
+ let date = dayjs(context.reference.instant);
40
40
 
41
41
  // This week
42
42
  if (unitWord.match(/week/i)) {
@@ -4,9 +4,8 @@ import { ParsingComponents } from "../../../results";
4
4
  import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
5
5
  import { reverseTimeUnits } from "../../../utils/timeunits";
6
6
 
7
- const PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "(?:ago|before|earlier)(?=(?:\\W|$))", "i");
8
-
9
- const STRICT_PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "ago(?=(?:\\W|$))", "i");
7
+ const PATTERN = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=(?:\\W|$))`, "i");
8
+ const STRICT_PATTERN = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}ago(?=(?:\\W|$))`, "i");
10
9
 
11
10
  export default class ENTimeUnitAgoFormatParser extends AbstractParserWithWordBoundaryChecking {
12
11
  constructor(private strictMode: boolean) {
@@ -21,6 +20,6 @@ export default class ENTimeUnitAgoFormatParser extends AbstractParserWithWordBou
21
20
  const timeUnits = parseTimeUnits(match[1]);
22
21
  const outputTimeUnits = reverseTimeUnits(timeUnits);
23
22
 
24
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, outputTimeUnits);
23
+ return ParsingComponents.createRelativeFromReference(context.reference, outputTimeUnits);
25
24
  }
26
25
  }
@@ -22,6 +22,6 @@ export default class ENTimeUnitCasualRelativeFormatParser extends AbstractParser
22
22
  break;
23
23
  }
24
24
 
25
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
25
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
26
26
  }
27
27
  }
@@ -4,7 +4,7 @@ import { ParsingComponents } from "../../../results";
4
4
  import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
5
5
 
6
6
  const PATTERN = new RegExp(
7
- "" + "(" + TIME_UNITS_PATTERN + ")" + "(later|after|from now|henceforth|forward|out)" + "(?=(?:\\W|$))",
7
+ `(${TIME_UNITS_PATTERN})\\s{0,5}(?:later|after|from now|henceforth|forward|out)` + "(?=(?:\\W|$))",
8
8
  "i"
9
9
  );
10
10
 
@@ -22,6 +22,6 @@ export default class ENTimeUnitLaterFormatParser extends AbstractParserWithWordB
22
22
 
23
23
  innerExtract(context: ParsingContext, match: RegExpMatchArray) {
24
24
  const fragments = parseTimeUnits(match[GROUP_NUM_TIMEUNITS]);
25
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, fragments);
25
+ return ParsingComponents.createRelativeFromReference(context.reference, fragments);
26
26
  }
27
27
  }
@@ -21,6 +21,6 @@ export default class ENTimeUnitWithinFormatParser extends AbstractParserWithWord
21
21
 
22
22
  innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
23
23
  const timeUnits = parseTimeUnits(match[1]);
24
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
24
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
25
25
  }
26
26
  }
@@ -105,7 +105,7 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType | QUnitType } =
105
105
 
106
106
  export const NUMBER_PATTERN = `(?:${matchAnyPattern(
107
107
  INTEGER_WORD_DICTIONARY
108
- )}|[0-9]+|[0-9]+\\.[0-9]+|une?|quelques?|demi-?)`;
108
+ )}|[0-9]+|[0-9]+\\.[0-9]+|une?\\b|quelques?|demi-?)`;
109
109
 
110
110
  export function parseNumberPattern(match: string): number {
111
111
  const num = match.toLowerCase();
@@ -17,6 +17,6 @@ export default class FRTimeUnitAgoFormatParser extends AbstractParserWithWordBou
17
17
  const timeUnits = parseTimeUnits(match[1]);
18
18
  const outputTimeUnits = reverseTimeUnits(timeUnits);
19
19
 
20
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, outputTimeUnits);
20
+ return ParsingComponents.createRelativeFromReference(context.reference, outputTimeUnits);
21
21
  }
22
22
  }
@@ -38,6 +38,6 @@ export default class FRTimeUnitAgoFormatParser extends AbstractParserWithWordBou
38
38
  timeUnits = reverseTimeUnits(timeUnits);
39
39
  }
40
40
 
41
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
41
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
42
42
  }
43
43
  }
@@ -10,6 +10,6 @@ export default class FRTimeUnitWithinFormatParser extends AbstractParserWithWord
10
10
 
11
11
  innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
12
12
  const timeUnits = parseTimeUnits(match[1]);
13
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
13
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
14
14
  }
15
15
  }
@@ -141,11 +141,14 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType } = {
141
141
  min: "minute",
142
142
  mins: "minute",
143
143
  minute: "minute",
144
+ minuut: "minute",
144
145
  minuten: "minute",
146
+ minuutje: "minute",
145
147
  h: "hour",
146
148
  hr: "hour",
147
149
  hrs: "hour",
148
150
  uur: "hour",
151
+ u: "hour",
149
152
  uren: "hour",
150
153
  dag: "d",
151
154
  dagen: "d",
@@ -160,18 +163,21 @@ export const TIME_UNIT_DICTIONARY: { [word: string]: OpUnitType } = {
160
163
 
161
164
  //-----------------------------
162
165
 
163
- export const NUMBER_PATTERN = `(?:${matchAnyPattern(INTEGER_WORD_DICTIONARY)}|[0-9]+|[0-9]+\\.[0-9]+|een?|halve?)`;
166
+ export const NUMBER_PATTERN = `(?:${matchAnyPattern(
167
+ INTEGER_WORD_DICTIONARY
168
+ )}|[0-9]+|[0-9]+[\\.,][0-9]+|halve?|half|paar)`;
164
169
 
165
170
  export function parseNumberPattern(match: string): number {
166
171
  const num = match.toLowerCase();
167
172
  if (INTEGER_WORD_DICTIONARY[num] !== undefined) {
168
173
  return INTEGER_WORD_DICTIONARY[num];
169
- } else if (num === "een") {
170
- return 1;
171
- } else if (num.match(/halve?/)) {
174
+ } else if (num === "paar") {
175
+ return 2;
176
+ } else if (num === "half" || num.match(/halve?/)) {
172
177
  return 0.5;
173
178
  }
174
- return parseFloat(num);
179
+ // Replace "," with "." to support some European languages
180
+ return parseFloat(num.replace(",", "."));
175
181
  }
176
182
 
177
183
  //-----------------------------
@@ -20,6 +20,10 @@ import NLSlashMonthFormatParser from "./parsers/NLSlashMonthFormatParser";
20
20
  import NLTimeExpressionParser from "./parsers/NLTimeExpressionParser";
21
21
  import NLCasualYearMonthDayParser from "./parsers/NLCasualYearMonthDayParser";
22
22
  import NLCasualDateTimeParser from "./parsers/NLCasualDateTimeParser";
23
+ import NLTimeUnitCasualRelativeFormatParser from "./parsers/NLTimeUnitCasualRelativeFormatParser";
24
+ import NLRelativeDateFormatParser from "./parsers/NLRelativeDateFormatParser";
25
+ import NLTimeUnitAgoFormatParser from "./parsers/NLTimeUnitAgoFormatParser";
26
+ import NLTimeUnitLaterFormatParser from "./parsers/NLTimeUnitLaterFormatParser";
23
27
 
24
28
  // Shortcuts
25
29
  export const casual = new Chrono(createCasualConfiguration());
@@ -38,6 +42,9 @@ export function createCasualConfiguration(littleEndian = true): Configuration {
38
42
  option.parsers.unshift(new NLCasualDateParser());
39
43
  option.parsers.unshift(new NLCasualTimeParser());
40
44
  option.parsers.unshift(new NLCasualDateTimeParser());
45
+ option.parsers.unshift(new NLMonthNameParser());
46
+ option.parsers.unshift(new NLRelativeDateFormatParser());
47
+ option.parsers.unshift(new NLTimeUnitCasualRelativeFormatParser());
41
48
  return option;
42
49
  }
43
50
 
@@ -49,13 +56,15 @@ export function createConfiguration(strictMode = true, littleEndian = true): Con
49
56
  {
50
57
  parsers: [
51
58
  new SlashDateFormatParser(littleEndian),
59
+ new NLTimeUnitWithinFormatParser(),
52
60
  new NLMonthNameMiddleEndianParser(),
53
61
  new NLMonthNameParser(),
54
- new NLTimeExpressionParser(),
55
- new NLTimeUnitWithinFormatParser(),
56
- new NLSlashMonthFormatParser(),
57
62
  new NLWeekdayParser(),
58
63
  new NLCasualYearMonthDayParser(),
64
+ new NLSlashMonthFormatParser(),
65
+ new NLTimeExpressionParser(strictMode),
66
+ new NLTimeUnitAgoFormatParser(strictMode),
67
+ new NLTimeUnitLaterFormatParser(strictMode),
59
68
  ],
60
69
  refiners: [new NLMergeDateTimeRefiner(), new NLMergeDateRangeRefiner()],
61
70
  },
@@ -0,0 +1,70 @@
1
+ import { TIME_UNIT_DICTIONARY } from "../constants";
2
+ import { ParsingContext } from "../../../chrono";
3
+ import { ParsingComponents } from "../../../results";
4
+ import dayjs from "dayjs";
5
+ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
6
+ import { matchAnyPattern } from "../../../utils/pattern";
7
+
8
+ const PATTERN = new RegExp(
9
+ `(dit|deze|komende|volgend|volgende|afgelopen|vorige)\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})(?=\\s*)` +
10
+ "(?=\\W|$)",
11
+ "i"
12
+ );
13
+
14
+ const MODIFIER_WORD_GROUP = 1;
15
+ const RELATIVE_WORD_GROUP = 2;
16
+
17
+ export default class NLRelativeDateFormatParser extends AbstractParserWithWordBoundaryChecking {
18
+ innerPattern(): RegExp {
19
+ return PATTERN;
20
+ }
21
+
22
+ innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
23
+ const modifier = match[MODIFIER_WORD_GROUP].toLowerCase();
24
+ const unitWord = match[RELATIVE_WORD_GROUP].toLowerCase();
25
+ const timeunit = TIME_UNIT_DICTIONARY[unitWord];
26
+
27
+ if (modifier == "volgend" || modifier == "volgende" || modifier == "komende") {
28
+ const timeUnits = {};
29
+ timeUnits[timeunit] = 1;
30
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
31
+ }
32
+
33
+ if (modifier == "afgelopen" || modifier == "vorige") {
34
+ const timeUnits = {};
35
+ timeUnits[timeunit] = -1;
36
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
37
+ }
38
+
39
+ const components = context.createParsingComponents();
40
+ let date = dayjs(context.reference.instant);
41
+
42
+ // This week
43
+ if (unitWord.match(/week/i)) {
44
+ date = date.add(-date.get("d"), "d");
45
+ components.imply("day", date.date());
46
+ components.imply("month", date.month() + 1);
47
+ components.imply("year", date.year());
48
+ }
49
+
50
+ // This month
51
+ else if (unitWord.match(/maand/i)) {
52
+ date = date.add(-date.date() + 1, "d");
53
+ components.imply("day", date.date());
54
+ components.assign("year", date.year());
55
+ components.assign("month", date.month() + 1);
56
+ }
57
+
58
+ // This year
59
+ else if (unitWord.match(/jaar/i)) {
60
+ date = date.add(-date.date() + 1, "d");
61
+ date = date.add(-date.month(), "month");
62
+
63
+ components.imply("day", date.date());
64
+ components.imply("month", date.month() + 1);
65
+ components.assign("year", date.year());
66
+ }
67
+
68
+ return components;
69
+ }
70
+ }
@@ -11,6 +11,10 @@ export default class NLTimeExpressionParser extends AbstractTimeExpressionParser
11
11
  return "\\s*(?:\\-|\\–|\\~|\\〜|om|\\?)\\s*";
12
12
  }
13
13
 
14
+ primarySuffix(): string {
15
+ return "(?:\\s*(?:uur))?(?!/)(?=\\W|$)";
16
+ }
17
+
14
18
  extractPrimaryTimeComponents(context: ParsingContext, match: RegExpMatchArray): ParsingComponents | null {
15
19
  // This looks more like a year e.g. 2020
16
20
  if (match[0].match(/^\s*\d{4}\s*$/)) {
@@ -0,0 +1,26 @@
1
+ import { ParsingContext } from "../../../chrono";
2
+ import { parseTimeUnits, TIME_UNITS_PATTERN } from "../constants";
3
+ import { ParsingComponents } from "../../../results";
4
+ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
5
+ import { reverseTimeUnits } from "../../../utils/timeunits";
6
+
7
+ const PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "(?:geleden|voor|eerder)(?=(?:\\W|$))", "i");
8
+
9
+ const STRICT_PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "geleden(?=(?:\\W|$))", "i");
10
+
11
+ export default class NLTimeUnitAgoFormatParser extends AbstractParserWithWordBoundaryChecking {
12
+ constructor(private strictMode: boolean) {
13
+ super();
14
+ }
15
+
16
+ innerPattern(): RegExp {
17
+ return this.strictMode ? STRICT_PATTERN : PATTERN;
18
+ }
19
+
20
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
21
+ const timeUnits = parseTimeUnits(match[1]);
22
+ const outputTimeUnits = reverseTimeUnits(timeUnits);
23
+
24
+ return ParsingComponents.createRelativeFromReference(context.reference, outputTimeUnits);
25
+ }
26
+ }
@@ -0,0 +1,27 @@
1
+ import { TIME_UNITS_PATTERN, parseTimeUnits } from "../constants";
2
+ import { ParsingContext } from "../../../chrono";
3
+ import { ParsingComponents } from "../../../results";
4
+ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
5
+ import { reverseTimeUnits } from "../../../utils/timeunits";
6
+
7
+ const PATTERN = new RegExp(`(deze|vorige|afgelopen|komende|over|\\+|-)\\s*(${TIME_UNITS_PATTERN})(?=\\W|$)`, "i");
8
+
9
+ export default class NLTimeUnitCasualRelativeFormatParser extends AbstractParserWithWordBoundaryChecking {
10
+ innerPattern(): RegExp {
11
+ return PATTERN;
12
+ }
13
+
14
+ innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
15
+ const prefix = match[1].toLowerCase();
16
+ let timeUnits = parseTimeUnits(match[2]);
17
+ switch (prefix) {
18
+ case "vorige":
19
+ case "afgelopen":
20
+ case "-":
21
+ timeUnits = reverseTimeUnits(timeUnits);
22
+ break;
23
+ }
24
+
25
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ import { ParsingContext } from "../../../chrono";
2
+ import { parseTimeUnits, TIME_UNITS_PATTERN } from "../constants";
3
+ import { ParsingComponents } from "../../../results";
4
+ import { AbstractParserWithWordBoundaryChecking } from "../../../common/parsers/AbstractParserWithWordBoundary";
5
+
6
+ const PATTERN = new RegExp(
7
+ "" + "(" + TIME_UNITS_PATTERN + ")" + "(later|na|vanaf nu|voortaan|vooruit|uit)" + "(?=(?:\\W|$))",
8
+ "i"
9
+ );
10
+
11
+ const STRICT_PATTERN = new RegExp("" + "(" + TIME_UNITS_PATTERN + ")" + "(later|vanaf nu)" + "(?=(?:\\W|$))", "i");
12
+ const GROUP_NUM_TIMEUNITS = 1;
13
+
14
+ export default class NLTimeUnitLaterFormatParser extends AbstractParserWithWordBoundaryChecking {
15
+ constructor(private strictMode: boolean) {
16
+ super();
17
+ }
18
+
19
+ innerPattern(): RegExp {
20
+ return this.strictMode ? STRICT_PATTERN : PATTERN;
21
+ }
22
+
23
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
24
+ const fragments = parseTimeUnits(match[GROUP_NUM_TIMEUNITS]);
25
+ return ParsingComponents.createRelativeFromReference(context.reference, fragments);
26
+ }
27
+ }
@@ -10,6 +10,6 @@ export default class NLTimeUnitWithinFormatParser extends AbstractParserWithWord
10
10
 
11
11
  innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingComponents {
12
12
  const timeUnits = parseTimeUnits(match[1]);
13
- return ParsingComponents.createRelativeFromRefInstant(context.refDate, timeUnits);
13
+ return ParsingComponents.createRelativeFromReference(context.reference, timeUnits);
14
14
  }
15
15
  }
package/src/results.ts CHANGED
@@ -8,7 +8,7 @@ dayjs.extend(quarterOfYear);
8
8
 
9
9
  export class ReferenceWithTimezone {
10
10
  readonly instant: Date;
11
- readonly timezoneOffset: number;
11
+ readonly timezoneOffset?: number;
12
12
 
13
13
  constructor(input?: ParsingReference | Date) {
14
14
  input = input ?? new Date();
@@ -17,7 +17,7 @@ export class ReferenceWithTimezone {
17
17
  this.timezoneOffset = -input.getTimezoneOffset();
18
18
  } else {
19
19
  this.instant = input.instant ?? new Date();
20
- this.timezoneOffset = toTimezoneOffset(input.timezone ?? -this.instant.getTimezoneOffset());
20
+ this.timezoneOffset = toTimezoneOffset(input.timezone);
21
21
  }
22
22
  }
23
23
  }
@@ -142,7 +142,7 @@ export class ParsingComponents implements ParsedComponents {
142
142
 
143
143
  date(): Date {
144
144
  const date = this.dateWithoutTimezoneAdjustment();
145
- return new Date(date.getTime() + this.getSystemTimezoneAdjustmentMinute() * 60000);
145
+ return new Date(date.getTime() + this.getSystemTimezoneAdjustmentMinute(date) * 60000);
146
146
  }
147
147
 
148
148
  private dateWithoutTimezoneAdjustment() {
@@ -160,31 +160,41 @@ export class ParsingComponents implements ParsedComponents {
160
160
  return date;
161
161
  }
162
162
 
163
- private getSystemTimezoneAdjustmentMinute() {
164
- const currentTimezoneOffset = -new Date().getTimezoneOffset();
165
- const targetTimezoneOffset = this.get("timezoneOffset") ?? this.reference.timezoneOffset;
163
+ private getSystemTimezoneAdjustmentMinute(date?: Date) {
164
+ if (!date || date.getTime() < 0) {
165
+ // Javascript date timezone calculation got effect when the time epoch < 0
166
+ // e.g. new Date('Tue Feb 02 1300 00:00:00 GMT+0900 (JST)') => Tue Feb 02 1300 00:18:59 GMT+0918 (JST)
167
+ date = new Date();
168
+ }
169
+
170
+ const currentTimezoneOffset = -date.getTimezoneOffset();
171
+ const targetTimezoneOffset =
172
+ this.get("timezoneOffset") ?? this.reference.timezoneOffset ?? currentTimezoneOffset;
166
173
 
167
174
  return currentTimezoneOffset - targetTimezoneOffset;
168
175
  }
169
176
 
170
- static createRelativeFromRefInstant(
171
- refInstant: Date,
177
+ static createRelativeFromReference(
178
+ reference: ReferenceWithTimezone,
172
179
  fragments: { [c in OpUnitType | QUnitType]?: number }
173
180
  ): ParsingComponents {
174
- let date = dayjs(refInstant);
181
+ let date = dayjs(reference.instant);
175
182
  for (const key in fragments) {
176
183
  date = date.add(fragments[key as OpUnitType], key as OpUnitType);
177
184
  }
178
185
 
179
- const reference = new ReferenceWithTimezone(refInstant);
180
186
  const components = new ParsingComponents(reference);
181
187
  if (fragments["hour"] || fragments["minute"] || fragments["second"]) {
182
188
  assignSimilarTime(components, date);
183
189
  assignSimilarDate(components, date);
184
- components.assign("timezoneOffset", -refInstant.getTimezoneOffset());
190
+ if (reference.timezoneOffset !== null) {
191
+ components.assign("timezoneOffset", -reference.instant.getTimezoneOffset());
192
+ }
185
193
  } else {
186
194
  implySimilarTime(components, date);
187
- components.imply("timezoneOffset", -refInstant.getTimezoneOffset());
195
+ if (reference.timezoneOffset !== null) {
196
+ components.imply("timezoneOffset", -reference.instant.getTimezoneOffset());
197
+ }
188
198
 
189
199
  if (fragments["d"]) {
190
200
  components.assign("day", date.date());
package/src/timezone.ts CHANGED
@@ -192,7 +192,11 @@ export const TIMEZONE_ABBR_MAP = {
192
192
  YEKT: 360,
193
193
  };
194
194
 
195
- export function toTimezoneOffset(timezoneInput: string | number): number {
195
+ export function toTimezoneOffset(timezoneInput: string | number): number | null {
196
+ if (timezoneInput === null) {
197
+ return null;
198
+ }
199
+
196
200
  if (typeof timezoneInput === "number") {
197
201
  return timezoneInput;
198
202
  }
@@ -2,7 +2,7 @@ type DictionaryLike = string[] | { [word: string]: unknown } | Map<string, unkno
2
2
 
3
3
  export function repeatedTimeunitPattern(prefix: string, singleTimeunitPattern: string): string {
4
4
  const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
5
- return `${prefix}${singleTimeunitPatternNoCapture}\\s*(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`;
5
+ return `${prefix}${singleTimeunitPatternNoCapture}\\s{0,5}(?:,?\\s{0,5}${singleTimeunitPatternNoCapture}){0,10}`;
6
6
  }
7
7
 
8
8
  export function extractTerms(dictionary: DictionaryLike): string[] {
@@ -21,6 +21,17 @@ test("Test - Single Expression", () => {
21
21
  expect(result.start).toBeDate(new Date(2012, 7, 10, 8, 9, 10, 11));
22
22
  });
23
23
 
24
+ testSingleCase(
25
+ chrono.casual,
26
+ "The Deadline is now, without implicit local timezone",
27
+ { instant: new Date(1637674343000), timezone: null },
28
+ (result) => {
29
+ expect(result.text).toBe("now");
30
+ expect(result.start).toBeDate(new Date(1637674343000));
31
+ expect(result.start.isCertain("timezoneOffset")).toBe(false);
32
+ }
33
+ );
34
+
24
35
  testSingleCase(chrono.casual, "The Deadline is today", new Date(2012, 7, 10, 14, 12), (result) => {
25
36
  expect(result.index).toBe(16);
26
37
  expect(result.text).toBe("today");