1

I try to add a time offset to a date generated with time C function. Calculation is wrong depending the offset value. if I increase the offset value the calculation became false!

I am using gcc on a CentOS 5.11

#include <stdio.h>
#include <time.h>
#define MAX_SIZE 80

int main( int argc, char * argv[] ) {

    time_t timestamp, offset;
    struct tm *pTime;
    char buffer[ MAX_SIZE ];

    //timestamp = time( NULL );
    timestamp = 1470356033L;
    printf("timestamp = %ld\n", timestamp);

    // offset calculation
    offset = atol(argv[1]) * (24L * 60L * 60L); 
    printf("offset = %ld\n", offset);

    timestamp += offset;
    printf("timestamp = %ld\n", timestamp);

    pTime = localtime( & timestamp );

    strftime( buffer, MAX_SIZE, "%d/%m/%Y %H:%M:%S", pTime );
    printf( "Date and french time : %s\n", buffer );

    return 0;
}

./testDate 0
timestamp = 1470356033
offset = 0
timestamp = 1470356033
Date and french time : 05/08/2016 02:13:53
This Result is OK, it is reference date without offset 

./testDate 4
timestamp = 1470356033
offset = 345600
timestamp = 1470701633
Date and french time : 09/08/2016 02:13:53
This Result is also OK, it is reference date with 4 days offset 

./testDate 90
timestamp = 1470356033
offset = 7776000
timestamp = 1478132033
Date and french time : 03/11/2016 01:13:53
This Result is wrong, it is reference date with 90 days offset.
Date is OK but 1 hour is missing, it should be 02:13:53 but actual output is 01:13:53
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Oli013
  • 21
  • 5

1 Answers1

3

time_t type represents Unix time, the number of seconds since Thursday, 1 January 1970, 00:00:00 UTC, minus leap seconds.

(The POSIX clock_gettime() interface might grow support for CLOCK_TAI, which would be the same except including leap seconds.)

For date manipulation, it is better to use the standard C broken down time, struct tm, as provided by localtime() or gmtime().

localtime() uses the current timezone. (Linux systems have a default timezone set in /etc/timezone, but each user can override it by setting the TZ environment variable. See tzset() POSIX.1 function for details on how to do that.) gmtime() uses UTC.

The "trick" is that if you call mktime() on a struct tm describing a date and time in the current timezone, it first normalizes the fields, then returns the Unix time as a time_t corresponding to that date and local time. For example, if the day of month is 45, it will adjust the day, month, and year (and related fields) to reflect the actual date.

So, if you wanted to find out the date and time five days and six hours from now:

    time_t     now, then;
    struct tm *t;

    now = time(NULL);
    t = localtime(&now);

    printf("Now is %llu = %04d-%02d-%02d %02d:%02d%02d\n",
           (unsigned long long)now,
           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec);

    t->tm_hour += 6;
    t->tm_mday += 5;
    t->tm_isdst = -1; /* Don't know if DST or not; please guess. */
    then = mktime(t);

    printf("Then is %llu = %04d-%02d-%02d %02d:%02d:%02d\n",
           (unsigned long long)then,
           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec);

If we omit printing the values of now and then, the above is perfectly standard C code and will work on all current operating systems.

If you use Linux or another POSIXy system (Mac, BSDs), it would be better to use

    time_t     now, then;
    struct tm  tbuffer, *t;

    now = time(NULL);
    t = localtime_r(&now, &tbuffer);

    printf("Now is %llu = %04d-%02d-%02d %02d:%02d%02d\n",
           (unsigned long long)now,
           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec);

    t->tm_hour += 6;
    t->tm_mday += 5;
    t->tm_isdst = -1; /* Don't know if DST or not; please guess. */
    then = mktime(t);

    printf("Then is %llu = %04d-%02d-%02d %02d:%02d:%02d\n",
           (unsigned long long)then,
           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec);

The difference is that localtime() returns a pointer to a statically allocated buffer, and another call to it (even in another thread) will overwrite the contents. The POSIX.1 localtime_r() takes a second parameter, a pointer to a struct tm, where the result is stored instead.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Alternative consideration: `%04d%02d%02d` --> `%04d-%02d-%02d` to closer mimic [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) as in `"%F"` of `strftime()`. – chux - Reinstate Monica Jan 18 '19 at 13:19
  • Sure about "another call to it (even in another thread) will overwrite the contents". I though there was something, perhaps also with `errno` that gives each thread its own copy. Hmmm. – chux - Reinstate Monica Jan 18 '19 at 13:24
  • @chux: Nope: At least glibc on x86-64 (libc6-2.23-0ubuntu10 on Ubuntu Xenial, 16.04.4 LTS) calling `localtime()` in any thread will overwrite the storage. It does make sense; the thread-local storage is limited, and `localtime_r()` and `gmtime_r()` are thread-safe (without needing thread-local storage). `errno` is thread-safe, of course (marked per-thread using `__thread`/`_Thread_local`). Also, I fully agree about that form (closer to ISO 8601) being the preferred one, so edited the answer. Thanks for the heads-up! – Nominal Animal Jan 18 '19 at 13:56
  • Note; `localtime_r()` is not in the STL. The maligned Annex K does have the similar `localtime_s()`. Your multi-thread concerns, IAC, are quite valid. – chux - Reinstate Monica Jan 18 '19 at 14:02