日期时间界面容易把“读取真实系统时钟、选择时区、格式化和显示”写在一起。结果是测试每次都不同,替换时间来源也必须修改界面。本例把变化隔离在一个小接口中,并保留原笔记中的提供者、格式化器和显示三层关系。
1. 先写清接口契约
跳转到“1. 先写清接口契约”本例的 DateTimeProvider::nowUtc() 返回一个有效的 UTC 时间值。调用是同步的;实现必须快速返回,不在 GUI 线程内访问网络。
DateTimeFormatter 借用提供者,不拥有它。构造时用引用表达“必须提供对象”,装配方保证提供者比格式化器活得更久。这里不需要共享所有权,因此不为了“保险”而引入 shared_ptr。
flowchart LR A[系统时间提供者] --> C[时间接口] B[固定时间测试替身] --> C C --> D[格式化器] D --> E[标签或控制台]箭头表示信息流。代码依赖上,格式化器只声明它需要的时间接口,不直接调用系统时间函数。
2. 可运行代码与固定输入测试
跳转到“2. 可运行代码与固定输入测试”以下 Qt 6 Core 程序没有窗口,不依赖本机时区来判断断言结果:
#include <QCoreApplication>#include <QDateTime>#include <QDebug>#include <QString>#include <cassert>
class DateTimeProvider {public: virtual ~DateTimeProvider() = default; virtual QDateTime nowUtc() const = 0;};
class SystemDateTimeProvider final : public DateTimeProvider {public: QDateTime nowUtc() const override { return QDateTime::currentDateTimeUtc(); }};
class FixedDateTimeProvider final : public DateTimeProvider {public: explicit FixedDateTimeProvider(QDateTime value) : value_(value.toUTC()) { assert(value_.isValid()); } QDateTime nowUtc() const override { return value_; }private: QDateTime value_;};
class DateTimeFormatter {public: explicit DateTimeFormatter(const DateTimeProvider &provider) : provider_(provider) {}
QString text() const { return provider_.nowUtc().toUTC().toString("yyyy-MM-dd HH:mm:ss 'UTC'"); }private: const DateTimeProvider &provider_;};
int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); const auto fixed = QDateTime::fromString("2026-10-02T08:30:00Z", Qt::ISODate); FixedDateTimeProvider testClock(fixed); DateTimeFormatter testFormatter(testClock); assert(testFormatter.text() == "2026-10-02 08:30:00 UTC"); qInfo().noquote() << testFormatter.text();
SystemDateTimeProvider systemClock; DateTimeFormatter actualFormatter(systemClock); qInfo().noquote() << actualFormatter.text();}第一行应固定为 2026-10-02 08:30:00 UTC,第二行取决于实际系统时间。测试断言用于验证例子;生产代码接收外部日期时,不能依赖会在 Release 构建中关闭的 assert 校验输入,应显式返回错误或采用项目约定的异常机制。
最小构建文件:
cmake_minimum_required(VERSION 3.21)project(ClockDemo LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)find_package(Qt6 REQUIRED COMPONENTS Core)add_executable(clock-demo main.cpp)target_link_libraries(clock-demo PRIVATE Qt6::Core)currentDateTimeUtc()、toUTC()、ISO 字符串解析与格式化见 QDateTime 文档。格式化是在表现层选择表示方式,不能通过给字符串追加 UTC 就把一个本地时间转换成 UTC。
3. 接入 Qt 界面
跳转到“3. 接入 Qt 界面”在 Widgets 程序中先创建 QApplication,再创建提供者、格式化器和窗口,使销毁顺序与借用关系相容:
SystemDateTimeProvider provider;DateTimeFormatter formatter(provider);QLabel label(formatter.text());label.show();// QApplication::exec() 必须在这些局部对象离开作用域前运行。上面是嵌入已有 Widgets main() 的片段,需包含 QLabel 并链接 Qt6::Widgets。仅创建一次标签会显示一次时间;需要持续更新时,用定时器定期重新调用 formatter.text()。不需要让每一个普通类都继承 QObject,也不需要在未使用信号、槽或其他元对象功能时额外添加 Q_OBJECT。
4. 扩展时保持原有约定
跳转到“4. 扩展时保持原有约定”- 更多格式:将格式或时区作为明确的构造参数,分别测试;不要隐式读全局设置。
- 网络时间:把网络同步与快速读取缓存分开,不在
nowUtc()中执行阻塞请求。 - 运行中换提供者:当前引用设计固定绑定对象,不能声称已经支持动态切换。确有需求时引入可切换的代理,并定义生命周期与同步方式。
- 计量时长:日历时钟会受校时影响;测量运行耗时应使用
QElapsedTimer等合适的单调计时工具。
本页删除原记录中重复出现的两套相同头文件,保留系统时间、可替换来源、格式化、UI 装配和扩展需求,并补上实际可运行的固定时间测试与借用生命周期约定。