修复游戏版本时间显示 1970 年:createdAt 按 Unix 秒换算成毫秒

- 写入侧真实单位是 Unix 秒:project/manifest.rs 的 GameIterationVersion.created_at 赋值为 unix_timestamp(),该函数返回 duration.as_secs()
- formatIterationVersionLabel 原来把秒值直接交给 new Date(),被当成毫秒解释,真机因此显示 1970/1/22(1788075047 毫秒 = 1970/1/22 00:41:15,与截图逐秒吻合)
- 显示侧改为 new Date(version.createdAt * 1000),与 ResourceAssetDeleteDialog 和待确认资源编辑创建时间标签的既有秒口径一致
- 给 GameIterationVersion.createdAt 补契约注释,写明单位是 Unix 秒及换算要求,避免后续再按毫秒消费
- resourceCanvasVersionBindingModel 夹具由毫秒改为真实秒值,并用真机取值 1788075047 补断言:年月日必须是 2026/8/30 而不是 1970
- resourceVersionSwitch 夹具同步改为真实秒值,避免继续沿用误导性的毫秒夹具
This commit is contained in:
2026-09-10 21:00:54 +08:00
parent dde860716e
commit 14d1635858
4 changed files with 53 additions and 8 deletions
@@ -19,14 +19,22 @@ export const ITERATION_VERSION_REASON_LABELS: Record<
'agent-revision': '智能体修订',
};
/** 版本在 UI 里的可读标识:创建原因 + 创建时间。 */
/**
* 版本在 UI 里的可读标识:创建原因 + 创建时间。
*
* `GameIterationVersion.createdAt` 的单位是 **Unix 秒**(写入侧为
* `project/manifest.rs` 的 `created_at: unix_timestamp()`,而 `unix_timestamp()` 取
* `duration.as_secs()`)。这里必须换算成毫秒再交给 `Date`,否则秒值会被当成毫秒,
* 渲染成 1970 年(例如 1788075047 会显示成 1970/1/22)。
* 同一约定在 `ResourceAssetDeleteDialog` 与待确认资源编辑的创建时间标签里一致使用。
*/
export function formatIterationVersionLabel(
version: Pick<GameIterationVersion, 'createdReason' | 'createdAt'>,
) {
const reason =
ITERATION_VERSION_REASON_LABELS[version.createdReason] ??
version.createdReason;
const createdAt = new Date(version.createdAt);
const createdAt = new Date(version.createdAt * 1000);
const time = Number.isFinite(createdAt.getTime())
? createdAt.toLocaleString('zh-CN')
: String(version.createdAt);
@@ -6,6 +6,13 @@ import {
isResourceUsedByCurrentVersion,
} from '../src/features/resource-canvas/resourceCanvasVersionBindingModel';
/**
* 夹具必须与落盘口径一致:`versions[].createdAt` 是 **Unix 秒**。
*
* 写入侧见 `project/manifest.rs` 的 `created_at: unix_timestamp()``as_secs()`)。
* `1788075047` 取自真机项目 `What do u wanna do kitten` 的 `versions[0].createdAt`
* 它被当成毫秒渲染时正好是 `1970/1/22 00:41:15`,即真机截图里的 1970 年。
*/
const versions = [
{
versionId: 'version-root',
@@ -15,7 +22,7 @@ const versions = [
{ slotId: 'asset:asset-player', resourceId: 'asset-player' },
],
createdReason: 'initial' as const,
createdAt: 1_760_000_000_000,
createdAt: 1_788_075_047,
},
{
versionId: 'version-child',
@@ -27,7 +34,7 @@ const versions = [
{ slotId: 'player', resourceId: 'asset-player' },
],
createdReason: 'agent-revision' as const,
createdAt: 1_760_003_600_000,
createdAt: 1_788_075_104,
},
];
@@ -80,10 +87,32 @@ describe('运行模块版本绑定判定', () => {
test('labels a version with its Chinese created reason and creation time', () => {
expect(formatIterationVersionLabel(versions[0])).toBe(
`初始版本 · ${new Date(1_760_000_000_000).toLocaleString('zh-CN')}`,
`初始版本 · ${new Date(1_788_075_047 * 1000).toLocaleString('zh-CN')}`,
);
expect(formatIterationVersionLabel(versions[1])).toBe(
`智能体修订 · ${new Date(1_760_003_600_000).toLocaleString('zh-CN')}`,
`智能体修订 · ${new Date(1_788_075_104 * 1000).toLocaleString('zh-CN')}`,
);
});
test('把版本 createdAt 的 Unix 秒换算成毫秒,不再渲染成 1970 年', () => {
// 期望值按同一个本地时区口径算出,用例结果不随运行机器时区漂移。
// 1788075047 秒 = 2026-08-30T07:30:47Z。
const createdAtLocal = new Date(1_788_075_047 * 1000).toLocaleString(
'zh-CN',
);
const label = formatIterationVersionLabel(versions[0]);
expect(label).toBe(`初始版本 · ${createdAtLocal}`);
// 年 / 月 / 日必须来自真实时刻,而不是 1970。这里用显式数值选项断言,
// 不依赖 `toLocaleString` 的具体排版(不同 ICU 版本会给出不同分隔符)。
expect(
new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
}).format(new Date(1_788_075_047 * 1000)),
).toBe('2026/8/30');
// 回归守卫:按毫秒解释同一个值会落到 1970/1/22,这正是真机截图里的错误显示。
expect(label).not.toContain('1970');
});
});
@@ -77,6 +77,7 @@ function createVersionedManifest(): GameCreationAppManifest {
source: { kind: 'generated' },
},
];
// createdAt 是 Unix 秒(写入侧 manifest.rs 用 unix_timestamp())。
manifest.versions = [
{
versionId: 'version-root',
@@ -86,7 +87,7 @@ function createVersionedManifest(): GameCreationAppManifest {
{ slotId: 'asset:asset-player', resourceId: 'asset-player' },
],
createdReason: 'initial',
createdAt: 1_760_000_000_000,
createdAt: 1_788_075_047,
},
{
versionId: 'version-child',
@@ -96,7 +97,7 @@ function createVersionedManifest(): GameCreationAppManifest {
{ slotId: 'asset:asset-town', resourceId: 'asset-town' },
],
createdReason: 'agent-revision',
createdAt: 1_760_003_600_000,
createdAt: 1_788_075_104,
},
];
return manifest;
@@ -767,6 +767,13 @@ export interface GameIterationVersion {
projectRevision: number;
resourceBindings: GameIterationVersionResourceBinding[];
createdReason: GameIterationVersionCreatedReason;
/**
* 创建时间,单位是 **Unix 秒**。
*
* 写入侧为 `project/manifest.rs` 的 `created_at: unix_timestamp()`,该函数返回
* `duration.as_secs()`,全仓版本条目统一按秒落盘。渲染时必须换算成毫秒
* `new Date(createdAt * 1000)`),不要把秒值直接交给 `Date`。
*/
createdAt: number;
editPrompt?: string | null;
}