4

Is this legit? Im trying to get to a time_t as fast as possible given a string formatted like YYYYMMDDHHMMSS.

static time_t ConvertToSecSince1970(char *szYYYYMMDDHHMMSS)
{
struct tm    Tm;    

    memset(&Tm, 0, sizeof(Tm));
    Tm.tm_year = makeInt(szYYYYMMDDHHMMSS +  0, 4) - 1900;
    Tm.tm_mon  = makeInt(szYYYYMMDDHHMMSS +  4, 2) - 1;
    Tm.tm_mday = makeInt(szYYYYMMDDHHMMSS +  6, 2);
    Tm.tm_hour = makeInt(szYYYYMMDDHHMMSS +  8, 2);
    Tm.tm_min  = makeInt(szYYYYMMDDHHMMSS + 10, 2);
    Tm.tm_sec  = makeInt(szYYYYMMDDHHMMSS + 12, 2);
    return mktime(&Tm);
}

It seems to produce the same answer if I created TM using:

strptime(szYYYYMMDDHHMMSS, "%Y%m%d%H%M%S", &Tm);

I am worried that tm_yday, tm_wday, tm_isdst, tm_gmtoff, tm_zone are important. My dates are UTC so I figured gmtoff = 0 and tm_zone = 0 might work.

By the way, Here is makeInt:

inline int makeInt(const char *p, int size)
{
    const char *endp;
    int intval = 0;

    endp = p + size;
    while (p < endp)
    {
        intval = intval * 10 + *p - '0';
        p++;
    }
    return intval;
}
johnnycrash
  • 5,184
  • 5
  • 34
  • 58
  • Well guess what. mktime takes 1.6 micro sec, so the fact that getdate / strptime are about .1 micro sec and my code is about .02 microsec doesnt really matter. The bottleneck is mktime. I guess I will make a lookup based upon year,mon,day and add to that the secs. – johnnycrash Jun 10 '11 at 08:49
  • 1
    Run strace on your process. Several of the glibc time function does a stat() call and possibly more on every function call if the TZ environment variable is not set. (you can set it to e.g. :/etc/localtime to avoid that, if that's the problem) – nos Jun 10 '11 at 08:58

3 Answers3

4

You would probably be better off using getdate unless you are sure it is too slow. Else, what you are doing looks pretty fine if not a slight bit cryptic.

Andrew White
  • 52,720
  • 19
  • 113
  • 137
  • +1. My (modest) box can do `getdate` about ten million times a second (unoptimised code). Unless you need it faster, I'd opt for the "standard" approach. Even if you _do_ need it faster, you'll probably find that difficult. – paxdiablo Jun 10 '11 at 02:36
  • two problems. Speed is important, and what I am doing is already pushing it...dang string date. GetDate works, except the timezone is GMT. Any ideas. I'm googling how to tell getdate the string in GMT or UTC. – johnnycrash Jun 10 '11 at 02:37
  • @johnny, I'm curious as to the speed requirement. What exactly are you doing that requires this to be so fast? – paxdiablo Jun 10 '11 at 02:39
  • Interesting thing i am noticing. getdate is not setting tm_isdstm, tm_gmtoff, or tm_zone. You need to send it a zeroed out Tm. – johnnycrash Jun 10 '11 at 02:52
  • My MakeInt is inlined. Overall my code is 3x faster. Seems like it produces the same answer except it can't set tm_wday or tm_yday – johnnycrash Jun 10 '11 at 02:53
  • Woops! Why so fast. I think I only need to call it max 4,000 times per transaction. Up to 16 transactions per sec, so I was just trying to keep it quick. getdate is taking about 100ns and my code takes about 20 ns. So the total is 6ms vs 1ms. Our perf testers will be able to see that; although, its not really that big a deal. If I detect any problems I am going to use getdate. – johnnycrash Jun 10 '11 at 03:32
  • 1
    From the getdate(3) manpage: "In glibc, getdate() is implemented using strptime(3)". As getdate() has to iterate trying different formats, it's slower than just calling strptime() directly with the known format. – janneb Jun 10 '11 at 06:37
2

mktime() ignores the tm_wday and tm_yday fields, and calculates new values for them based on the other fields. The same applies to the BSD extensions tm_gmtoff and tm_zone, except that they are calculated from the local time zone.

Note however that mktime() uses local time, not UTC, so if your input dates are UTC then your timezone must be set to UTC.

caf
  • 233,326
  • 40
  • 323
  • 462
  • What do you mean by the last sentence exactly? How would I set the timezone to UTC? Luckily ours is, but what if it were not? – johnnycrash Jun 10 '11 at 03:33
  • @johnnycrash: There's no way in standard C to do it, but on linux you can set the TZ environment variable before starting your application. What that sentence means is that `mktime()` interprets your `struct tm` as a time *in the local time zone*. – caf Jun 10 '11 at 04:33
1

Date and time handling in general has a lot of tricky gotchas, so I'd strongly recommend strptime() rather than rolling your own. If performance of strptime() is a bottleneck, work around it in some other way than trying to create a better strptime(), such as

  1. Your string, as well as time_t (as it's usually used), gives only second accuracy, so you could cache the converted value and update it only once per second.

  2. Don't work with timestamps in string form in the first place. E.g. pass around a time_t value (containing seconds since the Epoch as returned by time()) instead and only convert it to a string when/if you need to print it/show it to the user/.

janneb
  • 36,249
  • 2
  • 81
  • 97
  • Unfortunately the code is millions of lines and they loved strings. However a lookup table based upon year * 12 * 31 + month * 31 + day is < 10k entries for 20 years. I think I will do that. – johnnycrash Jun 10 '11 at 08:52