struct tm 是否将时区信息存储为它的数据成员
Does struct tm store time zone information as its data member
考虑以下 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
存储人类可读的date/time信息。然后我打印出仅基于 struct tm
对象的格式化信息(时区)。
根据cplusplus reference,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 问题。
感谢您对问题的所有评论,这些评论有助于指明正确的方向。我post下面是我自己的一些研究。我的发言基于我在 GitHub 上找到的 archived repo GNU C 库。它的版本是 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
确实存储了时区信息,至少在这个实现中是这样。
C 标准在 7.27.1 时间的组成部分中说:
The tm
structure shall contain at least the following members, in
any order. The semantics of the members and their normal ranges are
expressed in the comments.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
is replaced by the locale’s time zone name or abbreviation, or
by no characters if no time zone is determinable. [tm_isdst]
也就是说,允许供应商举手简单地说:"no time zone was determinable, so I'm not outputting any characters at all."
我的看法:C 时序 API 一团糟。
我正尝试在 <chrono>
库中针对即将推出的 C++20 标准进行改进。
C++20 规范将此从 "no characters" 更改为如果 time_zone
缩写不可用则抛出异常:
http://eel.is/c++draft/time.format#3
Unless explicitly requested, the result of formatting a chrono type
does not contain time zone abbreviation and time zone offset
information. If the information is available, the conversion
specifiers %Z
and %z
will format this information (respectively).
[ Note: If the information is not available and a %Z
or %z
conversion specifier appears in the chrono-format-spec, an exception
of type format_error
is thrown, as described above. — end note ]
除了上面的段落没有描述 C 的 strftime
,而是一个新的 format
函数,它在 std::chrono
类型上运行,而不是 tm
。此外还有一个新类型:std::chrono::zoned_time
(http://eel.is/c++draft/time.zone.zonedtime) always 有可用的 time_zone
缩写(和偏移量)并且可以用前面提到 format
函数。
示例代码:
#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
在此预览中,您将需要使用 header "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});
或者有一个 non-proposed-for-standardization 辅助工厂函数可以为您进行推导:
cout << format("%Z\n", make_zoned(current_zone(), now));
(#CTAD_eliminates_factory_functions)
日期和时间编程如此困难的原因之一是它从根本上说至少是一个有点困难的问题:"Thirty days hath September"、sexagesimal arithmetic、时区、夏令时,以及闰年,更不用说闰秒了。
但另一个困难的原因是太多的库和语言把它搞得一团糟,不幸的是,C 也不例外。 (正如霍华德在他的回答中提到的那样,C++ 正在努力做得更好。)
尽管每个人都知道全局变量不好,但 C 的 date/time 函数基本上使用其中的几个。实际上,"this system's current time zone" 的概念是一个全局变量,描述时区的全局数据在 localtime
和 strftime
以及许多其他函数之间随意共享。
因此 strftime
可以根据该全局数据填写 %z
和 %Z
,即使它不是作为 struct tm
值的一部分传入的。
这显然不是最佳安排,如果有一种方法可以让程序动态地更改它想要用于 [=10] 的时区,它就会开始引起真正的问题=] 和其余的。 (这种安排之所以持续存在,部分原因是 不是 实际上是一个很好的、可移植的、标准的程序更改它正在使用的本地时区的方法。)
多年来,一直有各种半心半意的尝试来清理一些混乱(当然,同时保持向后兼容性)。其中一项尝试涉及您在某些系统版本的 struct tm
中发现的扩展 tm_gmtoff
和 tm_zone
字段。这些添加是 巨大的 改进——我无法想象在没有它们的情况下在系统上进行严肃的 date/time 编程——但它们仍然不是标准的,而且仍然有许多系统没有它们(甚至 "hidden" 拼写 __tm_gmtoff
和 __tm_zone
)。
您可以在这篇论文中阅读更多关于 date/time C 支持的肮脏历史:Time, Clock, and Calendar Programming In C,Eric Raymond。
考虑以下 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
存储人类可读的date/time信息。然后我打印出仅基于 struct tm
对象的格式化信息(时区)。
根据cplusplus reference,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 问题。
感谢您对问题的所有评论,这些评论有助于指明正确的方向。我post下面是我自己的一些研究。我的发言基于我在 GitHub 上找到的 archived repo GNU C 库。它的版本是 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
确实存储了时区信息,至少在这个实现中是这样。
C 标准在 7.27.1 时间的组成部分中说:
The
tm
structure shall contain at least the following members, in any order. The semantics of the members and their normal ranges are expressed in the comments.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
is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable.[tm_isdst]
也就是说,允许供应商举手简单地说:"no time zone was determinable, so I'm not outputting any characters at all."
我的看法:C 时序 API 一团糟。
我正尝试在 <chrono>
库中针对即将推出的 C++20 标准进行改进。
C++20 规范将此从 "no characters" 更改为如果 time_zone
缩写不可用则抛出异常:
http://eel.is/c++draft/time.format#3
Unless explicitly requested, the result of formatting a chrono type does not contain time zone abbreviation and time zone offset information. If the information is available, the conversion specifiers
%Z
and%z
will format this information (respectively). [ Note: If the information is not available and a%Z
or%z
conversion specifier appears in the chrono-format-spec, an exception of typeformat_error
is thrown, as described above. — end note ]
除了上面的段落没有描述 C 的 strftime
,而是一个新的 format
函数,它在 std::chrono
类型上运行,而不是 tm
。此外还有一个新类型:std::chrono::zoned_time
(http://eel.is/c++draft/time.zone.zonedtime) always 有可用的 time_zone
缩写(和偏移量)并且可以用前面提到 format
函数。
示例代码:
#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
在此预览中,您将需要使用 header "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});
或者有一个 non-proposed-for-standardization 辅助工厂函数可以为您进行推导:
cout << format("%Z\n", make_zoned(current_zone(), now));
(#CTAD_eliminates_factory_functions)
日期和时间编程如此困难的原因之一是它从根本上说至少是一个有点困难的问题:"Thirty days hath September"、sexagesimal arithmetic、时区、夏令时,以及闰年,更不用说闰秒了。
但另一个困难的原因是太多的库和语言把它搞得一团糟,不幸的是,C 也不例外。 (正如霍华德在他的回答中提到的那样,C++ 正在努力做得更好。)
尽管每个人都知道全局变量不好,但 C 的 date/time 函数基本上使用其中的几个。实际上,"this system's current time zone" 的概念是一个全局变量,描述时区的全局数据在 localtime
和 strftime
以及许多其他函数之间随意共享。
因此 strftime
可以根据该全局数据填写 %z
和 %Z
,即使它不是作为 struct tm
值的一部分传入的。
这显然不是最佳安排,如果有一种方法可以让程序动态地更改它想要用于 [=10] 的时区,它就会开始引起真正的问题=] 和其余的。 (这种安排之所以持续存在,部分原因是 不是 实际上是一个很好的、可移植的、标准的程序更改它正在使用的本地时区的方法。)
多年来,一直有各种半心半意的尝试来清理一些混乱(当然,同时保持向后兼容性)。其中一项尝试涉及您在某些系统版本的 struct tm
中发现的扩展 tm_gmtoff
和 tm_zone
字段。这些添加是 巨大的 改进——我无法想象在没有它们的情况下在系统上进行严肃的 date/time 编程——但它们仍然不是标准的,而且仍然有许多系统没有它们(甚至 "hidden" 拼写 __tm_gmtoff
和 __tm_zone
)。
您可以在这篇论文中阅读更多关于 date/time C 支持的肮脏历史:Time, Clock, and Calendar Programming In C,Eric Raymond。