chrono-node 2.3.3 → 2.3.7

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 (109) hide show
  1. package/README.md +2 -2
  2. package/dist/common/casualReferences.js +3 -1
  3. package/dist/common/parsers/AbstractParserWithWordBoundary.js +2 -1
  4. package/dist/common/refiners/ExtractTimezoneAbbrRefiner.js +5 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +3 -1
  7. package/dist/locales/en/index.js +2 -1
  8. package/dist/locales/en/parsers/ENRelativeDateFormatParser.js +2 -2
  9. package/dist/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +1 -1
  10. package/dist/locales/en/refiners/ENMergeRelativeDateRefiner.d.ts +7 -0
  11. package/dist/locales/en/refiners/ENMergeRelativeDateRefiner.js +35 -0
  12. package/dist/locales/zh/hans/constants.d.ts +27 -0
  13. package/dist/locales/zh/hans/constants.js +51 -0
  14. package/dist/locales/zh/hans/index.d.ts +9 -0
  15. package/dist/locales/zh/hans/index.js +49 -0
  16. package/dist/locales/zh/hans/parsers/ZHHansCasualDateParser.d.ts +7 -0
  17. package/dist/locales/zh/hans/parsers/ZHHansCasualDateParser.js +138 -0
  18. package/dist/locales/zh/hans/parsers/ZHHansDateParser.d.ts +6 -0
  19. package/dist/locales/zh/hans/parsers/ZHHansDateParser.js +72 -0
  20. package/dist/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.d.ts +6 -0
  21. package/dist/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.js +78 -0
  22. package/dist/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.d.ts +7 -0
  23. package/dist/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.js +70 -0
  24. package/dist/locales/zh/hans/parsers/ZHHansTimeExpressionParser.d.ts +6 -0
  25. package/dist/locales/zh/hans/parsers/ZHHansTimeExpressionParser.js +438 -0
  26. package/dist/locales/zh/hans/parsers/ZHHansWeekdayParser.d.ts +7 -0
  27. package/dist/locales/zh/hans/parsers/ZHHansWeekdayParser.js +46 -0
  28. package/dist/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.d.ts +4 -0
  29. package/dist/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.js +12 -0
  30. package/dist/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.d.ts +4 -0
  31. package/dist/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.js +12 -0
  32. package/dist/locales/zh/hant/constants.d.ts +28 -0
  33. package/dist/locales/zh/hant/constants.js +52 -0
  34. package/dist/locales/zh/hant/index.d.ts +9 -0
  35. package/dist/locales/zh/hant/index.js +49 -0
  36. package/dist/locales/zh/hant/parsers/ZHHantCasualDateParser.d.ts +7 -0
  37. package/dist/locales/zh/hant/parsers/ZHHantCasualDateParser.js +138 -0
  38. package/dist/locales/zh/hant/parsers/ZHHantDateParser.d.ts +6 -0
  39. package/dist/locales/zh/hant/parsers/ZHHantDateParser.js +64 -0
  40. package/dist/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.d.ts +6 -0
  41. package/dist/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.js +78 -0
  42. package/dist/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.d.ts +7 -0
  43. package/dist/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.js +70 -0
  44. package/dist/locales/zh/hant/parsers/ZHHantTimeExpressionParser.d.ts +6 -0
  45. package/dist/locales/zh/hant/parsers/ZHHantTimeExpressionParser.js +438 -0
  46. package/dist/locales/zh/hant/parsers/ZHHantWeekdayParser.d.ts +7 -0
  47. package/dist/locales/zh/hant/parsers/ZHHantWeekdayParser.js +46 -0
  48. package/dist/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.d.ts +4 -0
  49. package/dist/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.js +12 -0
  50. package/dist/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.d.ts +4 -0
  51. package/dist/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.js +12 -0
  52. package/dist/locales/zh/index.d.ts +2 -0
  53. package/dist/locales/zh/index.js +16 -0
  54. package/dist/results.js +8 -5
  55. package/dist/timezone.js +1 -1
  56. package/dist/utils/dayjs.js +7 -0
  57. package/package.json +1 -1
  58. package/src/common/casualReferences.ts +3 -1
  59. package/src/common/parsers/AbstractParserWithWordBoundary.ts +1 -1
  60. package/src/common/refiners/ExtractTimezoneAbbrRefiner.ts +8 -0
  61. package/src/index.ts +2 -1
  62. package/src/locales/en/index.ts +2 -1
  63. package/src/locales/en/parsers/ENMonthNameLittleEndianParser.ts +0 -2
  64. package/src/locales/en/parsers/ENRelativeDateFormatParser.ts +2 -2
  65. package/src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts +1 -1
  66. package/src/locales/en/refiners/ENMergeRelativeDateRefiner.ts +58 -0
  67. package/src/locales/zh/hans/constants.ts +52 -0
  68. package/src/locales/zh/hans/index.ts +62 -0
  69. package/src/locales/zh/hans/parsers/ZHHansCasualDateParser.ts +128 -0
  70. package/src/locales/zh/hans/parsers/ZHHansDateParser.ts +75 -0
  71. package/src/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.ts +81 -0
  72. package/src/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.ts +69 -0
  73. package/src/locales/zh/hans/parsers/ZHHansTimeExpressionParser.ts +424 -0
  74. package/src/locales/zh/hans/parsers/ZHHansWeekdayParser.ts +46 -0
  75. package/src/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.ts +7 -0
  76. package/src/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.ts +7 -0
  77. package/src/locales/zh/hant/constants.ts +53 -0
  78. package/src/locales/zh/hant/index.ts +63 -0
  79. package/src/locales/zh/hant/parsers/ZHHantCasualDateParser.ts +128 -0
  80. package/src/locales/zh/hant/parsers/ZHHantDateParser.ts +68 -0
  81. package/src/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.ts +81 -0
  82. package/src/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.ts +69 -0
  83. package/src/locales/zh/hant/parsers/ZHHantTimeExpressionParser.ts +424 -0
  84. package/src/locales/zh/hant/parsers/ZHHantWeekdayParser.ts +46 -0
  85. package/src/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.ts +7 -0
  86. package/src/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.ts +7 -0
  87. package/src/locales/zh/index.ts +2 -0
  88. package/src/results.ts +11 -4
  89. package/src/timezone.ts +1 -1
  90. package/src/utils/dayjs.ts +6 -0
  91. package/test/en/en_casual.test.ts +11 -0
  92. package/test/en/en_merging_relative_dates.test.ts +51 -0
  93. package/test/en/en_month.test.ts +13 -0
  94. package/test/en/en_relative.test.ts +20 -0
  95. package/test/en/en_time_units_ago.test.ts +16 -0
  96. package/test/en/en_time_units_casual_relative.test.ts +16 -0
  97. package/test/en/en_time_units_later.test.ts +6 -0
  98. package/test/en/en_timezone_exp.test.ts +85 -10
  99. package/test/zh/zh.test.ts +18 -0
  100. package/test/zh/zh_hans_casual.test.ts +228 -0
  101. package/test/zh/zh_hans_date.test.ts +102 -0
  102. package/test/zh/zh_hans_deadline.test.ts +136 -0
  103. package/test/zh/zh_hans_time_exp.test.ts +182 -0
  104. package/test/zh/zh_hans_weekday.test.ts +132 -0
  105. package/test/zh/zh_hant_casual.test.ts +246 -0
  106. package/test/zh/zh_hant_date.test.ts +102 -0
  107. package/test/zh/zh_hant_deadline.test.ts +136 -0
  108. package/test/zh/zh_hant_time_exp.test.ts +182 -0
  109. package/test/zh/zh_hant_weekday.test.ts +132 -0
@@ -0,0 +1,424 @@
1
+ import dayjs from "dayjs";
2
+ import { ParsingContext } from "../../../../chrono";
3
+ import { AbstractParserWithWordBoundaryChecking } from "../../../../common/parsers/AbstractParserWithWordBoundary";
4
+ import { NUMBER, zhStringToNumber } from "../constants";
5
+
6
+ const FIRST_REG_PATTERN = new RegExp(
7
+ "(?:由|從|自)?" +
8
+ "(?:" +
9
+ "(今|明|前|大前|後|大後|聽|昨|尋|琴)(早|朝|晚)|" +
10
+ "(上(?:午|晝)|朝(?:早)|早(?:上)|下(?:午|晝)|晏(?:晝)|晚(?:上)|夜(?:晚)?|中(?:午)|凌(?:晨))|" +
11
+ "(今|明|前|大前|後|大後|聽|昨|尋|琴)(?:日|天)" +
12
+ "(?:[\\s,,]*)" +
13
+ "(?:(上(?:午|晝)|朝(?:早)|早(?:上)|下(?:午|晝)|晏(?:晝)|晚(?:上)|夜(?:晚)?|中(?:午)|凌(?:晨)))?" +
14
+ ")?" +
15
+ "(?:[\\s,,]*)" +
16
+ "(?:(\\d+|[" +
17
+ Object.keys(NUMBER).join("") +
18
+ "]+)(?:\\s*)(?:點|時|:|:)" +
19
+ "(?:\\s*)" +
20
+ "(\\d+|半|正|整|[" +
21
+ Object.keys(NUMBER).join("") +
22
+ "]+)?(?:\\s*)(?:分|:|:)?" +
23
+ "(?:\\s*)" +
24
+ "(\\d+|[" +
25
+ Object.keys(NUMBER).join("") +
26
+ "]+)?(?:\\s*)(?:秒)?)" +
27
+ "(?:\\s*(A.M.|P.M.|AM?|PM?))?",
28
+ "i"
29
+ );
30
+
31
+ const SECOND_REG_PATTERN = new RegExp(
32
+ "(?:^\\s*(?:到|至|\\-|\\–|\\~|\\〜)\\s*)" +
33
+ "(?:" +
34
+ "(今|明|前|大前|後|大後|聽|昨|尋|琴)(早|朝|晚)|" +
35
+ "(上(?:午|晝)|朝(?:早)|早(?:上)|下(?:午|晝)|晏(?:晝)|晚(?:上)|夜(?:晚)?|中(?:午)|凌(?:晨))|" +
36
+ "(今|明|前|大前|後|大後|聽|昨|尋|琴)(?:日|天)" +
37
+ "(?:[\\s,,]*)" +
38
+ "(?:(上(?:午|晝)|朝(?:早)|早(?:上)|下(?:午|晝)|晏(?:晝)|晚(?:上)|夜(?:晚)?|中(?:午)|凌(?:晨)))?" +
39
+ ")?" +
40
+ "(?:[\\s,,]*)" +
41
+ "(?:(\\d+|[" +
42
+ Object.keys(NUMBER).join("") +
43
+ "]+)(?:\\s*)(?:點|時|:|:)" +
44
+ "(?:\\s*)" +
45
+ "(\\d+|半|正|整|[" +
46
+ Object.keys(NUMBER).join("") +
47
+ "]+)?(?:\\s*)(?:分|:|:)?" +
48
+ "(?:\\s*)" +
49
+ "(\\d+|[" +
50
+ Object.keys(NUMBER).join("") +
51
+ "]+)?(?:\\s*)(?:秒)?)" +
52
+ "(?:\\s*(A.M.|P.M.|AM?|PM?))?",
53
+ "i"
54
+ );
55
+
56
+ const DAY_GROUP_1 = 1;
57
+ const ZH_AM_PM_HOUR_GROUP_1 = 2;
58
+ const ZH_AM_PM_HOUR_GROUP_2 = 3;
59
+ const DAY_GROUP_3 = 4;
60
+ const ZH_AM_PM_HOUR_GROUP_3 = 5;
61
+ const HOUR_GROUP = 6;
62
+ const MINUTE_GROUP = 7;
63
+ const SECOND_GROUP = 8;
64
+ const AM_PM_HOUR_GROUP = 9;
65
+
66
+ export default class ZHHantTimeExpressionParser extends AbstractParserWithWordBoundaryChecking {
67
+ innerPattern(): RegExp {
68
+ return FIRST_REG_PATTERN;
69
+ }
70
+
71
+ innerExtract(context: ParsingContext, match: RegExpMatchArray) {
72
+ // This pattern can be overlaped Ex. [12] AM, 1[2] AM
73
+ if (match.index > 0 && context.text[match.index - 1].match(/\w/)) {
74
+ return null;
75
+ }
76
+
77
+ const refMoment = dayjs(context.refDate);
78
+ const result = context.createParsingResult(match.index, match[0]);
79
+ const startMoment = refMoment.clone();
80
+
81
+ // ----- Day
82
+ if (match[DAY_GROUP_1]) {
83
+ var day1 = match[DAY_GROUP_1];
84
+ if (day1 == "明" || day1 == "聽") {
85
+ // Check not "Tomorrow" on late night
86
+ if (refMoment.hour() > 1) {
87
+ startMoment.add(1, "day");
88
+ }
89
+ } else if (day1 == "昨" || day1 == "尋" || day1 == "琴") {
90
+ startMoment.add(-1, "day");
91
+ } else if (day1 == "前") {
92
+ startMoment.add(-2, "day");
93
+ } else if (day1 == "大前") {
94
+ startMoment.add(-3, "day");
95
+ } else if (day1 == "後") {
96
+ startMoment.add(2, "day");
97
+ } else if (day1 == "大後") {
98
+ startMoment.add(3, "day");
99
+ }
100
+ result.start.assign("day", startMoment.date());
101
+ result.start.assign("month", startMoment.month() + 1);
102
+ result.start.assign("year", startMoment.year());
103
+ } else if (match[DAY_GROUP_3]) {
104
+ var day3 = match[DAY_GROUP_3];
105
+ if (day3 == "明" || day3 == "聽") {
106
+ startMoment.add(1, "day");
107
+ } else if (day3 == "昨" || day3 == "尋" || day3 == "琴") {
108
+ startMoment.add(-1, "day");
109
+ } else if (day3 == "前") {
110
+ startMoment.add(-2, "day");
111
+ } else if (day3 == "大前") {
112
+ startMoment.add(-3, "day");
113
+ } else if (day3 == "後") {
114
+ startMoment.add(2, "day");
115
+ } else if (day3 == "大後") {
116
+ startMoment.add(3, "day");
117
+ }
118
+ result.start.assign("day", startMoment.date());
119
+ result.start.assign("month", startMoment.month() + 1);
120
+ result.start.assign("year", startMoment.year());
121
+ } else {
122
+ result.start.imply("day", startMoment.date());
123
+ result.start.imply("month", startMoment.month() + 1);
124
+ result.start.imply("year", startMoment.year());
125
+ }
126
+
127
+ let hour = 0;
128
+ let minute = 0;
129
+ let meridiem = -1;
130
+
131
+ // ----- Second
132
+ if (match[SECOND_GROUP]) {
133
+ var second = parseInt(match[SECOND_GROUP]);
134
+ if (isNaN(second)) {
135
+ second = zhStringToNumber(match[SECOND_GROUP]);
136
+ }
137
+ if (second >= 60) return null;
138
+ result.start.assign("second", second);
139
+ }
140
+
141
+ hour = parseInt(match[HOUR_GROUP]);
142
+ if (isNaN(hour)) {
143
+ hour = zhStringToNumber(match[HOUR_GROUP]);
144
+ }
145
+
146
+ // ----- Minutes
147
+ if (match[MINUTE_GROUP]) {
148
+ if (match[MINUTE_GROUP] == "半") {
149
+ minute = 30;
150
+ } else if (match[MINUTE_GROUP] == "正" || match[MINUTE_GROUP] == "整") {
151
+ minute = 0;
152
+ } else {
153
+ minute = parseInt(match[MINUTE_GROUP]);
154
+ if (isNaN(minute)) {
155
+ minute = zhStringToNumber(match[MINUTE_GROUP]);
156
+ }
157
+ }
158
+ } else if (hour > 100) {
159
+ minute = hour % 100;
160
+ hour = Math.floor(hour / 100);
161
+ }
162
+
163
+ if (minute >= 60) {
164
+ return null;
165
+ }
166
+
167
+ if (hour > 24) {
168
+ return null;
169
+ }
170
+ if (hour >= 12) {
171
+ meridiem = 1;
172
+ }
173
+
174
+ // ----- AM & PM
175
+ if (match[AM_PM_HOUR_GROUP]) {
176
+ if (hour > 12) return null;
177
+ var ampm = match[AM_PM_HOUR_GROUP][0].toLowerCase();
178
+ if (ampm == "a") {
179
+ meridiem = 0;
180
+ if (hour == 12) hour = 0;
181
+ }
182
+
183
+ if (ampm == "p") {
184
+ meridiem = 1;
185
+ if (hour != 12) hour += 12;
186
+ }
187
+ } else if (match[ZH_AM_PM_HOUR_GROUP_1]) {
188
+ var zhAMPMString1 = match[ZH_AM_PM_HOUR_GROUP_1];
189
+ var zhAMPM1 = zhAMPMString1[0];
190
+ if (zhAMPM1 == "朝" || zhAMPM1 == "早") {
191
+ meridiem = 0;
192
+ if (hour == 12) hour = 0;
193
+ } else if (zhAMPM1 == "晚") {
194
+ meridiem = 1;
195
+ if (hour != 12) hour += 12;
196
+ }
197
+ } else if (match[ZH_AM_PM_HOUR_GROUP_2]) {
198
+ var zhAMPMString2 = match[ZH_AM_PM_HOUR_GROUP_2];
199
+ var zhAMPM2 = zhAMPMString2[0];
200
+ if (zhAMPM2 == "上" || zhAMPM2 == "朝" || zhAMPM2 == "早" || zhAMPM2 == "凌") {
201
+ meridiem = 0;
202
+ if (hour == 12) hour = 0;
203
+ } else if (zhAMPM2 == "下" || zhAMPM2 == "晏" || zhAMPM2 == "晚") {
204
+ meridiem = 1;
205
+ if (hour != 12) hour += 12;
206
+ }
207
+ } else if (match[ZH_AM_PM_HOUR_GROUP_3]) {
208
+ var zhAMPMString3 = match[ZH_AM_PM_HOUR_GROUP_3];
209
+ var zhAMPM3 = zhAMPMString3[0];
210
+ if (zhAMPM3 == "上" || zhAMPM3 == "朝" || zhAMPM3 == "早" || zhAMPM3 == "凌") {
211
+ meridiem = 0;
212
+ if (hour == 12) hour = 0;
213
+ } else if (zhAMPM3 == "下" || zhAMPM3 == "晏" || zhAMPM3 == "晚") {
214
+ meridiem = 1;
215
+ if (hour != 12) hour += 12;
216
+ }
217
+ }
218
+
219
+ result.start.assign("hour", hour);
220
+ result.start.assign("minute", minute);
221
+
222
+ if (meridiem >= 0) {
223
+ result.start.assign("meridiem", meridiem);
224
+ } else {
225
+ if (hour < 12) {
226
+ result.start.imply("meridiem", 0);
227
+ } else {
228
+ result.start.imply("meridiem", 1);
229
+ }
230
+ }
231
+
232
+ // ==============================================================
233
+ // Extracting the 'to' chunk
234
+ // ==============================================================
235
+
236
+ match = SECOND_REG_PATTERN.exec(context.text.substring(result.index + result.text.length));
237
+ if (!match) {
238
+ // Not accept number only result
239
+ if (result.text.match(/^\d+$/)) {
240
+ return null;
241
+ }
242
+ return result;
243
+ }
244
+
245
+ const endMoment = startMoment.clone();
246
+ result.end = context.createParsingComponents();
247
+
248
+ // ----- Day
249
+ if (match[DAY_GROUP_1]) {
250
+ var day1 = match[DAY_GROUP_1];
251
+ if (day1 == "明" || day1 == "聽") {
252
+ // Check not "Tomorrow" on late night
253
+ if (refMoment.hour() > 1) {
254
+ endMoment.add(1, "day");
255
+ }
256
+ } else if (day1 == "昨" || day1 == "尋" || day1 == "琴") {
257
+ endMoment.add(-1, "day");
258
+ } else if (day1 == "前") {
259
+ endMoment.add(-2, "day");
260
+ } else if (day1 == "大前") {
261
+ endMoment.add(-3, "day");
262
+ } else if (day1 == "後") {
263
+ endMoment.add(2, "day");
264
+ } else if (day1 == "大後") {
265
+ endMoment.add(3, "day");
266
+ }
267
+ result.end.assign("day", endMoment.date());
268
+ result.end.assign("month", endMoment.month() + 1);
269
+ result.end.assign("year", endMoment.year());
270
+ } else if (match[DAY_GROUP_3]) {
271
+ var day3 = match[DAY_GROUP_3];
272
+ if (day3 == "明" || day3 == "聽") {
273
+ endMoment.add(1, "day");
274
+ } else if (day3 == "昨" || day3 == "尋" || day3 == "琴") {
275
+ endMoment.add(-1, "day");
276
+ } else if (day3 == "前") {
277
+ endMoment.add(-2, "day");
278
+ } else if (day3 == "大前") {
279
+ endMoment.add(-3, "day");
280
+ } else if (day3 == "後") {
281
+ endMoment.add(2, "day");
282
+ } else if (day3 == "大後") {
283
+ endMoment.add(3, "day");
284
+ }
285
+ result.end.assign("day", endMoment.date());
286
+ result.end.assign("month", endMoment.month() + 1);
287
+ result.end.assign("year", endMoment.year());
288
+ } else {
289
+ result.end.imply("day", endMoment.date());
290
+ result.end.imply("month", endMoment.month() + 1);
291
+ result.end.imply("year", endMoment.year());
292
+ }
293
+
294
+ hour = 0;
295
+ minute = 0;
296
+ meridiem = -1;
297
+
298
+ // ----- Second
299
+ if (match[SECOND_GROUP]) {
300
+ var second = parseInt(match[SECOND_GROUP]);
301
+ if (isNaN(second)) {
302
+ second = zhStringToNumber(match[SECOND_GROUP]);
303
+ }
304
+
305
+ if (second >= 60) return null;
306
+ result.end.assign("second", second);
307
+ }
308
+
309
+ hour = parseInt(match[HOUR_GROUP]);
310
+ if (isNaN(hour)) {
311
+ hour = zhStringToNumber(match[HOUR_GROUP]);
312
+ }
313
+
314
+ // ----- Minutes
315
+ if (match[MINUTE_GROUP]) {
316
+ if (match[MINUTE_GROUP] == "半") {
317
+ minute = 30;
318
+ } else if (match[MINUTE_GROUP] == "正" || match[MINUTE_GROUP] == "整") {
319
+ minute = 0;
320
+ } else {
321
+ minute = parseInt(match[MINUTE_GROUP]);
322
+ if (isNaN(minute)) {
323
+ minute = zhStringToNumber(match[MINUTE_GROUP]);
324
+ }
325
+ }
326
+ } else if (hour > 100) {
327
+ minute = hour % 100;
328
+ hour = Math.floor(hour / 100);
329
+ }
330
+
331
+ if (minute >= 60) {
332
+ return null;
333
+ }
334
+
335
+ if (hour > 24) {
336
+ return null;
337
+ }
338
+ if (hour >= 12) {
339
+ meridiem = 1;
340
+ }
341
+
342
+ // ----- AM & PM
343
+ if (match[AM_PM_HOUR_GROUP]) {
344
+ if (hour > 12) return null;
345
+ var ampm = match[AM_PM_HOUR_GROUP][0].toLowerCase();
346
+ if (ampm == "a") {
347
+ meridiem = 0;
348
+ if (hour == 12) hour = 0;
349
+ }
350
+
351
+ if (ampm == "p") {
352
+ meridiem = 1;
353
+ if (hour != 12) hour += 12;
354
+ }
355
+
356
+ if (!result.start.isCertain("meridiem")) {
357
+ if (meridiem == 0) {
358
+ result.start.imply("meridiem", 0);
359
+
360
+ if (result.start.get("hour") == 12) {
361
+ result.start.assign("hour", 0);
362
+ }
363
+ } else {
364
+ result.start.imply("meridiem", 1);
365
+
366
+ if (result.start.get("hour") != 12) {
367
+ result.start.assign("hour", result.start.get("hour") + 12);
368
+ }
369
+ }
370
+ }
371
+ } else if (match[ZH_AM_PM_HOUR_GROUP_1]) {
372
+ var zhAMPMString1 = match[ZH_AM_PM_HOUR_GROUP_1];
373
+ var zhAMPM1 = zhAMPMString1[0];
374
+ if (zhAMPM1 == "朝" || zhAMPM1 == "早") {
375
+ meridiem = 0;
376
+ if (hour == 12) hour = 0;
377
+ } else if (zhAMPM1 == "晚") {
378
+ meridiem = 1;
379
+ if (hour != 12) hour += 12;
380
+ }
381
+ } else if (match[ZH_AM_PM_HOUR_GROUP_2]) {
382
+ var zhAMPMString2 = match[ZH_AM_PM_HOUR_GROUP_2];
383
+ var zhAMPM2 = zhAMPMString2[0];
384
+ if (zhAMPM2 == "上" || zhAMPM2 == "朝" || zhAMPM2 == "早" || zhAMPM2 == "凌") {
385
+ meridiem = 0;
386
+ if (hour == 12) hour = 0;
387
+ } else if (zhAMPM2 == "下" || zhAMPM2 == "晏" || zhAMPM2 == "晚") {
388
+ meridiem = 1;
389
+ if (hour != 12) hour += 12;
390
+ }
391
+ } else if (match[ZH_AM_PM_HOUR_GROUP_3]) {
392
+ var zhAMPMString3 = match[ZH_AM_PM_HOUR_GROUP_3];
393
+ var zhAMPM3 = zhAMPMString3[0];
394
+ if (zhAMPM3 == "上" || zhAMPM3 == "朝" || zhAMPM3 == "早" || zhAMPM3 == "凌") {
395
+ meridiem = 0;
396
+ if (hour == 12) hour = 0;
397
+ } else if (zhAMPM3 == "下" || zhAMPM3 == "晏" || zhAMPM3 == "晚") {
398
+ meridiem = 1;
399
+ if (hour != 12) hour += 12;
400
+ }
401
+ }
402
+
403
+ result.text = result.text + match[0];
404
+ result.end.assign("hour", hour);
405
+ result.end.assign("minute", minute);
406
+ if (meridiem >= 0) {
407
+ result.end.assign("meridiem", meridiem);
408
+ } else {
409
+ const startAtPM = result.start.isCertain("meridiem") && result.start.get("meridiem") == 1;
410
+ if (startAtPM && result.start.get("hour") > hour) {
411
+ // 10pm - 1 (am)
412
+ result.end.imply("meridiem", 0);
413
+ } else if (hour > 12) {
414
+ result.end.imply("meridiem", 1);
415
+ }
416
+ }
417
+
418
+ if (result.end.date().getTime() < result.start.date().getTime()) {
419
+ result.end.imply("day", result.end.get("day") + 1);
420
+ }
421
+
422
+ return result;
423
+ }
424
+ }
@@ -0,0 +1,46 @@
1
+ import dayjs from "dayjs";
2
+ import { ParsingContext } from "../../../../chrono";
3
+ import { AbstractParserWithWordBoundaryChecking } from "../../../../common/parsers/AbstractParserWithWordBoundary";
4
+ import { ParsingResult } from "../../../../results";
5
+ import { WEEKDAY_OFFSET } from "../constants";
6
+
7
+ const PATTERN = new RegExp("(?:星期|禮拜|週)(?<weekday>" + Object.keys(WEEKDAY_OFFSET).join("|") + ")");
8
+
9
+ export default class ZHHantWeekdayParser extends AbstractParserWithWordBoundaryChecking {
10
+ innerPattern(): RegExp {
11
+ return PATTERN;
12
+ }
13
+
14
+ innerExtract(context: ParsingContext, match: RegExpMatchArray): ParsingResult {
15
+ const result = context.createParsingResult(match.index, match[0]);
16
+
17
+ const dayOfWeek = match.groups.weekday;
18
+ const offset = WEEKDAY_OFFSET[dayOfWeek];
19
+ if (offset === undefined) return null;
20
+
21
+ let startMoment = dayjs(context.refDate);
22
+ const startMomentFixed = false;
23
+ const refOffset = startMoment.day();
24
+
25
+ if (Math.abs(offset - 7 - refOffset) < Math.abs(offset - refOffset)) {
26
+ startMoment = startMoment.day(offset - 7);
27
+ } else if (Math.abs(offset + 7 - refOffset) < Math.abs(offset - refOffset)) {
28
+ startMoment = startMoment.day(offset + 7);
29
+ } else {
30
+ startMoment = startMoment.day(offset);
31
+ }
32
+
33
+ result.start.assign("weekday", offset);
34
+ if (startMomentFixed) {
35
+ result.start.assign("day", startMoment.date());
36
+ result.start.assign("month", startMoment.month() + 1);
37
+ result.start.assign("year", startMoment.year());
38
+ } else {
39
+ result.start.imply("day", startMoment.date());
40
+ result.start.imply("month", startMoment.month() + 1);
41
+ result.start.imply("year", startMoment.year());
42
+ }
43
+
44
+ return result;
45
+ }
46
+ }
@@ -0,0 +1,7 @@
1
+ import AbstractMergeDateRangeRefiner from "../../../../common/refiners/AbstractMergeDateRangeRefiner";
2
+
3
+ export default class ZHHantMergeDateRangeRefiner extends AbstractMergeDateRangeRefiner {
4
+ patternBetween(): RegExp {
5
+ return /^\s*(至|到|\-|\~|~|-|ー)\s*$/i;
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import AbstractMergeDateTimeRefiner from "../../../../common/refiners/AbstractMergeDateTimeRefiner";
2
+
3
+ export default class ZHHantMergeDateTimeRefiner extends AbstractMergeDateTimeRefiner {
4
+ patternBetween(): RegExp {
5
+ return /^\s*$/i;
6
+ }
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./hant";
2
+ export { hans } from "./hans";
package/src/results.ts CHANGED
@@ -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,9 +160,16 @@ 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
  }
package/src/timezone.ts CHANGED
@@ -114,7 +114,7 @@ export const TIMEZONE_ABBR_MAP = {
114
114
  MHT: 720,
115
115
  MMT: 390,
116
116
  MSD: 240,
117
- MSK: 240,
117
+ MSK: 180,
118
118
  MST: -420,
119
119
  MUT: 240,
120
120
  MVT: 300,
@@ -1,5 +1,6 @@
1
1
  import { ParsingComponents } from "../results";
2
2
  import dayjs from "dayjs";
3
+ import { Meridiem } from "../index";
3
4
 
4
5
  export function assignTheNextDay(component: ParsingComponents, targetDayJs: dayjs.Dayjs) {
5
6
  targetDayJs = targetDayJs.add(1, "day");
@@ -18,6 +19,11 @@ export function assignSimilarTime(component: ParsingComponents, targetDayJs: day
18
19
  component.assign("minute", targetDayJs.minute());
19
20
  component.assign("second", targetDayJs.second());
20
21
  component.assign("millisecond", targetDayJs.millisecond());
22
+ if (component.get("hour") < 12) {
23
+ component.assign("meridiem", Meridiem.AM);
24
+ } else {
25
+ component.assign("meridiem", Meridiem.PM);
26
+ }
21
27
  }
22
28
 
23
29
  export function implySimilarTime(component: ParsingComponents, targetDayJs: dayjs.Dayjs) {
@@ -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");
@@ -0,0 +1,51 @@
1
+ import * as chrono from "../../src";
2
+ import { testSingleCase } from "../test_util";
3
+
4
+ test("Test - Single Expression", function () {
5
+ testSingleCase(chrono.casual, "2 weeks after yesterday", new Date(2022, 2 - 1, 2), (result) => {
6
+ expect(result.text).toBe("2 weeks after yesterday");
7
+
8
+ expect(result.start).not.toBeNull();
9
+ expect(result.start.get("year")).toBe(2022);
10
+ expect(result.start.get("month")).toBe(2);
11
+ expect(result.start.get("day")).toBe(15);
12
+ expect(result.start.get("weekday")).toBe(2);
13
+
14
+ expect(result.start.isCertain("day")).toBe(false);
15
+ expect(result.start.isCertain("month")).toBe(false);
16
+ expect(result.start.isCertain("year")).toBe(false);
17
+ expect(result.start.isCertain("weekday")).toBe(false);
18
+
19
+ expect(result.start).toBeDate(new Date(2022, 2 - 1, 15, 0));
20
+ });
21
+
22
+ testSingleCase(chrono.casual, "2 months before 02/02", new Date(2022, 2 - 1, 2), (result) => {
23
+ expect(result.text).toBe("2 months before 02/02");
24
+
25
+ expect(result.start).not.toBeNull();
26
+ expect(result.start.get("year")).toBe(2021);
27
+ expect(result.start.get("month")).toBe(12);
28
+ expect(result.start.get("day")).toBe(2);
29
+
30
+ expect(result.start.isCertain("day")).toBe(false);
31
+ expect(result.start.isCertain("month")).toBe(true);
32
+ expect(result.start.isCertain("year")).toBe(true);
33
+
34
+ expect(result.start).toBeDate(new Date(2021, 12 - 1, 2, 12));
35
+ });
36
+
37
+ testSingleCase(chrono.casual, "2 days after next Friday", new Date(2022, 2 - 1, 2), (result) => {
38
+ expect(result.text).toBe("2 days after next Friday");
39
+
40
+ expect(result.start).not.toBeNull();
41
+ expect(result.start.get("year")).toBe(2022);
42
+ expect(result.start.get("month")).toBe(2);
43
+ expect(result.start.get("day")).toBe(13);
44
+
45
+ expect(result.start.isCertain("day")).toBe(true);
46
+ expect(result.start.isCertain("month")).toBe(true);
47
+ expect(result.start.isCertain("year")).toBe(true);
48
+
49
+ expect(result.start).toBeDate(new Date(2022, 2 - 1, 13, 12));
50
+ });
51
+ });
@@ -178,3 +178,16 @@ test("Test - year 90's parsing", () => {
178
178
  expect(result.start.get("month")).toBe(8);
179
179
  });
180
180
  });
181
+
182
+ test("Test - Month should not have timezone", () => {
183
+ testSingleCase(
184
+ chrono,
185
+ "People visiting Buñol towards the end of August get a good chance to participate in La Tomatina (under normal circumstances)",
186
+ new Date(2012, 7, 10),
187
+ (result) => {
188
+ expect(result.text).toBe("August");
189
+ expect(result.start.get("year")).toBe(2012);
190
+ expect(result.start.get("month")).toBe(8);
191
+ }
192
+ );
193
+ });
@@ -135,6 +135,26 @@ test("Test - Future relative expressions", () => {
135
135
  expect(result.start.isCertain("millisecond")).toBe(false);
136
136
  expect(result.start.isCertain("timezoneOffset")).toBe(false);
137
137
  });
138
+
139
+ testSingleCase(chrono, "after this year", new Date(2020, 11 - 1, 22, 12, 11, 32, 6), (result, text) => {
140
+ expect(result.text).toBe(text);
141
+ expect(result.start.get("year")).toBe(2021);
142
+ expect(result.start.get("month")).toBe(11);
143
+ expect(result.start.get("day")).toBe(22);
144
+ expect(result.start.get("hour")).toBe(12);
145
+ expect(result.start.get("minute")).toBe(11);
146
+ expect(result.start.get("second")).toBe(32);
147
+ expect(result.start.get("millisecond")).toBe(6);
148
+
149
+ expect(result.start.isCertain("year")).toBe(true);
150
+ expect(result.start.isCertain("month")).toBe(false);
151
+ expect(result.start.isCertain("day")).toBe(false);
152
+ expect(result.start.isCertain("hour")).toBe(false);
153
+ expect(result.start.isCertain("minute")).toBe(false);
154
+ expect(result.start.isCertain("second")).toBe(false);
155
+ expect(result.start.isCertain("millisecond")).toBe(false);
156
+ expect(result.start.isCertain("timezoneOffset")).toBe(false);
157
+ });
138
158
  });
139
159
 
140
160
  test("Test - Relative date components' certainty", () => {