由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复: - wallets 原子扣款防 double-spend;refund 条件更新幂等;findTransactions 分页 - financial/imports/occupancies/attendance 事务与 advisory lock;重复生成/提交幂等 - 矛盾校验器、日期区间、实体双映射/DECIMAL/nullable、时区统一(china-time) - rbac-seed 防重激活、exam 权限恢复、状态一致性、路由顺序、N+1/IN 分块等性能项 Reviewed-by: OCR (open-codereview.ai)
40 lines
1.6 KiB
TypeScript
40 lines
1.6 KiB
TypeScript
import dayjs from './dayjs';
|
||
|
||
/**
|
||
* 中国时区(Asia/Shanghai)常量与日期工具。
|
||
* 服务端/生产统一按 UTC+8 处理「业务日期」,避免与服务器本地时区混用导致 off-by-one。
|
||
*/
|
||
export const CHINA_UTC_OFFSET = 8;
|
||
|
||
/** 当前时刻的中国日期(YYYY-MM-DD)。 */
|
||
export function getChinaDate(date: Date = new Date()): string {
|
||
return dayjs(date).utcOffset(CHINA_UTC_OFFSET).format('YYYY-MM-DD');
|
||
}
|
||
|
||
/** 把 'YYYY-MM-DD' 按 UTC+8 午夜解析为 Date(用于需要绝对时刻的场合)。 */
|
||
export function parseChinaDateOnly(dateOnly: string): Date {
|
||
return new Date(`${dateOnly}T00:00:00+08:00`);
|
||
}
|
||
|
||
/** 纯日期字符串加减天数,避免本地时区 getters 造成的偏移。 */
|
||
export function addDaysToDateOnly(dateOnly: string, days: number): string {
|
||
const [y, m, d] = dateOnly.split('-').map(Number);
|
||
const dt = new Date(Date.UTC(y, m - 1, d + days));
|
||
const yy = dt.getUTCFullYear();
|
||
const mm = String(dt.getUTCMonth() + 1).padStart(2, '0');
|
||
const dd = String(dt.getUTCDate()).padStart(2, '0');
|
||
return `${yy}-${mm}-${dd}`;
|
||
}
|
||
|
||
/** 返回 dateOnly 在中国日历下的星期(1=周一 … 7=周日),与本地时区无关。 */
|
||
export function getWeekDayFromDateOnly(dateOnly: string): number {
|
||
const [y, m, d] = dateOnly.split('-').map(Number);
|
||
const day = new Date(Date.UTC(y, m - 1, d)).getUTCDay();
|
||
return day === 0 ? 7 : day;
|
||
}
|
||
|
||
/** 两个 YYYY-MM-DD 是否按中国日历相邻(差 1 天)。 */
|
||
export function isConsecutiveDates(a: string, b: string): boolean {
|
||
return addDaysToDateOnly(a, 1) === b || addDaysToDateOnly(b, 1) === a;
|
||
}
|