Thread: How to display incrementing second counter using system timer?

  1. #1
    Registered User
    Join Date
    Jun 2009
    Posts
    101

    How to display incrementing second counter using system timer?

    I have an app that records frames from a camera and displays a min/sec counter to the user when record is pressed so they know how long the recording has been going. I need this counter to be accurate (e.g. frame rate-independent) so I'm using system-generated timestamps via clock_gettime(). The problem is, clock_gettime returns a struct timespec, whose numbering can start in the middle of a second. So, the first call after pressing record may result in a ns count halfway between 0 and 1000000000. I need the timer to start precisely at 0 and count up seconds from 0. Is there a way I can finagle this value to give me a second count from the moment record is pressed? Or is there a better approach?

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,666
    Yes, you figure out how to subtract one struct timespec from another.

    So you have
    struct timespec start = clock_gettime();

    Then at every frame, you have
    struct timespec now = clock_gettime();

    Then you do the equivalent of
    elapsed = now - start;
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Quote Originally Posted by Salem View Post
    Yes, you figure out how to subtract one struct timespec from another.

    So you have
    struct timespec start = clock_gettime();

    Then at every frame, you have
    struct timespec now = clock_gettime();

    Then you do the equivalent of
    elapsed = now - start;
    Thanks, Salem. The problem is the tv_nsec member of timespec resets to 0 every second. So if I try to subtract 'start' from 'now,' I eventually get a negative value. I could multiply now by tv_sec, but I'm afraid that would overflow very quickly.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,666
    > I eventually get a negative value.
    I take it you learnt how to subtract long hand at school, say
    27
    19 -

    You know, carry and borrow and all that sort of stuff.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Although I fully agree with the intent (of pushing to learn for themselves) and what Salem wrote above, I've seen so many incorrectly implemented time-related functions that "usually work" but fail in some corner cases that I really, really want code like this to be written correctly. To that end, I'll show you how to write this in a very defensive but robust manner. (Some would say these are "overkill"; I disagree.)

    There is a slight "trick" in here: the nanoseconds part is a long, which per the C99 specs can handle at least the range -2147483647 to +2147483647. In other words, we know that we can always add or substract two nanosecond parts without overflowing. However, we do need to fold the result back to the valid nanosecond range 0 to 999999999 (inclusive), as some implementations cannot handle a nanosecond part outside that range. (Although most fold it correctly to seconds, some return an error, and some just produce incorrect results.)
    Code:
    #define _POSIX_C_SOURCE 200809L
    #include <time.h>
    
    struct timespec timespec_since(const struct timespec after, const struct timespec before)
    {
        struct timespec result;
        result.tv_sec = after.tv_sec - before.tv_sec;
        result.tv_nsec = after.tv_nsec - before.tv_nsec;
        if (result.tv_nsec < -2000000000L) {
            /* This should not occur. We handle it because we can, not because we must. */
            result.tv_sec -= 3;
            /* 3000000000L might not be a valid long, so we do it in two steps. */
            result.tv_nsec += 2000000000L; 
            result.tv_nsec += 1000000000L;
        } else
        if (result.tv_nsec < -1000000000L) {
             /* This should not occur. We handle it because we can, not because we must. */
            result.tv_sec -= 2;
            result.tv_nsec += 2000000000L;
        } else
        if (result.tv_nsec < 0) {
            result.tv_sec -= 1;
            result.tv_nsec += 1000000000L;
        } else
        if (result.tv_nsec >= 1000000000L) {
             /* This should not occur. We handle it because we can, not because we must. */
             result.tv_sec += 1;
            result.tv_nsec -= 1000000000L;
        } else
        if (result.tv_nsec >= 2000000000L) {
             /* This should not occur. We handle it because we can, not because we must. */
             result.tv_sec += 2;
            result.tv_nsec -= 2000000000L;
        }
        return result;
    }
    Now, the C99 standard does state that (a/b)*b + a%b = a if a/b is representable (C99:6.5.5p6). We could use that with b = 1000000000L in our calculations above, but I fear it would make it impossible to see the corner cases in the code it is supposed to handle. (Also, I'm not at all convinced that it would be better used here, since negative values of a would require three additions anyway, since (tv_nsec/1000000000L)*1000000000L + 1000000000L may not be representable by a long.)

    In practice, I personally like to switch to the number of elapsed seconds as a double-precision number, since some past moment. This is much easier:
    Code:
    #define _POSIX_C_SOURCE 200809L
    #include <time.h>
    
    /* Return the number of seconds (since optional mark),
     * using the specified clock clk.
     * Returns 0.0 if the clock_gettime() call fails.
    */
    double seconds_since(const clockid_t clk, const struct timespec *const mark)
    {
        struct timespec  t;
    
        if (clock_gettime(clk, &t))
            return 0.0;
    
        if (mark)
            return (double)t.tv_sec - (double)mark->tv_sec + (double)(t.tv_nsec - mark->tv_nsec) / 1000000000.0;
        else
            return (double)t.tv_sec + (double)t.tv_nsec / 1000000000.0;
    }
    Since the double type has 53 significant bits, it will be accurate to within a nanosecond for durations up to 8388608 seconds (about 97 days). For longer durations, you just lose precision correspondingly (as the type has about 16 significant digits only).

    On microcontrollers, you can use long to represent time in milliseconds (just multiply the seconds by a thousand, and divide the nanoseconds by a million) to avoid requiring floating-point support. This can cover positive and negative durations of at least 24 days or more (596 hours or more), and is just as easy to format for display purposes.

  6. #6
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Quote Originally Posted by Nominal Animal View Post
    On microcontrollers, you can use long to represent time in milliseconds (just multiply the seconds by a thousand, and divide the nanoseconds by a million) to avoid requiring floating-point support. This can cover positive and negative durations of at least 24 days or more (596 hours or more), and is just as easy to format for display purposes.
    Thanks! This what what I ended up using:

    Code:
    struct timespec clk,recstart;
    unsigned long min,sec;
    
    if(current_frame==0)
    	recstart=clk;
    
    sec = (((clk.tv_sec-recstart.tv_sec)*1000) + ((clk.tv_nsec-recstart.tv_nsec)/1000000)) / 1000;
    
    min=sec/60;
    sec-=min*60;
    
    fprintf(stderr,"Mins: %lu  Secs: %lu\n",min,sec);

  7. #7
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by synthetix View Post
    Thanks! This what what I ended up using:
    That's not optimal. (It may overflow in 49 days, due to the multiplication and division by a thousand, and I don't like such hidden gotchas.) I'd use
    Code:
    struct timespec clk, recstart;
    unsigned long min, sec;
    
    if (current_frame == 0)
        recstart = clk;
    
    {
        /* Ignore increment in clk.tv_sec if clk.tv_nsec
         * is less than recstart.tv_nsec.
         * Only works right when clk >= recstart.
        */
        const unsigned long s = (long)(clk.tv_sec - recstart.tv_sec)
                              - (clk.tv_nsec < recstart.tv_nsec) ? 1UL : 0UL;
        min = s / 60UL;
        sec = s % 60UL;
    }
    
    fprintf(stderr,"Mins: %lu  Secs: %lu\n",min,sec);
    The idea is very simple: the increment in tv_sec is ignored when tv_nsec is less than the starting value. No need to multiply and divide by a thousand.

  8. #8
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Quote Originally Posted by Nominal Animal View Post
    Code:
        const unsigned long s = (long)(clk.tv_sec - recstart.tv_sec)
                              - (clk.tv_nsec < recstart.tv_nsec) ? 1UL : 0UL;
    This worked, but only after I put parenthesis around the conditional, like this:

    Code:
    /* A-ok on gcc 4.8 */
    const unsigned long s = (long)(clk.tv_sec - recstart.tv_sec) - ((clk.tv_nsec < recstart.tv_nsec) ? 1UL : 0UL);
    Works great now. Thanks!

  9. #9
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by synthetix View Post
    This worked, but only after I put parenthesis around the conditional, like this:
    Good catch!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Display system menu
    By silverAG in forum Windows Programming
    Replies: 1
    Last Post: 02-17-2009, 10:42 PM
  2. System.Timers.Timer problem
    By Rune Hunter in forum C# Programming
    Replies: 2
    Last Post: 01-29-2008, 02:29 PM
  3. Show current date without using system timer
    By stormrider in forum C Programming
    Replies: 21
    Last Post: 10-01-2006, 07:58 PM
  4. Display timer same time as input
    By Yasir_Malik in forum Linux Programming
    Replies: 17
    Last Post: 12-31-2005, 03:42 PM
  5. continuously updating timer on console display
    By revelation437 in forum C++ Programming
    Replies: 5
    Last Post: 02-24-2003, 12:28 PM