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
中的是一个明确的整数值,而local
和gm
是struct 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
的时区是HKT
,gm
的时区是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 彻底失败。看到这让我想起了我看到的非标准tm
s 有额外的信息。 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 是一团糟。
我正在尝试改进 <chrono>
库中即将推出的 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_time
以std::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 的日期/时间函数基本上使用其中的几个。实际上,“本系统当前时区”的概念是一个全局变量,描述该时区的全局数据在localtime
和strftime
以及许多其他函数之间随意共享。
所以strftime
可以根据该全局数据填写%z
和%Z
,即使它没有作为struct tm
值的一部分传入。
这显然是一个次优的安排,如果程序有办法动态更改它想要用于localtime
和其他时间的时区,它将开始导致真正的问题。 (这种安排之所以持续存在,部分原因是 实际上并没有一种好的、可移植的、标准的方式让程序更改它使用的本地时区。)
多年来,有各种半心半意的尝试来清理一些混乱(当然,同时保持向后兼容性)。其中一项尝试涉及您在某些系统版本的struct tm
中发现的扩展tm_gmtoff
和tm_zone
字段。这些添加是一个巨大的改进——我无法想象在没有它们的系统上进行严肃的日期/时间编程——但它们仍然不是标准的,而且仍然有很多系统没有没有它们(即使是“隐藏的”拼写 __tm_gmtoff
和 __tm_zone
)。
您可以在本文中阅读更多关于 C 中日期/时间支持的肮脏历史的信息:Time, Clock, and Calendar Programming In C,作者 Eric Raymond。
【讨论】:
以上是关于struct tm 是不是将时区信息存储为其数据成员的主要内容,如果未能解决你的问题,请参考以下文章