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 时间。

相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能14 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G14 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt