chrono-node 2.6.2 → 2.6.3

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 (97) hide show
  1. package/README.md +32 -2
  2. package/dist/cjs/common/refiners/AbstractMergeDateRangeRefiner.js +15 -7
  3. package/dist/cjs/common/refiners/AbstractMergeDateRangeRefiner.js.map +1 -1
  4. package/dist/cjs/common/refiners/ExtractTimezoneAbbrRefiner.d.ts +3 -4
  5. package/dist/cjs/common/refiners/ExtractTimezoneAbbrRefiner.js +6 -196
  6. package/dist/cjs/common/refiners/ExtractTimezoneAbbrRefiner.js.map +1 -1
  7. package/dist/cjs/common/refiners/ForwardDateRefiner.js +14 -14
  8. package/dist/cjs/common/refiners/ForwardDateRefiner.js.map +1 -1
  9. package/dist/cjs/configurations.js +1 -1
  10. package/dist/cjs/configurations.js.map +1 -1
  11. package/dist/cjs/index.d.ts +24 -3
  12. package/dist/cjs/index.js +16 -1
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/locales/en/constants.d.ts +4 -0
  15. package/dist/cjs/locales/en/constants.js +5 -18
  16. package/dist/cjs/locales/en/constants.js.map +1 -1
  17. package/dist/cjs/locales/en/index.js +1 -1
  18. package/dist/cjs/locales/en/index.js.map +1 -1
  19. package/dist/cjs/locales/en/parsers/ENTimeUnitAgoFormatParser.js +2 -2
  20. package/dist/cjs/locales/en/parsers/ENTimeUnitAgoFormatParser.js.map +1 -1
  21. package/dist/cjs/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.d.ts +2 -0
  22. package/dist/cjs/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +6 -1
  23. package/dist/cjs/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js.map +1 -1
  24. package/dist/cjs/locales/en/parsers/ENTimeUnitLaterFormatParser.js +1 -1
  25. package/dist/cjs/locales/en/parsers/ENTimeUnitLaterFormatParser.js.map +1 -1
  26. package/dist/cjs/locales/en/parsers/ENTimeUnitWithinFormatParser.d.ts +2 -0
  27. package/dist/cjs/locales/en/parsers/ENTimeUnitWithinFormatParser.js +10 -1
  28. package/dist/cjs/locales/en/parsers/ENTimeUnitWithinFormatParser.js.map +1 -1
  29. package/dist/cjs/locales/ru/constants.js +4 -1
  30. package/dist/cjs/locales/ru/constants.js.map +1 -1
  31. package/dist/cjs/results.d.ts +1 -1
  32. package/dist/cjs/results.js +3 -3
  33. package/dist/cjs/results.js.map +1 -1
  34. package/dist/cjs/timezone.d.ts +5 -194
  35. package/dist/cjs/timezone.js +80 -7
  36. package/dist/cjs/timezone.js.map +1 -1
  37. package/dist/esm/common/refiners/AbstractMergeDateRangeRefiner.js +15 -7
  38. package/dist/esm/common/refiners/AbstractMergeDateRangeRefiner.js.map +1 -1
  39. package/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.d.ts +3 -4
  40. package/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.js +6 -196
  41. package/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.js.map +1 -1
  42. package/dist/esm/common/refiners/ForwardDateRefiner.js +14 -14
  43. package/dist/esm/common/refiners/ForwardDateRefiner.js.map +1 -1
  44. package/dist/esm/configurations.js +1 -1
  45. package/dist/esm/configurations.js.map +1 -1
  46. package/dist/esm/index.d.ts +24 -3
  47. package/dist/esm/index.js +15 -0
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/locales/en/constants.d.ts +4 -0
  50. package/dist/esm/locales/en/constants.js +21 -0
  51. package/dist/esm/locales/en/constants.js.map +1 -1
  52. package/dist/esm/locales/en/index.js +1 -1
  53. package/dist/esm/locales/en/index.js.map +1 -1
  54. package/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js +3 -3
  55. package/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js.map +1 -1
  56. package/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.d.ts +2 -0
  57. package/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +7 -2
  58. package/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js.map +1 -1
  59. package/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js +2 -2
  60. package/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js.map +1 -1
  61. package/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.d.ts +2 -0
  62. package/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js +11 -2
  63. package/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js.map +1 -1
  64. package/dist/esm/locales/ru/constants.js +4 -1
  65. package/dist/esm/locales/ru/constants.js.map +1 -1
  66. package/dist/esm/results.d.ts +1 -1
  67. package/dist/esm/results.js +3 -3
  68. package/dist/esm/results.js.map +1 -1
  69. package/dist/esm/timezone.d.ts +5 -194
  70. package/dist/esm/timezone.js +74 -6
  71. package/dist/esm/timezone.js.map +1 -1
  72. package/package.json +4 -5
  73. package/src/common/refiners/AbstractMergeDateRangeRefiner.ts +13 -8
  74. package/src/common/refiners/ExtractTimezoneAbbrRefiner.ts +7 -201
  75. package/src/common/refiners/ForwardDateRefiner.ts +18 -16
  76. package/src/configurations.ts +3 -1
  77. package/src/index.ts +46 -2
  78. package/src/locales/en/constants.ts +30 -0
  79. package/src/locales/en/index.ts +1 -1
  80. package/src/locales/en/parsers/ENTimeUnitAgoFormatParser.ts +3 -3
  81. package/src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts +10 -2
  82. package/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts +5 -2
  83. package/src/locales/en/parsers/ENTimeUnitWithinFormatParser.ts +16 -3
  84. package/src/locales/ru/constants.ts +4 -1
  85. package/src/results.ts +3 -3
  86. package/src/timezone.ts +124 -7
  87. package/test/de/de_time_exp.test.ts +7 -7
  88. package/test/en/en_month.test.ts +70 -0
  89. package/test/en/en_time_units_ago.test.ts +12 -0
  90. package/test/en/en_time_units_casual_relative.test.ts +18 -0
  91. package/test/en/en_time_units_later.test.ts +13 -10
  92. package/test/en/en_time_units_within.test.ts +12 -1
  93. package/test/en/en_timezone_exp.test.ts +142 -0
  94. package/test/en/en_weekday.test.ts +26 -0
  95. package/test/en/negative_cases.test.ts +2 -0
  96. package/test/fr/fr_time_exp.test.ts +2 -2
  97. package/test/system.test.ts +23 -1
package/src/timezone.ts CHANGED
@@ -1,4 +1,7 @@
1
- export const TIMEZONE_ABBR_MAP = {
1
+ import dayjs from "dayjs";
2
+ import { TimezoneAbbrMap, Weekday, Month } from "./index";
3
+
4
+ export const TIMEZONE_ABBR_MAP: TimezoneAbbrMap = {
2
5
  ACDT: 630,
3
6
  ACST: 570,
4
7
  ADT: -180,
@@ -32,7 +35,14 @@ export const TIMEZONE_ABBR_MAP = {
32
35
  CCT: 390,
33
36
  CDT: -300,
34
37
  CEST: 120,
35
- CET: 60,
38
+ // Note: Many sources define CET as a constant UTC+1. In common usage, however,
39
+ // CET usually refers to the time observed in most of Europe, be it standard time or daylight saving time.
40
+ CET: {
41
+ timezoneOffsetDuringDst: 2 * 60,
42
+ timezoneOffsetNonDst: 60,
43
+ dstStart: (year: number) => getLastWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2),
44
+ dstEnd: (year: number) => getLastWeekdayOfMonth(year, Month.OCTOBER, Weekday.SUNDAY, 3),
45
+ },
36
46
  CHADT: 825,
37
47
  CHAST: 765,
38
48
  CKT: -600,
@@ -40,6 +50,12 @@ export const TIMEZONE_ABBR_MAP = {
40
50
  CLT: -240,
41
51
  COT: -300,
42
52
  CST: -360,
53
+ CT: {
54
+ timezoneOffsetDuringDst: -5 * 60,
55
+ timezoneOffsetNonDst: -6 * 60,
56
+ dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
57
+ dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
58
+ },
43
59
  CVT: -60,
44
60
  CXT: 420,
45
61
  ChST: 600,
@@ -54,7 +70,12 @@ export const TIMEZONE_ABBR_MAP = {
54
70
  EGST: 0,
55
71
  EGT: -60,
56
72
  EST: -300,
57
- ET: -300,
73
+ ET: {
74
+ timezoneOffsetDuringDst: -4 * 60,
75
+ timezoneOffsetNonDst: -5 * 60,
76
+ dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
77
+ dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
78
+ },
58
79
  FJST: 780,
59
80
  FJT: 720,
60
81
  FKST: -180,
@@ -116,6 +137,12 @@ export const TIMEZONE_ABBR_MAP = {
116
137
  MSD: 240,
117
138
  MSK: 180,
118
139
  MST: -420,
140
+ MT: {
141
+ timezoneOffsetDuringDst: -6 * 60,
142
+ timezoneOffsetNonDst: -7 * 60,
143
+ dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
144
+ dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
145
+ },
119
146
  MUT: 240,
120
147
  MVT: 300,
121
148
  MYT: 480,
@@ -143,7 +170,12 @@ export const TIMEZONE_ABBR_MAP = {
143
170
  PMST: -180,
144
171
  PONT: 660,
145
172
  PST: -480,
146
- PT: -480,
173
+ PT: {
174
+ timezoneOffsetDuringDst: -7 * 60,
175
+ timezoneOffsetNonDst: -8 * 60,
176
+ dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
177
+ dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
178
+ },
147
179
  PWT: 540,
148
180
  PYST: -180,
149
181
  PYT: -240,
@@ -192,8 +224,67 @@ export const TIMEZONE_ABBR_MAP = {
192
224
  YEKT: 360,
193
225
  };
194
226
 
195
- export function toTimezoneOffset(timezoneInput?: string | number): number | null {
196
- if (timezoneInput === null || timezoneInput === undefined) {
227
+ /**
228
+ * Get the date which is the nth occurence of a given weekday in a given month and year.
229
+ *
230
+ * @param year The year for which to find the date
231
+ * @param month The month in which the date occurs
232
+ * @param weekday The weekday on which the date occurs
233
+ * @param n The nth occurence of the given weekday on the month to return
234
+ * @param hour The hour of day which should be set on the returned date
235
+ * @return The date which is the nth occurence of a given weekday in a given
236
+ * month and year, at the given hour of day
237
+ */
238
+ export function getNthWeekdayOfMonth(year: number, month: Month, weekday: Weekday, n: 1 | 2 | 3 | 4, hour = 0): Date {
239
+ let dayOfMonth = 0;
240
+ let i = 0;
241
+ while (i < n) {
242
+ dayOfMonth++;
243
+ const date = new Date(year, month - 1, dayOfMonth);
244
+ if (date.getDay() === weekday) i++;
245
+ }
246
+ return new Date(year, month - 1, dayOfMonth, hour);
247
+ }
248
+
249
+ /**
250
+ * Get the date which is the last occurence of a given weekday in a given month and year.
251
+ *
252
+ * @param year The year for which to find the date
253
+ * @param month The month in which the date occurs
254
+ * @param weekday The weekday on which the date occurs
255
+ * @param hour The hour of day which should be set on the returned date
256
+ * @return The date which is the last occurence of a given weekday in a given
257
+ * month and year, at the given hour of day
258
+ */
259
+ export function getLastWeekdayOfMonth(year: number, month: Month, weekday: Weekday, hour = 0): Date {
260
+ // Procedure: Find the first weekday of the next month, compare with the given weekday,
261
+ // and use the difference to determine how many days to subtract from the first of the next month.
262
+ const oneIndexedWeekday = weekday === 0 ? 7 : weekday;
263
+ const date = new Date(year, month - 1 + 1, 1, 12);
264
+ const firstWeekdayNextMonth = date.getDay() === 0 ? 7 : date.getDay();
265
+ let dayDiff;
266
+ if (firstWeekdayNextMonth === oneIndexedWeekday) dayDiff = 7;
267
+ else if (firstWeekdayNextMonth < oneIndexedWeekday) dayDiff = 7 + firstWeekdayNextMonth - oneIndexedWeekday;
268
+ else dayDiff = firstWeekdayNextMonth - oneIndexedWeekday;
269
+ date.setDate(date.getDate() - dayDiff);
270
+ return new Date(year, month - 1, date.getDate(), hour);
271
+ }
272
+
273
+ /**
274
+ * Finds and returns timezone offset. If timezoneInput is numeric, it is returned. Otherwise, look for timezone offsets
275
+ * in the following order: timezoneOverrides -> {@link TIMEZONE_ABBR_MAP}.
276
+ *
277
+ * @param timezoneInput Uppercase timezone abbreviation or numeric offset in minutes
278
+ * @param date The date to use to determine whether to return DST offsets for ambiguous timezones
279
+ * @param timezoneOverrides Overrides for timezones
280
+ * @return timezone offset in minutes
281
+ */
282
+ export function toTimezoneOffset(
283
+ timezoneInput?: string | number,
284
+ date?: Date,
285
+ timezoneOverrides: TimezoneAbbrMap = {}
286
+ ): number | null {
287
+ if (timezoneInput == null) {
197
288
  return null;
198
289
  }
199
290
 
@@ -201,5 +292,31 @@ export function toTimezoneOffset(timezoneInput?: string | number): number | null
201
292
  return timezoneInput;
202
293
  }
203
294
 
204
- return TIMEZONE_ABBR_MAP[timezoneInput] ?? null;
295
+ const matchedTimezone = timezoneOverrides[timezoneInput] ?? TIMEZONE_ABBR_MAP[timezoneInput];
296
+ if (matchedTimezone == null) {
297
+ return null;
298
+ }
299
+ // This means that we have matched an unambiguous timezone
300
+ if (typeof matchedTimezone == "number") {
301
+ return matchedTimezone;
302
+ }
303
+
304
+ // The matched timezone is an ambiguous timezone, where the offset depends on whether the context (refDate)
305
+ // is during daylight savings or not.
306
+
307
+ // Without refDate as context, there's no way to know if DST or non-DST offset should be used. Return null instead.
308
+ if (date == null) {
309
+ return null;
310
+ }
311
+
312
+ // Return DST offset if the refDate is during daylight savings
313
+ if (
314
+ dayjs(date).isAfter(matchedTimezone.dstStart(date.getFullYear())) &&
315
+ !dayjs(date).isAfter(matchedTimezone.dstEnd(date.getFullYear()))
316
+ ) {
317
+ return matchedTimezone.timezoneOffsetDuringDst;
318
+ }
319
+
320
+ // refDate is not during DST => return non-DST offset
321
+ return matchedTimezone.timezoneOffsetNonDst;
205
322
  }
@@ -180,22 +180,22 @@ test("Test - Range Expression", function () {
180
180
  });
181
181
 
182
182
  test("Test - Timezone extraction", function () {
183
- testSingleCase(chrono.de, "um 14 Uhr", new Date(2016, 3, 28), (result, text) => {
183
+ testSingleCase(chrono.de, "um 14 Uhr", new Date(2016, 1, 28), (result, text) => {
184
184
  expect(result.text).toBe(text);
185
185
  expect(result.start.isCertain("timezoneOffset")).toBe(false);
186
186
  expect(!result.start.get("timezoneOffset")).not.toBeNull();
187
187
  });
188
188
 
189
- testSingleCase(chrono.de, "um 14 Uhr CET", new Date(2016, 3, 28), (result, text) => {
189
+ testSingleCase(chrono.de, "um 14 Uhr CET", new Date(2016, 1, 28), (result, text) => {
190
190
  expect(result.text).toBe(text);
191
191
  expect(result.start.isCertain("timezoneOffset")).toBe(true);
192
192
  expect(result.start.get("timezoneOffset")).toBe(60);
193
193
  });
194
194
 
195
- testSingleCase(chrono.de, "14 Uhr cet", new Date(2016, 3, 28), (result, text) => {
195
+ testSingleCase(chrono.de, "14 Uhr cet", new Date(2016, 4, 28), (result, text) => {
196
196
  expect(result.text).toBe(text);
197
197
  expect(result.start.isCertain("timezoneOffset")).toBe(true);
198
- expect(result.start.get("timezoneOffset")).toBe(60);
198
+ expect(result.start.get("timezoneOffset")).toBe(120);
199
199
  });
200
200
  });
201
201
 
@@ -209,15 +209,15 @@ test("Test - Random date + time expression", function () {
209
209
  expect(result.start.get("hour")).toBe(12);
210
210
  });
211
211
 
212
- testSingleCase(chrono.de, "am Freitag um 14 Uhr cetteln wir etwas an", new Date(2016, 3, 28), (result, text) => {
212
+ testSingleCase(chrono.de, "am Freitag um 14 Uhr cetteln wir etwas an", new Date(2016, 1, 28), (result, text) => {
213
213
  expect(result.text).toBe("am Freitag um 14 Uhr");
214
214
  expect(result.start.isCertain("timezoneOffset")).toBe(false);
215
215
  expect(!result.start.get("timezoneOffset")).not.toBeNull();
216
216
  });
217
217
 
218
- testSingleCase(chrono.de, "Freitag um 14 Uhr CET", new Date(2016, 3, 28), (result, text) => {
218
+ testSingleCase(chrono.de, "Freitag um 14 Uhr CET", new Date(2016, 4, 28), (result, text) => {
219
219
  expect(result.text).toBe(text);
220
220
  expect(result.start.isCertain("timezoneOffset")).toBe(true);
221
- expect(result.start.get("timezoneOffset")).toBe(60);
221
+ expect(result.start.get("timezoneOffset")).toBe(120);
222
222
  });
223
223
  });
@@ -117,6 +117,76 @@ test("Test - Month-Only expression", function () {
117
117
  });
118
118
  });
119
119
 
120
+ test("Test - Month-Only Range expression", () => {
121
+ testSingleCase(chrono, "From May to December", new Date(2023, 4 - 1, 9), (result) => {
122
+ expect(result.start.get("year")).toBe(2023);
123
+ expect(result.start.get("month")).toBe(5);
124
+ expect(result.end.get("year")).toBe(2023);
125
+ expect(result.end.get("month")).toBe(12);
126
+ });
127
+
128
+ testSingleCase(chrono, "From December to May", new Date(2023, 4 - 1, 9), (result) => {
129
+ expect(result.start.get("year")).toBe(2022);
130
+ expect(result.start.get("month")).toBe(12);
131
+ expect(result.end.get("year")).toBe(2023);
132
+ expect(result.end.get("month")).toBe(5);
133
+ });
134
+
135
+ testSingleCase(chrono, "From May to December, 2022", new Date(2023, 4 - 1, 9), (result) => {
136
+ expect(result.start.get("year")).toBe(2022);
137
+ expect(result.start.get("month")).toBe(5);
138
+ expect(result.end.get("year")).toBe(2022);
139
+ expect(result.end.get("month")).toBe(12);
140
+ });
141
+
142
+ testSingleCase(chrono, "From December to May 2022", new Date(2023, 4 - 1, 9), (result) => {
143
+ expect(result.start.get("year")).toBe(2021);
144
+ expect(result.start.get("month")).toBe(12);
145
+ expect(result.end.get("year")).toBe(2022);
146
+ expect(result.end.get("month")).toBe(5);
147
+ });
148
+
149
+ testSingleCase(chrono, "From December to May 2020", new Date(2023, 4 - 1, 9), (result) => {
150
+ expect(result.start.get("year")).toBe(2019);
151
+ expect(result.start.get("month")).toBe(12);
152
+ expect(result.end.get("year")).toBe(2020);
153
+ expect(result.end.get("month")).toBe(5);
154
+ });
155
+
156
+ testSingleCase(chrono, "From December to May 2025", new Date(2023, 4 - 1, 9), (result) => {
157
+ expect(result.start.get("year")).toBe(2024);
158
+ expect(result.start.get("month")).toBe(12);
159
+ expect(result.end.get("year")).toBe(2025);
160
+ expect(result.end.get("month")).toBe(5);
161
+ });
162
+ });
163
+
164
+ test("Test - Month with farward date option", () => {
165
+ testSingleCase(chrono, "in December", new Date(2023, 4 - 1, 9), { forwardDate: true }, (result) => {
166
+ expect(result.start.get("year")).toBe(2023);
167
+ expect(result.start.get("month")).toBe(12);
168
+ });
169
+
170
+ testSingleCase(chrono, "in May", new Date(2023, 4 - 1, 9), { forwardDate: true }, (result) => {
171
+ expect(result.start.get("year")).toBe(2023);
172
+ expect(result.start.get("month")).toBe(5);
173
+ });
174
+
175
+ testSingleCase(chrono, "From May to December", new Date(2023, 4 - 1, 9), { forwardDate: true }, (result) => {
176
+ expect(result.start.get("year")).toBe(2023);
177
+ expect(result.start.get("month")).toBe(5);
178
+ expect(result.end.get("year")).toBe(2023);
179
+ expect(result.end.get("month")).toBe(12);
180
+ });
181
+
182
+ testSingleCase(chrono, "From December to May", new Date(2023, 4 - 1, 9), { forwardDate: true }, (result) => {
183
+ expect(result.start.get("year")).toBe(2023);
184
+ expect(result.start.get("month")).toBe(12);
185
+ expect(result.end.get("year")).toBe(2024);
186
+ expect(result.end.get("month")).toBe(5);
187
+ });
188
+ });
189
+
120
190
  test("Test - Month expression in context", function () {
121
191
  testSingleCase(chrono, "The date is Sep 2012 is the date", (result) => {
122
192
  expect(result.index).toBe(12);
@@ -300,6 +300,18 @@ test("Test - Before with reference", () => {
300
300
  });
301
301
  });
302
302
 
303
+ test("Test - Strict mode", function () {
304
+ testSingleCase(chrono.strict, "5 minutes ago", new Date(2012, 7, 10, 12, 14), (result, text) => {
305
+ expect(result.start.get("hour")).toBe(12);
306
+ expect(result.start.get("minute")).toBe(9);
307
+ expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 9));
308
+ });
309
+
310
+ testUnexpectedResult(chrono.strict, "5m ago", new Date(2012, 7, 10, 12, 14));
311
+ testUnexpectedResult(chrono.strict, "5hr before", new Date(2012, 7, 10, 12, 14));
312
+ testUnexpectedResult(chrono.strict, "5 h ago", new Date(2012, 7, 10, 12, 14));
313
+ });
314
+
303
315
  test("Test - Negative cases", function () {
304
316
  testUnexpectedResult(chrono, "15 hours 29 min");
305
317
  testUnexpectedResult(chrono, "a few hour");
@@ -1,5 +1,6 @@
1
1
  import * as chrono from "../../src";
2
2
  import { testSingleCase, testUnexpectedResult } from "../test_util";
3
+ import ENTimeUnitCasualRelativeFormatParser from "../../src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser";
3
4
 
4
5
  test("Test - Positive time units", () => {
5
6
  testSingleCase(chrono, "next 2 weeks", new Date(2016, 10 - 1, 1, 12), (result, text) => {
@@ -141,6 +142,23 @@ test("Test - Minus '-' sign", () => {
141
142
  });
142
143
  });
143
144
 
145
+ test("Test - Without custom parser without abbreviations", function () {
146
+ const custom = chrono.en.strict.clone();
147
+ custom.parsers.push(new ENTimeUnitCasualRelativeFormatParser(false));
148
+
149
+ testUnexpectedResult(custom, "-3y");
150
+ testUnexpectedResult(custom, "last 2m");
151
+
152
+ testSingleCase(custom, "-2 hours 5 minutes", new Date(2016, 10 - 1, 1, 12), (result, text) => {
153
+ expect(result.text).toBe(text);
154
+ expect(result.start.get("year")).toBe(2016);
155
+ expect(result.start.get("month")).toBe(10);
156
+ expect(result.start.get("day")).toBe(1);
157
+ expect(result.start.get("hour")).toBe(9);
158
+ expect(result.start.get("minute")).toBe(55);
159
+ });
160
+ });
161
+
144
162
  test("Test - Negative cases", () => {
145
163
  testUnexpectedResult(chrono.casual, "3y", new Date(2015, 7 - 1, 10, 12, 14));
146
164
  testUnexpectedResult(chrono.casual, "1 m", new Date(2015, 7 - 1, 10, 12, 14));
@@ -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
  import { Meridiem } from "../../src/";
4
4
 
5
5
  test("Test - Later Expression", function () {
@@ -262,6 +262,16 @@ test("Test - From now Expression", () => {
262
262
  });
263
263
 
264
264
  test("Test - Strict mode", function () {
265
+ testSingleCase(chrono, "the min after", new Date(2012, 7, 10, 12, 14), (result) => {
266
+ expect(result.index).toBe(0);
267
+ expect(result.text).toBe("the min after");
268
+ expect(result.start.get("hour")).toBe(12);
269
+ expect(result.start.get("minute")).toBe(15);
270
+ expect(result.start.get("meridiem")).toBe(Meridiem.PM);
271
+
272
+ expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 15));
273
+ });
274
+
265
275
  testSingleCase(chrono.strict, "15 minutes from now", new Date(2012, 7, 10, 12, 14), (result, text) => {
266
276
  expect(result.text).toBe(text);
267
277
  expect(result.start.get("hour")).toBe(12);
@@ -279,15 +289,8 @@ test("Test - Strict mode", function () {
279
289
  expect(result.start).toBeDate(new Date(2012, 7, 10, 13, 5));
280
290
  });
281
291
 
282
- testSingleCase(chrono, "the min after", new Date(2012, 7, 10, 12, 14), (result) => {
283
- expect(result.index).toBe(0);
284
- expect(result.text).toBe("the min after");
285
- expect(result.start.get("hour")).toBe(12);
286
- expect(result.start.get("minute")).toBe(15);
287
- expect(result.start.get("meridiem")).toBe(Meridiem.PM);
288
-
289
- expect(result.start).toBeDate(new Date(2012, 7, 10, 12, 15));
290
- });
292
+ testUnexpectedResult(chrono.strict, "15m from now");
293
+ testUnexpectedResult(chrono.strict, "15s later");
291
294
  });
292
295
 
293
296
  test("Test - After with reference", () => {
@@ -1,5 +1,6 @@
1
1
  import * as chrono from "../../src";
2
- import { testSingleCase } from "../test_util";
2
+ import { testSingleCase, testUnexpectedResult } from "../test_util";
3
+ import { Meridiem } from "../../src";
3
4
 
4
5
  test("Test - The normal within expression", () => {
5
6
  testSingleCase(chrono, "we have to make something in 5 days.", new Date(2012, 7, 10), (result) => {
@@ -327,3 +328,13 @@ test("Test - Time units' certainty", () => {
327
328
  expect(result.start.isCertain("minute")).toBeFalsy();
328
329
  });
329
330
  });
331
+
332
+ test("Test - Strict mode", function () {
333
+ testSingleCase(chrono, "in 2hour", new Date(2016, 10 - 1, 1, 14, 52), (result, text) => {
334
+ expect(result.start.get("hour")).toBe(16);
335
+ expect(result.start.get("minute")).toBe(52);
336
+ });
337
+
338
+ testUnexpectedResult(chrono.strict, "in 15m");
339
+ testUnexpectedResult(chrono.strict, "within 5hr");
340
+ });
@@ -1,4 +1,6 @@
1
1
  import * as chrono from "../../src";
2
+ import { Month, Weekday } from "../../src";
3
+ import { getLastWeekdayOfMonth } from "../../src/timezone";
2
4
  import { testSingleCase } from "../test_util";
3
5
 
4
6
  test("Test - Parsing date/time with UTC offset", function () {
@@ -100,6 +102,146 @@ test("Test - Parsing date/time with timezone abbreviation", function () {
100
102
  });
101
103
  });
102
104
 
105
+ test("Test - Parsing date/time with ambiguous timezone abbreviations", function () {
106
+ // Test dates around transition to DST for ET
107
+ testSingleCase(chrono, "2022-03-12 23:00 ET", (result, text) => {
108
+ expect(result.text).toBe(text);
109
+ expect(result.start.get("hour")).toBe(23);
110
+ expect(result.start.get("timezoneOffset")).toBe(-5 * 60); // ET during standard time (EST) in 2022
111
+ });
112
+ testSingleCase(chrono, "2022-03-13 23:00 ET", (result, text) => {
113
+ expect(result.text).toBe(text);
114
+ expect(result.start.get("hour")).toBe(23);
115
+ expect(result.start.get("timezoneOffset")).toBe(-4 * 60); // ET during DST (EDT) in 2022
116
+ });
117
+ testSingleCase(chrono, "2021-03-13 23:00 ET", (result, text) => {
118
+ expect(result.text).toBe(text);
119
+ expect(result.start.get("hour")).toBe(23);
120
+ expect(result.start.get("timezoneOffset")).toBe(-5 * 60); // ET during standard time (EST) in 2021
121
+ });
122
+ testSingleCase(chrono, "2021-03-14 23:00 ET", (result, text) => {
123
+ expect(result.text).toBe(text);
124
+ expect(result.start.get("hour")).toBe(23);
125
+ expect(result.start.get("timezoneOffset")).toBe(-4 * 60); // ET during DST (EDT) in 2021
126
+ });
127
+
128
+ // Also test around transition *from* DST
129
+ testSingleCase(chrono, "2021-11-06 23:00 ET", (result, text) => {
130
+ expect(result.text).toBe(text);
131
+ expect(result.start.get("hour")).toBe(23);
132
+ expect(result.start.get("timezoneOffset")).toBe(-4 * 60); // ET during DST (EDT) in 2021
133
+ });
134
+ testSingleCase(chrono, "2021-11-07 23:00 ET", (result, text) => {
135
+ expect(result.text).toBe(text);
136
+ expect(result.start.get("hour")).toBe(23);
137
+ expect(result.start.get("timezoneOffset")).toBe(-5 * 60); // ET during standard time (EST) in 2021
138
+ });
139
+ testSingleCase(chrono, "2020-10-31 23:00 ET", (result, text) => {
140
+ expect(result.text).toBe(text);
141
+ expect(result.start.get("hour")).toBe(23);
142
+ expect(result.start.get("timezoneOffset")).toBe(-4 * 60); // ET during DST (EDT) in 2020
143
+ });
144
+ testSingleCase(chrono, "2020-11-01 23:00 ET", (result, text) => {
145
+ expect(result.text).toBe(text);
146
+ expect(result.start.get("hour")).toBe(23);
147
+ expect(result.start.get("timezoneOffset")).toBe(-5 * 60); // ET during standard time (EST) in 2020
148
+ });
149
+
150
+ // Same checks, but for CET (which transitions at different dates with different rules)
151
+ // Test dates around transition to DST for CET
152
+ testSingleCase(chrono, "2022-03-26 23:00 CET", (result, text) => {
153
+ expect(result.text).toBe(text);
154
+ expect(result.start.get("hour")).toBe(23);
155
+ expect(result.start.get("timezoneOffset")).toBe(60); // CET during standard time (CET) in 2022
156
+ });
157
+ testSingleCase(chrono, "2022-03-27 23:00 CET", (result, text) => {
158
+ expect(result.text).toBe(text);
159
+ expect(result.start.get("hour")).toBe(23);
160
+ expect(result.start.get("timezoneOffset")).toBe(2 * 60); // CET during DST (CEST) in 2022
161
+ });
162
+ testSingleCase(chrono, "2021-03-27 23:00 CET", (result, text) => {
163
+ expect(result.text).toBe(text);
164
+ expect(result.start.get("hour")).toBe(23);
165
+ expect(result.start.get("timezoneOffset")).toBe(60); // CET during standard time (CET) in 2021
166
+ });
167
+ testSingleCase(chrono, "2021-03-28 23:00 CET", (result, text) => {
168
+ expect(result.text).toBe(text);
169
+ expect(result.start.get("hour")).toBe(23);
170
+ expect(result.start.get("timezoneOffset")).toBe(2 * 60); // CET during DST (CEST) in 2021
171
+ });
172
+
173
+ // Also test around transition *from* DST for CET
174
+ testSingleCase(chrono, "2022-10-29 23:00 CET", (result, text) => {
175
+ expect(result.text).toBe(text);
176
+ expect(result.start.get("hour")).toBe(23);
177
+ expect(result.start.get("timezoneOffset")).toBe(2 * 60); // CET during DST (CEST) in 2022
178
+ });
179
+ testSingleCase(chrono, "2022-10-30 23:00 CET", (result, text) => {
180
+ expect(result.text).toBe(text);
181
+ expect(result.start.get("hour")).toBe(23);
182
+ expect(result.start.get("timezoneOffset")).toBe(60); // CET during standard time (CET) in 2022
183
+ });
184
+ testSingleCase(chrono, "2021-10-30 23:00 CET", (result, text) => {
185
+ expect(result.text).toBe(text);
186
+ expect(result.start.get("hour")).toBe(23);
187
+ expect(result.start.get("timezoneOffset")).toBe(2 * 60); // CET during DST (CEST) in 2021
188
+ });
189
+ testSingleCase(chrono, "2021-10-31 23:00 CET", (result, text) => {
190
+ expect(result.text).toBe(text);
191
+ expect(result.start.get("hour")).toBe(23);
192
+ expect(result.start.get("timezoneOffset")).toBe(60); // CET during standard time (CET) in 2021
193
+ });
194
+ });
195
+
196
+ test("Test - Timezone parsing overrides", function () {
197
+ // chrono.parse('at 10:00 XYZ', new Date(2023, 3, 20), {timezones: {XYZ: -180}})
198
+ // XYZ shouldn't be recognized or parsed as a timezone
199
+ testSingleCase(chrono, "Jan 1st 2023 at 10:00 XYZ", (result) => {
200
+ expect(result.text).toBe("Jan 1st 2023 at 10:00");
201
+ expect(result.start).toBeDate(new Date(2023, 1 - 1, 1, 10));
202
+ });
203
+ // Parse the correct tzoffset when XYZ is provided as a custom tz in parsingOptions
204
+ testSingleCase(
205
+ chrono,
206
+ "Jan 1st 2023 at 10:00 XYZ",
207
+ new Date(2023, 1, 1),
208
+ { timezones: { XYZ: -180 } },
209
+ (result) => {
210
+ expect(result.text).toBe("Jan 1st 2023 at 10:00 XYZ");
211
+ expect(result.start.get("timezoneOffset")).toBe(-180);
212
+ }
213
+ );
214
+ // Parse the correct tzoffset when XYZ is provided as a custom ambiguous tz in parsingOptions
215
+ const parseXYZAsAmbiguousTz = {
216
+ timezoneOffsetDuringDst: -120,
217
+ timezoneOffsetNonDst: -180,
218
+ dstStart: (year: number) => getLastWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2),
219
+ dstEnd: (year: number) => getLastWeekdayOfMonth(year, Month.OCTOBER, Weekday.SUNDAY, 3),
220
+ };
221
+ // Parsing a non-DST date
222
+ testSingleCase(
223
+ chrono,
224
+ "Jan 1st 2023 at 10:00 XYZ",
225
+ new Date(2023, 1, 1),
226
+ { timezones: { XYZ: parseXYZAsAmbiguousTz } },
227
+ (result) => {
228
+ expect(result.text).toBe("Jan 1st 2023 at 10:00 XYZ");
229
+ expect(result.start.get("timezoneOffset")).toBe(-180);
230
+ }
231
+ );
232
+ // Parsing a DST date
233
+ testSingleCase(
234
+ chrono,
235
+ "Jun 1st 2023 at 10:00 XYZ",
236
+ new Date(2023, 1, 1),
237
+ { timezones: { XYZ: parseXYZAsAmbiguousTz } },
238
+ (result) => {
239
+ expect(result.text).toBe("Jun 1st 2023 at 10:00 XYZ");
240
+ expect(result.start.get("timezoneOffset")).toBe(-120);
241
+ }
242
+ );
243
+ });
244
+
103
245
  test("Test - Parsing date with timezone abbreviation", function () {
104
246
  testSingleCase(chrono, "Wednesday, September 16, 2020, EST", (result, text) => {
105
247
  expect(result.text).toBe(text);
@@ -273,6 +273,32 @@ test("Test - Weekday Overlap", function () {
273
273
  });
274
274
  });
275
275
 
276
+ test("Test - Weekday range", () => {
277
+ testSingleCase(chrono.casual, "Friday to Monday", new Date(2023, 4 - 1, 9) /*Sunday*/, (result) => {
278
+ expect(result.start.get("year")).toBe(2023);
279
+ expect(result.start.get("month")).toBe(4);
280
+ expect(result.start.get("day")).toBe(7);
281
+ expect(result.start.get("weekday")).toBe(5);
282
+
283
+ expect(result.end.get("year")).toBe(2023);
284
+ expect(result.end.get("month")).toBe(4);
285
+ expect(result.end.get("day")).toBe(10);
286
+ expect(result.end.get("weekday")).toBe(1);
287
+ });
288
+
289
+ testSingleCase(chrono.casual, "Monday to Friday", new Date(2023, 4 - 1, 9) /*Sunday*/, (result) => {
290
+ expect(result.start.get("year")).toBe(2023);
291
+ expect(result.start.get("month")).toBe(4);
292
+ expect(result.start.get("day")).toBe(10);
293
+ expect(result.start.get("weekday")).toBe(1);
294
+
295
+ expect(result.end.get("year")).toBe(2023);
296
+ expect(result.end.get("month")).toBe(4);
297
+ expect(result.end.get("day")).toBe(14);
298
+ expect(result.end.get("weekday")).toBe(5);
299
+ });
300
+ });
301
+
276
302
  test("Test - forward dates only option", () => {
277
303
  testSingleCase(
278
304
  chrono.casual,
@@ -63,6 +63,8 @@ test("Test - Skip impossible dates/times", () => {
63
63
 
64
64
  testUnexpectedResult(chrono, "14PM");
65
65
  testUnexpectedResult(chrono, "25:12");
66
+
67
+ testUnexpectedResult(chrono, "An appointment on 13/31/2018");
66
68
  });
67
69
 
68
70
  test("Test - Skip version number pattern", () => {
@@ -488,13 +488,13 @@ test("Test - Timezone extraction", function () {
488
488
  expect(result.start.get("timezoneOffset")).toBe(-300);
489
489
  });
490
490
 
491
- testSingleCase(chrono.fr, "vendredi 15h CET", new Date(2016, 3, 28), (result, text) => {
491
+ testSingleCase(chrono.fr, "vendredi 15h CET", new Date(2016, 1, 28), (result, text) => {
492
492
  expect(result.text).toBe(text);
493
493
  expect(result.start.isCertain("timezoneOffset")).toBe(true);
494
494
  expect(result.start.get("timezoneOffset")).toBe(60);
495
495
  });
496
496
 
497
- testSingleCase(chrono.fr, "vendredi 15h cest", new Date(2016, 3, 28), (result, text) => {
497
+ testSingleCase(chrono.fr, "vendredi 15h cest", new Date(2016, 1, 28), (result, text) => {
498
498
  expect(result.text).toBe(text);
499
499
  expect(result.start.isCertain("timezoneOffset")).toBe(true);
500
500
  expect(result.start.get("timezoneOffset")).toBe(120);