Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

does a C++ locale have an associated timezone? And if yes, how do you access it?

Tags:

c++

locale

I've done a little research on this, and I have pretty convincing evidence for the answer YES, and the answer NO. I'm not sure which side to believe.

First, the documentation I've found on cppreference.com, and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf appear to say nothing about this. I take that as evidence that locales do NOT support a timezone.

But https://en.cppreference.com/w/cpp/locale/time_get/get and https://en.cppreference.com/w/cpp/locale/time_put/put both say:

%z writes offset from UTC in the ISO 8601 format (e.g. -0430), or no characters if the time zone information is not available %Z writes time zone name or abbreviation, or no characters if the time zone information is not available (locale dependent)

which appears to suggest there is a timezone SOMETIMES associated with a locale () object.

Now, if you take the locale en_US.utf8 (one of my favorites ;-)), there really isn't any sensible timezone to associate (the United States contains at least 4 or more timezones).

So time to get empirical.

I ran the code:

#include <iostream>
#include <cstdlib>
#include <locale>
#include <sstream>
using namespace std;
int main ()
{
    //  locale l;
    locale                l = locale::classic ();
    tm                    when{};
    const time_put<char>& tmput = use_facet<time_put<char>> (l);
    ostringstream         oss;
    oss.imbue (l);
    static const string   kTZOffsetPattern_{"%z"};
    tmput.put (oss, oss, ' ', &when, kTZOffsetPattern_.c_str (), kTZOffsetPattern_.c_str () + kTZOffsetPattern_.length ());
    cout << oss.str ();
    return 0;
}

On Linux (ubuntu), this gave the answer I expected, +0000 (OK, I also wouldn't have been surprised with error or empty string).

But on Windows (visual studio.net 2k17 - 15.8.7) - this gives: -0500

Yes, as you may have guessed, I'm testing this in the eastern timezone. But I still would have expected 0, or the empty string (especially for the locale::classic() case).

like image 725
lewis Avatar asked Oct 16 '18 16:10

lewis


People also ask

How do I get locale time zone?

The short answer: you can't. The long answer: There is no such thing as "proper time zone for a locale". That's just because there are a few countries that have more than one time zone (for example United States).

What is timezone offset in JavaScript?

Timezone offset is the time difference in hours or minutes between the Coordinated Universal Time (UTC) and a given time zone. The JavaScript getTimezoneOffset() method is used to find the timezone offset. It returns the timezone difference in minutes, between the UTC and the current local time.


2 Answers

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:

  1. A single time standard: UTC, which is closely modeled by Unix Time.

  2. 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 the time_zone's current UTC offset in seconds.
  • If save != 0min, the time_zone is currently considered to be in daylight savings.
  • The time_zone's current abbreviation is stored in abbrev.

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.

like image 172
Howard Hinnant Avatar answered Nov 01 '22 00:11

Howard Hinnant


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));
like image 27
eerorika Avatar answered Nov 01 '22 00:11

eerorika