Qt修改操作系统的日期与时间

UTC 时间系统时间时区 三者之间的关系

核心概念

概念 定义 特性
UTC 时间 协调世界时,一种基于原子钟的国际时间标准,没有时区偏移。 可以看作"绝对时间",全球同一时刻 UTC 值相同。
系统时间(内核时间) Linux 内核内部维护的时间,本质上就是 UTC 时间 内核只存储 UTC,不存储时区信息。
时区 本地时间相对于 UTC 的偏移规则(如 UTC+8)以及夏令时规则。 仅用于将 UTC 转换为用户可读的本地时间。

它们在系统中的流转

复制代码
硬件时钟 (RTC)
    ↓ (启动时或通过 hwclock)
内核系统时间 (UTC)
    ↓ (通过时区转换)
本地时间 (用户看到的时间,如"2026-05-20 18:30:00 CST")
  • 硬件时钟 (RTC):主板上的电池供电时钟。可以存储为 UTC 或本地时间(Windows 通常存本地时间,Linux 推荐存 UTC)。

  • 内核系统时间 :Linux 启动时从 RTC 读取并转换为 UTC 存储。运行中可通过 settimeofday()clock_settime() 修改。

  • 时区配置 :位于 /etc/localtime(通常链接到 /usr/share/zoneinfo/ 下的文件)。glibctimedatectl 使用它完成转换。

常见命令与现象

命令 输出内容 说明
date 本地时间(如 Wed May 20 18:30:00 CST 2026 内部将系统时间(UTC)加上时区偏移显示。
date -u UTC 时间 忽略时区设置,直接显示系统时间(即 UTC)。
timedatectl 同时显示 Local time, Universal time, RTC time, Time zone 清晰展示三者的关系。
hwclock 硬件时钟的时间 可以显示为本地或 UTC(通过 /etc/adjtime 配置)。

示例输出 (timedatectl)

text

复制代码
               Local time: Wed 2026-05-20 18:30:00 CST
           Universal time: Wed 2026-05-20 10:30:00 UTC
                 RTC time: Wed 2026-05-20 10:30:00
                Time zone: Asia/Shanghai (CST, +0800)

可以看到:

  • Universal time = 内核系统时间 = UTC = 10:30

  • Local time = UTC + 8 小时 = 18:30

  • RTC 时间通常也与 UTC 一致(推荐配置)。

在 Qt 中的对应

Qt 类/方法 对应概念
QDateTime::currentDateTime() 本地时间(基于系统时区)
QDateTime::currentDateTimeUtc() UTC 时间(系统时间)
QDateTime::toLocalTime() / toUTC() 时区转换
QTimeZone 处理任意时区偏移

注意 :Qt 中的 QDateTime 内部存储的是 毫秒数相对于 1970-01-01 00:00:00 UTC ,没有显式时区。只有当你调用 toLocalTime()toString() 时,才会根据当前系统时区进行转换。

完整示例:设置系统时间为 UTC+8 的指定日期(保留时间)

复制代码
// 目标时区(例如 +8)
QTimeZone targetZone("Asia/Shanghai");

// 获取当前 UTC 时间
QDateTime currentUtc = QDateTime::currentDateTimeUtc();

// 将当前 UTC 转换为目标时区的本地时间
QDateTime currentLocal = currentUtc.toTimeZone(targetZone);

// 构造新的本地时间:目标日期 + 当前时间部分
QDate newDate(2026, 12, 31);
QTime currentTime = currentLocal.time();
QDateTime newLocal(newDate, currentTime, targetZone);

// 转换为 UTC
QDateTime newUtc = newLocal.toUTC();

// 再转换为系统当前时区的本地时间(供 timedatectl 使用)
QDateTime finalLocal = newUtc.toLocalTime();

// 然后调用 timedatectl set-time
...

QTimeZone类

QTimeZone 是 Qt 中处理时区的核心类,它提供了时区偏移、名称、夏令时规则等信息,并能帮助你在 UTC 和本地时间之间进行准确转换。结合你之前设置系统时间的需求,理解 QTimeZone 可以避免时区错误导致的偏差。

常用功能

功能 方法示例
获取系统当前时区 QTimeZone::systemTimeZone()
获取 UTC 时区 QTimeZone::utc()
根据 IANA 名称获取时区 QTimeZone("Asia/Shanghai")
获取 UTC 偏移(秒) offsetFromUtc(QDateTime::currentDateTime())
时区显示名称 displayName()
检查是否支持夏令时 hasDaylightTime()

基本用法示例

复制代码
#include <QTimeZone>
#include <QDateTime>
#include <QDebug>

void timezoneExample() {
    // 1. 获取系统当前时区
    QTimeZone sysZone = QTimeZone::systemTimeZone();
    qDebug() << "System timezone:" << sysZone.id();            // "Asia/Shanghai"
    qDebug() << "Display name:" << sysZone.displayName();      // "中国标准时间"
    qDebug() << "UTC offset (seconds):" 
             << sysZone.offsetFromUtc(QDateTime::currentDateTime()); // 28800 (8小时)

    // 2. UTC 时区
    QTimeZone utcZone = QTimeZone::utc();
    qDebug() << "UTC offset:" << utcZone.offsetFromUtc(QDateTime::currentDateTime()); // 0

    // 3. 使用特定时区
    QTimeZone nyZone("America/New_York");
    if (nyZone.isValid()) {
        qDebug() << "NY timezone:" << nyZone.displayName();  // "Eastern Standard Time"
    }
}

在时间转换中的应用

当你有一个 UTC 时间,想转换为某个时区的本地时间:

复制代码
QDateTime utcTime = QDateTime::currentDateTimeUtc();  // UTC 时间
QTimeZone targetZone("Asia/Tokyo");
QDateTime tokyoTime = utcTime.toTimeZone(targetZone);  // 转换为东京时间
qDebug() << "Tokyo time:" << tokyoTime.toString("yyyy-MM-dd HH:mm:ss");

反过来,将一个本地时间转换为 UTC:

复制代码
QDateTime localTime = QDateTime::currentDateTime();   // 本地时间(系统时区)
QDateTime utcFromLocal = localTime.toUTC();
// 或者显式指定时区
QDateTime localInZone = QDateTime(QDate(2026,5,20), QTime(10,30,0), nyZone);
QDateTime utcFromNy = localInZone.toUTC();

与设置系统时间的关系

在你之前的代码中,timedatectl set-time 将传入的字符串视为本地时间,然后根据系统时区计算 UTC 并设置内核。如果你手头的数据是 UTC,必须先转换为系统本地时间,否则会导致错误的偏移。

正确做法(使用 QTimeZone 辅助转换)

复制代码
// 假设你有一个 UTC 时间(例如从服务器获取)
QDateTime utcTime = QDateTime::fromString("2026-05-20 10:30:00", "yyyy-MM-dd HH:mm:ss");
utcTime.setTimeSpec(Qt::UTC);   // 明确标记为 UTC

// 方法1:直接利用系统时区转换
QDateTime localTime = utcTime.toLocalTime();  // 等价于 utcTime.toTimeZone(QTimeZone::systemTimeZone())

// 方法2:使用 QTimeZone 显式转换
QTimeZone sysZone = QTimeZone::systemTimeZone();
QDateTime localTime2 = utcTime.toTimeZone(sysZone);

// 然后再调用 timedatectl set-time
QProcess::execute("sudo", {"timedatectl", "set-time", localTime.toString("yyyy-MM-dd HH:mm:ss")});

反过来,如果你设置的本地时间来自用户输入,要检查是否正确 :用户选择的时间可能基于不同于系统时区的假设,这时需要先用 QTimeZone 转换。

处理夏令时(DST)

某些时区有夏令时,QTimeZone 可以帮你判断某时间是否为夏令时:

复制代码
QTimeZone euZone("Europe/London");
QDateTime winter = QDateTime(QDate(2026,1,1), QTime(12,0,0));
QDateTime summer = QDateTime(QDate(2026,6,1), QTime(12,0,0));
qDebug() << "Winter offset:" << euZone.offsetFromUtc(winter);   // 0 (UTC+0)
qDebug() << "Summer offset:" << euZone.offsetFromUtc(summer);   // 3600 (UTC+1, BST)
qDebug() << "Is summer DST?" << euZone.isDaylightTime(summer);  // true

当你在夏令时边界附近设置时间时,toLocalTime()toUTC() 会自动处理。

注意事项

  1. 时区数据库QTimeZone 依赖操作系统的 IANA 时区数据库(Linux 通常位于 /usr/share/zoneinfo/)。嵌入式环境可能需要手动部署。

  2. 时区标识符 :使用 IANA 名称(如 "Asia/Shanghai"),不要使用 Windows 风格(如 "China Standard Time",除非使用 QTimeZone::fromWindowsId())。

  3. timedatectl 的一致性QTimeZone::systemTimeZone() 读取的是 /etc/localtime,与 timedatectl 显示的时区一致。

  4. 性能QTimeZone 对象构造可能会访问文件系统,对于频繁调用可考虑缓存。

Windows 平台:调用原生 API设置时间

在 Windows 系统上,最直接的方法是调用 SetSystemTime() API,它能精确到毫秒。

复制代码
// Windows_TimeSetter.h
#ifndef WINDOWS_TIMESETTER_H
#define WINDOWS_TIMESETTER_H

#include <QDateTime>

class WindowsTimeSetter
{
public:
    static bool setSystemTime(const QDateTime &newTime);
};

#endif // WINDOWS_TIMESETTER_H

// Windows_TimeSetter.cpp
#include "Windows_TimeSetter.h"
#include <windows.h>
#include <QDebug>

bool WindowsTimeSetter::setSystemTime(const QDateTime &newTime)
{
    // 1. 检查时间有效性
    if (!newTime.isValid()) {
        qWarning() << "Invalid time provided.";
        return false;
    }

    // 2. 将本地时间转换为 UTC 时间(API 要求)
    QDateTime utcTime = newTime.toUTC();

    // 3. 构造 SYSTEMTIME 结构体
    SYSTEMTIME st{};
    st.wYear         = static_cast<WORD>(utcTime.date().year());
    st.wMonth        = static_cast<WORD>(utcTime.date().month());
    st.wDay          = static_cast<WORD>(utcTime.date().day());
    st.wHour         = static_cast<WORD>(utcTime.time().hour());
    st.wMinute       = static_cast<WORD>(utcTime.time().minute());
    st.wSecond       = static_cast<WORD>(utcTime.time().second());
    st.wMilliseconds = static_cast<WORD>(utcTime.time().msec());
    st.wDayOfWeek    = static_cast<WORD>(utcTime.date().dayOfWeek());

    // 4. 调用 Windows API 设置系统时间
    if (!SetSystemTime(&st)) {
        qWarning() << "SetSystemTime failed, error code:" << GetLastError();
        return false;
    }

    qInfo() << "System time successfully updated to:" << newTime.toString("yyyy-MM-dd HH:mm:ss.zzz");
    return true;
}

Windows 平台权限处理

  • 以管理员身份运行 :编译后,右键点击可执行文件 (.exe),选择"以管理员身份运行"。

  • 在 Qt Creator 中配置 :在项目的 .pro 文件中加入以下配置,编译后程序每次运行都会自动请求管理员权限。

    win32 {
    QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'"
    }

注意 :Windows 系统时间内部存储的是 UTC,你设置的日期也会被当作 UTC 日期。如果你希望设置的日期是本地日期 ,需要先转换时区。上面的代码使用 GetSystemTime() 得到 UTC 时间,修改后仍为 UTC,适合大多数场景。

Linux 平台:执行系统命令设置时间

在 Linux 上,通过 QProcess 执行 datetimedatectl 命令是更通用的做法,Qt 本身并不直接提供修改时间的接口。

使用 date 命令

这是一个广泛兼容的方法,适用于大多数 Linux 发行版。

复制代码
// Linux_TimeSetter.h
#ifndef LINUX_TIMESETTER_H
#define LINUX_TIMESETTER_H

#include <QDateTime>

class LinuxTimeSetter
{
public:
    static bool setSystemTimeWithDate(const QDateTime &newTime);
    static bool setSystemTimeWithTimedatectl(const QDateTime &newTime);
    static void syncToHardwareClock();
};

#endif // LINUX_TIMESETTER_H

// Linux_TimeSetter.cpp
#include "Linux_TimeSetter.h"
#include <QProcess>
#include <QDebug>

bool LinuxTimeSetter::setSystemTimeWithDate(const QDateTime &newTime) {
    // 使用 Unix 时间戳方式设置,格式简单不易出错
    qint64 timestamp = newTime.toSecsSinceEpoch();
    QStringList args;
    args << "date" << "-s" << QString("@%1").arg(timestamp);

    int exitCode = QProcess::execute("sudo", args);
    if (exitCode != 0) {
        qWarning() << "date command failed with exit code:" << exitCode;
        return false;
    }
    return true;
}

使用 timedatectl 命令(推荐)

这是现代 Linux 发行版(如 Ubuntu 16.04+, CentOS 7+)中更推荐的方式,它能更好地管理系统时间和 NTP 服务。

复制代码
bool LinuxTimeSetter::setSystemTimeWithTimedatectl(const QDateTime &newTime) {
    // 1. 先关闭 NTP 自动同步,避免冲突
    QProcess::execute("sudo", QStringList() << "timedatectl" << "set-ntp" << "false");
    // 2. 设置新的系统时间
    QStringList args;
    args << "timedatectl" << "set-time" << newTime.toString("yyyy-MM-dd HH:mm:ss");

    int exitCode = QProcess::execute("sudo", args);
    if (exitCode != 0) {
        qWarning() << "timedatectl command failed with exit code:" << exitCode;
        return false;
    }
    return true;
}

同步硬件时钟

为了防止重启后时间丢失,设置好系统时间后,务必将其写入硬件时钟。

复制代码
void LinuxTimeSetter::syncToHardwareClock() {
    QProcess::execute("sudo", QStringList() << "hwclock" << "-w");
    qInfo() << "System time synced to hardware clock.";
}

注意事项

  • 时间格式 :使用 date 命令时,时间字符串格式错误会导致设置失败,使用 toSecsSinceEpoch() 是更可靠的方式。

  • NTP 服务冲突 :Linux 下手动设置时间,系统可能很快被 NTP 服务自动同步回去。建议设置前,通过命令 sudo timedatectl set-ntp false 临时关闭 NTP 服务。

  • Qt 版本toSecsSinceEpoch() 方法在 Qt 5.8 及以上版本可用。若使用较旧版本,可使用 toTime_t() 替代。

Failed to set time: Automatic time synchronization is enabled

解决方法:根据错误信息 Failed to set time: Automatic time synchronization is enabled,这说明你在 Linux 系统上尝试手动修改时间时,NTP 自动同步服务(如 systemd-timesyncdchronyd)正处于启用状态,阻止了手动设置。

Failed to set time: Previous request is not finished, refusing.

解决方法:你在用 Qt 调用 timedatectl 时遇到的 Failed to set time: Previous request is not finished, refusing. 错误,通常意味着前一个时间服务请求还卡在后台,导致新的请求被拒绝了。增加休眠时间,以便完成操作。

最终代码如下:

复制代码
    QDateTime m_currentDateTime;
    m_currentDateTime = QDateTime::currentDateTimeUtc();    
//先关闭 NTP 自动同步,避免冲突
    QProcess::execute("sudo", QStringList() << "timedatectl" << "set-ntp" << "false");
    QThread::sleep(3);
    //设置新的系统时间
    QStringList args;
    args << "timedatectl" << "set-time" << m_currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
    int exitCode = QProcess::execute("sudo", args);
    if (exitCode != 0) {
        qWarning() << "timedatectl set-time command failed with exit code:" << exitCode;
        return;
    }
    else{
        qInfo() << "timedatectl set-time. currentDateTime:" << m_currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
        QThread::sleep(1);

        // 同步到硬件时钟
        QProcess::execute("sudo", QStringList() << "hwclock" << "-w");
        qInfo() << "System datetime set to" << m_currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
    }

设置时间命令需要root权限,sudo提权运行或者将命令添加到sudoers文件中提权。

注意 :在 Linux 系统中,系统时间内部存储的是 UTC 时间。

相关推荐
小短腿的代码世界4 小时前
Qt属性系统揭秘:从Q_PROPERTY宏到动态元对象系统的完整架构解析
开发语言·qt·架构
丁劲犇5 小时前
QodeAssist:为msys2 ucrt64 Qt Creator 注入 AI 灵魂的开源插件
开发语言·人工智能·qt
listhi5205 小时前
基于QT的串口心电波形实时显示系统
开发语言·qt
charlie11451419117 小时前
现代Qt开发教程(新手篇)2.3——QImage、QPixmap、QIcon 图像处理基础
开发语言·图像处理·qt
AoDeLuo20 小时前
SOEM2.0编译与Qt调用
qt·机器视觉
大树学长21 小时前
【QT开发】Windows 10 + Qt 5.15.2 手动编译安装 Qt OPC UA 模块完整记录
开发语言·windows·qt
小短腿的代码世界1 天前
Qt低级网络编程与零拷贝技术在高频交易中的应用:从QTcpSocket到共享内存的全链路优化
开发语言·网络·qt
qq_401700411 天前
Qt 自定义无边框窗口:标题栏、拖拽移动与缩放
开发语言·qt
xiaoye-duck1 天前
Qt 信号与槽深度解析:connect 用法、自定义信号槽与 Lambda 实战
开发语言·qt