From d324b2fb0a79e040ec0db96d4001253eaab108c4 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Sat, 8 Aug 2026 17:27:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(server):=20=E6=97=A5=E6=9C=9F=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=BB=9F=E4=B8=80=E8=BF=81=E5=85=A5=20dayjs=EF=BC=88u?= =?UTF-8?q?tc=20=E6=8F=92=E4=BB=B6=EF=BC=89=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=89=8B=E5=86=99=20Intl/padStart=20=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/package.json | 1 + .../agent-context/pending-tasks.service.ts | 14 ++------- .../attendance-generation.service.ts | 17 ++++------- .../attendance-settlement.service.ts | 18 +++++------ apps/server/src/attendance/attendance-time.ts | 14 ++++----- .../attendance/attendance.controller-base.ts | 7 ++--- apps/server/src/common/dayjs.ts | 7 +++++ apps/server/src/rbac/rbac-presets.ts | 30 ++++--------------- package-lock.json | 1 + 9 files changed, 37 insertions(+), 72 deletions(-) create mode 100644 apps/server/src/common/dayjs.ts diff --git a/apps/server/package.json b/apps/server/package.json index 7fc9b33..274eabe 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -45,6 +45,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "compression": "^1.8.1", + "dayjs": "^1.11.21", "dotenv": "^17.4.1", "exceljs": "^4.4.0", "express": "^5.2.1", diff --git a/apps/server/src/agent-context/pending-tasks.service.ts b/apps/server/src/agent-context/pending-tasks.service.ts index 18bf444..c8f34de 100644 --- a/apps/server/src/agent-context/pending-tasks.service.ts +++ b/apps/server/src/agent-context/pending-tasks.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import dayjs from '../common/dayjs'; import { DataSource } from 'typeorm'; import type { AgentToolContext } from '../agent-tools/agent-tool.types'; import type { StudentAccessScope } from '../students/student-access-scope'; @@ -55,20 +56,11 @@ function teacherScopedCountSql(sql: (scope: StudentAccessScope) => string): SqlB } function today(): string { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; + return dayjs().format('YYYY-MM-DD'); } function inDays(days: number): string { - const date = new Date(); - date.setDate(date.getDate() + days); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; + return dayjs().add(days, 'day').format('YYYY-MM-DD'); } const TASKS: readonly TaskDefinition[] = [ diff --git a/apps/server/src/attendance/attendance-generation.service.ts b/apps/server/src/attendance/attendance-generation.service.ts index 734b1a8..bf43f9f 100644 --- a/apps/server/src/attendance/attendance-generation.service.ts +++ b/apps/server/src/attendance/attendance-generation.service.ts @@ -11,6 +11,7 @@ import { ScheduleType, } from '../entities'; import { toMinutes, isClassStudentActiveOnDate } from './attendance-time'; +import dayjs from '../common/dayjs'; import type { BatchCreateAttendanceDto, GenerateAttendanceFromSchedulesDto, GenerateFromSchedulesDto, SaveAttendancePeriodConfigsDto } from './dto/attendance.dto'; @Injectable() @@ -162,23 +163,15 @@ export class AttendanceGenerationService { } private getCourseClock(date: Date): { date: string; minutes: number } { - const parts = Object.fromEntries( - new Intl.DateTimeFormat('en-CA', { - timeZone: 'Asia/Shanghai', - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', hourCycle: 'h23', - }).formatToParts(date).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]), - ); + const c = dayjs.utc(date).add(8, 'hour'); return { - date: `${parts.year}-${parts.month}-${parts.day}`, - minutes: Number(parts.hour) * 60 + Number(parts.minute), + date: c.format('YYYY-MM-DD'), + minutes: c.hour() * 60 + c.minute(), }; } private shiftDate(date: string, days: number): string { - const shifted = new Date(`${date}T00:00:00.000Z`); - shifted.setUTCDate(shifted.getUTCDate() + days); - return shifted.toISOString().slice(0, 10); + return dayjs.utc(`${date}T00:00:00.000Z`).add(days, 'day').format('YYYY-MM-DD'); } private async ensureAttendancePeriodConfigs() { diff --git a/apps/server/src/attendance/attendance-settlement.service.ts b/apps/server/src/attendance/attendance-settlement.service.ts index c55086e..553bf1f 100644 --- a/apps/server/src/attendance/attendance-settlement.service.ts +++ b/apps/server/src/attendance/attendance-settlement.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import dayjs from '../common/dayjs'; import { Cron } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { In, LessThan, LessThanOrEqual, MoreThanOrEqual, Repository } from 'typeorm'; @@ -283,18 +284,13 @@ export class AttendanceSettlementService { } private getCourseClock(date: Date): { date: string; weekDay: number; minutes: number } { - const parts = Object.fromEntries( - new Intl.DateTimeFormat('en-CA', { - timeZone: this.courseTimeZone, - year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short', - hour: '2-digit', minute: '2-digit', hourCycle: 'h23', - }).formatToParts(date).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]), - ); - const weekDays: Record = { Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6, Sun: 7 }; + // Asia/Shanghai 无夏令时,固定 UTC+8 与 Intl 时区格式化等价 + const c = dayjs.utc(date).add(8, 'hour'); + const weekDay = c.day() === 0 ? 7 : c.day(); return { - date: `${parts.year}-${parts.month}-${parts.day}`, - weekDay: weekDays[parts.weekday], - minutes: Number(parts.hour) * 60 + Number(parts.minute), + date: c.format('YYYY-MM-DD'), + weekDay, + minutes: c.hour() * 60 + c.minute(), }; } diff --git a/apps/server/src/attendance/attendance-time.ts b/apps/server/src/attendance/attendance-time.ts index a38d875..ac5fa79 100644 --- a/apps/server/src/attendance/attendance-time.ts +++ b/apps/server/src/attendance/attendance-time.ts @@ -1,22 +1,20 @@ +import dayjs from '../common/dayjs'; + export function toMinutes(time: string): number { const [hour, minute] = time.split(':').map(Number); return hour * 60 + minute; } export function getCourseClock(date: Date): { date: string; minutes: number } { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); + const d = dayjs(date); return { - date: `${year}-${month}-${day}`, - minutes: date.getHours() * 60 + date.getMinutes(), + date: d.format('YYYY-MM-DD'), + minutes: d.hour() * 60 + d.minute(), }; } export function shiftDate(date: string, days: number): string { - const d = new Date(`${date}T00:00:00.000Z`); - d.setUTCDate(d.getUTCDate() + days); - return d.toISOString().slice(0, 10); + return dayjs.utc(`${date}T00:00:00.000Z`).add(days, 'day').format('YYYY-MM-DD'); } export function mapLessonScheduleTimeToSession(startTime: string): string { diff --git a/apps/server/src/attendance/attendance.controller-base.ts b/apps/server/src/attendance/attendance.controller-base.ts index 491131d..ea77673 100644 --- a/apps/server/src/attendance/attendance.controller-base.ts +++ b/apps/server/src/attendance/attendance.controller-base.ts @@ -1,4 +1,5 @@ import { UseGuards } from '@nestjs/common'; +import dayjs from '../common/dayjs'; import { AttendanceService } from './attendance.service'; import { AttendanceImportService } from './attendance-import.service'; import { OperationLogsService } from '../operation-logs/operation-logs.service'; @@ -32,11 +33,7 @@ export abstract class AttendanceControllerBase { ) {} protected getTodayDateOnly(): string { - const today = new Date(); - const year = today.getFullYear(); - const month = String(today.getMonth() + 1).padStart(2, '0'); - const day = String(today.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; + return dayjs().format('YYYY-MM-DD'); } protected canManageAllAttendance(req: { user: RequestUser }): boolean { diff --git a/apps/server/src/common/dayjs.ts b/apps/server/src/common/dayjs.ts new file mode 100644 index 0000000..d448c0b --- /dev/null +++ b/apps/server/src/common/dayjs.ts @@ -0,0 +1,7 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; + +// 服务端统一 dayjs 实例:扩展 utc 插件以支持固定时区(Asia/Shanghai 无夏令时,恒为 UTC+8)。 +dayjs.extend(utc); + +export default dayjs; diff --git a/apps/server/src/rbac/rbac-presets.ts b/apps/server/src/rbac/rbac-presets.ts index e8deccc..ea514f7 100644 --- a/apps/server/src/rbac/rbac-presets.ts +++ b/apps/server/src/rbac/rbac-presets.ts @@ -144,33 +144,13 @@ export const DEPRECATED_PERMISSION_CODES = [ ] as const; export const DEPRECATED_PERMISSION_CODE_SET = new Set(DEPRECATED_PERMISSION_CODES); +import dayjs from '../common/dayjs'; export function getChinaDateParts(date = new Date()): { date: string; weekDay: number } { - const parts = Object.fromEntries( - new Intl.DateTimeFormat('en-CA', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - weekday: 'short', - }) - .formatToParts(date) - .filter((part) => part.type !== 'literal') - .map((part) => [part.type, part.value]), - ); - const weekDays: Record = { - Mon: 1, - Tue: 2, - Wed: 3, - Thu: 4, - Fri: 5, - Sat: 6, - Sun: 7, - }; - return { - date: `${parts.year}-${parts.month}-${parts.day}`, - weekDay: weekDays[parts.weekday], - }; + // Asia/Shanghai 无夏令时,固定 UTC+8 与 Intl en-CA 格式化等价 + const c = dayjs.utc(date).add(8, 'hour'); + const weekDay = c.day() === 0 ? 7 : c.day(); + return { date: c.format('YYYY-MM-DD'), weekDay }; } export const PRESET_ROLES: Array<{ diff --git a/package-lock.json b/package-lock.json index b310c3a..4d72a44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,6 +108,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "compression": "^1.8.1", + "dayjs": "^1.11.21", "dotenv": "^17.4.1", "exceljs": "^4.4.0", "express": "^5.2.1",