tags:

views:

63

answers:

3
A: 

Check the expected behaviour of strptime, in some implementations it is greedy and will consume as many digits as it can.

This has been a problem for me on MacOS, the implementation changed in 10.5 with the UNIX standards compliance efforts. Before this the following call worked fine.

strptime("20071124", "%Y%m%d")

As of 10.5 you have to do the following to get it to work.

#define _NONSTD_SOURCE

Your compiler libraries and OS may differ though.

Gary
A: 

I can tell you what the problem is. When you use strptime() with the %j format, it only populates the tm_yday field on your struct tm. This isn't limited to Solaris by the way, CygWin gcc is doing the same thing.

Because your strftime() is most likely using other fields (tm_mon and tm_mday), and these are still set to zero, that's why you're getting the wrong day of the year.

The following code illustrates this:

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

#define dump() \
    printf ("DEBUG tm_sec = %d, tm_min = %d, tm_hour = %d, tm_mday = %d, " \
        "tm_mon = %d, tm_year = %d, tm_wday = %d, tm_yday = %d, " \
        "tm_isdst = %d\n", \
        tmpptr.tm_sec, tmpptr.tm_min, tmpptr.tm_hour, tmpptr.tm_mday, \
        tmpptr.tm_mon, tmpptr.tm_year, tmpptr.tm_wday, tmpptr.tm_yday, \
        tmpptr.tm_isdst)

int convertDateTime(char* source_fmt,char* dest_fmt,char* source,char* dest) {
    struct tm tmpptr;
    memset (&tmpptr,0,sizeof(tmpptr));
    dump();
    if (strptime(source,source_fmt,&tmpptr) == NULL) {
            strcpy(dest,"");
            return -1;
    }
    dump();
    strftime(dest,100,dest_fmt,&tmpptr);
    return 0;
}

int main (int argc, char *argv[]) {
    char dest[1000];
    printf ("1: [%s]\n", argv[1]);
    printf ("2: [%s]\n", argv[2]);
    printf ("3: [%s]\n", argv[3]);
    printf ("retval = %d\n", convertDateTime (argv[1],argv[2],argv[3],dest));
    printf ("=: [%s]\n", dest);
    return 0;
}

When you run it thus:

pax> date ; ./tetsprog %y%j %Y-%m-%d 10162

you get:

Tue Aug  3 12:46:13 WAST 2010
1: [%y%j]
2: [%Y-%m-%d]
3: [10162]
DEBUG tm_sec = 0, tm_min = 0, tm_hour = 0,
      tm_mday = 0, tm_mon = 0, tm_year = 0,
      tm_wday = 0, tm_yday = 0,  tm_isdst = 0
DEBUG tm_sec = 0, tm_min = 0, tm_hour = 0,
      tm_mday = 0, tm_mon = 0, tm_year = 110,
      tm_wday = 0, tm_yday = 161,  tm_isdst = 0
retval = 0
=: [2010-01-00]

Fixing it is tricky, I'm not aware of any standard time function that will rebuild tm_mday and tm_mon from tm_yday.

But, if you're stuck for a solution, try this out:

static void fixIt (struct tm *t) {
    static int monthDaysN[] = {31,28,31,30,31,30,31,31,30,31,30,31};
    static int monthDaysL[] = {31,29,31,30,31,30,31,31,30,31,30,31};
    int *monthDays = monthDaysN;
    int base = 0;
    int i;
    if (((t->tm_year + 1900) % 4) == 0) monthDays = monthDaysL;
    if (((t->tm_year + 1900) % 100) == 0) monthDays = monthDaysN;
    if (((t->tm_year + 1900) % 400) == 0) monthDays = monthDaysL;
    // Leap years irrelevant for January dates.
    if (t->tm_yday < 31) monthDays = monthDaysN;
    for (i = 0; i < 12; i++) {
        if (t->tm_yday - base < monthDays[i]) {
            t->tm_mday = t->tm_yday - base + 1;
            t->tm_mon = i;
            return;
        }
        base += monthDays[i];
    }
}

It will set those two fields based on tm_year and tm_yday and it's a bit of a kludge but will get you going at least (and maybe you'll find a better way).

I would insert a call to this in your convert function and only call it under specific circumstances so as not to overwrite values that are already set:

int convertDateTime(char* source_fmt,char* dest_fmt,char* source,char* dest) {
    struct tm tmpptr;
    memset (&tmpptr,0,sizeof(tmpptr));
    dump();
    if (strptime(source,source_fmt,&tmpptr) == NULL) {
            strcpy(dest,"");
            return -1;
    }
    if ((tmpptr.tm_yday != 0) && (tmpptr.tm_mday == 0))
        fixIt (&tmpptr);
    dump();
    strftime(dest,100,dest_fmt,&tmpptr);
    return 0;
}

which gives:

pax> date ; testprog %y%j %Y-%m-%d 10162
Tue Aug  3 13:34:36 WAST 2010
1: [%y%j]
2: [%Y-%m-%d]
3: [10162]
DEBUG tm_sec = 0, tm_min = 0, tm_hour = 0,
      tm_mday = 0, tm_mon = 0, tm_year = 0,
      tm_wday = 0, tm_yday = 0, tm_isdst = 0
DEBUG tm_sec = 0, tm_min = 0, tm_hour = 0,
      tm_mday = 11, tm_mon = 5, tm_year = 110,
      tm_wday = 0, tm_yday = 161, tm_isdst = 0
retval = 0
=: [2010-06-11]

And, like all code here, you should test it thoroughly. I'm pretty certain I got all the edge cases but, since you haven't paid me cold hard cash for my services, you should assume this is general advice only, not a specific solution :-)

paxdiablo
thanks for your advice (and time) :-) But i'll go with the simpler solution..
vineet
A: 

Update
The original answer (below) presumed that the "%y%j" format was used on the output (strftime) not the input (strptime). The mktime function will compute yday from valid info, but it doesn't work the other way.

If you want to decode something like that you need to do it manually. Some example code follows. It has had minimal testing and almost certainly has bugs. You may need to alter ir, depending in the input format you want.

#include <string>
#include <ctime>
#include <cassert>
#include <iostream>

int GetCurrentYear()
{
    time_t tNow(::time(NULL));
    struct tm tmBuff = *::localtime(&tNow);
    return tmBuff.tm_year;
}
bool IsLeapYear(int nYear)
{
    if (0 == (nYear%1000)) return true;
    if (0 == (nYear%100))  return false;
    if (0 == (nYear%4))    return true;
    return false;
}
// nMonth = 0 (Jan) to 11 (Dec)
int DaysPerMonth(int nMonth, bool bLeapYear)
{
    //                 J   F   M   A   M   J   J   A   S   O   N   D
    int nDays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    assert(nMonth>=0 && nMonth<12);
    int nRet = nDays[nMonth];
    if (bLeapYear && nMonth==1)
        nRet++;
    return nRet;
}

// sDate is in the format YYDDD where YY is the last 2 digits of the year
// and YYY is the day of the year (1/1 = 1, 31/12 = 365 for non-leap year)
bool DecodeDate(const std::string &sDate, struct tm &tmBuff)
{
    if (sDate.length() != 5 ||
        !isdigit(sDate[0])  ||
        !isdigit(sDate[1])  ||
        !isdigit(sDate[2])  ||
        !isdigit(sDate[3])  ||
        !isdigit(sDate[4]))
    {
        return false;
    }
    ::memset(&tmBuff, 0, sizeof(struct tm));
    tmBuff.tm_year = GetCurrentYear();
    // drop last 2 digits
    tmBuff.tm_year -= tmBuff.tm_year%100; 
    // replace last 2 digits
    tmBuff.tm_year += ::atoi(sDate.substr(0, 2).c_str());    

    tmBuff.tm_yday = ::atoi(sDate.substr(2).c_str());
    int nDays(tmBuff.tm_yday);
    bool bLeapYear(IsLeapYear(1900 + tmBuff.tm_year));
    int nTmp = DaysPerMonth(0, bLeapYear);
    while (nTmp < nDays)
    {
        nDays -= nTmp;
        tmBuff.tm_mon++;
        nTmp = DaysPerMonth(tmBuff.tm_mon, bLeapYear);
    }
    tmBuff.tm_mday = nDays;
    ::mktime(&tmBuff);
    return true;
}

int main(int argc, char *argv[])
{
    for (int i=1; i<argc; i++)
    {
        struct tm tmBuff;
        DecodeDate(argv[i], tmBuff);
        const size_t nSize(128);
        char szBuff[nSize];
        strftime(szBuff, nSize, "%A, %d %B %Y", &tmBuff);
        std::cout << argv[i] << '\t' << szBuff << std::endl;
    }
    return 0;
}

================================================

C:\Dvl\Tmp>Test.exe 07123 08123 08124
07123   Thursday, 03 May 2007
08123   Friday, 02 May 2008
08124   Saturday, 03 May 2008

End Update

After you call strptime(), call mktime() which will populate any missing members of the struct. Also, you should zero out the struct before beginning.

#include <string>
#include <ctime>

int convertDateTime(const std::string &sSourceFmt, 
                    const std::string &sDestFmt,
                    const std::string &sSource,
                    std::string &sDest)
{
    struct tm tmbuff = { 0 };

    if (::strptime(sSource.c_str(), sSourceFmt.c_str(), &tmbuff) != NULL)
    {
        ::mktime(&tmbuff);
        const size_t nSize(256);
        char szBuff[nSize+1] = "";

        if (::strftime(szBuff, nSize, sDestFmt.c_str(), &tmbuff))
        {
            sDest = szBuff;
            return 0;
        }
    }
    sDest.clear();
    return -1;
}
Michael J
thanks! this works perfectly!
vineet
I'm actually surprised that works. It didn't under Cygwin gcc with just tm_year and tm_yday set and, in fact, the doco you reference states "The original values of the tm_wday and tm_yday components of the timeptr structure are ignored". But, if the OP say it's okay, I guess it's okay :-)
paxdiablo
Actually, that _still_ doesn't work under CygWin, and the glibc 2.9 source code doesn't appear to use tm_yday in its calculations at all so I remain unconvinced. Can you try it with an input format of `%y%j`, input value of `10032` and output format of `%Y-%m-%d` and see what it does?
paxdiablo