You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
4.0 KiB
107 lines
4.0 KiB
import { _decorator } from 'cc';
|
|
import { Singleton } from '../../tools/Singleton';
|
|
|
|
const { ccclass } = _decorator;
|
|
|
|
const MS_PER_MINUTE = 60 * 1000;
|
|
const MS_PER_DAY = 24 * 60 * MS_PER_MINUTE;
|
|
|
|
@ccclass('TimeModule')
|
|
export class TimeModule extends Singleton {
|
|
|
|
/** ISO 或已解析的 UTC 毫秒;热路径传 `number` 可避免 `new Date` 与字符串解析 */
|
|
private static utcMs(t: string | number): number {
|
|
return typeof t === 'number' ? t : new Date(t).getTime();
|
|
}
|
|
|
|
/** 当前 UTC 时间毫秒数(无 Date 分配,适合每帧调用) */
|
|
get now(): number {
|
|
return Date.now();
|
|
}
|
|
|
|
/**
|
|
* 将本地日历时刻按「数值等同 UTC」方式表示的毫秒数(常用于与服务器约定字段对齐)
|
|
* 等价:UTC 时间与本地墙钟相同数字时的 epoch 毫秒
|
|
*/
|
|
get localNow(): number {
|
|
const d = new Date();
|
|
return d.getTime() + d.getTimezoneOffset() * MS_PER_MINUTE;
|
|
}
|
|
|
|
/** 解析带时区的时间字符串为 UTC 毫秒,如 2026-02-08T00:00:00+08:00 */
|
|
getUTCTimeByLocalTimeStr(timeStr: string): number {
|
|
return new Date(timeStr).getTime();
|
|
}
|
|
|
|
/** 带时区字符串 → 本地墙钟对齐的「伪 UTC」毫秒(与 localNow 同一套坐标系) */
|
|
getLocalTimeByLocalTimeStr(timeStr: string): number {
|
|
const d = new Date(timeStr);
|
|
return d.getTime() + d.getTimezoneOffset() * MS_PER_MINUTE;
|
|
}
|
|
|
|
/** 本地墙钟对齐毫秒 → 真实 UTC 毫秒 */
|
|
getUTCTimeByLocalTime(localTime: number): number {
|
|
const d = new Date(localTime);
|
|
return d.getTime() - d.getTimezoneOffset() * MS_PER_MINUTE;
|
|
}
|
|
|
|
/**
|
|
* 根据时间段判断当前时间状态(比较均在真实 UTC 时间轴上)
|
|
* @param timeNow 当前 UTC 毫秒
|
|
* @param startTime 活动开始:ISO 字符串(含时区)或已解析的 UTC 毫秒
|
|
* @param endTime 活动结束:同上
|
|
* @param name 可选;非空会打日志,勿在高频逻辑中传入
|
|
*/
|
|
checkTimeStatus(timeNow: number, startTime: string | number, endTime: string | number, name = ''): 0 | 1 | 2 {
|
|
const start = TimeModule.utcMs(startTime);
|
|
const end = TimeModule.utcMs(endTime);
|
|
|
|
if (name) {
|
|
console.log('活动时间:', name, timeNow, start, end);
|
|
}
|
|
|
|
if (timeNow > end) return 2;
|
|
if (timeNow >= start) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 当天剩余毫秒数(到下一次「本地/UTC」的 0 点)
|
|
* 仅分配 **一个** `Date`(原实现为两个)
|
|
*/
|
|
getRemainTimeForCurrentDay(timeNow: number | null = null, isLocal = true): number {
|
|
const d = new Date(timeNow ?? Date.now());
|
|
const now = d.getTime();
|
|
if (isLocal) {
|
|
d.setHours(24, 0, 0, 0);
|
|
} else {
|
|
d.setUTCHours(24, 0, 0, 0);
|
|
}
|
|
return d.getTime() - now;
|
|
}
|
|
|
|
/**
|
|
* 距离活动结束的剩余毫秒,负表示已过期
|
|
* @param endTime 结束时刻:ISO 字符串(含时区)或 UTC 毫秒
|
|
* @param timeNow 当前 UTC 毫秒,默认 `Date.now()`
|
|
*/
|
|
getRemainTime(endTime: string | number, timeNow: number = Date.now()): number {
|
|
return TimeModule.utcMs(endTime) - timeNow;
|
|
}
|
|
|
|
/** 自 startTime 起算的第几天(满 24h 计一天,向下取整);`startTime` 为 number 时零分配 */
|
|
getDayAfterStartTime(timeNow: number, startTime: number | string): number {
|
|
const start = typeof startTime === 'string' ? new Date(startTime).getTime() : startTime;
|
|
return Math.floor((timeNow - start) / MS_PER_DAY);
|
|
}
|
|
|
|
/** 与当前真实时间是否同一自然日(本地时区);仅 **一个** `Date` 实例 */
|
|
isSameDay(lastLoginTime: number): boolean {
|
|
const d = new Date(lastLoginTime);
|
|
const y = d.getFullYear();
|
|
const m = d.getMonth();
|
|
const day = d.getDate();
|
|
d.setTime(Date.now());
|
|
return d.getFullYear() === y && d.getMonth() === m && d.getDate() === day;
|
|
}
|
|
}
|
|
|