struct tm 是不是将时区信息存储为其数据成员

Posted

技术标签:

【中文标题】struct tm 是不是将时区信息存储为其数据成员【英文标题】: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 对象的格式化信息(时区)。

根据cplusplusreference,struct 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 的实现,看看他们是否扩展了结构。 【参考方案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_timecurrent_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。

【讨论】:

以上是关于struct tm 是不是将时区信息存储为其数据成员的主要内容,如果未能解决你的问题,请参考以下文章

python time模块

用户日期时间设置为 GMT,如何将日期转换为其本地化设置?

常用模块

我是不是必须将 Azure Blob 存储中的 PDF 文件存储到 OCR 并为其编制索引?

四日期时间

如何使用时区信息在 MySQL 中存储日期时间