全球会议排期工具 — 技术实现篇-鸿蒙国际化


一、业务需求(为什么这么设计)
跨国团队最痛的场景是对时间:产品周会定在"北京时间 15:00",伦敦同事要换算成 08:00,纽约同事要知道那是前一天的 03:00。如果每个会议都靠心算时差,迟早有人迟到或错过。
本应用把会议时间统一用 UTC 存储,展示时按每个参会人的时区 + 语言习惯格式化——“3pm UTC” 这个唯一事实,在北京显示 23:00、在纽约显示 11:00 AM,且日期写法各不相同。
二、总体架构
┌─ 数据层:MEETINGS(会议)+ CITIES(城市/时区表)
├─ 格式化层:fmtInTz / fmtStyle / clockTz(intl.DateTimeFormat 三件套)
├─ 文案层:STRINGS 文案表 + t() 降级
└─ 状态层:@StorageLink 语言 + @State now(秒级时钟)/ convertIdx
核心设计:时间只有一个事实(UTC 毫秒时间戳),展示是各时区的投影。业务逻辑里永远操作 utcTime,界面输出时传入 timeZone。
数据模型:时区表的两个关键字段
城市表与会议表是这个应用的数据地基,它们的字段设计直接决定上层代码的复杂度:
interface City {
key: string;
name: string; // 显示名
tz: string; // IANA 时区
flag: string;
}
const CITIES: City[] = [
{ key: 'utc', name: 'UTC', tz: 'UTC', flag: '🌐' },
{ key: 'bj', name: '北京', tz: 'Asia/Shanghai', flag: '🇨🇳' },
{ key: 'tokyo', name: '东京', tz: 'Asia/Tokyo', flag: '🇯🇵' },
{ key: 'london', name: '伦敦', tz: 'Europe/London', flag: '🇬🇧' },
{ key: 'ny', name: '纽约', tz: 'America/New_York', flag: '🇺🇸' },
{ key: 'dubai', name: '迪拜', tz: 'Asia/Dubai', flag: '🇦🇪' }
];
设计要点:
tz存 IANA 名,不存偏移:伦敦的夏令时偏移是UTC+1、冬令时是UTC+0——如果数据表里写死+1,半年后就会差一小时。IANA 名Europe/London让intl自动处理切换;key是稳定标识:ForEach的 key 生成器用c.key(如'utc'、'bj'),语言切换时城市名变了但 key 不变,列表不会重建;flag是装饰字段:城市名会随语言变化(北京 → Beijing),国旗不翻译,用来锚定视觉。
会议表则把"时区"建模到发起人身上——这是会议产品的关键约定:
interface Meeting {
id: string;
topic: string;
host: string;
hostTz: string; // 发起人时区
utcTime: number; // 会议时刻(毫秒时间戳)
durationMin: number;
}
const MEETINGS: Meeting[] = [
{
id: 'm1', topic: '产品周会 · Sprint 规划', host: '北京 · 王芳', hostTz: 'Asia/Shanghai',
utcTime: Date.UTC(2026, 7, 19, 7, 0), durationMin: 60
},
{
id: 'm2', topic: '海外市场同步 · 欧洲 Q3 复盘', host: '伦敦 · Sarah', hostTz: 'Europe/London',
utcTime: Date.UTC(2026, 7, 19, 14, 30), durationMin: 45
},
{
id: 'm3', topic: '技术评审 · 分布式存储方案', host: '纽约 · David', hostTz: 'America/New_York',
utcTime: Date.UTC(2026, 7, 20, 2, 0), durationMin: 90
},
{
id: 'm4', topic: '设计走查 · 新首页改版', host: '东京 · 佐藤', hostTz: 'Asia/Tokyo',
utcTime: Date.UTC(2026, 7, 20, 9, 0), durationMin: 30
}
];
要点:
utcTime用Date.UTC()构造:不经过本地时区解析,直接指定 UTC 时刻,避免new Date('2026-08-19 07:00')这类字符串解析在不同设备上的歧义(有的设备按本地时区解析、有的按 UTC);- 不存"发起人本地时间":如果存了
'北京时间 15:00'这样的字符串,伦敦同事看到的就是一串汉字而不是他自己的时刻——存 UTC、展示时投影,是唯一正确的做法; durationMin只做展示(“90m” 徽标),不参与时区运算——时长是纯量,与时刻无关。
三、核心实现
3.1 时区格式化(本应用核心)
function fmtInTz(code: string, tz: string, ms: number): string {
try {
const fmt = new intl.DateTimeFormat(code, {
timeZone: tz, // IANA 时区,如 Asia/Shanghai
dateStyle: 'full', // 2026年8月19日星期三
timeStyle: 'short' // 14:30 / 2:30 PM
});
return fmt.format(new Date(ms));
} catch (err) {
return new Date(ms).toString();
}
}
关键点:
timeZone用 IANA 时区名(Asia/Shanghai),不是UTC+8偏移——IANA 名自动处理夏令时(伦敦夏季是 BST,冬季是 GMT,用Europe/London一处搞定);dateStyle: 'full'输出本地化星期与完整日期;- 同一
ms传入不同timeZone,得到的是同一时刻在各时区的本地表达——这正是"会议排期"的本质。
3.2 会议列表:UTC 存储 + 本地展示
会议数据存 UTC 时间戳(Date.UTC(2026, 7, 19, 7, 0) 表示 UTC 07:00),列表里同时显示"UTC 时刻"(便于对照)和"发起人本地时刻"(用户真正关心的):
private meetingLocal(m: Meeting): string {
return fmtInTz(this.currentLocale, m.hostTz, m.utcTime);
}
private meetingUtc(m: Meeting): string {
return fmtInTz(this.currentLocale, 'UTC', m.utcTime);
}
用户切换语言时,currentLocale 变化触发重渲染,所有 fmtInTz 重新执行——同一时刻的显示文字随语言变化。
3.3 世界时钟:秒级刷新
private timer = setInterval(() => { this.now = Date.now(); }, 1000);
function clockTz(code: string, tz: string, now: number): string {
try {
const fmt = new intl.DateTimeFormat(code, {
timeZone: tz,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
return fmt.format(new Date(now));
} catch (err) {
return '--:--:--';
}
}
@State now每秒更新 → 6 个城市的时间行全部重渲染;aboutToDisappear必须clearInterval,否则页面销毁后定时器空转(内存泄漏 + 无意义刷新);hour12: false强制 24 小时制,避免不同 locale 默认 12/24 小时制不一致造成对比困难。
3.4 日期风格对比
同一日期(Date.UTC(2026, 7, 19))在同一 locale 下用四种 dateStyle 展示:
function fmtStyle(code: string, style: string): string {
try {
const fmt = new intl.DateTimeFormat(code, {
dateStyle: style as 'full'
});
return fmt.format(new Date(Date.UTC(2026, 7, 19)));
} catch (err) {
return style;
}
}
| dateStyle | 中文 | 英文 |
|---|---|---|
'full' |
2026年8月19日星期三 | Wednesday, August 19, 2026 |
'long' |
2026年8月19日 | August 19, 2026 |
'medium' |
2026-8-19 | Aug 19, 2026 |
'short' |
26-8-19 | 8/19/26 |
注意 'short' 的差异最大:中文 26-8-19,英文 8/19/26——顺序完全不同(日月年 vs 月日年)。这就是"格式化交给 intl、UI 不拼日期"的原因。
3.5 时区换算器
固定一个 UTC 时刻(Date.UTC(2026, 7, 19, 7, 0)),选择目标城市后展示该时刻在该城市的本地表达:
Text(`→ ${fmtInTz(this.currentLocale, CITIES[this.convertIdx].tz, Date.UTC(2026, 7, 19, 7, 0))}`)
convertIdx 是 @State,点击城市标签即更新,换算结果实时刷新。
四、文案与降级
与 01 应用相同的 STRINGS + t() 三级降级方案,页面所有字段(会议/时钟/风格/换算)文案随语言切换。语言列表 5 种(简中/繁中/英/日/韩)。
五、ArkTS 兼容要点
timeZone/dateStyle等选项在intl.DateTimeFormat中均有定义,直接传字符串即可(SDK 声明已确认);setInterval返回值类型为number,存私有字段在aboutToDisappear清理;- UI 分支内不声明
const,所有派生数据抽成私有方法(meetingLocal()/clockTz()); ForEachkey 生成器用稳定值:会议m.id,城市c.key;intl调用全部 try/catch,异常时降级toString()。
六、性能与扩展
- 秒级刷新只影响 6 行时间文本,ArkUI 增量渲染开销可忽略;若城市上百,应只刷新变化的文本节点;
- 夏令时陷阱:直接存
UTC+8偏移的应用,在伦敦/纽约用户身上会算错 1 小时;用 IANA 时区名交给intl处理是唯一正确解; - 真实产品扩展:会议应存
startTime(UTC) + duration,参会人加"我的时区"字段;日历接入可用@ohos.calendarManager。
更多推荐




所有评论(0)