chrono-node 2.3.2 → 2.3.6
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.
- package/README.md +1 -1
- package/dist/common/casualReferences.js +3 -1
- package/dist/common/parsers/AbstractParserWithWordBoundary.js +2 -1
- package/dist/common/refiners/ExtractTimezoneAbbrRefiner.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/locales/de/constants.js +1 -1
- package/dist/locales/en/constants.js +1 -1
- package/dist/locales/en/parsers/ENRelativeDateFormatParser.js +3 -3
- package/dist/locales/en/parsers/ENTimeUnitAgoFormatParser.js +1 -1
- package/dist/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js +1 -1
- package/dist/locales/en/parsers/ENTimeUnitLaterFormatParser.js +1 -1
- package/dist/locales/en/parsers/ENTimeUnitWithinFormatParser.js +1 -1
- package/dist/locales/fr/constants.js +1 -1
- package/dist/locales/fr/parsers/FRTimeUnitAgoFormatParser.js +1 -1
- package/dist/locales/fr/parsers/FRTimeUnitRelativeFormatParser.js +1 -1
- package/dist/locales/fr/parsers/FRTimeUnitWithinFormatParser.js +1 -1
- package/dist/locales/nl/parsers/NLRelativeDateFormatParser.js +3 -3
- package/dist/locales/nl/parsers/NLTimeUnitAgoFormatParser.js +1 -1
- package/dist/locales/nl/parsers/NLTimeUnitCasualRelativeFormatParser.js +1 -1
- package/dist/locales/nl/parsers/NLTimeUnitLaterFormatParser.js +1 -1
- package/dist/locales/nl/parsers/NLTimeUnitWithinFormatParser.js +1 -1
- package/dist/locales/zh/hans/constants.d.ts +27 -0
- package/dist/locales/zh/hans/constants.js +51 -0
- package/dist/locales/zh/hans/index.d.ts +9 -0
- package/dist/locales/zh/hans/index.js +49 -0
- package/dist/locales/zh/hans/parsers/ZHHansCasualDateParser.d.ts +7 -0
- package/dist/locales/zh/hans/parsers/ZHHansCasualDateParser.js +138 -0
- package/dist/locales/zh/hans/parsers/ZHHansDateParser.d.ts +6 -0
- package/dist/locales/zh/hans/parsers/ZHHansDateParser.js +72 -0
- package/dist/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.d.ts +6 -0
- package/dist/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.js +78 -0
- package/dist/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.d.ts +7 -0
- package/dist/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.js +70 -0
- package/dist/locales/zh/hans/parsers/ZHHansTimeExpressionParser.d.ts +6 -0
- package/dist/locales/zh/hans/parsers/ZHHansTimeExpressionParser.js +438 -0
- package/dist/locales/zh/hans/parsers/ZHHansWeekdayParser.d.ts +7 -0
- package/dist/locales/zh/hans/parsers/ZHHansWeekdayParser.js +46 -0
- package/dist/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.d.ts +4 -0
- package/dist/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.js +12 -0
- package/dist/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.d.ts +4 -0
- package/dist/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.js +12 -0
- package/dist/locales/zh/hant/constants.d.ts +28 -0
- package/dist/locales/zh/hant/constants.js +52 -0
- package/dist/locales/zh/hant/index.d.ts +9 -0
- package/dist/locales/zh/hant/index.js +49 -0
- package/dist/locales/zh/hant/parsers/ZHHantCasualDateParser.d.ts +7 -0
- package/dist/locales/zh/hant/parsers/ZHHantCasualDateParser.js +138 -0
- package/dist/locales/zh/hant/parsers/ZHHantDateParser.d.ts +6 -0
- package/dist/locales/zh/hant/parsers/ZHHantDateParser.js +64 -0
- package/dist/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.d.ts +6 -0
- package/dist/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.js +78 -0
- package/dist/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.d.ts +7 -0
- package/dist/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.js +70 -0
- package/dist/locales/zh/hant/parsers/ZHHantTimeExpressionParser.d.ts +6 -0
- package/dist/locales/zh/hant/parsers/ZHHantTimeExpressionParser.js +438 -0
- package/dist/locales/zh/hant/parsers/ZHHantWeekdayParser.d.ts +7 -0
- package/dist/locales/zh/hant/parsers/ZHHantWeekdayParser.js +46 -0
- package/dist/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.d.ts +4 -0
- package/dist/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.js +12 -0
- package/dist/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.d.ts +4 -0
- package/dist/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.js +12 -0
- package/dist/locales/zh/index.d.ts +2 -0
- package/dist/locales/zh/index.js +16 -0
- package/dist/results.d.ts +2 -2
- package/dist/results.js +18 -12
- package/dist/timezone.d.ts +1 -1
- package/dist/timezone.js +3 -0
- package/dist/utils/dayjs.js +7 -0
- package/package.json +1 -1
- package/src/common/casualReferences.ts +3 -1
- package/src/common/parsers/AbstractParserWithWordBoundary.ts +1 -1
- package/src/common/refiners/ExtractTimezoneAbbrRefiner.ts +8 -0
- package/src/index.ts +2 -1
- package/src/locales/de/constants.ts +1 -1
- package/src/locales/en/constants.ts +1 -1
- package/src/locales/en/parsers/ENMonthNameLittleEndianParser.ts +0 -2
- package/src/locales/en/parsers/ENRelativeDateFormatParser.ts +3 -3
- package/src/locales/en/parsers/ENTimeUnitAgoFormatParser.ts +1 -1
- package/src/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.ts +1 -1
- package/src/locales/en/parsers/ENTimeUnitLaterFormatParser.ts +1 -1
- package/src/locales/en/parsers/ENTimeUnitWithinFormatParser.ts +1 -1
- package/src/locales/fr/constants.ts +1 -1
- package/src/locales/fr/parsers/FRTimeUnitAgoFormatParser.ts +1 -1
- package/src/locales/fr/parsers/FRTimeUnitRelativeFormatParser.ts +1 -1
- package/src/locales/fr/parsers/FRTimeUnitWithinFormatParser.ts +1 -1
- package/src/locales/nl/parsers/NLRelativeDateFormatParser.ts +3 -3
- package/src/locales/nl/parsers/NLTimeUnitAgoFormatParser.ts +1 -1
- package/src/locales/nl/parsers/NLTimeUnitCasualRelativeFormatParser.ts +1 -1
- package/src/locales/nl/parsers/NLTimeUnitLaterFormatParser.ts +1 -1
- package/src/locales/nl/parsers/NLTimeUnitWithinFormatParser.ts +1 -1
- package/src/locales/zh/hans/constants.ts +52 -0
- package/src/locales/zh/hans/index.ts +62 -0
- package/src/locales/zh/hans/parsers/ZHHansCasualDateParser.ts +128 -0
- package/src/locales/zh/hans/parsers/ZHHansDateParser.ts +75 -0
- package/src/locales/zh/hans/parsers/ZHHansDeadlineFormatParser.ts +81 -0
- package/src/locales/zh/hans/parsers/ZHHansRelationWeekdayParser.ts +69 -0
- package/src/locales/zh/hans/parsers/ZHHansTimeExpressionParser.ts +424 -0
- package/src/locales/zh/hans/parsers/ZHHansWeekdayParser.ts +46 -0
- package/src/locales/zh/hans/refiners/ZHHansMergeDateRangeRefiner.ts +7 -0
- package/src/locales/zh/hans/refiners/ZHHansMergeDateTimeRefiner.ts +7 -0
- package/src/locales/zh/hant/constants.ts +53 -0
- package/src/locales/zh/hant/index.ts +63 -0
- package/src/locales/zh/hant/parsers/ZHHantCasualDateParser.ts +128 -0
- package/src/locales/zh/hant/parsers/ZHHantDateParser.ts +68 -0
- package/src/locales/zh/hant/parsers/ZHHantDeadlineFormatParser.ts +81 -0
- package/src/locales/zh/hant/parsers/ZHHantRelationWeekdayParser.ts +69 -0
- package/src/locales/zh/hant/parsers/ZHHantTimeExpressionParser.ts +424 -0
- package/src/locales/zh/hant/parsers/ZHHantWeekdayParser.ts +46 -0
- package/src/locales/zh/hant/refiners/ZHHantMergeDateRangeRefiner.ts +7 -0
- package/src/locales/zh/hant/refiners/ZHHantMergeDateTimeRefiner.ts +7 -0
- package/src/locales/zh/index.ts +2 -0
- package/src/results.ts +22 -12
- package/src/timezone.ts +5 -1
- package/src/utils/dayjs.ts +6 -0
- package/test/en/en_casual.test.ts +11 -0
- package/test/en/en_month.test.ts +13 -0
- package/test/en/en_relative.test.ts +8 -0
- package/test/en/en_time_units_ago.test.ts +16 -0
- package/test/en/en_time_units_later.test.ts +6 -0
- package/test/en/en_timezone_exp.test.ts +85 -10
- package/test/en/negative_cases.test.ts +2 -0
- package/test/test_util.ts +5 -2
- package/test/zh/zh.test.ts +18 -0
- package/test/zh/zh_hans_casual.test.ts +228 -0
- package/test/zh/zh_hans_date.test.ts +102 -0
- package/test/zh/zh_hans_deadline.test.ts +136 -0
- package/test/zh/zh_hans_time_exp.test.ts +182 -0
- package/test/zh/zh_hans_weekday.test.ts +132 -0
- package/test/zh/zh_hant_casual.test.ts +246 -0
- package/test/zh/zh_hant_date.test.ts +102 -0
- package/test/zh/zh_hant_deadline.test.ts +136 -0
- package/test/zh/zh_hant_time_exp.test.ts +182 -0
- 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
|
+
}
|
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
|
|
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
|
|
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
|
-
|
|
165
|
-
|
|
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
|
|
171
|
-
|
|
177
|
+
static createRelativeFromReference(
|
|
178
|
+
reference: ReferenceWithTimezone,
|
|
172
179
|
fragments: { [c in OpUnitType | QUnitType]?: number }
|
|
173
180
|
): ParsingComponents {
|
|
174
|
-
let date = dayjs(
|
|
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
|
-
|
|
190
|
+
if (reference.timezoneOffset !== null) {
|
|
191
|
+
components.assign("timezoneOffset", -reference.instant.getTimezoneOffset());
|
|
192
|
+
}
|
|
185
193
|
} else {
|
|
186
194
|
implySimilarTime(components, date);
|
|
187
|
-
|
|
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
|
}
|
package/src/utils/dayjs.ts
CHANGED
|
@@ -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");
|
package/test/en/en_month.test.ts
CHANGED
|
@@ -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
|
+
});
|
|
@@ -230,4 +230,12 @@ test("Test - Relative date components' certainty and imply timezone", () => {
|
|
|
230
230
|
expect(result).toBeDate(new Date("Sun Nov 29 2020 13:34:13 GMT+0900 (Japan Standard Time)"));
|
|
231
231
|
expect(result).toBeDate(new Date("Sun Nov 29 2020 5:34:13 GMT+0100"));
|
|
232
232
|
}
|
|
233
|
+
|
|
234
|
+
{
|
|
235
|
+
const text = "in 20 minutes";
|
|
236
|
+
const result = chrono.parse(text, { instant: refDate, timezone: null })[0] as ParsingResult;
|
|
237
|
+
|
|
238
|
+
expect(result.text).toBe(text);
|
|
239
|
+
expect(result.start.isCertain("timezoneOffset")).toBe(false);
|
|
240
|
+
}
|
|
233
241
|
});
|