forked from DIYgod/RSSHub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse-date.js
199 lines (176 loc) · 6.7 KB
/
parse-date.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
const dayjs = require('dayjs');
dayjs.extend(require('dayjs/plugin/customParseFormat'));
dayjs.extend(require('dayjs/plugin/duration'));
dayjs.extend(require('dayjs/plugin/isSameOrBefore'));
dayjs.extend(require('dayjs/plugin/weekday'));
const words = [
{
startAt: dayjs(),
regExp: /^(?:今(?:天|日)|t(?:o)?da(?:y)?)(.*)/,
},
{
startAt: dayjs().subtract(1, 'days'),
regExp: /^(?:昨(?:天|日)|y(?:ester)?da(?:y)?)(.*)/,
},
{
startAt: dayjs().subtract(2, 'days'),
regExp: /^(?:前天|(?:the)?d(?:ay)?b(?:eforeyesterda)?y)(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(1)) ? dayjs().weekday(1).subtract(1, 'week') : dayjs().weekday(1),
regExp: /^(?:周|星期)一(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(2)) ? dayjs().weekday(2).subtract(1, 'week') : dayjs().weekday(2),
regExp: /^(?:周|星期)二(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(3)) ? dayjs().weekday(3).subtract(1, 'week') : dayjs().weekday(3),
regExp: /^(?:周|星期)三(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(4)) ? dayjs().weekday(4).subtract(1, 'week') : dayjs().weekday(4),
regExp: /^(?:周|星期)四(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(5)) ? dayjs().weekday(5).subtract(1, 'week') : dayjs().weekday(5),
regExp: /^(?:周|星期)五(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(6)) ? dayjs().weekday(6).subtract(1, 'week') : dayjs().weekday(6),
regExp: /^(?:周|星期)六(.*)/,
},
{
startAt: dayjs().isSameOrBefore(dayjs().weekday(7)) ? dayjs().weekday(7).subtract(1, 'week') : dayjs().weekday(7),
regExp: /^(?:周|星期)(?:日|天)(.*)/,
},
{
startAt: dayjs().add(1, 'days'),
regExp: /^(?:明(?:天|日)|y(?:ester)?da(?:y)?)(.*)/,
},
{
startAt: dayjs().add(2, 'days'),
regExp: /^(?:(?:后|後)(?:天|日)|(?:the)?d(?:ay)?a(?:fter)?t(?:omrrow)?)(.*)/,
},
];
const patterns = [
{
unit: 'years',
regExp: /(\d+)(?:年|y(?:ea)?r(?:s)?)/,
},
{
unit: 'months',
regExp: /(\d+)(?:(?:个|個)?月|month(?:s)?)/,
},
{
unit: 'weeks',
regExp: /(\d+)(?:周|(?:个|個)?星期|week(?:s)?)/,
},
{
unit: 'days',
regExp: /(\d+)(?:天|日|d(?:ay)?(?:s)?)/,
},
{
unit: 'hours',
regExp: /(\d+)(?:(?:个|個)?(?:(?:小)?时|時|点|點)|h(?:(?:ou)?r)?(?:s)?)/,
},
{
unit: 'minutes',
regExp: /(\d+)(?:分(?:钟|鐘)?|m(?:in(?:ute)?)?(?:s)?)/,
},
{
unit: 'seconds',
regExp: /(\d+)(?:秒(?:钟|鐘)?|s(?:ec(?:ond)?)?(?:s)?)/,
},
];
const patternSize = Object.keys(patterns).length;
/**
* 预处理日期字符串
* @param {String} date 原始日期字符串
*/
const toDate = (date) =>
date
.toLowerCase()
.replace(/(^a(?:n)?\s)|(\sa(?:n)?\s)/g, '1') // 替换 `a` 和 `an` 为 `1`
.replace(/几|幾/g, '3') // 如 `几秒钟前` 视作 `3秒钟前`
.replace(/[\s,]/g, ''); // 移除所有空格
/**
* 将 `['\d+时', ..., '\d+秒']` 转换为 `{ hours: \d+, ..., seconds: \d+ }`
* 用于描述时间长度
* @param {Array.<String>} matches 所有匹配结果
*/
const toDurations = (matches) => {
const durations = {};
let p = 0;
for (const m of matches) {
for (; p <= patternSize; p++) {
const match = patterns[p].regExp.exec(m);
if (match) {
durations[patterns[p].unit] = match[1];
break;
}
}
}
return durations;
};
module.exports = {
parseDate: (date, ...options) => dayjs(date, ...options).toDate(),
parseRelativeDate: (date) => {
// 预处理日期字符串 date
const theDate = toDate(date);
// 将 `\d+年\d+月...\d+秒前` 分割成 `['\d+年', ..., '\d+秒前']`
const matches = theDate.match(/(?:\D+)?\d+(?!:|-|\/|(a|p)m)\D+/g);
if (matches) {
// 获得最后的时间单元,如 `\d+秒前`
const lastMatch = matches.pop();
// 若最后的时间单元含有 `前`、`以前`、`之前` 等标识字段,减去相应的时间长度
// 如 `1分10秒前`
const beforeMatches = /(.*)(?:(?:之|以)?前|ago)$/.exec(lastMatch);
if (beforeMatches) {
matches.push(beforeMatches[1]);
return dayjs()
.subtract(dayjs.duration(toDurations(matches)))
.toDate();
}
// 若最后的时间单元含有 `后`、`以后`、`之后` 等标识字段,加上相应的时间长度
// 如 `1分10秒后`
const afterMatches = /(?:^in(.*)|(.*)(?:之|以)?(?:后|後))$/.exec(lastMatch);
if (afterMatches) {
matches.push(afterMatches[1] ?? afterMatches[2]);
return dayjs()
.add(dayjs.duration(toDurations(matches)))
.toDate();
}
// 以下处理日期字符串 date 含有特殊词的情形
// 如 `今天1点10分`
matches.push(lastMatch);
const firstMatch = matches.shift();
for (const w of words) {
const wordMatches = w.regExp.exec(firstMatch);
if (wordMatches) {
matches.unshift(wordMatches[1]);
// 取特殊词对应日零时为起点,加上相应的时间长度
return w.startAt
.set('hour', 0)
.set('minute', 0)
.set('second', 0)
.set('millisecond', 0)
.add(dayjs.duration(toDurations(matches)))
.toDate();
}
}
} else {
// 若日期字符串 date 不匹配 patterns 中所有模式,则默认为 `特殊词 + 标准时间格式` 的情形,此时直接将特殊词替换为对应日期
// 如今天为 `2022-03-22`,则 `今天 20:00` => `2022-03-22 20:00`
for (const w of words) {
const wordMatches = w.regExp.exec(theDate);
if (wordMatches) {
// The default parser of dayjs() can parse '8:00 pm' but not '8:00pm'
// so we need to insert a space in between
return dayjs(`${w.startAt.format('YYYY-MM-DD')} ${/a|pm$/.test(wordMatches[1]) ? wordMatches[1].replace(/a|pm/, ' $&') : wordMatches[1]}`).toDate();
}
}
}
return date;
},
};