Linux时区在应用程序未发送夏令时标志时也会自动应用夏令时

后端开发 2026-07-09

我正在开发一套嵌入式Linux系统,在该系统中应用程序根据用户选择的UTC偏移动态设置系统时区。

我们使用一个内部枚举与Linux时区名称之间的预定义映射,然后通过创建一个指向 /etc/localtime 的符号链接来应用时区:

static std::map<int, std::pair<string, string>> GmtValue = {
    { GMT_11, { "Etc/GMT+11", "GMT -11 Midway Islands, Samoa, Niue"}},
    { GMT_10, { "Etc/GMT+10", "GMT -10 Fakaofo, Hawaii, Tahiti"}},
    { GMT_9,  { "US/Alaska", "GMT -9 Alaska, Gambier"}},
    { GMT_8,  { "PST8PDT", "GMT -8 Pacific Time"}},
    { GMT_7,  { "Etc/GMT+7", "GMT -7 Mountain Time"}},
    { GMT_6,  { "CST6CDT", "GMT -6 Central Time"}},
    { GMT_5,  { "EST5EDT", "GMT -5 Eastern Time"}},
    { GMT_4,  { "Etc/GMT+4", "GMT -4 Atlantic Time"}},
    { GMT_3,  { "Etc/GMT+3", "GMT -3 Rio de Janeiro, Buenos Aires"}},
    { GMT_2,  { "Etc/GMT+2", "GMT -2 Mid-Atlantic"}},
    { GMT_1,  { "Etc/GMT+1", "GMT -1 Azores, Cape Verde"}},
    { GMT0,   { "GB", "GMT 0 London, Lisbon, Dublin"}},
    { GMT1,   { "CET", "GMT +1 Paris, Zurich, Rome"}},
    { GMT2,   { "EET", "GMT +2 Athens, Sofia, Cairo"}},
    { GMT3,   { "Etc/GMT-3", "GMT +3 Moscow, Kuwait"}},
    { GMT33,  { "Asia/Tehran", "GMT +3.30 Tehran"}},
    { GMT4,   { "Etc/GMT-4", "GMT +4 Abu Dhabi"}},
    { GMT43,  { "Asia/Kabul", "GMT +4.30 Kabul"}},
    { GMT5,   { "Etc/GMT-5", "GMT +5 Islamabad"}},
    { GMT53,  { "Asia/Colombo", "GMT +5.30 Colombo"}},
    { GMT6,   { "Etc/GMT-6", "GMT +6 Dhaka"}},
    { GMT7,   { "Etc/GMT-7", "GMT +7 Bangkok"}},
    { GMT8,   { "Etc/GMT-8", "GMT +8 Beijing"}},
    { GMT9,   { "Etc/GMT-9", "GMT +9 Tokyo"}},
    { GMT93,  { "Australia/Darwin", "GMT +9.30 Darwin"}},
    { GMT10,  { "Etc/GMT-10", "GMT +10 Sydney"}},
    { GMT11,  { "Etc/GMT-11", "GMT +11 New Caledonia"}},
    { GMT12,  { "Etc/GMT-12", "GMT +12 Auckland"}},
    { GMT13,  { "Etc/GMT-13", "GMT +13 Nuku'alofa"}},
    { GMT14,  { "Pacific/Apia", "GMT +14 Apia"}}
};

用于更新时区的函数:

void DeviceManagement::updateSystemZoneInfo(int zone)
{
    printf("updateSystemZoneInfo!\n");

    if (zone >= ApplicationDateTimeSt::DATETIME_NUM)
        return;

    const map<string, string> tzPaths =
    {
        { "/etc/localtime", "/usr/share/zoneinfo/" },
        { "/etc/TZ", "/usr/share/zoneinfo/uclibc/" }
    };

    pair<string, string> tzPath;
    struct stat buf;

    for (auto &path : tzPaths)
    {
        if (!lstat(path.first.c_str(), &buf))
        {
            tzPath = path;
            break;
        }
    }

    if (tzPath.first.empty())
        return;

    unlink("/etc/comelit/localtime");

    string newZonePath =
        tzPath.second + ApplicationDateTimeSt::GmtValue[zone].first;

    symlink(newZonePath.data(), "/etc/comelit/localtime");
}

问题在于,某些时区名称例如 CETPST8PDTEST5EDTEET 会自动应用夏令时(DST),使系统时钟在夏季偏移+1小时。

然而,应用程序本身并不发送或管理任何DST标志。

系统仍然应用来自Linux时区数据库 (tzdata) 的DST规则,导致我们嵌入式产品的时间行为不正确。

我们不能直接修改映射,因为它在多个软件组件之间共享。

在不改变现有时区映射的前提下,在嵌入式Linux系统上强制固定的UTC偏移且不启用DST的正确做法是什么?

解决方案

然而,应用程序本身并不发送或管理任何DST标志。

这并不完全准确。应用程序“发送”了时区 US/Alaska,而这个时区被定义为在标准时间为UTC-9,夏令时为UTC-8。关于切换发生的时间规则也包含在tzdata对 US/Alaska 的定义中。因此,使用该时区会“标记”出使用DST的意图。若不想使用DST,应用程序需要指定一个不含DST成分的时区,比如 Etc/GMT+9(首选)或 Pacific/Gambier(不太鲁棒,因为未来DST可能被使用)。

维基百科维护着tz数据库时区列表。基于此,你的应用使用并被定义为使用DST的时区包括 US/AlaskaPST8PDT(并不意外,因为缩写中的“DT”代表“Daylight Time”)、CST6CDT(也不奇怪)、EST5EDT(不仅提到“DT”,而且 EST 被定义为一个不使用DST的独立时区)、GBCET、和 EET。所以基本上,应用通过使用tz数据库中的这些时区定义来请求DST。

有趣的是,应用实现了“GMT +14 Apia”,你确实需要DST才能得到+14。萨摩亚(包括阿皮亚)的标准时区是GMT+13,因此名称“GMT +14 Apia”似乎表示应始终应用DST,而不是从不应用。对于应用来说不利的是,维基百科指出自2021年以来萨摩亚未再观察DST,tzdata将 Pacific/Apia 定义为GMT+13,而不是+14,适用于2021年之后的时间点。

在不改变现有时区映射的前提下,在嵌入式Linux系统上强制固定的UTC偏移且不启用DST的正确做法是什么?

正确做法取决于为何这点重要。你可能遇到一个所谓的XY问题,问题并非出在系统范围设置,而在于你如何使用它。修改系统范围的设置有可能带来比解决当前问题更多的问题。因此,可能需要进行一次深入的设计评审(别在有缺陷的设计上打补丁)。如果你坚持这种做法,请继续阅读。

1) 避免系统时间出现DST调整的正确做法是使用专门为此目的定义的时区,即 Etc/GMT-14Etc/GMT+12。其他时区不可靠,因为它们可能根据各国政府的行动增减DST。为了保持现有映射不变,你可以定义一个从内部枚举映射到无DST时区的新映射。(如果在这里使用的映射与其他组件中的映射不同导致问题,那么你可能有更深层次的问题需要解决。其他组件是否假设会有DST调整?它们在多大程度上依赖具体的时区名称?)

2) 或者,你可以放弃修改系统时区的计划。DST是 tz数据库有用的一个重要原因(跟踪历史变更是另一大原因)。既然你不想要DST,这个数据库对你几乎没有价值,因此你可以在内部实现时区变更。让系统保持在UTC/GMT,并把当前偏移量存储在某处。定义一个使用偏移量的API,例如一个获取当前UTC时间再加上偏移量的函数,并让各软件组件改用这个API而不是系统时间。根据你为何需要时区信息以及你是否使用了 [std::chrono],实现起来可能相对简单。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章