2
\$\begingroup\$

My goal is to precisely calculate the current timezone using C++ standard library, represented as the difference (in seconds) between GMT time and local time.

Example: Italy is GMT+1 so my expected output is -3600. This offset represents the difference between GMT time (i.e. 12:00) and italian time (i.e. 13:00), ignoring daylight savings.

My actual code looks like this and it seems to work:

long timezone = 0;

bool _tzset() {
   std::time_t now = std::time(NULL);
   std::time_t local = std::mktime(std::localtime(&now));
   std::time_t gmt = std::mktime(std::gmtime(&now));
   timezone = static_cast<long> (gmt - local);
   return true;
}

From my understanding, what I'm doing here is getting the local time (ignoring DST) and the GMT time (ignoring DST) and just returning the difference.

It looks pretty straightforward but is it correct? Do you see any flaws?

EDIT: I was not clear about my real goal here. I am working on a Win32 framework that has to be ported to the Universal Windows Platform. The problem is that this framework uses a lot of CRT functions that are not supported anymore in the UWP. As you can see, _tzset() is one of them (also it looks like that the timezone variable is defined globally in a Win32 environment). In other words what I'm doing here is giving an alternative implementation to these unsupported functions. I can't change the signature of them.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Headers

You're missing #include <ctime>.

Naming

Identifiers beginning with underscore are reserved for the implementation, so _tzset() is a risky choice, and may result in undefined behaviour.

Interface

What does the return value mean? Although declared to return bool, it never returns a false value, so it's unclear how the result is intended to be used. void may be a better choice, but I think it's more reasonable to return an arithmetic result (and thus eliminate the use of the global timezone).

It is likely that we might want to find this information for an arbitrary time value, rather than only for "now", so I suggest adding a defaulted parameter to pass in the time to be considered.

Assumptions about std::time_t

This code assumes that time_t is a signed type; if it's unsigned, then the subtraction gmt - local may overflow. It also assumes that time_t counts in units of seconds; that is not required by the C++ standard (though it is a common implementation choice, and is perhaps required for POSIX compliance). Instead of using arithmetic subtraction, it is portable to use std::difftime() - it is safe if time_t is unsigned, and it is defined to return its results in seconds.

Assumptions about std::mktime()

This code assumes that std::mktime() ignores the tm_isdst field of std::tm. In my experience, implementations do not ignore it, meaning that local and gmt values are always equal in time zones such as Europe/London.

Failure around DST transitions

Even if corrected to account for tm_isdst, I'm finding it hard to reason whether the code is correct shortly before (or shortly after) a daylight-savings transition (in either direction). I'd expect to see four unit-tests (in a specific timezone) that verify the behaviour, but there are no tests provided at all.

Alternative approach

Instead of (ab)using std::mktime() in this way, it's far safer and more transparent to use std::time_put() with a format string of %z to obtain the offset from UTC in ISO-8601 format (±HHMM) and convert that string into seconds (taking care to apply the sign to both the hours and the minutes):

#include <ctime>
#include <iomanip>
#include <sstream>

static constexpr time_t const NULL_TIME = -1;

// returns difference in seconds from UTC at given time
// or at current time if not specified
long tz_offset(time_t when = NULL_TIME)
{
    if (when == NULL_TIME)
        when = std::time(nullptr);
    auto const tm = *std::localtime(&when);
    std::ostringstream os;
    os << std::put_time(&tm, "%z");
    std::string s = os.str();
    // s is in ISO 8601 format: "±HHMM"
    int h = std::stoi(s.substr(0,3), nullptr, 10);
    int m = std::stoi(s[0]+s.substr(3), nullptr, 10);

    return h * 3600 + m * 60;
}

#include <iostream>
int main()
{
    std::cout << tz_offset() << std::endl;
}
\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much Toby. Unfortunately I'm not free to change the function signature and parameters, please see my EDIT to the post. \$\endgroup\$
    – Oneiros
    Sep 18, 2017 at 12:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.