日期与时间处理
# 04.日期与时间处理
# 目录介绍
# 4.1 时间类
Qt 提供了多个类用于处理日期、时间和时区,包括 QDate、QTime、QDateTime 和 QTimeZone。
- QDate:日期处理
- QTime:时间处理
- QDateTime:日期时间处理
- QTimeZone:时区支持
# 4.1.1 QDate
QDate 用于处理日期(年、月、日),不包含时间信息。
(1)基本用法
#include <QDate>
#include <QDebug>
int main() {
// 创建日期
QDate date(2023, 10, 5);
// 获取日期信息
qDebug() << "Year:" << date.year(); // 2023
qDebug() << "Month:" << date.month(); // 10
qDebug() << "Day:" << date.day(); // 5
// 格式化日期
qDebug() << "Formatted date:" << date.toString("yyyy-MM-dd"); // 2023-10-05
// 计算日期
QDate nextWeek = date.addDays(7);
qDebug() << "Next week:" << nextWeek.toString("yyyy-MM-dd"); // 2023-10-12
return 0;
}
(2)常用方法
isValid():检查日期是否有效。daysInMonth():返回当前月份的天数。daysInYear():返回当前年份的天数。dayOfWeek():返回星期几(1 = 星期一,7 = 星期日)。
# 4.1.2 QTime
QTime 用于处理时间(时、分、秒、毫秒),不包含日期信息。
(1)基本用法
#include <QTime>
#include <QDebug>
int main() {
// 创建时间
QTime time(14, 30, 45);
// 获取时间信息
qDebug() << "Hour:" << time.hour(); // 14
qDebug() << "Minute:" << time.minute(); // 30
qDebug() << "Second:" << time.second(); // 45
// 格式化时间
qDebug() << "Formatted time:" << time.toString("HH:mm:ss"); // 14:30:45
// 计算时间
QTime laterTime = time.addSecs(3600); // 增加 1 小时
qDebug() << "Later time:" << laterTime.toString("HH:mm:ss"); // 15:30:45
return 0;
}
(2)常用方法
isValid():检查时间是否有效。msecsSinceStartOfDay():返回从当天开始到当前时间的毫秒数。secsTo():计算两个时间之间的秒数差。
# 4.1.3 QDateTime
QDateTime 用于处理日期和时间,结合了 QDate 和 QTime 的功能。
(1)基本用法
#include <QDateTime>
#include <QDebug>
int main() {
// 创建日期时间
QDateTime dateTime(QDate(2023, 10, 5), QTime(14, 30, 45));
// 获取日期和时间信息
qDebug() << "Date:" << dateTime.date().toString("yyyy-MM-dd"); // 2023-10-05
qDebug() << "Time:" << dateTime.time().toString("HH:mm:ss"); // 14:30:45
// 格式化日期时间
qDebug() << "Formatted datetime:" << dateTime.toString("yyyy-MM-dd HH:mm:ss"); // 2023-10-05 14:30:45
// 计算日期时间
QDateTime laterDateTime = dateTime.addDays(1).addSecs(3600); // 增加 1 天和 1 小时
qDebug() << "Later datetime:" << laterDateTime.toString("yyyy-MM-dd HH:mm:ss"); // 2023-10-06 15:30:45
return 0;
}
(2)常用方法
isValid():检查日期时间是否有效。toTime_t():将日期时间转换为 Unix 时间戳。fromTime_t():从 Unix 时间戳创建日期时间。setTimeZone():设置时区(需要 Qt 5.2 及以上版本)。
# 4.1.4 QTimeZone
QTimeZone 用于处理时区信息,支持全球时区的转换和计算。
(1)基本用法
#include <QDateTime>
#include <QTimeZone>
#include <QDebug>
int main() {
// 创建时区
QTimeZone timeZone("America/New_York");
// 创建日期时间并设置时区
QDateTime dateTime(QDate(2023, 10, 5), QTime(14, 30, 45), timeZone);
// 转换为 UTC 时间
QDateTime utcDateTime = dateTime.toUTC();
qDebug() << "Local time:" << dateTime.toString("yyyy-MM-dd HH:mm:ss"); // 2023-10-05 14:30:45
qDebug() << "UTC time:" << utcDateTime.toString("yyyy-MM-dd HH:mm:ss"); // 2023-10-05 18:30:45
// 获取时区信息
qDebug() << "Time zone ID:" << timeZone.id(); // America/New_York
qDebug() << "Time zone offset:" << timeZone.offsetFromUtc(dateTime); // -14400 (秒)
return 0;
}
(2)常用方法
isValid():检查时区是否有效。offsetFromUtc():返回时区与 UTC 的偏移量(秒)。id():返回时区的 ID(如 "America/New_York")。availableTimeZoneIds():返回所有可用的时区 ID。
# 4.2 计时器
# 4.2.1 计时器分类
Qt 提供了多种计时器类,用于实现定时任务、高精度计时和截止时间管理。
QTimer:用于定时任务,支持周期性或单次触发。QElapsedTimer:用于高精度计时,适合测量代码执行时间。QDeadlineTimer:用于管理截止时间,适合超时控制。(Qt 6)
# 4.3 QTimer
QTimer 是一个定时器类,用于在指定的时间间隔触发信号。
QTimer 是 Qt 框架中用于定时器功能的核心类,它允许你在指定的时间间隔内触发事件或执行代码。
# 4.3.1 基本用法
#include <QTimer>
#include <QDebug>
#include <QCoreApplication>
class MyClass : public QObject {
Q_OBJECT
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {
// 创建定时器
QTimer *timer = new QTimer(this);
// 连接定时器的 timeout 信号到槽函数
connect(timer, &QTimer::timeout, this, &MyClass::onTimeout);
// 启动定时器,每隔 1 秒触发一次
timer->start(1000);
}
private slots:
void onTimeout() {
qDebug() << "Timer triggered at:" << QTime::currentTime().toString("HH:mm:ss");
}
};
int main(int argc, char *argv[]){
QCoreApplication app(argc, argv);
MyClass myObject;
return app.exec();
}
# 4.3.2 QTimer用法
start(int msec):启动定时器,msec为时间间隔(毫秒)。stop():停止定时器。setSingleShot(bool singleShot):设置是否为单次触发。isActive():检查定时器是否正在运行。
(1) 基本用法
#include <QTimer>
#include <QDebug>
QTimer *timer = new QTimer(this);
// 连接定时器的 timeout() 信号到槽函数
connect(timer, &QTimer::timeout, this, []() {
qDebug() << "Timer triggered!";
});
// 启动定时器,每隔 1000 毫秒(1 秒)触发一次
timer->start(1000);
(2) 停止定时器
timer->stop(); // 停止定时器
(3) 单次触发定时器
timer->setSingleShot(true); // 设置为单次触发
timer->start(1000); // 1 秒后触发一次,然后停止
// 使用静态方法创建单次定时器
QTimer::singleShot(2000, this, {
qDebug() << "This will run once after 2 seconds";
});
(4) 检查定时器状态
if (timer->isActive()) {
qDebug() << "Timer is running";
} else {
qDebug() << "Timer is not running";
}
# 4.3.3 QTimer作用
(1) 定时触发任务:QTimer 可以在指定的时间间隔内触发 timeout() 信号,用于执行周期性任务,例如:
- 更新 UI。
- 轮询数据。
- 执行定时操作。
(2) 单次延迟执行:通过 setSingleShot(true),QTimer 可以用于延迟执行某个任务,例如:
- 延迟加载数据。
- 延迟显示提示信息。
(3) 事件驱动:QTimer 依赖于 Qt 的事件循环,因此可以与其他事件驱动的功能无缝集成。
# 4.3.4 QTimer原理
(1) 底层实现:QTimer 基于操作系统的定时器机制(如 Linux 的 timerfd 或 Windows 的 SetTimer)。
- 在 Qt 的事件循环中,
QTimer会注册一个定时器事件,当时间到达时,事件循环会触发timeout()信号。
(2) 事件循环依赖:QTimer 必须运行在 Qt 的事件循环中。如果没有事件循环,定时器将无法工作。例如:
- 在主线程中,
QApplication::exec()启动了事件循环。 - 在子线程中,需要调用
QThread::exec()。
(3) 精度问题:QTimer 的精度取决于操作系统和硬件,通常为毫秒级别。
- 如果需要高精度定时器,可以考虑使用平台特定的 API(如
nanosleep或QueryPerformanceCounter)。
# 4.3.5 高级用法
(1) 动态调整定时器间隔
timer->setInterval(500); // 将定时器间隔调整为 500 毫秒
(2) 多定时器管理
如果需要管理多个定时器,可以使用 QTimer 的 timerId() 方法:
int timerId = timer->timerId();
(3) 在子线程中使用 QTimer
在子线程中使用 QTimer 时,需要确保子线程有事件循环:
QThread *thread = new QThread;
QTimer *timer = new QTimer;
timer->moveToThread(thread);
connect(thread, &QThread::started, [timer]() {
timer->start(1000);
});
connect(timer, &QTimer::timeout, []() {
qDebug() << "Timer triggered in thread!";
});
thread->start();
# 4.4 QElapsedTimer
QElapsedTimer 用于高精度计时,适合测量代码执行时间。
# 4.4.1 基本用法
#include <QElapsedTimer>
#include <QDebug>
#include <QThread>
int main() {
QElapsedTimer timer;
timer.start(); // 开始计时
// 模拟耗时操作
QThread::msleep(1000); // 休眠 1 秒
// 获取经过的时间
qint64 elapsed = timer.elapsed(); // 毫秒
qDebug() << "Elapsed time:" << elapsed << "ms";
// 获取纳秒级时间
qint64 nsecsElapsed = timer.nsecsElapsed(); // 纳秒
qDebug() << "Elapsed time in nanoseconds:" << nsecsElapsed << "ns";
return 0;
}
# 4.4.2 常用方法
start():开始计时。elapsed():返回从开始计时到现在的毫秒数。nsecsElapsed():返回从开始计时到现在的纳秒数。hasExpired(qint64 timeout):检查是否已经超过指定的时间(毫秒)。
# 4.5 QDeadlineTimer
QDeadlineTimer 用于管理截止时间,适合需要设置超时时间的场景。
# 4.5.1 基本用法
#include <QDeadlineTimer>
#include <QDebug>
#include <QThread>
int main() {
// 设置截止时间为 2 秒后
QDeadlineTimer deadline(2000); // 2000 毫秒
// 模拟耗时操作
QThread::msleep(1000); // 休眠 1 秒
// 检查是否超时
if (deadline.hasExpired()) {
qDebug() << "Deadline has expired!";
} else {
qDebug() << "Time remaining:" << deadline.remainingTime() << "ms";
}
return 0;
}
# 4.5.2 常用方法
setRemainingTime(qint64 msec):设置剩余时间(毫秒)。remainingTime():返回剩余时间(毫秒)。hasExpired():检查是否已经超时。isForever():检查是否为无限时间。
# 4.X QDateTime 内部存储与陷阱
# 时区转换
QDateTime dt(QDate(2025, 7, 1), QTime(12, 0, 0), Qt::UTC);
// 内部: m_msecs = 1751371200000 (自1970-01-01 UTC的毫秒数)
dt.toLocalTime();
// → 查zoneinfo: /usr/share/zoneinfo/Asia/Shanghai → +8h
// → 输出 2025-07-01 20:00:00
⚠️ 嵌入式陷阱:如果 /usr/share/zoneinfo 不存在
→ toLocalTime() 静默返回 UTC(无错误!)→ 界面显示错误时间
# 性能对比
| 定时器 | 底层 | 精度 | 是否耦合事件循环 |
|---|---|---|---|
| QTimer::singleShot | 事件循环 | ~5ms | 是 |
| QBasicTimer | QObject内部 | ~1ms | 是 |
| QElapsedTimer | 硬件时钟 | 纳秒级 | 否 |
| QDeadlineTimer | futex | 毫秒级 | 否 |
// QElapsedTimer——不触发系统调用
QElapsedTimer ticker;
ticker.start();
while (running) {
if (ticker.elapsed() % 100 < 20) process(); // 粗略100ms判断
}
// QDateTime::currentMSecsSinceEpoch() 每次都是 gettimeofday() 系统调用
# 4.6 QDateTime 内部存储与陷阱
# 4.6.1 内部数据结构
Qt 6 中 QDateTime 的核心存储是一个 64 位毫秒值 + 时区偏移:
// QDateTime 内部(简化)
class QDateTime {
qint64 m_msecs; // 自 1970-01-01T00:00:00.000 UTC 的毫秒数
QTimeZone m_timeZone; // Qt 6 始终存储完整时区信息
// Qt 5 只存偏移量 → 无法正确表示 DST(夏令时)
};
// 构造过程:
QDateTime dt(QDate(2025, 7, 1), QTime(12, 0, 0), Qt::UTC);
// → m_msecs = (2025-07-01T12:00:00 UTC - 1970-01-01 UTC) = 1751371200000 ms
QDateTime dt(QDate(2025, 7, 1), QTime(12, 0, 0), Qt::LocalTime);
// → m_msecs = 先按 local 转 UTC: 12:00 - 8h = 04:00 UTC
// → m_msecs = 1751342400000 ms
# 4.6.2 嵌入式时区陷阱
// ⚠️ 如果 /usr/share/zoneinfo 不存在:
QDateTime dt = QDateTime::currentDateTime();
dt.toLocalTime(); // 静默返回 UTC——不报错!
// → 界面可能显示错误时间却没有任何警告
// ✅ 防御策略:验证时区
if (QTimeZone::systemTimeZone().isValid()) {
updateClock();
} else {
qWarning() << "No timezone data found! Using UTC";
setTimeZoneFixed(QTimeZone::utc());
}
# 4.6.3 QDateTime 常见错误
// ❌ 错误 1:直接比较不同时区的 QDateTime
QDateTime utc(QDate(2025,7,1), QTime(12,0,0), Qt::UTC);
QDateTime local(QDate(2025,7,1), QTime(20,0,0), Qt::LocalTime); // UTC+8
if (utc == local) {
// 是否相等?这取决于实现!
// Qt 6: 默认转换到同一时区比较 → 相等(都是同一时刻)
// Qt 5: 可能直接比较 m_msecs → 不相等(12:00 vs 20:00)
}
// ✅ 正确:统一转换到 UTC 再比较
if (utc.toUTC() == local.toUTC()) {
// 始终正确
}
// ❌ 错误 2:对无效 QDateTime 操作
QDateTime dt; // 默认构造 = 无效
if (dt.isValid()) {
dt.toString(); // 安全
}
# 4.7 QCalendar 与 QBasicTimer
# 4.7.1 QCalendar(Qt 5.14+)
支持非公历日历系统(农历、伊斯兰历等):
#include <QCalendar>
// 创建农历日期
QCalendar lunarCal = QCalendar(QCalendar::System::Chinese);
QDate lunarDate(2025, 1, 1); // 公历
QDate chineseDate = lunarCal.dateFromParts(2025, 6, 15);
qDebug() << "Lunar date:" << chineseDate.toString("yyyy-MM-dd");
// QDate 的内置日历支持
QDate today = QDate::currentDate();
qDebug() << "Gregorian:" << today;
qDebug() << "Julian:" << today.toString(Qt::ISODate, QCalendar(QCalendar::System::Julian));
# 4.7.2 QBasicTimer
QBasicTimer 是比 QTimer 更轻量的定时器——不创建 QObject,直接嵌入在类中:
class MyWidget : public QWidget {
QBasicTimer m_timer;
protected:
void startAnimation() {
m_timer.start(16, this); // ~60 FPS
}
void timerEvent(QTimerEvent* event) override {
if (event->timerId() == m_timer.timerId()) {
updateAnimation(); // 轻量级帧更新
}
}
};
QBasicTimer vs QTimer:
| QTimer | QBasicTimer | |
|---|---|---|
| 类型 | QObject | 值类型 POD |
| 使用 | 信号槽 | 重写 timerEvent() |
| 开销 | 信号槽分发 | 直接函数调用 |
| 适合 | 通用场景 | 高频定时(>60Hz) |
# 4.8 嵌入式时间同步与优化
# 4.8.1 NTP 时间同步策略
class TimeSyncManager : public QObject {
Q_OBJECT
public:
void syncWithNTP(const QString& ntpServer) {
// 方式 1:使用系统命令(嵌入式常见)
QProcess ntpdate;
ntpdate.start("ntpdate", {"-u", ntpServer});
ntpdate.waitForFinished();
qDebug() << "Time synced:" << ntpdate.readAllStandardOutput();
// 方式 2:HTTP 时间获取(无 NTP 环境)
QNetworkAccessManager* mgr = new QNetworkAccessManager(this);
QNetworkRequest req(QUrl("http://" + ntpServer + "/api/time"));
QNetworkReply* reply = mgr->get(req);
connect(reply, &QNetworkReply::finished, [reply]() {
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qint64 serverTime = doc["timestamp"].toVariant().toLongLong();
adjustSystemTime(serverTime);
reply->deleteLater();
});
}
void adjustSystemTime(qint64 utcMsecs) {
// Linux: settimeofday()
struct timeval tv;
tv.tv_sec = utcMsecs / 1000;
tv.tv_usec = (utcMsecs % 1000) * 1000;
settimeofday(&tv, nullptr);
}
};
# 4.8.2 定时器合并(Timer Coalescing)
嵌入式 Linux 内核支持定时器合并——将多个相近的定时器合并为一次唤醒,节省功耗:
传统:Timer1(100ms) Timer2(110ms) Timer3(120ms)
→ 3 次 CPU 唤醒
合并后:
→ 1 次唤醒 @ 110ms(内核自动合并 ±10ms 内的定时器)
// Qt 中启用(需要 Qt 5.13+ 和 Linux 4.20+)
QTimer::coarseTimer // 粗粒度定时器(合并友好)
// 环境变量:QT_USE_COARSE_TIMER=1
# 4.9 综合案例
# 案例:嵌入式设备的时间管理系统
class DeviceTimeSystem : public QObject {
Q_OBJECT
public:
DeviceTimeSystem() {
// 1. 启动时同步时间
syncTime();
// 2. 每分钟检查时钟漂移
m_driftCheckTimer.setInterval(60000);
connect(&m_driftCheckTimer, &QTimer::timeout, this, &DeviceTimeSystem::checkDrift);
m_driftCheckTimer.start();
// 3. 用于定时任务
m_schedulerTimer.setInterval(1000);
connect(&m_schedulerTimer, &QTimer::timeout, this, &DeviceTimeSystem::onTick);
m_schedulerTimer.start();
}
// 获取可靠的当前时间
QDateTime reliableNow() const {
// 如果有硬件 RTC,用 RTC 时间 + 单调时钟偏移
if (m_rtcValid) {
return m_rtcBase.addMSecs(m_uptimeTimer.elapsed());
}
return QDateTime::currentDateTime();
}
void scheduleTask(const QTime& timeOfDay, std::function<void()> task) {
m_scheduledTasks.append({timeOfDay, std::move(task)});
}
private:
void onTick() {
QTime now = QTime::currentTime();
for (auto& task : m_scheduledTasks) {
if (qAbs(now.msecsTo(task.time)) < 500) { // 500ms 宽容度
task.fn();
}
}
}
void checkDrift() {
// 比较系统时间与可靠时间源
qint64 systemTime = QDateTime::currentMSecsSinceEpoch();
qint64 reliableTime = reliableNow().toMSecsSinceEpoch();
if (qAbs(systemTime - reliableTime) > 5000) { // 漂移 > 5s
qWarning() << "Clock drift detected:" << (systemTime - reliableTime) << "ms";
syncTime(); // 重新同步
}
}
void syncTime() {
// NTP/HTTP 时间同步...
m_rtcBase = QDateTime::currentDateTime();
m_uptimeTimer.start();
m_rtcValid = true;
}
struct ScheduledTask {
QTime time;
std::function<void()> fn;
};
QTimer m_driftCheckTimer;
QTimer m_schedulerTimer;
QElapsedTimer m_uptimeTimer;
QDateTime m_rtcBase;
bool m_rtcValid = false;
QList<ScheduledTask> m_scheduledTasks;
};
# 4.10 速查表
| 类 | 用途 | 精度 | 事件循环依赖 |
|---|---|---|---|
QDate | 日期(不含时区) | 天 | 否 |
QTime | 时间(不含日期) | 毫秒 | 否 |
QDateTime | 日期+时间+时区 | 毫秒 | 否 |
QTimeZone | 时区管理 | 秒偏移 | 否 |
QTimer | 定时任务 | ~5ms | 是 |
QBasicTimer | 轻量定时器 | ~1ms | 是 |
QElapsedTimer | 高性能计时 | 纳秒 | 否 |
QDeadlineTimer | 截止时间 | 毫秒 | 否 |
QCalendar | 非公历日历 | 天 | 否 |
时间处理铁律:
QElapsedTimer用于性能测量——不触发系统调用,纳秒级QDateTime比较前统一转 UTC——避免时区差异导致错误- 嵌入式必须验证
/usr/share/zoneinfo存在——否则静默返回 UTC - 高频定时用
QBasicTimer——比QTimer信号槽少一次分发 - 不要在主线程中做 NTP 同步——同步阻塞可能导致 UI 冻结
QTimer::singleShot(0, ...)是延迟到下一轮事件循环——不是立即执行- 休眠用
QThread::msleep,不要用系统sleep——前者可与事件循环配合