3

I'm writing a function that, among other things, must print out an elapsed time counter. It receives by reference a start time _start, and compares it to current, both typed as time_t. I want to use strftime() to print out the observed time delta in ISO 8601 format. Here's what I attempted to do:

// Negative start time implies program has not begun
if (*_start > 0) {
    time_t elapsed = current - *_start;
    strftime(time_str, sizeof(time_str) - 1, "%T", localtime(&elapsed));
    printf("%s\n", time_str);
}

And here is the output I get immediately after running the program

01:00:00
01:00:00
01:00:01
01:00:01
01:00:01

The seconds work fine, if I let it run longer they get incremented as expected, so do the minutes, however the hour starts as 01 as opposed to 00. Why is this happening? How can I get the hours to start zeroed, like the minutes?

Bernardo Meurer
  • 2,295
  • 5
  • 31
  • 52
  • 1
    By using `localtime` you're really getting the date since midnight Jan 1st, 1970 *in your local time zone*. For example, here in the west coast of the US if `elapsed_time = 0` then `strftime` says 16:00:00 because `localtime(0)` here is `Wed Dec 31 16:00:00 1969`. I'm guessing you're on continental Europe time. You'd use `gmtime` instead, but that would only work up to 24 hours. – Schwern Mar 30 '17 at 02:06

4 Answers4

3

time_t typically (see edit) stores absolute timestamps (the number of seconds since midnight UTC, January 1, 1970). By calculating current - *_start, you're getting elapsed time in seconds (as desired), but by then passing that to localtime and strftime, you're telling the computer to take the time elapsed since the start of your program and treat it as the time elapsed since midnight UTC 1-1-1970.

I'm guessing that happens to be 01:00:00 in your system's local time zone.

I'm not aware of a C99 function to print elapsed time, but it's not hard to write one yourself.

void format_elapsed_time(char *time_str, int total_seconds_elapsed) {
    int hours_elapsed = total_seconds_elapsed / 3600;
    int minutes_elapsed = (total_seconds_elapsed % 3600) / 60;
    int seconds_elapsed = total_seconds_elapsed % 60;
    sprintf(time_str, "%02i:%02i:%02i", hours_elapsed, minutes_elapsed, seconds_elapsed);
}

Edit: As @chux points out, time_t doesn't have to store timestamps as seconds since 1-1-1970. See this answer for details.

Community
  • 1
  • 1
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • This won't handle leap seconds by the way :-) I'm not sure why you didn't just suggest `gmtime` rather than `localtime`. – paxdiablo Mar 30 '17 at 02:17
  • 1
    `time_t` is very common as the number of seconds since Jan 1, 1970, yet that is not specified by C. `time_t` could be any real type including [`double`](http://stackoverflow.com/q/2792551/2410359) starting at any epoch. – chux - Reinstate Monica Mar 30 '17 at 02:23
  • @paxdiablo - Partly because I didn't think of it. :-) Partly because taking an elapsed time value and calling it an absolute timestamp and counting on the fact that `gmtime` + `strftime` will generally do what I want feels a bit icky. Partly because I know there are times when it wouldn't do what I want (e.g., >24 hours elapsed since the program started). – Josh Kelley Mar 30 '17 at 03:03
1

To portably find the number of elapsed seconds between 2 time_t, use difftime() as subtracting 2 time_t values is not specified in C to be a difference in seconds.

double difftime(time_t time1, time_t time0); C11dr §7.26.2.2
The difftime function returns the difference expressed in seconds as a double.

double elapsed_seconds = difftime(current, *_start);
printf("elapsed_seconds: %f\n", elapsed_seconds);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

If you want to use this method, you should be aware that time() returns UTC. If you then subtract that from your local time (as returned by localtime()), time zones will have an effect on the result (it's likely your particular time zone is removed from UTC by one hour).

Consider the following complete program, similar to your snippet:

#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main(void) {
    time_t start = time(0);
    char time_str[100];
    for (int i = 5; i > 0; i--) {
        sleep (1);
        time_t elapsed = time(0) - start;
        strftime(time_str, sizeof(time_str) - 1, "%T", localtime(&elapsed));
        printf("%s\n", time_str);
    }

    return 0;
}

Also consider that my particular time zone is removed from UTC by eight hours:

pax> date ; date -u
Thursday 30 March  10:25:57 AWST 2017
Thursday 30 March  02:25:57 UTC 2017

When I run it, I see:

08:00:01
08:00:02
08:00:03
08:00:04
08:00:05

You can see there that the eight-hour time difference from UTC affects the value. The fix for that is actually quite simple: don't use local time at all. If you replace the localtime() call with gmtime(), you get the output you expect:

00:00:01
00:00:02
00:00:03
00:00:04
00:00:05
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Minor: As I read "If the total number of resulting characters including the terminating null character is not more than maxsize, the strftime function returns the number of characters placed into the array pointed to by s not including the terminating null character" `strftime(..., sizeof(time_str) - 0,...);` is sufficient. Your thoughts? – chux - Reinstate Monica Mar 30 '17 at 03:27
1

I realize this question already has an answer, but I thought I might shift the focus a little and expand on the accepted answer. Since the OP specified that they are dealing with elapsed times, one can avoid dealing with absolute timestamps altogether using the clock() function.

clock_t start = clock();
.
.
clock_t end = clock();
double elapsed = (end - start) / (double)CLOCKS_PER_SEC;

then you can call the notionally revised format_elapsed_time() function, like so:

void format_elapsed_time(char *time_str, double elapsed) {
    int h, m, s, ms;

    h = m = s = ms = 0;
    ms = elapsed * 1000; // promote the fractional part to milliseconds
    h = ms / 3600000;
    ms -= (h * 3600000);
    m = ms / 60000;
    ms -= (m * 60000);
    s = ms / 1000;
    ms -= (s * 1000);
    sprintf(time_str, "%02i:%02i:%02i.%03i", h, m, s, ms);
}
Mark Benningfield
  • 2,800
  • 9
  • 31
  • 31