Does struct tm store time zone information as its data member
The C standard says in 7.27.1 Components of time:
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
(emphasis is mine)
That is, implementations are allowed to add additional members to tm
, as you found with glibc/time/bits/types/struct_tm.h
. The POSIX spec has nearly identical wording.
The result is that %Z
(or even %z
) can not be considered portable in strftime
. The spec for %Z
reflects this:
%Z
is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable.[tm_isdst]
That is, vendors are allowed to throw up their hands and simply say: "no time zone was determinable, so I'm not outputting any characters at all."
My opinion: The C timing API is a mess.
I am attempting to improve things for the upcoming C++20 standard within the <chrono>
library.
The C++20 spec changes this from "no characters" to an exception being thrown if the time_zone
abbreviation is not available:
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 ]
Except that the above paragraph is not describing C's strftime
, but a new format
function that operates on std::chrono
types, not tm
. Additionally there is a new type: std::chrono::zoned_time
(http://eel.is/c++draft/time.zone.zonedtime) that always has the time_zone
abbreviation (and offset) available and can be formatted with the afore mentioned format
function.
Example code:
#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
}
(Disclaimer: The final syntax of the formatting string in the format
function is likely to be slightly different, but the functionality will be there.)
If you would like to experiment with a preview of this library, it is free and open source here: https://github.com/HowardHinnant/date
Some installation is required: https://howardhinnant.github.io/date/tz.html#Installation
In this preview, you will need to use the header "date/tz.h"
, and the contents of the library are in namespace date
instead of namespace std::chrono
.
The preview library can be used with C++11 or later.
zoned_time
is templated on a std::chrono::duration
which specifies the precision of the time point, and is deduced in the example code above using C++17's CTAD feature. If you are using this preview library in C++11 or C++14, the syntax would look more like:
cout << format("%Z\n", zoned_time<system_clock::duration>{current_zone(), now});
Or there is a non-proposed-for-standardization helper factory function which will do the deduction for you:
cout << format("%Z\n", make_zoned(current_zone(), now));
(#CTAD_eliminates_factory_functions)
Thanks for all the comments to the question which help pointing to the right direction. I post some of my own research below. I speak based on an archived repo of GNU C Library that I found on the GitHub. Its version is 2.28.9000
.
In glibc/time/bits/types/struct_tm.h
there is
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
};
It seems that struct tm
does store time zone information, at least in this implementation.