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 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)


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.

Tags:

C++

C

Time.H

Ctime