0

Depending on the amount of input data, I have a program that runs in seconds or in days. At the end of my program, I want to print the elapsed "clock wall" time: in seconds if it is less then one minute, in min and sec if it is less than one hour, in hour-min-sec if it is less than one day, and in day-hour-min-sec otherwise. Here is the code I am using:

#include <cstdio>
#include <ctime>
#include <unistd.h> // for sleep

int main (int argc, char ** argv)
{
  time_t startRawTime, endRawTime;

  time (&startRawTime);
  printf ("%s", ctime (&startRawTime));

  sleep (3); // any preprocessing of input data

  time (&endRawTime);
  printf ("%s", ctime (&endRawTime));

  printf ("%.0fs\n", difftime (endRawTime, startRawTime));

  time_t elapsed = static_cast<time_t>(difftime (endRawTime, startRawTime));
  struct tm * ptm = gmtime (&elapsed);
  printf ("%id %ih %im %is\n", ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

  return 0;
}

Here is what it prints:

Mon Apr  9 14:43:16 2012
Mon Apr  9 14:43:19 2012
3s
1d 0h 0m 3s

Of course the last line is wrong (it should be "0d"). It seems it can be solved easily by printing ptm->tm_mday - 1. However, ptm->tm_mday will also be "1" when there really was one day elapsed between the two dates. And so in that case, I don't want to make it appear as "0d".

So is there a way to handle this properly? Or should I get the result of difftime as a double (that is, as a number of seconds) and then calculate myself the number of sec/min/hours/days?

Remark: my code is used only on Linux, compiled with gcc -lstdc++.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
tflutre
  • 3,354
  • 9
  • 39
  • 53
  • *"compiled with gcc -lstdc++"* -- Why not use `g++` instead? It takes care of figuring out what options to use for compiling C++ code. – Keith Thompson Apr 09 '12 at 20:59
  • This is really abusing the time library. For example a tm struct represents a specific date where tm_mday is the day of month, not a number of days. `` is a type safe time library that will stop you from making this kind of mistake. – bames53 Apr 09 '12 at 22:59

2 Answers2

3

A time_t value represents a particular moment in time. The result of difftime is the interval, in seconds, between two moments. That's a very different thing.

In your code, difftime() returns 3.0, since there are 3 seconds between the two specified times. Converting that to time_t gives you a moment 3 seconds after the epoch; on most systems, that's going to be 3 seconds past midnight GMT on January 1, 1970. The tm_mday value is 1 because that was the first day of the month.

You might be able to make this work by subtracting 1 from the tm_mday value, since tm_mday is 1-based rather than 0-based. But you'll still get meaningless results for longer intervals. For example, an interval of 31.5 days will give you noon on February 1, because January has 31 days; that's not relevant to the information you're trying to get.

Just treat the result of difftime() as a double (because that's what it is) and compute the number of days, hours, minutes, and seconds by simple arithmetic.

(With some loss of portability, you can just subract the time_t values directly rather than using difftime(). That will make some of the arithmetic a little easier, but it will break on systems where a time_t value is something other than an integer count of seconds since some epoch. difftime() exists for a reason.)

Of course the last line is wrong (it should be "0d"). It seems it can be solved easily by printing "ptm->tm_mday - 1". However, ptm->tm_mday will also be "1" when there really was one day elapsed between the two dates. And so in that case, I don't want to make it appear as "0d".

That's not correct; if the time interval is just over 1 day, ptm->tm_mday will be 2. You can verify this with a small modification to your code:

time (&endRawTime);
endRawTime += 86400; // add this line
printf ("%s", ctime (&endRawTime));

When I make this change, I get this output:

Mon Apr  9 13:56:49 2012
Tue Apr 10 13:56:52 2012
86403s
2d 0h 0m 3s

which could be corrected by subtracting 1 from ptm->tm_mday. But again, that's not the right approach.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • thanks, for those who are interested, here is a way to calculate the hours, min and secs http://stackoverflow.com/a/2419597/597069 – tflutre Apr 09 '12 at 21:43
1

Here's an example using the <chrono> library, a typesafe timing library that will prevent you from making the kind of mistake you're making. In chrono time_points and durations are not interchangeable, and if you try to use them that way then you get compiler errors.

#include <chrono>
#include <iostream>
#include <thread>
#include <cassert>

template<typename Rep,typename Period>
void print_duration(std::chrono::duration<Rep,Period> t) {
    assert(0<=t.count() && "t must be >= 0");

    // approximate because a day doesn't have a fixed length
    typedef std::chrono::duration<int,std::ratio<60*60*24>> days;

    auto d = std::chrono::duration_cast<days>(t);
    auto h = std::chrono::duration_cast<std::chrono::hours>(t - d);
    auto m = std::chrono::duration_cast<std::chrono::minutes>(t - d - h);
    auto s = std::chrono::duration_cast<std::chrono::seconds>(t - d - h - m);
    if(t>=days(1))
        std::cout << d.count() << "d ";
    if(t>=std::chrono::hours(1))
        std::cout << h.count() << "h ";
    if(t>=std::chrono::minutes(1))
        std::cout << m.count() << "m ";
    std::cout << s.count() << "s";
}

int main() {
    auto start = std::chrono::steady_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(3));
    auto finish = std::chrono::steady_clock::now();

    print_duration(finish-start);
    std::cout << '\n';
}

Notes for GCC

Older versions of GCC have monotonic_clock instead of steady_clock. 4.7 has steady_clock.

In order to access std::this_thread::sleep_for you may have to define _GLIBCXX_USE_NANOSLEEP for some reason.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • thanks, I didn't know about chrono, but apparently I will need a more recent version of gcc – tflutre Apr 10 '12 at 14:38
  • @wfoolhill Added some notes for gcc. If you're at least at GCC 4.5 you should be able to use chrono. I'm not sure about support earlier than that except that it definitely was not in 4.2. – bames53 Apr 10 '12 at 14:55