How do I get time_t in GMT on Windows in C
Asked Answered
D

4

6

I am writing some code that will run on multiple intercommunicating systems. I was using time() to get time_t, but this was causing problems with time zone differences between the systems, so I want to get time_t in GMT. I've been looking through the time.h functions, but it is unclear to me how I can be sure that I will get the time correctly. This is what I've come up with so far:

time_t t = time();
struct tm *gtm = gmtime(&t);
time_t gt = mktime(gtm);

Now, this seems to get the correct answer on my machine, but I want to know if it will work universally before I push it out, even if the other machines' clocks are set to local time or GMT or in different time zones or whatever. The reason I am concerned is because of mktime. In the description, it says that it interprets the tm struct as "a calendar time expressed in local time." That sounds to me like it will not be returning the GMT time, though it seems to be on my machine. Also, when I print out gt, it is 4 hours ahead of t, which seems right. But if I run the following:

time_t t = time();
struct tm *gtm = gmtime(&t);
struct tm *ltm = localtime(&t);
printf("%d, %d\n", gtm->tm_hour, ltm->tm_hour);

the hours are the same and are local time, which is not what I expected.

For the record, in another answer, I saw reference to timegm(), which sounds perfect, but it does not exist on my system.

So in short, how do I get time_t in GMT on any Windows machine in C?

Edit: removed msvcrt tag that was added, as I am not using the msvcrt.

Dearden answered 2/8, 2012 at 20:31 Comment(0)
C
7

time_t is always in UTC, by definition. So time() does what you want. Timezones only come into play when you are converting between time_t and broken-down time representation.

If you have the time in UTC in broken-down time representation, you need to temporarily switch the timezone to UTC, use mktime() to convert to a time_t, and switch the timezone back, like this:

time_t convert_utc_tm_to_time_t (struct tm *tm)
{
    char *tz;
    time_t result;

    /* temporarily set timezone to UTC for conversion */
    tz = getenv("TZ");
    if (tz) {
      tz = strdup (tz);
      if (!tz) {
        // out of memory
        return -1;
      }
    }
    setenv("TZ", "", 1);
    tzset();

    tm->tm_isidst = 0;
    result = mktime (tm);

    /* restore timezone */
    if (tz) {
      setenv("TZ", tz, 1);
      free (tz);
    }
    else {
      unsetenv("TZ");
    }
    tzset();

    return result;
}
Crayon answered 2/8, 2012 at 21:11 Comment(6)
You need to copy the string returned by getenv(), since it returns a direct pointer into the current environment, which gets invalidated whenever you modify the environment.Lagomorph
Also FWIW, GNU's libc and some BSDs have a non-standard function timegm(3) which is the inverse of gmtime, but unless you're targeting a specific system, I wouldn't use it for portability's sake.Lagomorph
Thanks, that's exactly what I was looking for. So, if time() is giving me different results in different time zones, the problem is likely the computer, not the code. Maybe their clock is set wrong or something.Dearden
Pedantic note: Likely want to insure tm->tm_isidst == 0 before calling mktime().Bowers
"time_t is always in UTC, by definition." may be true in *nix, certainly common in windows and elsewhere, yet it is not defined as that per the C spec.Bowers
time_t may always be in UTC, but this is no the issue. Conversion to local TZ is later in the code - when calling localtime. @craig65535 pointed out the correct answer.Malignity
Z
3

Looking at the first part of your code:

time_t t = time();
struct tm *gtm = gmtime(&t);
time_t gt = mktime(gtm);

You're setting gt to a bogus value. When calling mktime, you need to pass in a struct tm* that is in local time. time_t is defined as the number of seconds since January 1, 1970, 00:00 UTC. Unless you're currently in the +0000 time zone, the return value from your call to mktime will represent some time a few hours in the past or future.

As for the second part:

time_t t = time();
struct tm *gtm = gmtime(&t);
struct tm *ltm = localtime(&t);
printf("%d, %d\n", gtm->tm_hour, ltm->tm_hour);

I think gmtime and localtime are returning the same buffer in memory, which might be why you're getting the same two hours in your output. The second call is overwriting the output of the first. Try this instead:

struct tm gtm, ltm;
struct tm *tm_buf;
time_t t = time();
tm_buf = gmtime(&t);
memcpy(&gtm, tm_buf, sizeof(struct tm));
tm_buf = localtime(&t);
memcpy(&ltm, tm_buf, sizeof(struct tm));
printf("%d, %d\n", gtm.tm_hour, ltm.tm_hour);
Zeus answered 3/8, 2012 at 0:1 Comment(2)
That's very helpful, thanks. Now that you mention it, I saw something about gmtime and localtime sharing a struct, but I forgot. That certainly leads to confusing results.Dearden
And this is the only correct answer. The error was fully technical.Malignity
B
2

Use gmtime() as you want your time figure in GMT. localtime will report the time in the local time zone, and will only by GMT when the local time zone is also GMT.

Windows has its own code and data structures for dealing with time and time zones. To get GMT using windows specific c code, you can use GetSystemTime(). You can find more information about it at this URL: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724390(v=vs.85).aspx

Please note that Windows uses its own date/time structure, but that is also detailed with the GetSystemTime() and its related pages. In short, GetSystemTime() retrieves the current system date and time, expressed in Coordinated Universal Time (UTC) (also known as GMT). It uses the Windows structure SYSTEMTIME which is a structure with many elements of time.

Barograph answered 2/8, 2012 at 21:6 Comment(1)
I forgot to mention that I don't have access to the Windows functions, only standard library ones. Also, I was using localtime only for the sake of comparison with gmt, not because I thought it would get me the correct value.Dearden
R
0

Note that for such a trivial conversion, you can use your own function. There is a copy here of the mkgmtime()

https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/mkgmtime.c

This allows you to convert a struct tm to a time_t value ignoring any timezone information. Not including the comments, this is probably less than 100 lines of code, so very easy to add it to your project.

Rabid answered 2/12, 2014 at 8:24 Comment(2)
Link reports "Error 404 We're sorry but we weren't able to process this request.". Suggest update or codeBowers
@chux Ah! It moved several times. It's easier to look for it on github now, I would think. The code is a bit much, like 258 lines of code just for the .c and some more for the .hRabid

© 2022 - 2024 — McMap. All rights reserved.