Convert time_t from localtime zone to UTC

You can use gmtime:

Convert time_t to tm as UTC time Uses the value pointed by timer to fill a tm structure with the values that represent the corresponding time, expressed as a UTC time (i.e., the time at the GMT timezone).

(c) http://www.cplusplus.com/reference/ctime/gmtime/


If you are okay with using Abseil's time library, one other way to do this is:

auto civil_second =
  absl::LocalTimeZone().At(absl::FromTimeT(<your time_t>)).cs;

time_t time_in_utc = absl::ToTimeT(absl::FromCivil(civil_second, absl::UTCTimeZone()));

(Maybe there is a simpler set of calls in the library to do this, but I have not explored further. :))


I'm going to show two ways of doing this:

  1. Using the C API.
  2. Using a modern C++11/14 library based on top of <chrono>.

For the purposes of this demo, I'm assuming that the current number of seconds in the local time zone is 1,470,003,841. My local time zone is America/New_York, and so the results I get reflect that we are currently at -0400 UTC.

First the C API:

This API is not type-safe and is very error prone. I made several mistakes just while coding up this answer, but I was able to quickly detect these mistakes because I was checking the answers against the 2nd technique.

#include <ctime>
#include <iostream>

int
main()
{
    std::time_t lt = 1470003841;
    auto local_field = *std::gmtime(&lt);
    local_field.tm_isdst = -1;
    auto utc = std::mktime(&local_field);
    std::cout << utc << '\n'; // 1470018241
    char buf[30];
    std::strftime(buf, sizeof(buf), "%F %T %Z\n", &local_field);
    std::cout << buf;
    auto utc_field = *std::gmtime(&utc);
    std::strftime(buf, sizeof(buf), "%F %T UTC\n", &utc_field);
    std::cout << buf;
}

First I initialize the time_t. Now there is no C API to go from a local time_t to a UTC time_t. However you can use gmtime to go from a UTC time_t to a UTC tm (from serial to field type, all in UTC). So the first step is to lie to gmtime, telling it you've got a UTC time_t. And then when you get the result back you just pretend you've got a local tm instead of a UTC tm. Clear so far? This is:

auto local_field = *std::gmtime(&lt);

Now before you go (and I personally messed this part up the first time through) you have to augment this field type to say that you don't know if it is currently daylight saving or not. This causes subsequent steps to figure that out for you:

local_field.tm_isdst = -1;

Next you can use make_time to convert a local tm to a UTC time_t:

auto utc = std::mktime(&local_field);

You can print that out, and for me it is:

1470018241

which is 4h greater. The rest of the function is to print out these times in human readable format so that you can debug this stuff. For me it output:

2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC

A modern C++ API:

There exist no facilities in the std::lib to do this. However you can use this free, open source (MIT license) library for this.

#include "date/tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto zt = make_zoned(current_zone(), local_seconds{1470003841s});
    std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
    std::cout << zt << '\n';
    std::cout << zt.get_sys_time() << " UTC\n";
}

The first step is to create the local time in terms of seconds since the epoch:

local_seconds{1470003841s}

The next thing to do is to create a zoned_time which is a pairing of this local time and the current time zone:

auto zt = make_zoned(current_zone(), local_seconds(1470003841s));

Then you can simply print out the UTC number of seconds of this pairing:

std::cout << zt.get_sys_time().time_since_epoch() << '\n';

This output for me:

1470018241s

(4h later than the input). To print out this result as I did in the C API:

std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";

which outputs:

2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC

In this modern C++ approach, the local time and the UTC time are different types, making it much more likely that I catch accidental mixing of these two concepts at compile time (as opposed to creating run time errors).

Update for C++20

The second technique will be available in C++20 with the following syntax:

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std::chrono;
    zoned_time zt{current_zone(), local_seconds{1470003841s}};
    std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
    std::cout << zt << '\n';
    std::cout << zt.get_sys_time() << " UTC\n";
}

Tags:

C++