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
tmstructure 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:
%Zis 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
%Zand%zwill format this information (respectively). [ Note: If the information is not available and a%Zor%zconversion specifier appears in the chrono-format-spec, an exception of typeformat_erroris 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