Search code examples
cdatetimetimestampredhattime-t

Producing UNIX timestamp out of datetime passed in C


I'm porting a C app from Solaris to RedHat, and this function does not work very well on RedHat (and I need your help determing why):

int toTimestamp (const char *InputDate, const char *DateFormat, time_t *lTimestamp){
    struct tm tm;

    if (NULL == strptime(InputDate, DateFormat, &tm)){
        return FALSE;
    }
    *lTimestamp = mktime(&tm);
    return TRUE;    
}

Basically, it produces UNIX timestamp out of datetime passed in as a string, with specified format.

char *effPaymentDate = NULL;
char *CohDueDate = NULL;
//...
effPaymentDate = PayRec->RecDate;//char RecDate[8+1];, value 20141005
CohDueDate = PayRec->DueDate;//char DueDate[8+1];, value 20140820

time_t currentDateUNIX;
time_t DueDateUNIX;
if (FALSE == toTimestamp(CohDueDate, "%Y%m%d", &DueDateUNIX) ||
     FALSE == toTimestamp(effPaymentDate, "%Y%m%d", &currentDateUNIX)) {
  return NULL;
}
//...

However, it does not seem to work correctly (works OK for effPaymentDate, gives wrong dates for CohDueDate - i.e. year 2543) - any ideas why?


Solution

  • TL;DR:

    You need to initialize tm before passing it to APIs:

    memset(&tm, 0, sizeof tm);
    

    Why ?

    strptime might fail without you noticed it:

    $ man strptime
    

    RETURN VALUE
    The return value of the function is a pointer to the first character not processed in this function call. In case the input string contains more characters than required by the format string the return value points right after the last consumed input character. In case the whole input string is consumed the return value points to the null byte at the end of the string. If strptime() fails to match all of the for‐ mat string and therefore an error occurred the function returns NULL.

    NOTES

    In principle, this function does not initialize tm but stores only the values specified. This means that tm should be initialized before the call. Details differ a bit between different UNIX systems. The glibc implementation does not touch those fields which are not explicitly specified, except that it recomputes the tm_wday and tm_yday field if any of the year, month, or day elements changed.

    So because tm is not set to 0, some fields may contain random values.

    Moreover:

    $ man mktime
    

    The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. Calling mktime() also sets the external variable tzname with information about the current timezone.

    The random values are likely to be out of range, and mktime tries to normalize it. It did a good job though, but the year betrayed it !