does a C++ locale have an associated timezone? And if yes, how do you access it?
does a C++ locale have an associated timezone?
All aspects of current timezone are implementation defined.
The exact wording of %Z
specifier from C99 (C++ delegates C library function specification to the C standard) is:
is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable.
It seems a bit ambiguous. One interpretation is indeed that locale may affect the time zone. Another, which doesn't quite fit the wording as well, would be that the locale affects the name or abbreviation of the time zone. Regardless, there appears to be no guarantee that the time zone isn't affected by the locale, although I would not expect that it were.
how do you access it?
As far as I know, you can't using standard library utilities. Not directly anyway, and no way of modifying it.
A way to print the current timezone is to use the %z
or %Z
format specifiers of strftime
/put_time
/time_put
as you've shown.
There is a way to get the zone difference as an integer as well. std::mktime
unparses a std::tm
structure into a timestamp according to the locale, while std::gmtime
parses a timestamp into std::tm
structure according to the UTC, so if you start with the epoch and combine those two, you will get the difference of the current locale time zone and the UTC in seconds.
std::time_t t = 0;
std::cout << -1 * std::mktime(std::gmtime(&t));
Direct Answer to your question
Does a C++ locale have an associated time zone?
No.
Nor will it in the future. As correctly noted in the question, for many locales it wouldn't make sense as the geographical area represented by the locale can have more than one time zone.
The C standard does say in the spec for strftime
:
%Z
is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable.[tm_isdst]
But the C spec for struct lconv
provides no such member to store that information. The spec does allow implementations to add such members, but in practice, implementations do not store that information with the C locale.
The C++ locale facets time_put
and time_get
define themselves in terms of the C spec for strftime
, the POSIX spec for strptime
, and some additions, which do not include a time zone name or abbreviation.
The POSIX spec for strftime
is much more detailed than the C spec, and removes the association with "locale":
Z
Replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists.[ tm_isdst]
The POSIX spec for struct lconv
is also much more detailed than the C spec, but still does not provide storage for a time zone name or abbreviation.
But the future does bring hope of more easily and effectively accessing information about time zones, at least in C++.
Prior to C++20, C++ has knowledge of:
A single time standard: UTC, which is closely modeled by Unix Time.
A single time zone: the "local time zone" set by the user or administrator of the computer. UTC can also be used as a local time zone.
As detailed above, the local time zone is not part of the C++ (or C) locale data. The locale data does include some calendrical data such as:
- Full and abbreviated weekday names.
- Full and abbreviated month names.
- Local conventional formats for displaying date and time (e.g. year, month, day ordering).
The UTC offset (%z
) and time zone abbreviation (%Z
) may be available, but would be stored as part of the local time zone data, instead of with the current locale data, largely because there is not a good one-to-one mapping between time zones and locales.
Explanation of what happened with the code presented in the OP's question
In your example: tm when{};
zeroes all members of the tm
, including tm_isdst
. When tm_isdst
is zero, this means Daylight Saving Time is known to not be in effect, for this particular tm
.
tm
is also allowed to have members not specified by the standard. A popular extension is to have a member tm_gmtoff
which holds the UTC offset in seconds. If your Linux implementation has such a member, tm when{};
would have set it to 0 seconds. If your Windows implementation does not have such a member, the UTC offset of the local time zone would be stored elsewhere. This explains the differences you're seeing, and both implementations are conforming.
Helpful information about how to access time zones since C++ locales do not provide access
In the C++20 spec, there exists a new type called std::chrono::time_zone
. One of the member functions of time_zone
is:
template<class Duration> sys_info get_info(const sys_time<Duration>& st) const;
sys_time<Duration>
is just a system_clock::time_point
, but of any precision. So you give a time_zone
a time_point
, and you get back a sys_info
which contains all kinds of useful information about that time_zone
at that time_point
:
struct sys_info
{
sys_seconds begin;
sys_seconds end;
seconds offset;
minutes save;
string abbrev;
};
- The range
[begin, end)
tells you for what times this information is valid (these are UTC time points). offset
is thetime_zone
's current UTC offset inseconds
.- If
save != 0min
, thetime_zone
is currently considered to be in daylight savings. - The
time_zone
's current abbreviation is stored inabbrev
.
Additionally, there is a non-member function:
const time_zone* current_zone();
which returns a pointer to your current local time zone. Putting this all together, here is a C++20 program that prints out interesting information about your current local time zone:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
std::cout << current_zone()->get_info(system_clock::now()) << '\n';
}
This just output for me:
2018-03-11 07:00:00
2018-11-04 06:00:00
-04:00:00
01:00
EDT
If you like, you can experiment with this part of C++20 using C++11, 14 or 17 by using Howard Hinnant's timezone library. This library puts everything in namespace date
instead of std::chrono
.
You can also get information on any IANA time zone, for example:
#include "date/tz.h"
#include <chrono>
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
std::cout << locate_zone("Australia/Sydney")->get_info(system_clock::now()) << '\n';
}
which just output for me:
2018-10-06 16:00:00
2019-04-06 16:00:00
11:00:00
01:00
AEDT
Note though that even in C++20, time zones and locales are not coupled. It just doesn't make sense to do so.