Consider the following C++ code
#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;
}
So stored in now
is an unambiguous integral value, while local
and gm
are struct tm
that store human-readable date/time information. Then I print out the formatted information (timezone) based only on the struct tm
objects.
According to the cplusplus reference, the data members of struct tm
are
tm_sec
tm_min
tm_hour
tm_mday
tm_mon
tm_year
tm_wday
tm_yday
tm_isdst
If that's all that a struct tm
contains, how does the program know that the timezone information from it? That is, how does it know that the timezone is HKT
for local
, and that the timezone is UTC
for gm
?
If that's not all that a struct tm
contains, please explain how it stores timezone information.
By the way, though the demo code is in C++, I guess this question in essence stands as a legitimate C question as well.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With