views:

581

answers:

3

This seems like something no one should ever have to do, but I'm working on a kernel module for an embedded system (OpenWRT) in which it seems that time.h does include the timespec and time_t types, and the clock_gettime and gmtime functions, but does not include localtime, ctime, time, or, critically, the tm type.

When I attempt to cast the return pointer from gmtime to my own struct, I get a segfault.

So I guess I'd be content to solve the problem either of two ways—it'd be great to figure out how to get access to that missing type, or alternatively, how to roll my own method for decomposing a unix timestamp.

A: 

In userspace glibc will do a lot of work with regards to handling the "local" part of time representation. Within the kernel this is not available. Probably you should not try to bother with this within your module, if needed do it in userspace.

hlovdal
I ended up creating a cron job that would periodically write the date to a proc interface. Seems roundabout, but it was ultimately a better solution than this timing stuff. So yes, good call on just pulling it in from userspace.
mikepurvis
A: 

A time_t is the number of seconds since Jan 1, 1970 UTC so decomposing that into month, day, and year isn't that difficult provided that you want the result in UTC. There is a bunch of source available by Googling "gmtime source". Most embedded systems leave out local time processing since it is a little more difficult due to the reliance on timezone setting and the environment.

D.Shawley
+1  A: 

This should be accurate (fills out a cut-down imitation of a struct tm, my year uses Common Era instead of a 1900 CE epoch):

struct xtm
{
    unsigned int year, mon, day, hour, min, sec;
};

#define YEAR_TO_DAYS(y) ((y)*365 + (y)/4 - (y)/100 + (y)/400)

void untime(unsigned long unixtime, struct xtm *tm)
{
    /* First take out the hour/minutes/seconds - this part is easy. */

    tm->sec = unixtime % 60;
    unixtime /= 60;

    tm->min = unixtime % 60;
    unixtime /= 60;

    tm->hour = unixtime % 24;
    unixtime /= 24;

    /* unixtime is now days since 01/01/1970 UTC
     * Rebaseline to the Common Era */

    unixtime += 719499;

    /* Roll forward looking for the year.  This could be done more efficiently
     * but this will do.  We have to start at 1969 because the year we calculate here
     * runs from March - so January and February 1970 will come out as 1969 here.
     */
    for (tm->year = 1969; unixtime > YEAR_TO_DAYS(tm->year + 1) + 30; tm->year++)
        ;

    /* OK we have our "year", so subtract off the days accounted for by full years. */
    unixtime -= YEAR_TO_DAYS(tm->year);

    /* unixtime is now number of days we are into the year (remembering that March 1
     * is the first day of the "year" still). */

    /* Roll forward looking for the month.  1 = March through to 12 = February. */
    for (tm->mon = 1; tm->mon < 12 && unixtime > 367*(tm->mon+1)/12; tm->mon++)
        ;

    /* Subtract off the days accounted for by full months */
    unixtime -= 367*tm->mon/12;

    /* unixtime is now number of days we are into the month */

    /* Adjust the month/year so that 1 = January, and years start where we
     * usually expect them to. */
    tm->mon += 2;
    if (tm->mon > 12)
    {
        tm->mon -= 12;
        tm->year++;
    }

    tm->day = unixtime;
}

My apologies for all the magic numbers. 367*month/12 is a neat trick to generate the 30/31 day sequence of the calendar. The calculation works with years that start in March until the fixup at the end, which makes things easy because then the leap day falls at the end of a "year".

caf
Rather than apologize for the magic numbers, why not at least comment some of the less obvious things in the code? This function would benefit from some a fair bit of commenting.
Matt Nizol
I've added some comments which will hopefully be helpful.
caf