befly 3.62.0 → 3.64.0
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/apis/auth/login.js +65 -13
- package/apis/tongJi/_tongJi.js +92 -41
- package/apis/tongJi/dailyReport.js +73 -51
- package/apis/tongJi/dailyReportSelect.js +36 -0
- package/apis/tongJi/dailyStats.js +114 -126
- package/apis/tongJi/dailyStatsDistribution.js +86 -0
- package/apis/tongJi/errorList.js +5 -9
- package/apis/tongJi/errorReport.js +81 -44
- package/apis/tongJi/errorStats.js +19 -125
- package/apis/tongJi/projectDelete.js +20 -0
- package/apis/tongJi/projectInsert.js +35 -0
- package/apis/tongJi/projectSelect.js +31 -0
- package/apis/tongJi/projectUpdate.js +52 -0
- package/configs/beflyMenus.json +20 -3
- package/index.js +5 -6
- package/lib/logger.js +20 -15
- package/package.json +1 -1
- package/plugins/cron.js +96 -0
- package/sql/befly.sql +67 -25
- package/tables/dailyReport.json +124 -0
- package/tables/errorReport.json +31 -48
- package/tables/loginLog.json +29 -32
- package/tables/project.json +42 -0
- package/utils/is.js +3 -36
- package/utils/userAgent.js +229 -104
- package/apis/tongJi/_daily.js +0 -159
package/utils/userAgent.js
CHANGED
|
@@ -5,168 +5,293 @@ const WINDOWS_VERSION_MAP = {
|
|
|
5
5
|
6.1: "7"
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
const matched = regexp.exec(
|
|
10
|
-
|
|
8
|
+
function firstMatch(text, regexp) {
|
|
9
|
+
const matched = regexp.exec(text);
|
|
10
|
+
|
|
11
|
+
if (!matched) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (let index = 1; index < matched.length; index++) {
|
|
16
|
+
if (matched[index]) {
|
|
17
|
+
return matched[index];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
11
22
|
}
|
|
12
23
|
|
|
13
|
-
function
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
function firstNonEmptyMatch(text, regexp) {
|
|
25
|
+
const matched = regexp.exec(text);
|
|
26
|
+
|
|
27
|
+
if (!matched) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (let index = 1; index < matched.length; index++) {
|
|
32
|
+
if (matched[index]) {
|
|
33
|
+
return matched[index];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasMatch(text, regexp) {
|
|
41
|
+
return regexp.test(text) ? "" : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeVersion(version) {
|
|
45
|
+
return String(version || "").replace(/_/g, ".");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function majorVersion(version) {
|
|
49
|
+
const parts = String(version || "").split(".");
|
|
50
|
+
return parts[0] || "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function matchBrowser(text) {
|
|
54
|
+
const rules = [
|
|
55
|
+
{ name: "wxwork", regexp: /wxwork\/([\d.]+)/ },
|
|
56
|
+
{ name: "WeChat", regexp: /MicroMessenger\/([\d.]+)/ },
|
|
57
|
+
{ name: "DingTalk", regexp: /DingTalk\/([\d.]+)/ },
|
|
58
|
+
{ name: "Alipay", regexp: /AlipayClient\/([\d.]+)/ },
|
|
59
|
+
{ name: "Taobao", regexp: /(?:TaoBao|AliApp\(TB|WindVane)/, match: hasMatch },
|
|
60
|
+
{ name: "Jingdong", regexp: /jdapp/i, match: hasMatch },
|
|
61
|
+
{ name: "Weibo", regexp: /Weibo|__weibo__/, match: hasMatch },
|
|
62
|
+
{ name: "Douyin", regexp: /(?:aweme|Trill|Douyin)/i, match: hasMatch },
|
|
63
|
+
{ name: "Lark", regexp: /(?:Lark|Feishu)\S*?\/([\d.]+)/ },
|
|
64
|
+
{ name: "QQ Browser", regexp: /(?:MQQBrowser|QQBrowser)\/([\d.]+)/ },
|
|
65
|
+
{ name: "UC Browser", regexp: /UCBrowser\/([\d.]+)/ },
|
|
66
|
+
{ name: "Quark", regexp: /Quark\/([\d.]+)/ },
|
|
67
|
+
{ name: "Baidu", regexp: /(?:baiduboxapp|baiduapp|BaiduBrowser|FlyFlow)\S*?\/([\d.]+)/i },
|
|
68
|
+
{ name: "Sogou", regexp: /(?:SogouMobileBrowser|SogouMSE|MetaSr)\S*?\/([\d.]+)/i },
|
|
69
|
+
{ name: "360", regexp: /(?:360SE|360EE|QHBrowser|QihooBrowser)\S*?(?:\s|v|\/)([\d.]+)/i },
|
|
70
|
+
{ name: "Huawei Browser", regexp: /HuaweiBrowser\/([\d.]+)/ },
|
|
71
|
+
{ name: "MiuiBrowser", regexp: /MiuiBrowser\/([\d.]+)/ },
|
|
72
|
+
{ name: "HeyTapBrowser", regexp: /HeyTapBrowser\/([\d.]+)/ },
|
|
73
|
+
{ name: "VivoBrowser", regexp: /VivoBrowser\/([\d.]+)/ },
|
|
74
|
+
{ name: "Samsung Browser", regexp: /SamsungBrowser\/([\d.]+)/ },
|
|
75
|
+
{ name: "Maxthon", regexp: /Maxthon\/([\d.]+)/ },
|
|
76
|
+
{ name: "Edge", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
|
|
77
|
+
{ name: "Opera", regexp: /(?:OPR|Opera)\/([\d.]+)/ },
|
|
78
|
+
{ name: "Firefox", regexp: /(?:FxiOS|Firefox)\/([\d.]+)/ },
|
|
79
|
+
{ name: "Chrome", regexp: /(?:CriOS|Chrome)\/([\d.]+)/ },
|
|
80
|
+
{ name: "Safari", regexp: /Version\/([\d.]+).*Safari/ },
|
|
81
|
+
{ name: "IE", regexp: /(?:MSIE\s([\d.]+)|Trident\/.*rv:([\d.]+))/, match: firstNonEmptyMatch }
|
|
22
82
|
];
|
|
23
83
|
|
|
24
|
-
for (const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
84
|
+
for (const rule of rules) {
|
|
85
|
+
const matcher = rule.match || firstMatch;
|
|
86
|
+
const version = matcher(text, rule.regexp);
|
|
87
|
+
|
|
88
|
+
if (version !== null) {
|
|
89
|
+
const name = rule.name === "Safari" && /iPhone|iPad|iPod/i.test(text) ? "Mobile Safari" : rule.name;
|
|
90
|
+
|
|
91
|
+
return { name: name, version: version };
|
|
31
92
|
}
|
|
32
93
|
}
|
|
33
94
|
|
|
34
|
-
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
return { name: "", version: "" };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function matchEngine(text) {
|
|
99
|
+
if (/AppleWebKit/i.test(text) && /iPhone|iPad|iPod/i.test(text)) {
|
|
100
|
+
return { name: "WebKit", version: firstMatch(text, /AppleWebKit\/([\d.]+)/) };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rules = [
|
|
104
|
+
{ name: "Blink", regexp: /Chrome\/([\d.]+)/ },
|
|
105
|
+
{ name: "Blink", regexp: /CriOS\/([\d.]+)/ },
|
|
106
|
+
{ name: "Blink", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
|
|
107
|
+
{ name: "Blink", regexp: /OPR\/([\d.]+)/ },
|
|
108
|
+
{ name: "Gecko", regexp: /Firefox\/([\d.]+)/ },
|
|
109
|
+
{ name: "Gecko", regexp: /FxiOS\/([\d.]+)/ },
|
|
110
|
+
{ name: "WebKit", regexp: /AppleWebKit\/([\d.]+)/ },
|
|
111
|
+
{ name: "Trident", regexp: /Trident\/([\d.]+)/ },
|
|
112
|
+
{ name: "Trident", regexp: /MSIE\s([\d.]+)/ }
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
for (const rule of rules) {
|
|
116
|
+
const version = firstMatch(text, rule.regexp);
|
|
117
|
+
|
|
118
|
+
if (version) {
|
|
119
|
+
return { name: rule.name, version: version };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { name: "", version: "" };
|
|
38
124
|
}
|
|
39
125
|
|
|
40
|
-
function
|
|
41
|
-
const androidVersion =
|
|
126
|
+
function matchOs(text) {
|
|
127
|
+
const androidVersion = firstMatch(text, /Android\s([\d.]+)/);
|
|
128
|
+
|
|
42
129
|
if (androidVersion) {
|
|
43
|
-
return {
|
|
44
|
-
osName: "Android",
|
|
45
|
-
osVersion: androidVersion
|
|
46
|
-
};
|
|
130
|
+
return { name: "Android", version: androidVersion };
|
|
47
131
|
}
|
|
48
132
|
|
|
49
|
-
const iosVersion =
|
|
133
|
+
const iosVersion = firstMatch(text, /(?:iPhone|iPad|iPod).*OS\s([\d_]+)/);
|
|
134
|
+
|
|
50
135
|
if (iosVersion) {
|
|
51
|
-
return {
|
|
52
|
-
osName: "iOS",
|
|
53
|
-
osVersion: iosVersion
|
|
54
|
-
};
|
|
136
|
+
return { name: "iOS", version: normalizeVersion(iosVersion) };
|
|
55
137
|
}
|
|
56
138
|
|
|
57
|
-
const windowsVersion =
|
|
139
|
+
const windowsVersion = firstMatch(text, /Windows NT\s([\d.]+)/);
|
|
140
|
+
|
|
58
141
|
if (windowsVersion) {
|
|
59
|
-
return {
|
|
60
|
-
osName: "Windows",
|
|
61
|
-
osVersion: WINDOWS_VERSION_MAP[windowsVersion] || windowsVersion
|
|
62
|
-
};
|
|
142
|
+
return { name: "Windows", version: WINDOWS_VERSION_MAP[windowsVersion] || windowsVersion };
|
|
63
143
|
}
|
|
64
144
|
|
|
65
|
-
const macVersion =
|
|
145
|
+
const macVersion = firstMatch(text, /Mac OS X\s([\d_]+)/);
|
|
146
|
+
|
|
66
147
|
if (macVersion) {
|
|
67
|
-
return {
|
|
68
|
-
osName: "macOS",
|
|
69
|
-
osVersion: macVersion
|
|
70
|
-
};
|
|
148
|
+
return { name: "macOS", version: normalizeVersion(macVersion) };
|
|
71
149
|
}
|
|
72
150
|
|
|
73
|
-
if (/
|
|
74
|
-
return {
|
|
75
|
-
osName: "Linux",
|
|
76
|
-
osVersion: ""
|
|
77
|
-
};
|
|
151
|
+
if (/HarmonyOS/i.test(text)) {
|
|
152
|
+
return { name: "HarmonyOS", version: "" };
|
|
78
153
|
}
|
|
79
154
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
155
|
+
if (/Linux/i.test(text)) {
|
|
156
|
+
return { name: "Linux", version: "" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (/CrOS/i.test(text)) {
|
|
160
|
+
return { name: "Chrome OS", version: "" };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { name: "", version: "" };
|
|
84
164
|
}
|
|
85
165
|
|
|
86
|
-
function getDeviceType(
|
|
87
|
-
if (/iPad|Tablet|PlayBook|Silk/i.test(
|
|
166
|
+
function getDeviceType(text) {
|
|
167
|
+
if (/iPad|Tablet|PlayBook|Silk/i.test(text) || (/Android/i.test(text) && !/Mobile/i.test(text))) {
|
|
88
168
|
return "tablet";
|
|
89
169
|
}
|
|
90
|
-
|
|
170
|
+
|
|
171
|
+
if (/PlayStation|Xbox|Nintendo|CrKey|Roku|AppleTV|SmartTV|TV/i.test(text)) {
|
|
172
|
+
return "console";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (/bot|crawler|spider|googlebot|bingbot|baiduspider|slurp|yandex|duckduckgo/i.test(text)) {
|
|
176
|
+
return "bot";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (/Mobi|iPhone|iPod|Phone\s*\d|Android.*Mobile|Windows Phone/i.test(text)) {
|
|
180
|
+
return "mobile";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (/HarmonyOS/i.test(text) && !/Pad|Tablet/i.test(text)) {
|
|
91
184
|
return "mobile";
|
|
92
185
|
}
|
|
186
|
+
|
|
187
|
+
if (/HarmonyOS/i.test(text)) {
|
|
188
|
+
return "tablet";
|
|
189
|
+
}
|
|
190
|
+
|
|
93
191
|
return "desktop";
|
|
94
192
|
}
|
|
95
193
|
|
|
96
|
-
function
|
|
97
|
-
const matched = /Android[\d.\s]*;\s*([^;)]+?)(?:\s+Build\/|;|\))/i.exec(
|
|
194
|
+
function matchAndroidModel(text) {
|
|
195
|
+
const matched = /Android[\d.\s]*;\s*([^;)]+?)(?:\s+Build\/|;|\))/i.exec(text);
|
|
98
196
|
return matched ? matched[1].trim() : "";
|
|
99
197
|
}
|
|
100
198
|
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
deviceType: deviceType,
|
|
106
|
-
deviceVendor: "Apple",
|
|
107
|
-
deviceModel: "iPhone"
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (/iPad/i.test(userAgent)) {
|
|
111
|
-
return {
|
|
112
|
-
deviceType: deviceType,
|
|
113
|
-
deviceVendor: "Apple",
|
|
114
|
-
deviceModel: "iPad"
|
|
115
|
-
};
|
|
199
|
+
function matchVendor(text) {
|
|
200
|
+
if (/(iPhone|iPad|iPod|Macintosh)\b/i.test(text)) {
|
|
201
|
+
return "Apple";
|
|
116
202
|
}
|
|
117
203
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
deviceModel: getAndroidDeviceModel(userAgent)
|
|
122
|
-
};
|
|
123
|
-
}
|
|
204
|
+
if (/Samsung|SM-\w+/i.test(text)) {
|
|
205
|
+
return "Samsung";
|
|
206
|
+
}
|
|
124
207
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return "Blink";
|
|
208
|
+
if (/Huawei|HUAWEI|Honor|HONOR/i.test(text)) {
|
|
209
|
+
return "Huawei";
|
|
128
210
|
}
|
|
129
|
-
|
|
130
|
-
|
|
211
|
+
|
|
212
|
+
if (/Xiaomi|Redmi|Mi\s|MI\s|M200|M201|M202|M210|M211|M220|M221|M230|M231|M240/i.test(text)) {
|
|
213
|
+
return "Xiaomi";
|
|
131
214
|
}
|
|
132
|
-
|
|
133
|
-
|
|
215
|
+
|
|
216
|
+
if (/OPPO|PB[A-Z]|PCHM\d+|PCAM\d+/i.test(text)) {
|
|
217
|
+
return "OPPO";
|
|
134
218
|
}
|
|
135
|
-
|
|
136
|
-
|
|
219
|
+
|
|
220
|
+
if (/vivo|V\d{4}[A-Z]/i.test(text)) {
|
|
221
|
+
return "vivo";
|
|
137
222
|
}
|
|
223
|
+
|
|
138
224
|
return "";
|
|
139
225
|
}
|
|
140
226
|
|
|
141
|
-
function
|
|
142
|
-
|
|
227
|
+
function matchDevice(text) {
|
|
228
|
+
const type = getDeviceType(text);
|
|
229
|
+
|
|
230
|
+
if (/iPad/i.test(text)) {
|
|
231
|
+
return { vendor: "Apple", model: "iPad", type: type };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (/iPod/i.test(text)) {
|
|
235
|
+
return { vendor: "Apple", model: "iPod", type: type };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (/iPhone/i.test(text)) {
|
|
239
|
+
return { vendor: "Apple", model: "iPhone", type: type };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { vendor: matchVendor(text), model: matchAndroidModel(text), type: type };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function matchCpuArchitecture(text) {
|
|
246
|
+
if (/x86_64|Win64|x64|amd64/i.test(text)) {
|
|
143
247
|
return "amd64";
|
|
144
248
|
}
|
|
145
|
-
|
|
249
|
+
|
|
250
|
+
if (/arm64|aarch64/i.test(text)) {
|
|
146
251
|
return "arm64";
|
|
147
252
|
}
|
|
148
|
-
|
|
253
|
+
|
|
254
|
+
if (/arm/i.test(text)) {
|
|
149
255
|
return "arm";
|
|
150
256
|
}
|
|
257
|
+
|
|
151
258
|
return "";
|
|
152
259
|
}
|
|
153
260
|
|
|
154
261
|
export function parseUserAgent(userAgent) {
|
|
155
262
|
const text = String(userAgent || "");
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
263
|
+
const browser = matchBrowser(text);
|
|
264
|
+
const engine = matchEngine(text);
|
|
265
|
+
const os = matchOs(text);
|
|
266
|
+
const device = matchDevice(text);
|
|
267
|
+
const cpu = { architecture: matchCpuArchitecture(text) };
|
|
159
268
|
|
|
160
269
|
return {
|
|
161
270
|
userAgent: text,
|
|
162
|
-
browserName:
|
|
163
|
-
browserVersion:
|
|
164
|
-
osName:
|
|
165
|
-
osVersion:
|
|
166
|
-
deviceType:
|
|
167
|
-
deviceVendor:
|
|
168
|
-
deviceModel:
|
|
169
|
-
engineName:
|
|
170
|
-
|
|
271
|
+
browserName: browser.name,
|
|
272
|
+
browserVersion: browser.version,
|
|
273
|
+
osName: os.name,
|
|
274
|
+
osVersion: os.version,
|
|
275
|
+
deviceType: device.type,
|
|
276
|
+
deviceVendor: device.vendor,
|
|
277
|
+
deviceModel: device.model,
|
|
278
|
+
engineName: engine.name,
|
|
279
|
+
engineVersion: engine.version,
|
|
280
|
+
cpuArchitecture: cpu.architecture,
|
|
281
|
+
browser: { name: browser.name, version: browser.version, major: majorVersion(browser.version) },
|
|
282
|
+
engine: engine,
|
|
283
|
+
os: os,
|
|
284
|
+
device: device,
|
|
285
|
+
cpu: cpu
|
|
171
286
|
};
|
|
172
287
|
}
|
|
288
|
+
|
|
289
|
+
export function getUserAgentFields(userAgent) {
|
|
290
|
+
const text = typeof userAgent === "string" ? userAgent.trim() : "";
|
|
291
|
+
|
|
292
|
+
if (text.length === 0) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return parseUserAgent(text);
|
|
297
|
+
}
|
package/apis/tongJi/_daily.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { DAY_MS } from "#befly/utils/datetime.js";
|
|
2
|
-
|
|
3
|
-
const DEFAULT_TIME_ZONE = "Asia/Shanghai";
|
|
4
|
-
|
|
5
|
-
function getTimeZoneDateParts(timestamp, timeZone) {
|
|
6
|
-
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
7
|
-
timeZone: timeZone,
|
|
8
|
-
year: "numeric",
|
|
9
|
-
month: "2-digit",
|
|
10
|
-
day: "2-digit"
|
|
11
|
-
});
|
|
12
|
-
const parts = formatter.formatToParts(timestamp);
|
|
13
|
-
const result = {};
|
|
14
|
-
|
|
15
|
-
for (const part of parts) {
|
|
16
|
-
if (part.type !== "literal") {
|
|
17
|
-
result[part.type] = Number(part.value);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function buildYmdNumber(year, month, day) {
|
|
25
|
-
return year * 10000 + month * 100 + day;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeTimeZone(timeZone) {
|
|
29
|
-
return typeof timeZone === "string" && timeZone.trim().length > 0 ? timeZone.trim() : DEFAULT_TIME_ZONE;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function findTimestampInDay(targetYmd, timeZone) {
|
|
33
|
-
const year = Math.floor(targetYmd / 10000);
|
|
34
|
-
const month = Math.floor((targetYmd % 10000) / 100);
|
|
35
|
-
const day = targetYmd % 100;
|
|
36
|
-
// 取当天正午作为锚点,再向前/后逐小时修正,确保落在目标日期内
|
|
37
|
-
let utc = Date.UTC(year, month - 1, day, 12, 0, 0, 0);
|
|
38
|
-
const ymdAtUtc = getDailyDateYmdNumber(utc, timeZone);
|
|
39
|
-
|
|
40
|
-
if (ymdAtUtc === targetYmd) {
|
|
41
|
-
return utc;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const step = ymdAtUtc < targetYmd ? 3600000 : -3600000;
|
|
45
|
-
|
|
46
|
-
do {
|
|
47
|
-
utc += step;
|
|
48
|
-
} while (getDailyDateYmdNumber(utc, timeZone) !== targetYmd);
|
|
49
|
-
|
|
50
|
-
return utc;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getWeekdayIndex(timestamp, timeZone) {
|
|
54
|
-
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
55
|
-
timeZone: timeZone,
|
|
56
|
-
weekday: "short"
|
|
57
|
-
});
|
|
58
|
-
const parts = formatter.formatToParts(timestamp);
|
|
59
|
-
const shortWeekday = parts.find((item) => item.type === "weekday").value;
|
|
60
|
-
const map = {
|
|
61
|
-
Sun: 0,
|
|
62
|
-
Mon: 1,
|
|
63
|
-
Tue: 2,
|
|
64
|
-
Wed: 3,
|
|
65
|
-
Thu: 4,
|
|
66
|
-
Fri: 5,
|
|
67
|
-
Sat: 6
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
return map[shortWeekday] ?? 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function getDailyDateYmdNumber(timestamp = Date.now(), timeZone = DEFAULT_TIME_ZONE) {
|
|
74
|
-
const parts = getTimeZoneDateParts(timestamp, normalizeTimeZone(timeZone));
|
|
75
|
-
|
|
76
|
-
return buildYmdNumber(parts.year, parts.month, parts.day);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function getDailyDayStartTime(timestamp = Date.now(), timeZone = DEFAULT_TIME_ZONE) {
|
|
80
|
-
const zone = normalizeTimeZone(timeZone);
|
|
81
|
-
const targetYmd = getDailyDateYmdNumber(timestamp, zone);
|
|
82
|
-
const timestampInDay = findTimestampInDay(targetYmd, zone);
|
|
83
|
-
let low = timestampInDay - DAY_MS;
|
|
84
|
-
let high = timestampInDay + DAY_MS;
|
|
85
|
-
|
|
86
|
-
while (getDailyDateYmdNumber(low, zone) >= targetYmd) {
|
|
87
|
-
low -= DAY_MS;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
while (getDailyDateYmdNumber(high, zone) <= targetYmd) {
|
|
91
|
-
high += DAY_MS;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
while (low + 1 < high) {
|
|
95
|
-
const mid = Math.floor((low + high) / 2);
|
|
96
|
-
|
|
97
|
-
if (getDailyDateYmdNumber(mid, zone) < targetYmd) {
|
|
98
|
-
low = mid;
|
|
99
|
-
} else {
|
|
100
|
-
high = mid;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return high;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function getDailyMonthStartDate(timestamp = Date.now(), timeZone = DEFAULT_TIME_ZONE) {
|
|
108
|
-
const parts = getTimeZoneDateParts(timestamp, normalizeTimeZone(timeZone));
|
|
109
|
-
|
|
110
|
-
return buildYmdNumber(parts.year, parts.month, 1);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function getDailyWeekStartDate(timestamp = Date.now(), timeZone = DEFAULT_TIME_ZONE) {
|
|
114
|
-
const zone = normalizeTimeZone(timeZone);
|
|
115
|
-
let dayStart = getDailyDayStartTime(timestamp, zone);
|
|
116
|
-
|
|
117
|
-
while (getWeekdayIndex(dayStart, zone) !== 1) {
|
|
118
|
-
dayStart = getDailyDayStartTime(dayStart - DAY_MS, zone);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return getDailyDateYmdNumber(dayStart, zone);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function getDailyRecentDateList(timestamp = Date.now(), limit = 30, timeZone = DEFAULT_TIME_ZONE) {
|
|
125
|
-
const zone = normalizeTimeZone(timeZone);
|
|
126
|
-
const dayStart = getDailyDayStartTime(timestamp, zone);
|
|
127
|
-
const list = [];
|
|
128
|
-
|
|
129
|
-
for (let i = limit - 1; i >= 0; i -= 1) {
|
|
130
|
-
list.push(getDailyDateYmdNumber(dayStart - i * DAY_MS, zone));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return list;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function getDailyDayStartTimeForYmd(reportDate, timeZone = DEFAULT_TIME_ZONE) {
|
|
137
|
-
return getDailyDayStartTime(findTimestampInDay(reportDate, normalizeTimeZone(timeZone)), timeZone);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function getDailyDateRangeList(startDateYmd, endDateYmd, timeZone = DEFAULT_TIME_ZONE) {
|
|
141
|
-
const zone = normalizeTimeZone(timeZone);
|
|
142
|
-
const startDayStart = getDailyDayStartTimeForYmd(startDateYmd, zone);
|
|
143
|
-
const endDayStart = getDailyDayStartTimeForYmd(endDateYmd, zone);
|
|
144
|
-
const list = [];
|
|
145
|
-
|
|
146
|
-
for (let t = startDayStart; t <= endDayStart; t += DAY_MS) {
|
|
147
|
-
list.push(getDailyDateYmdNumber(t, zone));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return list;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function getDailyStatsDayMembersKey(reportDate) {
|
|
154
|
-
return `daily:day:${reportDate}:members`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function getDailyStatsDayProductMembersKey(reportDate, productCode) {
|
|
158
|
-
return `daily:day:${reportDate}:product:${encodeURIComponent(productCode)}:members`;
|
|
159
|
-
}
|