一、引言

在移动应用开发中,时间的显示是一个无处不在的需求。聊天消息的时间戳、日历应用中的日期选择、锁屏界面的时钟显示、多时区的国际会议安排——这些场景都离不开时间的展示。传统做法是创建一个 Text 组件,然后通过 setInterval 定时器周期性获取当前系统时间,再手动格式化字符串并更新 UI。这种方式不仅代码冗长,还存在定时器泄漏、刷新频率与 UI 渲染不同步、性能浪费等问题。

以"每秒刷新的数字时钟"为例,传统实现需要至少 20 行代码:声明定时器 ID、在 aboutToAppear 中启动定时器、格式化时/分/秒(处理前导零)、更新 @State 变量、在 aboutToDisappear 中清除定时器。任何一个环节出错——忘记清理定时器导致内存泄漏、格式化的字符串未正确补零、定时器延迟导致时间跳秒——都会带来 Bug。

而 HarmonyOS 提供了 TextClock 组件——一个内置时间获取和格式化逻辑的文本显示组件。它自动读取系统时间,按照指定的格式模式(如 HH:mm:ss 表示 24 小时制时:分:秒、yyyy-MM-dd EEEE 表示年-月-日+星期几)实时渲染,开发者无需手动管理定时器,无需编写时间格式化代码。

本文通过一个"数字时钟展廊"Demo 深入讲解 TextClock 组件的用法:12/24 小时制如何切换?格式字符串有哪些常用模式?如何实现多个时区的同时对比显示?以及 TextClock 与传统 Text + setInterval 方案的优劣对比。

阅读完本文,你将能够:

  • 使用 TextClock 组件替代 Text + setInterval 实现时间显示
  • 掌握常用日期时间格式模式(HH/hh/mm/ss/yyyy/MM/dd/EEEE/a)
  • 实现 12/24 小时制切换和秒数显示控制
  • 通过 timeZoneOffset 实现多时区时钟
  • 理解 TextClock 的内部刷新机制与最佳实践

二、TextClock 组件 API 总览

2.1 构造函数

TextClock(options?: TextClockOptions)
interface TextClockOptions {
  timeZoneOffset?: number; // 时区偏移量,单位:毫秒
}
参数 类型 默认值 说明
timeZoneOffset number 系统时区偏移 相对于 UTC 的毫秒偏移量

timeZoneOffset 的正值表示 UTC 以东的时区,负值表示 UTC 以西。例如北京时间(UTC+8)对应的偏移量为 8 * 60 * 60 * 1000 = 28800000(毫秒),纽约时间(UTC-5)对应的偏移量为 -5 * 60 * 60 * 1000 = -18000000(毫秒)。如果不传入此参数,TextClock 默认使用系统当前时区。

2.2 核心方法:format

.format(format: string): TextClockAttribute

format 是 TextClock 最重要的方法,它接受一个日期时间格式模式字符串,决定了时间的显示格式。格式字符串使用 Unicode CLDR(Common Locale Data Repository)日期格式模式中的字段符号:

符号 含义 示例输出
yyyy 四位年份 2026
yy 两位年份 26
MM 两位月份(01-12) 07
M 月份(1-12) 7
dd 两位日期(01-31) 04
d 日期(1-31) 4
HH 24 小时制小时(00-23) 14
hh 12 小时制小时(01-12) 02
mm 分钟(00-59) 30
ss 秒(00-59) 45
EEEE 完整星期名称 星期五
E 缩写星期名称 周五
a AM/PM 标记 下午

通过组合这些符号,可以构造出各种常见的时间格式:

TextClock().format('HH:mm:ss')           // 14:30:45(24 小时制)
TextClock().format('hh:mm:ss a')         // 02:30:45 下午(12 小时制)
TextClock().format('yyyy-MM-dd EEEE')    // 2026-07-04 星期五
TextClock().format('yyyy-MM-dd HH:mm:ss') // 2026-07-04 14:30:45
TextClock().format('MM-dd HH:mm')        // 07-04 14:30

2.3 支持的通用属性

TextClock 继承自 Text 组件,因此支持 Text 的大多数通用属性方法:

// 字体大小
.fontSize(value: number | string | Resource)

// 字体颜色
.fontColor(value: ResourceColor)

// 字体粗细
.fontWeight(value: number | FontWeight | string)

// 字体系列
.fontFamily(value: string | Resource)

// 文本装饰线
.decoration(value: { type: TextDecorationType; color?: ResourceColor })

// 文本阴影
.textShadow(value: ShadowOptions | Array<ShadowOptions>)

注意:TextClock 不支持 .textAlign() 方法——如果需要对齐控制,应将 TextClock 放入一个设置了对齐方式的容器(如 Column({ align: HorizontalAlign.Center }))中。

2.4 TextClock 的刷新机制

TextClock 内部维护了一个与系统时钟同步的刷新定时器。它不依赖于 JavaScript 层的 setInterval,而是注册了一个系统级的时钟回调。这意味着:

  1. 精确同步:TextClock 的刷新与系统时间变化精确同步,不会因为 JavaScript 线程阻塞而出现延迟
  2. 自动启停:当 TextClock 组件挂载到组件树时自动开始计时,卸载时自动停止——无需 aboutToAppear / aboutToDisappear 中的手动管理
  3. 省电优化:当页面不可见(如被其他页面覆盖)时,系统会自动降低 TextClock 的刷新频率
    在这里插入图片描述

三、Demo 设计:数字时钟展廊

3.1 功能概述

Demo 是一个"数字时钟展廊",模拟锁屏时钟、智能手表表盘、会议多时区工具等场景:

  1. 主时钟区:大号数字时钟(52sp),深色背景,模拟锁屏或表盘风格
  2. 格式控制:一键切换 12/24 小时制,Toggle 控制秒数显示
  3. 格式预览画廊:四组 TextClock 并排展示,对比不同的格式模式
  4. 多时区对比:北京、东京、纽约、伦敦四个时区的时钟同时显示

3.2 交互点

# 交互 说明
1 12/24 小时制切换 所有 TextClock 同步切换小时显示格式
2 显示/隐藏秒 Switch 开关控制所有时钟的秒数是否显示
3 格式预览对比 同时展示完整日期时间/仅时间/仅日期/简写四种格式
4 多时区对比 北京、东京、纽约、伦敦四地时钟同时显示

四、完整代码实现

在这里插入图片描述

4.1 状态与格式构建

@State is24Hour: boolean = true;
@State showSeconds: boolean = true;
@State timeFormat: string = 'HH:mm:ss';
@State shortFormat: string = 'HH:mm';
@State fullFormat: string = 'yyyy-MM-dd HH:mm:ss';

aboutToAppear(): void {
  this.updateFormats();
}

updateFormats(): void {
  const h = this.is24Hour ? 'HH' : 'hh';
  const s = this.showSeconds ? ':ss' : '';
  const suffix = this.is24Hour ? '' : ' a';
  this.timeFormat = h.concat(':mm', s, suffix);
  this.shortFormat = h.concat(':mm', suffix);
  this.fullFormat = 'yyyy-MM-dd '.concat(h, ':mm', s, suffix);
}

核心设计思路:格式字符串不作为 getter 每次重新计算,而是存储在 @State 变量中。当用户切换 12/24 小时制或秒数显示时,updateFormats() 一次性更新所有格式字符串,所有绑定到这些 @State 的 TextClock 自动刷新显示。

h 变量根据 is24Hour 决定用 HH(24 小时制)还是 hh(12 小时制)。suffix 在 12 小时制时追加 a 以显示 AM/PM 标记。s 根据 showSeconds 决定是否保留 :ss

4.2 主时钟区(Hero Clock)

Column() {
  TextClock({ timeZoneOffset: 0 })
    .format(this.timeFormat)
    .fontSize(52)
    .fontColor('#FFFFFF')
    .fontWeight(FontWeight.Bold)
    .fontFamily('monospace')
    .margin({ bottom: 8 })

  TextClock({ timeZoneOffset: 0 })
    .format('yyyy-MM-dd EEEE')
    .fontSize(16)
    .fontColor('#FFFFFF88')
    .fontFamily('monospace')
}
.width('100%')
.padding({ top: 40, bottom: 40 })
.borderRadius(16)
.backgroundColor('#1a1a2e')

主时钟采用深色背景(#1a1a2e),大号白色等宽字体时间(52sp),下方显示半透明的日期和星期。两个 TextClock 都使用默认的 timeZoneOffset: 0(即系统时区)。

52sp 是一个精心选择的字号:在普通手机(约 360dp 宽)上,“HH:mm:ss” 大约占据 260dp 宽度,留出左右各约 50dp 的留白;在更大屏幕的折叠屏或平板上,大字号更能体现"时钟展廊"的展示感。

4.3 格式控制区

Row() {
  Text('时间制式')
    .fontSize(14).fontColor('#1a1a2e')
    .fontWeight(FontWeight.Medium).layoutWeight(1)
  Row() {
    Text('24 小时')
      .fontSize(12)
      .fontColor(this.is24Hour ? '#FFFFFF' : '#1a1a2e')
      .fontWeight(this.is24Hour ? FontWeight.Bold : FontWeight.Normal)
      .padding({ top: 5, bottom: 5, left: 12, right: 12 })
      .borderRadius(10)
      .backgroundColor(this.is24Hour ? '#1677FF' : '#F2F3F5')
      .onClick(() => {
        if (!this.is24Hour) {
          this.is24Hour = true;
          this.updateFormats();
        }
      })
    Text('12 小时')
      // ... 同样的样式,反向选中状态
      .onClick(() => {
        if (this.is24Hour) {
          this.is24Hour = false;
          this.updateFormats();
        }
      })
  }
}
// ... 分隔线 ...
Row() {
  Text('显示秒数')
  Toggle({ type: ToggleType.Switch, isOn: this.showSeconds })
    .selectedColor('#1677FF')
    .onChange((value: boolean) => {
      this.showSeconds = value;
      this.updateFormats();
    })
}

控制区使用两个分段按钮切换时间制式,使用 Switch 控制秒数显示。每个状态变化都触发 updateFormats() 重构所有格式字符串。

关于 onClick 中的 if 判断:它防止了重复点击同一选项时的无效更新——点击已选中的选项不会触发任何操作,仅点击未选中选项时才修改状态并更新格式。

4.4 格式预览画廊

@Builder
formatCard(label: string, fmt: string, color: string) {
  Row() {
    Text(label)
      .fontSize(13).fontColor('#9999AA').width(90)

    TextClock({ timeZoneOffset: 0 })
      .format(fmt)
      .fontSize(20)
      .fontColor(color)
      .fontWeight(FontWeight.Bold)
      .fontFamily('monospace')

    Blank()
  }
  .width('100%')
  .padding({ top: 12, bottom: 12, left: 4, right: 4 })
  .border({ width: { bottom: 0.5 }, color: '#F2F3F5' })
}

// 在 build 中使用:
this.formatCard('完整日期时间', this.fullFormat, '#1a1a2e')
this.formatCard('仅时间', this.timeFormat, '#1677FF')
this.formatCard('仅日期', 'yyyy-MM-dd EEEE', '#52C41A')
this.formatCard('简写时间', this.shortFormat, '#FF9800')

四个格式卡片使用 @Builder 复用布局。format 参数绑定到 @State 变量(this.timeFormat 等),当用户在控制区切换 12/24 小时制或秒数时,这些 TextClock 会立即响应变化。

每组使用不同的字体颜色进行视觉区分:深色用于完整日期时间(主信息),蓝色用于时间(强调),绿色用于日期(辅助),橙色用于简写(提示)。

4.5 多时区对比

@Builder
timezoneCard(city: string, offset: number, color: string) {
  Column() {
    Text(city)
      .fontSize(11).fontColor('#9999AA').margin({ bottom: 6 })

    TextClock({ timeZoneOffset: offset })
      .format(this.timeFormat)
      .fontSize(24)
      .fontColor(color)
      .fontWeight(FontWeight.Bold)
      .fontFamily('monospace')
      .margin({ bottom: 4 })

    TextClock({ timeZoneOffset: offset })
      .format('MM-dd EEEE')
      .fontSize(11)
      .fontColor('#BBBBCC')
  }
  .layoutWeight(1)
  .padding({ top: 16, bottom: 16 })
  .borderRadius(12)
  .backgroundColor('#FAFBFC')
}

// 在 build 中使用:
Row() {
  this.timezoneCard('北京 (UTC+8)', 28800000, '#1677FF')
  this.timezoneCard('东京 (UTC+9)', 32400000, '#FF4D4F')
}
Row() {
  this.timezoneCard('纽约 (UTC-5)', -18000000, '#52C41A')
  this.timezoneCard('伦敦 (UTC+0)', 0, '#FF9800')
}

每个时区卡片内包含两个 TextClock:上方显示当前时间(使用 this.timeFormat,跟随 12/24h 和秒数设置),下方显示月-日+星期。timeZoneOffset 是相对于 UTC 的毫秒偏移量。

时区偏移量的计算公式为:偏移小时数 × 60 × 60 × 1000。例如:

城市 UTC 偏移 timeZoneOffset 值
北京 UTC+8 28800000
东京 UTC+9 32400000
纽约 UTC-5 -18000000
伦敦 UTC+0 0

五、关键技术点详解

5.1 格式字符串的字段符号规则

TextClock 的 .format() 方法使用的格式模式遵循 Unicode CLDR(Common Locale Data Repository)标准。以下是每个字段符号的完整行为说明:

年份符号(y)

  • yyyy 输出四位年份(如 2026),yy 输出后两位(如 26
  • 如果只写一个 y,会输出完整年份但不去补零

月份符号(M)

  • MM 输出两位月份(01-12),不足两位前面补零
  • M 输出实际数字(1-12),不补零

日期符号(d)

  • dd 输出两位日期(01-31),不足两位前面补零
  • d 输出实际数字(1-31),不补零

小时符号(H 和 h)

  • HH 是 24 小时制,范围 00-23,不足两位补零
  • hh 是 12 小时制,范围 01-12,不足两位补零
  • 使用 hh 时通常配合 a(AM/PM 标记)使用,否则无法区分上午和下午

分钟和秒(m 和 s)

  • mm 输出两位分钟(00-59)
  • ss 输出两位秒数(00-59)
  • 这两个符号始终补零,基本上总是写两位

星期名称(E)

  • EEEE 输出完整星期名称(如"星期五")
  • E 输出缩写(如"周五")

AM/PM 标记(a)

  • 仅在 12 小时制(hh)下有意义
  • 中文环境下通常输出"上午"或"下午"

5.2 12/24 小时制切换的实现思路

Demo 中的核心切换逻辑是 updateFormats() 方法。这里有一个值得深入的设计决策:格式字符串全部存储在 @State 变量中,而非实时计算

方案 A(getter 方式):

get timeFormat(): string {
  const h = this.is24Hour ? 'HH' : 'hh';
  return h + ':mm' + (this.showSeconds ? ':ss' : '');
}

方案 B(@State 方式,Demo 采用):

@State timeFormat: string = 'HH:mm:ss';
updateFormats(): void {
  const h = this.is24Hour ? 'HH' : 'hh';
  this.timeFormat = h.concat(':mm', this.showSeconds ? ':ss' : '', suffix);
}

方案 B 的优势在于:格式字符串的生成逻辑集中在 updateFormats() 中,不依赖 getter 的计算。在 ArkTS 中,getter 虽然是响应式的,但其返回值类型推断和字符串动态拼接可能存在运行时问题(尤其在模板字符串支持受限的版本)。将格式字符串存储为 @State 变量是最稳妥的做法。

此外,Demo 使用 .concat() 而非模板字符串(`${h}:mm${s}`)拼接格式字符串。这是为了兼容 ArkTS 对原生字符串操作的限制——.concat() 是 String 的标准方法,在 ArkTS 中完全支持。

5.3 时区偏移量的计算与应用

timeZoneOffset 参数接受一个数字——相对于 UTC(协调世界时)的毫秒偏移量。这个概念需要区分子午线零度时刻:UTC+8 意味着当地时间 = UTC + 8 小时,所以偏移量 = 8 × 3600 × 1000 = 28800000 毫秒(正值)。UTC-5 意味着当地时间 = UTC - 5 小时,偏移量 = -5 × 3600 × 1000 = -18000000 毫秒(负值)。

常用时区偏移量速查表:

时区 偏移小时 偏移量(毫秒)
UTC+0(伦敦) 0 0
UTC+1(巴黎) +1 3600000
UTC+3(莫斯科) +3 10800000
UTC+5:30(孟买) +5.5 19800000
UTC+8(北京) +8 28800000
UTC+9(东京) +9 32400000
UTC-5(纽约) -5 -18000000
UTC-8(洛杉矶) -8 -28800000
UTC-10(夏威夷) -10 -36000000

注意:timeZoneOffset 不会自动处理夏令时(DST)。如果应用需要精确处理夏令时切换(如伦敦夏季是 UTC+1 而非 UTC+0),需要在应用层面手动判断当前日期是否处于夏令时期间,并调整偏移量。对于不需要精确到小时级别的多时区展示场景(如 Demo 中的"展廊"功能),使用固定的标准时间偏差值即可。

5.4 TextClock 与 Text + setInterval 的对比

为了理解 TextClock 的设计价值,下面的对比表格和代码示例能直观展示两种方案的差异:

维度 Text + setInterval TextClock
实现代码量 ~25 行 ~5 行
定时器管理 手动创建和清理 自动管理
内存泄漏风险 高(遗忘 clearInterval)
时间同步精确度 与 JS 事件循环相关 系统级时钟同步
页面不可见时 继续刷新(浪费 CPU) 自动降频
时区支持 需手动计算 Date 对象 内置 timeZoneOffset
格式切换 需手动处理字符串 format 方法直接传入

传统方式实现一个数字时钟:

// Text + setInterval:繁琐且容易出错的传统方案
@Component
struct TraditionalClock {
  @State timeStr: string = '';
  private timerId: number = -1;

  aboutToAppear(): void {
    this.updateTime();
    this.timerId = setInterval(() => { this.updateTime(); }, 1000);
  }

  aboutToDisappear(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
  }

  updateTime(): void {
    const now = new Date();
    const h = now.getHours().toString().padStart(2, '0');
    const m = now.getMinutes().toString().padStart(2, '0');
    const s = now.getSeconds().toString().padStart(2, '0');
    this.timeStr = `${h}:${m}:${s}`;
  }

  build() {
    Text(this.timeStr).fontSize(48).fontColor('#FFFFFF');
  }
}

TextClock 方式:

// TextClock:一行 format 替换了 20+ 行定时器逻辑
TextClock()
  .format('HH:mm:ss')
  .fontSize(48)
  .fontColor('#FFFFFF')

代码量从约 25 行缩减到 5 行,且不需要担心定时器的清理和内存泄漏问题。更重要的是,TextClock 使用系统级时钟回调而非 JavaScript 的 setInterval,这意味着即使 JavaScript 线程正在处理其他繁重任务,TextClock 的显示也不会出现"跳秒"(即时间在某一秒停留过久然后跳过下一秒)。

5.5 format 方法的响应式更新

format 方法接受一个字符串参数。Demo 中这个参数来自 @State timeFormat 等状态变量。当用户切换 12/24 小时制时,updateFormats() 方法更新这些 @State 变量,所有依赖这些变量的 TextClock 组件的 format 参数自动更新。

这里有一个关键细节:format 方法的参数更新不会导致 TextClock 组件"重启"。它的内部计时状态在 format 变更时保持不变,只有下一次系统时钟回调来临时才使用新的格式进行渲染。这意味着在切换格式的瞬间,TextClock 不会短暂闪烁或显示空白——它平滑地从旧格式过渡到新格式。

六、运行效果

6.1 初始状态

进入"数字时钟展廊"页面,主时钟区以深色背景展示大号白色数字时钟(52sp),格式为 24 小时制 HH:mm:ss(如 14:30:45);下方显示半透明日期 yyyy-MM-dd EEEE(如 2026-07-04 星期五)。

格式预览画廊展示四组不同配色的 TextClock:

  • 完整日期时间(深色字):2026-07-04 14:30:45
  • 仅时间(蓝色字):14:30:45
  • 仅日期(绿色字):2026-07-04 星期五
  • 简写时间(橙色字):14:30

多时区对比区显示四个时区卡片:北京 14:30:45,东京 15:30:45,纽约 01:30:45,伦敦 06:30:45

6.2 切换 12 小时制

点击"12 小时"按钮,所有时钟从 HH 切换为 hh 并追加 AM/PM 标记。主时钟从 14:30:45 变为 02:30:45 下午,格式预览和多时区时钟同步切换。

6.3 隐藏秒数

关闭"显示秒数"开关,所有时钟格式中的 :ss 部分消失。主时钟从 14:30:45 变为 14:30,时区时钟同理。这种快速切换展示了 format 的响应式更新能力。

6.4 多时区对比观察

观察四个时区的小时差异:北京和东京相差 1 小时,纽约比北京晚 13 小时(或早 11 小时,取决于夏令时),伦敦比北京晚 8 小时。这验证了 timeZoneOffset 参数的准确性。

七、总结

本文通过一个"数字时钟展廊"的实战 Demo,深入讲解了 HarmonyOS TextClock 文本时钟组件的核心用法:

  1. 构造函数timeZoneOffset 参数设置时区偏移量(毫秒),默认使用系统时区
  2. format 方法:使用 Unicode CLDR 格式模式(HH/mm/ss/yyyy/MM/dd/EEEE/a)定制显示格式
  3. 12/24 小时制切换:通过 HH vs hh + a 控制小时显示方式,格式字符串存储在 @State 变量中实现响应式切换
  4. 多时区对比:传入不同的 timeZoneOffset 值,多个 TextClock 可同时展示不同时区的时间
  5. 与传统方案对比:TextClock 用 5 行代码替代了 25 行的 setInterval 方案,且避免了定时器泄漏和跳秒问题

TextClock 看似简单——它就是一个显示时间的文本组件。但正是因为它将"获取时间→格式化→渲染→清理"这一整套逻辑封装进了组件内部,开发者才能用寥寥几行代码实现专业的时钟功能。这种"让组件做它该做的事"的设计哲学,正是 ArkUI 声明式框架的核心价值。

从锁屏时钟到多时区会议工具,从计时器应用到日程日历,TextClock 以简洁优雅的 API 覆盖了时间显示的所有场景。希望本文能帮助你在实际项目中高效运用 TextClock 组件。


本文基于 HarmonyOS NEXT API 24 编写,代码经 DevEco Studio 6.1.1 编译验证通过。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐