3

I have a function that I wrote (if there is a good standard substitute, please let me know...)

time_t get_unix_time(string time_str) {
    time_t loctime;
    time(&loctime);

    struct tm *given_time;
    time_str = time_str.substr(0, time_str.find_first_of('.'));

    replace(time_str.begin(), time_str.end(), ':', ',');
    replace(time_str.begin(), time_str.end(), '-', ',');
    replace(time_str.begin(), time_str.end(), '/', ',');
    replace(time_str.begin(), time_str.end(), ' ', ',');

    given_time = localtime(&loctime);
    vector<string> trecord = split_string(time_str, ',');

    given_time->tm_year = atoi(trecord.at(0).c_str()) - 1900;
    given_time->tm_mon  = atoi(trecord.at(1).c_str()) - 1;
    given_time->tm_mday = atoi(trecord.at(2).c_str());
    given_time->tm_hour = atoi(trecord.at(3).c_str());
    given_time->tm_min  = atoi(trecord.at(4).c_str());
    given_time->tm_sec  = atoi(trecord.at(5).c_str());

    return mktime(given_time);
}

The input (time_str) to the function is of the format 1970-01-01 00:00:00.0. The split_string() function splits the string time_str into a vector containing:

{ 1970, 01, 01, 00, 00, 00 }

which is used to fill in the given_time structure.

I wrote a function to test it, and passed it exactly that input (start of epoch). However, the time it gives me back is 21600, which is 1970-01-01 06:00:00, or UTC+6. The expected output is 0 (start of the epoch).

Note: that I am in the US-Central time zone, which is UTC - 6. At midnight on 1st Jan 1970 CST, time @ UTC would be 1st Jan 1970 06:00:00.

Is there anything in my function that is making it specific to my timezone? Am I doing something wrong in this function, or can I do something different to make it zone independent, or at least always UTC.

Sagar
  • 9,456
  • 6
  • 54
  • 96
  • Also, I cannot use the boost library or any other additional libraries. I can only use standard C++ stuff. – Sagar Jan 13 '11 at 16:22
  • 1
    Timezone handling in the C++ library is pretty darned obscure. If you started your program with the environment variable `TZ` set to the value `UTC` this code snippet would work. – Omnifarious Jan 13 '11 at 16:36
  • 1
    mktime interprets the given time as local time. So if you give 1970-01-01 00:00:00 it will be in your local time, so mktime will return UTC-0 time, i.e. 1970-01-01 06:00:00 – Robert Jan 13 '11 at 16:38

6 Answers6

5

If you are using glibc you have the timegm function at your disposal, which is a version of mktime that always interprets the time as if it were in the GMT timezone. Unfortunately, the documentation for that function basically states that it cannot otherwise be implemented using standard library calls. So you're sort of out of luck unless you have it.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Thank you! That worked perfectly. I was looking at the manpage for gmtime and did not see it anywhere. – Sagar Jan 13 '11 at 16:47
4

mktime takes a time in the local time zone. So, if you pass it 1970-01-01 00:00:00 local time, it returns 1970-01-01 06:00:00 UTC, as it should.

As an alternative, you can call timegm, if you're using glibc. If you're not using glibc, you temporarily change the local time to UTC when calling mktime by messing with the TZ environment variable, as described on the timegm manpage:

time_t my_timegm (struct tm *tm) {
    time_t ret;
    char *tz;
    tz = getenv("TZ");
    setenv("TZ", "", 1);
    tzset();
    ret = mktime(tm);
    if (tz)
        setenv("TZ", tz, 1);
    else
        unsetenv("TZ");
    tzset();
    return ret;
}

Also, your call to localtime is unnecessary, and you probably ought to set given_time->tm_isdst, to avoid possible daylight savings time issues.

Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • Thanks. I checked timegm and it does what I want it to, but I will definitely check and set the dst issue. – Sagar Jan 13 '11 at 16:49
1

Maybe you should use gmtime instead of time, to rid yourself of timezone issues.

Edit: I don't really understand why you fill the structure with the current time, then overwrite all its components. Why not just:

time_t get_unix_time(const string& time_str)
{
    vector<string> trecord = split_string(time_str, ',');

    tm given_time;
    given_time.tm_year = atoi(trecord.at(0).c_str()) - 1900;
    given_time.tm_mon  = atoi(trecord.at(1).c_str()) - 1;
    given_time.tm_mday = atoi(trecord.at(2).c_str());
    given_time.tm_hour = atoi(trecord.at(3).c_str());
    given_time.tm_min  = atoi(trecord.at(4).c_str());
    given_time.tm_sec  = atoi(trecord.at(5).c_str());

    return mktime(&given_time);
}

Another edit:

Ugh, mktime considers local time too. I'm not really sure how you can get around this other than setting your timezone locale to UTC.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • - replacing the current time and replacing with that I need is not really the issue. I tried your function, and it still gives me 21600 instead of 0. 21600 is = UTC + 6. – Sagar Jan 13 '11 at 16:41
  • @Sagar: "Ugh, mktime considers local time too. I'm not really sure how you can get around this other than setting your timezone locale to UTC." Did you do that? Or did you just put the code in, change nothing else, and hope for the best? – Lightness Races in Orbit Jan 13 '11 at 16:45
  • Unfortunately, while I can change the timezone on my development machine, it will not be possible on the client's machine. – Sagar Jan 13 '11 at 16:51
  • @Sagar: Not so. You have control of the environment variables that affect the process. I believe the command is `setenv`. – Lightness Races in Orbit Jan 13 '11 at 17:31
1

Just avoid these awkward functions and do the math yourself. POSIX specifies that time_t is an arithmetic type in the form of seconds since "the epoch" (1970-01-01 00:00:00 GMT) without any leapsecond nonsense (all days are exactly 86400 calendar seconds, which differ from SI seconds by a tiny amount), so aside from a little leapyear logic, the computation is extremely straightforward.

Calendar calculations like this are a standard introductory programming exercise so I'm sure you can work it out or find solutions on the web.

As an aside, perhaps the reason ISO C and POSIX omit such a function is that, unlike conversions involving timezones which can be arbitrarily complex and which only the host's library can perform reliably and consistently across different applications, GMT conversions are pure arithmetic with no external parameters.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • I considered doing that if I could not find a solution to this, but figured if there is such a function already, why not try looking for..it saves me some extra testing work. – Sagar Jan 13 '11 at 20:40
  • By the way, here is the reference with formula for the other direction: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 – R.. GitHub STOP HELPING ICE Jan 13 '11 at 21:46
0

When you call mktime, it interpretes the parameter as a local time. You have also used function like "localtime" that seems to be useless and I think you can drop them.

Simone-Cu
  • 1,109
  • 8
  • 21
0

You could write a wrapper around strptime to do the parsing.

struct tm given_time;

strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &given_time);

return mktime(&given_time);

@Josh Kelley's answer explains the timezone issue thoroughly.

Daniel Gallagher
  • 6,915
  • 25
  • 31