Skip to content

Commit c274b1c

Browse files
committed
fix: 优化中国时区日期处理,确保销售趋势和库存统计准确
1 parent 5671586 commit c274b1c

1 file changed

Lines changed: 98 additions & 36 deletions

File tree

packages/server/src/modules/dashboard/dashboard.service.ts

Lines changed: 98 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type TrendBucket = {
1919
end: Date;
2020
};
2121

22+
const CHINA_OFFSET_MS = 8 * 60 * 60 * 1000;
23+
2224
@Injectable()
2325
export class DashboardService {
2426
constructor(
@@ -29,8 +31,17 @@ export class DashboardService {
2931

3032
async getOverview() {
3133
const now = new Date();
32-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
33-
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
34+
const chinaNow = this.getChinaShiftedDate(now);
35+
const todayStart = this.createChinaBoundaryDate(
36+
chinaNow.getUTCFullYear(),
37+
chinaNow.getUTCMonth(),
38+
chinaNow.getUTCDate(),
39+
);
40+
const monthStart = this.createChinaBoundaryDate(
41+
chinaNow.getUTCFullYear(),
42+
chinaNow.getUTCMonth(),
43+
1,
44+
);
3445

3546
const [
3647
todayRevenue,
@@ -83,12 +94,12 @@ export class DashboardService {
8394
const rows = await this.dataSource
8495
.getRepository(SaleOrderEntity)
8596
.createQueryBuilder('saleOrder')
86-
.select(["datetime(saleOrder.created_at, 'localtime') AS createdAt", 'saleOrder.total_amount AS totalAmount'])
87-
.where("datetime(saleOrder.created_at, 'localtime') >= :start", {
88-
start: this.formatSqliteDateTime(start),
97+
.select(['saleOrder.created_at AS createdAt', 'saleOrder.total_amount AS totalAmount'])
98+
.where('saleOrder.created_at >= :start', {
99+
start: this.formatUtcStorageDateTime(start),
89100
})
90-
.andWhere("datetime(saleOrder.created_at, 'localtime') <= :end", {
91-
end: this.formatSqliteDateTime(end),
101+
.andWhere('saleOrder.created_at <= :end', {
102+
end: this.formatUtcStorageDateTime(end),
92103
})
93104
.getRawMany<{ createdAt: string; totalAmount: number }>();
94105

@@ -99,7 +110,7 @@ export class DashboardService {
99110
}));
100111

101112
rows.forEach((row) => {
102-
const createdAt = new Date(row.createdAt);
113+
const createdAt = this.parseUtcStorageDateTime(row.createdAt);
103114
const bucketIndex = buckets.findIndex(
104115
(bucket) => createdAt >= bucket.start && createdAt <= bucket.end,
105116
);
@@ -207,11 +218,11 @@ export class DashboardService {
207218
.getRepository(SaleOrderEntity)
208219
.createQueryBuilder('saleOrder')
209220
.select('COALESCE(SUM(saleOrder.total_amount), 0)', 'amount')
210-
.where("datetime(saleOrder.created_at, 'localtime') >= :start", {
211-
start: this.formatSqliteDateTime(start),
221+
.where('saleOrder.created_at >= :start', {
222+
start: this.formatUtcStorageDateTime(start),
212223
})
213-
.andWhere("datetime(saleOrder.created_at, 'localtime') <= :end", {
214-
end: this.formatSqliteDateTime(end),
224+
.andWhere('saleOrder.created_at <= :end', {
225+
end: this.formatUtcStorageDateTime(end),
215226
})
216227
.getRawOne<{ amount: number }>();
217228

@@ -283,20 +294,30 @@ export class DashboardService {
283294
}
284295

285296
private buildTrendBuckets(period: 'day' | 'week' | 'month'): TrendBucket[] {
286-
const now = new Date();
297+
const chinaNow = this.getChinaShiftedDate();
287298

288299
if (period === 'week') {
289300
return Array.from({ length: 8 }, (_, index) => {
290-
const start = new Date(now);
291-
start.setDate(now.getDate() - (7 - index) * 7);
292-
start.setHours(0, 0, 0, 0);
293-
294-
const end = new Date(start);
295-
end.setDate(start.getDate() + 6);
296-
end.setHours(23, 59, 59, 999);
301+
const startChina = new Date(Date.UTC(
302+
chinaNow.getUTCFullYear(),
303+
chinaNow.getUTCMonth(),
304+
chinaNow.getUTCDate() - (7 - index) * 7,
305+
));
306+
const endChina = new Date(Date.UTC(
307+
startChina.getUTCFullYear(),
308+
startChina.getUTCMonth(),
309+
startChina.getUTCDate() + 6,
310+
23,
311+
59,
312+
59,
313+
999,
314+
));
315+
316+
const start = new Date(startChina.getTime() - CHINA_OFFSET_MS);
317+
const end = new Date(endChina.getTime() - CHINA_OFFSET_MS);
297318

298319
return {
299-
label: `${start.getMonth() + 1}/${start.getDate()}`,
320+
label: `${startChina.getUTCMonth() + 1}/${startChina.getUTCDate()}`,
300321
start,
301322
end,
302323
};
@@ -305,30 +326,73 @@ export class DashboardService {
305326

306327
if (period === 'month') {
307328
return Array.from({ length: 12 }, (_, index) => {
308-
const start = new Date(now.getFullYear(), now.getMonth() - (11 - index), 1);
309-
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0, 23, 59, 59, 999);
329+
const startChina = new Date(Date.UTC(
330+
chinaNow.getUTCFullYear(),
331+
chinaNow.getUTCMonth() - (11 - index),
332+
1,
333+
));
334+
const endChina = new Date(Date.UTC(
335+
startChina.getUTCFullYear(),
336+
startChina.getUTCMonth() + 1,
337+
0,
338+
23,
339+
59,
340+
59,
341+
999,
342+
));
343+
const start = new Date(startChina.getTime() - CHINA_OFFSET_MS);
344+
const end = new Date(endChina.getTime() - CHINA_OFFSET_MS);
310345

311346
return {
312-
label: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}`,
347+
label: `${startChina.getUTCFullYear()}-${String(startChina.getUTCMonth() + 1).padStart(2, '0')}`,
313348
start,
314349
end,
315350
};
316351
});
317352
}
318353

319354
return Array.from({ length: 7 }, (_, index) => {
320-
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - (6 - index));
321-
const end = new Date(start);
322-
end.setHours(23, 59, 59, 999);
355+
const startChina = new Date(Date.UTC(
356+
chinaNow.getUTCFullYear(),
357+
chinaNow.getUTCMonth(),
358+
chinaNow.getUTCDate() - (6 - index),
359+
));
360+
const endChina = new Date(Date.UTC(
361+
startChina.getUTCFullYear(),
362+
startChina.getUTCMonth(),
363+
startChina.getUTCDate(),
364+
23,
365+
59,
366+
59,
367+
999,
368+
));
369+
const start = new Date(startChina.getTime() - CHINA_OFFSET_MS);
370+
const end = new Date(endChina.getTime() - CHINA_OFFSET_MS);
323371

324372
return {
325-
label: `${start.getMonth() + 1}/${start.getDate()}`,
373+
label: `${startChina.getUTCMonth() + 1}/${startChina.getUTCDate()}`,
326374
start,
327375
end,
328376
};
329377
});
330378
}
331379

380+
private getChinaShiftedDate(date = new Date()) {
381+
return new Date(date.getTime() + CHINA_OFFSET_MS);
382+
}
383+
384+
private createChinaBoundaryDate(
385+
year: number,
386+
month: number,
387+
day: number,
388+
hours = 0,
389+
minutes = 0,
390+
seconds = 0,
391+
milliseconds = 0,
392+
) {
393+
return new Date(Date.UTC(year, month, day, hours, minutes, seconds, milliseconds) - CHINA_OFFSET_MS);
394+
}
395+
332396
private buildExpiryWarning(product: ProductEntity, now: Date) {
333397
if (!product.producedAt || !product.shelfLife || product.shelfLife <= 0) {
334398
return null;
@@ -364,14 +428,12 @@ export class DashboardService {
364428
};
365429
}
366430

367-
private formatSqliteDateTime(date: Date) {
368-
const year = date.getFullYear();
369-
const month = String(date.getMonth() + 1).padStart(2, '0');
370-
const day = String(date.getDate()).padStart(2, '0');
371-
const hours = String(date.getHours()).padStart(2, '0');
372-
const minutes = String(date.getMinutes()).padStart(2, '0');
373-
const seconds = String(date.getSeconds()).padStart(2, '0');
431+
private formatUtcStorageDateTime(date: Date) {
432+
return date.toISOString().slice(0, 19).replace('T', ' ');
433+
}
374434

375-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
435+
private parseUtcStorageDateTime(value: string) {
436+
const normalized = value.trim().replace(' ', 'T');
437+
return new Date(normalized.endsWith('Z') ? normalized : `${normalized}Z`);
376438
}
377439
}

0 commit comments

Comments
 (0)