【问题标题】:Does struct tm store time zone information as its data memberstruct tm 是否将时区信息存储为其数据成员
【发布时间】:2020-02-15 01:02:48
【问题描述】:

考虑以下 C++ 代码

#include <ctime>
#include <iostream>

int main()
{
    std::time_t now = std::time(nullptr);
    struct tm local = *std::localtime(&now);
    struct tm gm = *std::gmtime(&now);
    char str[20];
    std::strftime(str, 20, "%Z", &local);
    std::cout << str << std::endl;          // HKT
    std::strftime(str, 20, "%Z", &gm);
    std::cout << str << std::endl;          // UTC

    return 0;
}

所以存储在now 中的是一个明确的整数值,而localgmstruct tm 存储人类可读的日期/时间信息。然后我打印出仅基于struct tm 对象的格式化信息(时区)。

根据cplusplusreferencestruct tm的数据成员是

tm_sec  
tm_min  
tm_hour 
tm_mday 
tm_mon  
tm_year 
tm_wday 
tm_yday 
tm_isdst

如果这就是struct tm 包含的全部内容,程序如何知道其中的时区信息?也就是说,它怎么知道local的时区是HKTgm的时区是UTC

如果struct tm 包含的内容不止这些,请说明它是如何存储时区信息的。

顺便说一句,虽然演示代码是用 C++ 编写的,但我想这个问题本质上也是一个合法的 C 问题。

【问题讨论】:

  • tm 不包含时区信息。 strftime 通过幕后巫术获取时区。如果你想获得一般的时区,那就有点乱了。 (currently) 没有获取时区的标准方法。 Fortunately Howard Hinnant is on that job....
  • 谢谢@user4581301 这部分回答了我的问题。但我还有后续问题:鉴于tm 中存储的所有信息,strftime 如何知道以不同方式响应两个struct tm 对象?除非tm 包含诸如this tm is created by localtime 之类的信息,that tm is created by gmtime
  • tm 结构不存储时区信息,是什么让您认为它可以存储?区别在于对gmtime()localtime() 的调用。
  • Man page 涵盖了如何在 POSIX 系统上获取时区信息。仍在寻找strftime 如何区分吸盘。应该添加 POSIX 未定义发生的事情。
  • 在 Windows 下使用来自 MSYS2 的 gcc 9.2.0 彻底失败。看到这让我想起了我看到的非标准tms 有额外的信息。 Here's one。注意const char *tm_zone 成员。你是为什么平台编译的?看看tm 的实现,看看他们是否扩展了结构。

标签: c++ c ctime time.h


【解决方案1】:

C 标准在 7.27.1 时间的组成部分中说:

tm 结构应包含至少以下成员,在 任何订单。成员的语义及其正常范围是 用 cmets 表示。318)

int tm_sec;    // seconds after the minute — [0, 60]
int tm_min;    // minutes after the hour — [0, 59]
int tm_hour;   // hours since midnight — [0, 23]
int tm_mday;   // day of the month — [1, 31]
int tm_mon;    // months since January — [0, 11]
int tm_year;   // years since 1900
int tm_wday;   // days since Sunday — [0, 6]
int tm_yday;   // days since January 1 — [0, 365]
int tm_isdst;  // Daylight Saving Time flag

(重点是我的)

也就是说,允许实现向tm 添加其他成员,正如您在glibc/time/bits/types/struct_tm.h 中发现的那样。 POSIX 规范的措辞几乎相同。

结果是%Z(甚至%z)在strftime中不能被认为是可移植的。 %Z 的规范反映了这一点:

%Z 替换为区域设置的时区名称或缩写,或 如果无法确定时区,则无字符。 [tm_isdst]

也就是说,供应商可以举手并简单地说:“没有时区是可确定的,所以我根本不输出任何字符。”

我的观点:C 计时 API 是一团糟。


我正在尝试改进 &lt;chrono&gt; 库中即将推出的 C++20 标准。

如果time_zone 缩写不可用,C++20 规范会将其从“无字符”更改为引发异常:

http://eel.is/c++draft/time.format#3

除非明确要求,格式化计时类型的结果 不包含时区缩写和时区偏移 信息。如果信息可用,则转换 说明符 %Z%z 将(分别)格式化此信息。 [ 注意: 如果信息不可用并且%Z%z 转换说明符出现在 chrono-format-spec 中,例外 format_­error 类型的被抛出,如上所述。 ——结束注释 ]

除了上面这段不是描述C的strftime,而是一个新的format函数,它作用于std::chrono类型,而不是tm。此外还有一个新类型:std::chrono::zoned_time (http://eel.is/c++draft/time.zone.zonedtime),它始终具有可用的time_zone 缩写(和偏移量),并且可以使用上述format 函数进行格式化。 p>

示例代码:

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std;
    using namespace std::chrono;
    auto now = system_clock::now();
    std::cout << format("%Z\n", zoned_time{current_zone(), now});   // HKT (or whatever)
    std::cout << format("%Z\n", zoned_time{"Asia/Hong_Kong", now}); // HKT or HKST
    std::cout << format("%Z\n", zoned_time{"Etc/UTC", now});        // UTC
    std::cout << format("%Z\n", now);                               // UTC
}

(免责声明:format 函数中格式化字符串的最终语法可能略有不同,但功能会在那里。)

如果您想试用此库的预览版,请点击此处免费且开源:https://github.com/HowardHinnant/date

需要一些安装:https://howardhinnant.github.io/date/tz.html#Installation

在此预览中,您将需要使用标题"date/tz.h",并且库的内容在namespace date 而不是namespace std::chrono

预览库可用于 C++11 或更高版本。

zoned_timestd::chrono::duration 为模板,指定时间点的精度,并在上面的示例代码中使用C++17's CTAD feature 推导出来。如果你在 C++11 或 C++14 中使用这个预览库,语法看起来更像:

cout << format("%Z\n", zoned_time<system_clock::duration>{current_zone(), now});

或者有一个非提议的标准化辅助工厂函数将为您进行扣除:

cout << format("%Z\n", make_zoned(current_zone(), now));

(#CTAD_eliminates_factory_functions)

【讨论】:

    【解决方案2】:

    感谢所有帮助指出正确方向的问题的cmets。我在下面发布了一些我自己的研究。我根据我在 GitHub 上找到的 GNU C 库的archived repo 发言。它的版本是2.28.9000

    glibc/time/bits/types/struct_tm.h

    struct tm
    {
      int tm_sec;           /* Seconds. [0-60] (1 leap second) */
      int tm_min;           /* Minutes. [0-59] */
      int tm_hour;          /* Hours.   [0-23] */
      int tm_mday;          /* Day.     [1-31] */
      int tm_mon;           /* Month.   [0-11] */
      int tm_year;          /* Year - 1900.  */
      int tm_wday;          /* Day of week. [0-6] */
      int tm_yday;          /* Days in year.[0-365] */
      int tm_isdst;         /* DST.     [-1/0/1]*/
    
    # ifdef __USE_MISC
      long int tm_gmtoff;       /* Seconds east of UTC.  */
      const char *tm_zone;      /* Timezone abbreviation.  */
    # else
      long int __tm_gmtoff;     /* Seconds east of UTC.  */
      const char *__tm_zone;    /* Timezone abbreviation.  */
    # endif
    };
    

    似乎struct tm 确实存储了时区信息,至少在这个实现中是这样。

    【讨论】:

      【解决方案3】:

      日期和时间编程如此困难的原因之一是,从根本上说,它至少是一个有点困难的问题:“九月有三十天”、sexagesimal arithmetic、时区、夏令时和闰年,更别提闰秒了。

      但它难的另一个原因是太多的库和语言把它弄得一团糟,不幸的是 C 也不例外。 (正如霍华德在他的回答中提到的那样,C++ 正在努力做得更好。)

      尽管每个人都知道全局变量不好,但 C 的日期/时间函数基本上使用其中的几个。实际上,“本系统当前时区”的概念是一个全局变量,描述该时区的全局数据在localtimestrftime 以及许多其他函数之间随意共享。

      所以strftime 可以根据该全局数据填写%z%Z,即使它没有作为struct tm 值的一部分传入。

      这显然是一个次优的安排,如果程序有办法动态更改它想要用于localtime 和其他时间的时区,它将开始导致真正的问题。 (这种安排之所以持续存在,部分原因是 实际上并没有一种好的、可移植的、标准的方式让程序更改它使用的本地时区。)

      多年来,有各种半心半意的尝试来清理一些混乱(当然,同时保持向后兼容性)。其中一项尝试涉及您在某些系统版本的struct tm 中发现的扩展tm_gmtofftm_zone 字段。这些添加是一个巨大的改进——我无法想象在没有它们的系统上进行严肃的日期/时间编程——但它们仍然不是标准的,而且仍然有很多系统没有没有它们(即使是“隐藏的”拼写 __tm_gmtoff__tm_zone)。

      您可以在本文中阅读更多关于 C 中日期/时间支持的肮脏历史的信息:Time, Clock, and Calendar Programming In C,作者 Eric Raymond。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-15
        • 2012-08-03
        • 2015-09-09
        • 2015-05-06
        • 2015-06-30
        • 2013-06-08
        • 2018-02-06
        相关资源
        最近更新 更多