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 type format_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)
tm
doesn't contain timezone information.strftime
gets the timezone by behind-the-scenes voodoo. If you want to get the timezone in general, that's a bit of a mess. There is (currently) no standard way to get a time zone. Fortunately Howard Hinnant is on that job.... – Tinnertm
, how doesstrftime
know to respond in different ways to twostruct tm
objects? Unlesstm
contains some information like thistm
is created bylocaltime
, thattm
is created bygmtime
. – Schorltm
struct doesn't store timezone infos, what makes you think it does? The difference is rather in the calls togmtime()
andlocaltime()
. – Speightstrftime
tells the suckers apart. Should add that POSIX leaves what happens undefined. – Tinnertm
s with extra info. Here's one. Note theconst char *tm_zone
member. What platform are you compiling for? Take a look at thetm
implementation to see if they've extended the structure. – Tinner