Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does struct tm store time zone information as its data member

Tags:

c++

c

time.h

ctime

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.

like image 242
aafulei Avatar asked Feb 04 '23 17:02

aafulei


1 Answers

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)

like image 193
Howard Hinnant Avatar answered Feb 06 '23 11:02

Howard Hinnant