Convert unix timestamp to date without system libs - c

I am building a embedded project which displays the time retrieved from a GPS module on a display, but I would also like to display the current date. I currently have the time as a unix time stamp and the progject is written in C.
I am looking for a way to calculate the current UTC date from the timestamp, taking leap years into account? Remember, this is for an embedded project where there is no FPU, so floating point math is emulated, avoiding it as much as possible for performance is required.
EDIT
After looking at #R...'s code, I decided to have a go a writing this myself and came up with the following.
void calcDate(struct tm *tm)
{
uint32_t seconds, minutes, hours, days, year, month;
uint32_t dayOfWeek;
seconds = gpsGetEpoch();
/* calculate minutes */
minutes = seconds / 60;
seconds -= minutes * 60;
/* calculate hours */
hours = minutes / 60;
minutes -= hours * 60;
/* calculate days */
days = hours / 24;
hours -= days * 24;
/* Unix time starts in 1970 on a Thursday */
year = 1970;
dayOfWeek = 4;
while(1)
{
bool leapYear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
uint16_t daysInYear = leapYear ? 366 : 365;
if (days >= daysInYear)
{
dayOfWeek += leapYear ? 2 : 1;
days -= daysInYear;
if (dayOfWeek >= 7)
dayOfWeek -= 7;
++year;
}
else
{
tm->tm_yday = days;
dayOfWeek += days;
dayOfWeek %= 7;
/* calculate the month and day */
static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
for(month = 0; month < 12; ++month)
{
uint8_t dim = daysInMonth[month];
/* add a day to feburary if this is a leap year */
if (month == 1 && leapYear)
++dim;
if (days >= dim)
days -= dim;
else
break;
}
break;
}
}
tm->tm_sec = seconds;
tm->tm_min = minutes;
tm->tm_hour = hours;
tm->tm_mday = days + 1;
tm->tm_mon = month;
tm->tm_year = year;
tm->tm_wday = dayOfWeek;
}

First divide by 86400; the remainder can be used trivially to get the HH:MM:SS part of your result. Now, you're left with a number of days since Jan 1 1970. I would then adjust that by a constant to be the number of days (possibly negative) since Mar 1 2000; this is because 2000 is a multiple of 400, the leap year cycle, making it easy (or at least easier) to count how many leap years have passed using division.
Rather than trying to explain this in more detail, I'll refer you to my implementation:
http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c?h=v0.9.15

Here's a portable implementation of mktime(). It includes support for DST that you might remove in order reduce the size somewhat for UTC only. It also normalizes the data (so if for example you had 65 seconds, it would increment the minute and set the seconds to 5, so perhaps has some overhead that you don't need.
It seems somewhat more complex than the solution you have arrived at already; you may want to consider whether there is a reason for that? I would perhaps implement both as a test (on a PC rather than embedded) and iterate through a large range of epoch time values and compare the results with the PC compiler's own std::mktime (using C++ will avoid the name clash without having to rename). If they all produce identical results, then use the fastest/smallest implementation as required, otherwise use the one that is correct!
I think that the typical library mktime performs a binary convergence comparing the return of localtime() with the target. This is less efficient than a direct calendrical calculation, but I presume is done to ensure that a round-trip conversion from struct tm to time_t (or vice versa) and back produces the same result. The portable implementation I suggested above uses the same convergence technique but replaces localtime() to remove library dependencies. On reflection therefore, I suspect that the direct calculation method is preferable in your case since you don't need reversibility - so long as it is correct of course.

Related

How can i add a certain number of days to a current date in c

I've been trying to add a certain number of days (I'm trying to add 30) to the current date.
Let's say today's date is 1/1/2023, after adding the number of days I want to add, I would get 31/1/2023, the main problem would be if I wanted to add if February has 29 days instead of 28.
I got the current date, couldn't add the number of days I wanted because I don't know other way to get the current date without the format being like this "Sun Jan 01 16:29:19 2023".
Just a side note, I saw people adding x days to a date using c, but those problem had the user inserting the date they wanted to the problem, my problem is adding it to the CURRENT DATE.
Anyone can help me with this one?
The standard library time functions already know about leap years and number of days in a month etc. So it makes sense to use that calendrical "expertise".
This is a "duplicate" of my answer at Algorithm to add or subtract days from a date? but adapted slightly to remove the C++ specifics (hence not marking it as a duplicate). Other answers there may be adapted of course.
#include <time.h>
// Adjust date by a number of days +/-
void DatePlusDays( const struct tm* date, int days )
{
const time_t ONE_DAY = 24 * 60 * 60 ;
// Seconds since start of epoch
time_t date_seconds = mktime( date ) + (days * ONE_DAY) ;
// Update caller's date
// Use localtime because mktime converts to UTC so may change date
*date = *localtime( &date_seconds ) ;
}
Regarding:
"I saw people adding x days to a date using c, but those problem had the user inserting the date they wanted to the problem, my problem is adding it to the CURRENT DATE."
So surely you need only supply the current date as the input. No need to restrict the solution to be quite so inflexible.
Example usage for "current date":
#include <stdio.h>
#include <time.h>
int main( void )
{
struct tm today ;
today = *localtime( time(NULL) ) ;
// Date, plus 30 days
DatePlusDays( &today, 30 ) ;
// Show time/date using default formatting
printf( "%s\n", asctime( &today) ) ;
}
It can be used to subtract days too (pass negative days).
Of course, if you truly do want to restrict the solution to just the current date:
#include <time.h>
// Adjust date by a number of days +/-
const struct tm* TodayPlusDays( int days )
{
const time_t ONE_DAY = 24 * 60 * 60 ;
// Seconds since start of epoch
time_t date_seconds = time( NULL ) + (days * ONE_DAY) ;
// Update caller's date
// Use localtime because mktime converts to UTC so may change date
return localtime( &date_seconds ) ;
}
Example usage:
#include <stdio.h>
#include <time.h>
int main( void )
{
struct tm today = *TodayPlusDays( 30 ) ;
// Show time/date using default formatting
printf( "%s\n", asctime( &today) ) ;
}
Assuming you've already parsed the date into its component day, month, and year. We can "add days" with a very simple algorithm. And we can even subtract days (or add negative days).
Nothing below uses any of the standard libraries for managing date and time. Nothing against those solutions or the standard libraries. But I wanted to present a simple portable way with the Gregorian calendar that also takes leap years into account.
First a struct to indicate a Date:
struct Date
{
int day;
int month;
int year;
};
Then we need a little helper for leap years.
bool isLeapYear(int year)
{
// leap years happen on 4 year boundaries unless year is divisible by 100 and not 400
// 2000 was a leap year, but 2100 is not
if (year % 400 == 0) return true;
if (year % 100 == 0) return false;
return (year % 4 == 0);
}
And with this data structure for a Date and a leap year detection function, we can build a very simple solution. The basic algorithm is to increment a given date "one day at a time" until the number of added days has been achieved. Each time we increment the day member past the number of days in a month, it gets reset to 1 and the month member gets incremented. And similarly, we handle incrementing to the next year. And then adjust for leap years as well.
// adds "numberOfDays" to the date object pointed to by "d"
void AddDays(Date* d, int numberOfDays)
{
static const int daysInMonths[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static const int daysInMonthsLeap[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const int* monthTable = isLeapYear(d->year) ? daysInMonthsLeap : daysInMonths;
// handle the case where we are adding a positive number of days to "d"
while (numberOfDays > 0)
{
d->day++;
if (d->day > monthTable[d->month])
{
d->month++;
if (d->month > 12)
{
d->month = 1;
d->year++;
monthTable = isLeapYear(d->year) ? daysInMonthsLeap : daysInMonths;
}
d->day = 1;
}
numberOfDays--;
}
// handle the case where we are adding a negative number of days to "d".
while (numberOfDays < 0)
{
d->day--;
if (d->day <= 0)
{
d->month--;
if (d->month <= 0)
{
d->year--;
d->month = 12;
monthTable = isLeapYear(d->year) ? daysInMonthsLeap : daysInMonths;
}
d->day = monthTable[d->month];
}
numberOfDays++;
}
}
The above isn't the most efficient - especially if you are adding millions of days onto a given Date object. But for dealing with time adjustments in terms of a small number of days, weeks, or even less than a couple thousand years, you could do a lot worse. A quick benchmark in an optimized build shows that the above works reasonably fast for adding 5 million days to today's date to get to the year 16000 and later.
There's also plenty of opportunities for optimizations in the loops above.

Changing the timezone in libc time at compile time

We have firmware that will be deployed on devices all over the world and that will be synced to UTC by a host device. To track time, we end up converting between struct tm and time_t (Unix time) because we use a hardware RTC whose registers closely mirror struct tm, but any time-based operations are done on Unix time. However, when we use mktime it attempts to put everything into localtime, which is not UTC on the build system. We could just add an offset, but it would be easier to just tell time.h that our local time should be UTC, since the device is otherwise agnostic to the local time.
Is there a (non-invasive) way to do this other than changing the local time on the build system to UTC? Like, can we somehow use tzset to inject this data? Or just set TZ to something? I'm having difficulty understanding how I would set TZ if I just wanted UTC.
However, when we use mktime it attempts to put everything into localtime ...
The compiler's time zone setting at compile time is irrelevant to how code handles time at run time.
The easiest way to avoid local time in conversions is to hope your compiler offers struct_tm (UTC) to time_t, instead of using mktime() (time zone dependent) as an extension like time_t timegm(struct tm *tm).
There is no simple standard solution. Tricks with divining the offset via locatime(), gmtime() fail corner cases.
IMO, to convert struct_tm (UTC) to UNIX time time_t, simply write that code and get it reviewed. It is not that hard as there are few corner cases (aside from overflow).
Some sample code to change int year, int month, int day (0:00:00) to MJD to get OP started.
#include <stdint.h>
static const short DaysMarch1ToBeginingOfMonth[12] = { //
0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};
#ifndef INT32_C
#define INT32_C(x) ((int_least32_t)1*(x))
#endif
#define DaysPer400Years (INT32_C(365)*400 + 97)
#define DaysPer100Years (INT32_C(365)*100 + 24)
#define DaysPer4Years (365*4 + 1)
#define DaysPer1Year 365
#define MonthsPerYear 12
#define MonthsPer400Years (12*400)
#define MonthMarch 3
#define mjdOffset 0xA5BE1
#define mjd1900Jan1 15020
// November 17, 1858
// Example: 2015 December 31 --> ymd_to_mjd(2015, 12, 31)
int2x ymd_to_mjd(int year, int month, int day) {
// int2x is a type twice as wide as int to handle extreme int values.
// Use int (at least 32-bit) to handle common values.
int2x year2x = year;
year2x += month / MonthsPerYear;
month %= MonthsPerYear;
// Adjust for month/year to Mar ... Feb
while (month < MonthMarch) {
month += MonthsPerYear;
year2x--;
}
int2x d = (year2x / 400) * DaysPer400Years;
int y400 = (int) (year2x % 400);
d += (y400 / 100) * DaysPer100Years;
int y100 = y400 % 100;
d += (y100 / 4) * DaysPer4Years;
int y4 = y100 % 4;
d += y4 * DaysPer1Year;
d += DaysMarch1ToBeginingOfMonth[month - MonthMarch];
d += day;
// November 17, 1858 == MJD 0
d--;
d -= mjdOffset;
return d;
}

How to get a UTC timestamp in OP-TEE Trusted Application (TA) in datetime format?

Disclaimer: It took me a solid 4-5 hours of looking for an answer and after figuring it out I decided to post it here for people in the same place.
OP-TEE is quite a good environment to develop TAs and CAs, however, there is no straightforward method of acquiring a datetime formatted properly. There is no struct tm either. Therefore, it made me wonder how do I get a datetime format in OP-TEE TAs?
What I spent a long time trying was to utilize the already supported mbedTLS libraries which, for a newcomer, would seem like they do support getting datetime format. After all, they do have gmtime which is supposed to return this value.
However, unfortunately, the gmtime and relevant functions have no implementation for the platform OP-TEE on ARMv8. That's a pretty tough luck.
So how do you get UTC time in an OP-TEE TA?
All OP-TEE development for ARMv8 is done using C. However, it lacks major libc support. Practically, it has very little libraries (e.g. string.h) which are skimmed down versions from the original libc corresponding libraries.
With that, the provided <time.h> in the OP-TEE contains nothing but a typedef for time_t and that's it.
The problem can be broken down to two sections:
How do you get the epochs since 1970 Jan 1st 00:00:00?
This is an interesting problem, and while the straight forward solution is to simply do this:
TEE_Time tt;
TEE_GetREETime(&tt);
This solution may not be satisfactory for many people who would not want to rely on REE's (Rich Execution Environment, AKA the vulnerable environment) count of epochs. This can be problematic for security sensitive operations where you need time to be legit and with no room for REE to modify it to perform a certain attack.
In the case described above, you will have to obtain the epochs from a hardware clock which will depend on the hardware board you are developing the TA on. You can even retrieve it from location devices which also return UTC time in NMEA statements. While it may not be 100% precise to the second, it may just be enough. If you need very high precision, you will need to get it from a hardware device securely.
Either way, you have to figure out how to get the epochs on your own. This answer focuses on the second part: getting the datetime.
Getting the datetime from the epochs. Once you resolved step 1, you need to process it to datetime. For this purpose, you need gmtime which does not exist in the OP-TEE. It has no implementation. And you need a minimalist implementation to keep things simple.
Luckily, I was able to find this answer. Which links to newlib libraries that develop for Free BSD which are ideal for embedded systems. Hence why it's useful here!
I was able to put it together from their implementation and you can use it here:
gmtime_r.h:
#include <inttypes.h>
#define SECSPERMIN 60L
#define MINSPERHOUR 60L
#define HOURSPERDAY 24L
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY (SECSPERHOUR * HOURSPERDAY)
#define DAYSPERWEEK 7
#define MONSPERYEAR 12
#define YEAR_BASE 1900
#define EPOCH_YEAR 1970
#define EPOCH_WDAY 4
#define EPOCH_YEARS_SINCE_LEAP 2
#define EPOCH_YEARS_SINCE_CENTURY 70
#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370
#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
typedef int64_t time_t;
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
struct tm* gmtime_r (time_t tim_p, struct tm* res);
gmtime_r.c:
#include "gmtime_r.h"
#define EPOCH_ADJUSTMENT_DAYS 719468L
/* year to which the adjustment was made */
#define ADJUSTED_EPOCH_YEAR 0
/* 1st March of year 0 is Wednesday */
#define ADJUSTED_EPOCH_WDAY 3
/* there are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 * 366) */
#define DAYS_PER_ERA 146097L
/* there are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 * 366) */
#define DAYS_PER_CENTURY 36524L
/* there is one leap year every 4 years */
#define DAYS_PER_4_YEARS (3 * 365 + 366)
/* number of days in a non-leap year */
#define DAYS_PER_YEAR 365
/* number of days in January */
#define DAYS_IN_JANUARY 31
/* number of days in non-leap February */
#define DAYS_IN_FEBRUARY 28
/* number of years per era */
#define YEARS_PER_ERA 400
struct tm* gmtime_r (time_t tim_p, struct tm* res)
{
time_t days, rem;
const time_t lcltime = tim_p;
int era, weekday, year;
unsigned erayear, yearday, month, day;
unsigned long eraday;
days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS;
rem = lcltime % SECSPERDAY;
if (rem < 0)
{
rem += SECSPERDAY;
--days;
}
/* compute hour, min, and sec */
res->tm_hour = (int) (rem / SECSPERHOUR);
rem %= SECSPERHOUR;
res->tm_min = (int) (rem / SECSPERMIN);
res->tm_sec = (int) (rem % SECSPERMIN);
/* compute day of week */
if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
weekday += DAYSPERWEEK;
res->tm_wday = weekday;
/* compute year, month, day & day of year */
/* for description of this algorithm see
* http://howardhinnant.github.io/date_algorithms.html#civil_from_days */
era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;
eraday = days - era * DAYS_PER_ERA; /* [0, 146096] */
erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) + eraday / DAYS_PER_CENTURY -
eraday / (DAYS_PER_ERA - 1)) / 365; /* [0, 399] */
yearday = eraday - (DAYS_PER_YEAR * erayear + erayear / 4 - erayear / 100); /* [0, 365] */
month = (5 * yearday + 2) / 153; /* [0, 11] */
day = yearday - (153 * month + 2) / 5 + 1; /* [1, 31] */
month += month < 10 ? 2 : -10;
year = ADJUSTED_EPOCH_YEAR + erayear + era * YEARS_PER_ERA + (month <= 1);
res->tm_yday = yearday >= DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?
yearday - (DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :
yearday + DAYS_IN_JANUARY + DAYS_IN_FEBRUARY + isleap(erayear);
res->tm_year = year - YEAR_BASE;
res->tm_mon = month;
res->tm_mday = day;
res->tm_isdst = 0;
return (res);
}
You can put both of these files in your TA folder, and make sure you add gmtime.c in the sources list in the sub.mk. And finally, in the TA itself you can use it:
TEE_Time tt;
TEE_GetREETime(&tt);
struct tm *lt, temp;
lt = gmtime((time_t)tt.seconds, &temp);
DMSG("%4d-%2d-%2d %2d:%2d:%2d", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);
This will print the time and date in a proper format.
Currently, I have not yet ported the implementation of strftime, but soon I will do that as well which will auto-format the tm struct and add the 1900 to tm_year and 1 to tm_mon.
In the meantime, I hope this finds someone in need.
Timer security
To retrieve the current time in milliseconds since 1970-01-01, call TEE_GetSystemTime. The security level depends on what secure clocks are available on the specific system. The absolute minimum guarantee is that this value cannot decrease while a trusted application is running, but the REE may be able to control at what speed this proceeds. There is no guarantee across reboots.
OP-TEE comes with an implementation of TEE_GetSystemTime based on CNTPCT. This can be configured to be exclusive to the TrustZone secure world on any armv8 chip, but I don't know if all chip manufacturers actually do so.
When the timer is secure, this is a guarantee that short delays will be respected. For example, if you want to block at least 1 second between authentication attempts, this is good enough.
Many platforms cannot keep the date securely, because that requires a clock that's always powered on, which requires a battery that doesn't run out. Often the non-trusted world can arrange to set the time to an arbitrary value after a system reset. So if you need the current date with enough precision to verify that a certificate has not expired, that's not good enough, and you need to do something like establish a connection to a secure time server. Which is a bootstrap problem, since you can't verify that the time server's certificate hasn't expired.
Breaking down the time
OP-TEE provides a thin C library, not including anything like gmtime. If you want to calculate the date and time from the epoch time, you need to provide your own implementation.
Mbed TLS is a consumer, not a provider, of gmtime (more precisely gmtime_r). It uses this function to check the validity of certificates.

How does one compute a time_t for the epoch in pure ISO C?

Using nothing but the C standard library (plain ISO C, no POSIX, and thus no assumption that time_t is represented in "seconds since the epoch"), what is the simplest way to get a time_t value corresponding to 01 Jan 1970 00:00:00 UTC?
The UTC part is the key; otherwise, just using mktime on a properly initialized struct tm would trivially solve the problem.
Alternatively (this is actually the "point" of the question), how does one portably determine the number of POSIX seconds between a given time_t value, e.g. the current time obtained via time(0), and the epoch? By "POSIX seconds" I mean the definition used in POSIX's "Seconds Since the Epoch" which does not use leap seconds. If this sounds too complicated, just go back to the question as originally stated in the first paragraph and assume the epoch is representable in time_t.
Here's an entry for a way to do it, "simplest" if nobody beats it:
call mktime on a struct tm for 02 Jan 1970 00:00:00
call mktime on a struct tm for 31 Dec 1969 00:00:00. This could reasonably return -1, in which case treat it as 0.
Binary search between the two for a value of time_t that, when passed to gmtime, results in 01 Jan 1970 00:00:00
Assumes that no local time is ever more than 24 hours different from UTC, which I'm pretty sure is a fact. We could widen the boundaries if necessary, in extremis we could search between 0 and time(0).
The binary search could be improved on, for example by interpolation. But who knows, maybe some crazy time zone (or broken tzinfo data) could cause a daylight savings change in December/January. I doubt that happened for any real time zone. But it's not forbidden by the C standard, only by common sense.
If it wasn't for that, I think we could calculate based on gmtime(mktime(01 Jan)) (to get the time zone) and a comparison of 01 Jan 1970 00:00:00 vs 01 Jan 1970 00:00:01 (to get the precision of time_t).
Your problem is rather fundamental: ISO C punts on time zones almost entirely, simply providing mktime() and the localtime() and gmtime() conversions, with a hook for daylight savings. (They implement, you decide.)
So there seems like only two things you can do:
assume that time_t is seconds-since-epoch UTC and use gmtime() to verify that, and panic or alert if it ever fails; or
rely on a more comprehensive standard than ISO C
Step 1: Choose any time_t (the current time will work just fine) as a reference point; call it t0.
Step 2: Call gmtime on t0 and compute the difference between the result and the epoch in a broken-down struct tm form.
Step 3: Call localtime on t0 and apply the broken-down difference from step 2 to the resulting struct tm. Then call mktime on it to get back a time_t.
The result should be a time_t representing the epoch.
My first attempt to implement this had problems when the local time offsets are not constant over time, for example in localities where daylight time has been added or abandoned or which switched from observing one zone to another. This seems to be because the data in the struct tm on which the time zone information is based gets changed. Here is the original implementation, with its problems:
time_t get_epoch(time_t t0)
{
struct tm gmt = *gmtime(&t0);
struct tm tmp = *localtime(&t0);
tmp.tm_sec -= gmt.tm_sec;
tmp.tm_min -= gmt.tm_min;
tmp.tm_hour -= gmt.tm_hour;
tmp.tm_mday -= gmt.tm_mday-1;
tmp.tm_mon -= gmt.tm_mon;
tmp.tm_year -= gmt.tm_year-70;
return mktime(&tmp);
}
and an improved version, where posix_time is a function to compute the seconds since the epoch for a given struct tm using the POSIX formulae (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15), with additional work to handle years before 1970, etc. if needed:
time_t get_epoch(time_t t0)
{
struct tm gmt = *gmtime(&t0);
struct tm tmp = *localtime(&t0);
long long offset = posix_time(&gmt);
while (offset > INT_MAX) {
offset -= INT_MAX;
tmp.tm_sec -= INT_MAX;
mktime(&tmp);
}
while (offset < -INT_MAX+61) {
offset -= -INT_MAX+61;
tmp.tm_sec -= -INT_MAX+61;
mktime(&tmp);
}
tmp.tm_sec -= offset;
return mktime(&tmp);
}
For C89 compatibility, long long would have to be dropped and the number of mktime calls needed increases dramatically; offset could not be computed as a single value, but a loop would be needed to call mktime multiple times per year.
Edit: Perhaps the following implementation of the non-standard timegm would meet the non-POSIX requirements of the question:
#include <stdio.h>
#include <string.h>
#include <time.h>
time_t my_timegm(struct tm *tm) {
time_t t, g;
double dt; // seconds
struct tm *gm;
t = mktime(tm);
gm = gmtime(&t);
gm->tm_isdst = 0;
g = mktime(gm);
dt = difftime(t, g);
if (dt >= 0) {
tm->tm_sec += fmod(dt, 60); // needed to handle 16-bit ints
tm->tm_min += dt / 60;
}
else {
tm->tm_sec -= fmod(-dt, 60);
tm->tm_min -= -dt / 60;
}
return mktime(tm);
}
int main(void) { // prints time_t for 01 Jan 1970 00:00:00 UTC
struct tm start;
memset(&start, 0, sizeof start);
start.tm_year = 70; // = 1970
start.tm_mday = 1; // = 1st
printf("%ld\n" my_timegm(&start)); // gives 0 on any POSIX system
return 0;
}
This assumes mktime will, as per the Linux man page, act so that structure members .. outside their valid interval ... will be normalized, or at least ensure something sensible is returned. I don't know whether plain ISO C guarantees this.
My initial version of my_timegm was from http://lists.samba.org/archive/samba-technical/2002-November/025571.html, and is credited there to Beeman, Baushke, Sabol, and Zawinski:
time_t my_timegm(struct tm *tm) {
time_t t, g;
struct tm *gm;
t = mktime(tm);
if (t == -1) { // perhaps needed for DST changeover?
tm->tm_hour--;
if ((t = mktime(tm)) != -1)
t += 3600;
}
gm = gmtime(&t);
gm->tm_isdst = 0;
g = mktime(gm);
if (g == -1) {
gm->tm_hour--;
if ((g = mktime(gm)) != -1)
g += 3600;
}
return (t == -1 || g == -1) ? -1 : t - (g - t); // or difftime
}
I'm still thinking about the need for the code: tm->tm_hour--, etc.
My previous approach had too many problems stemming from ambiguity about how mktime resolves denormalized time representations for me to be comfortable with it, so I'm going to try merging the idea with Steve Jessop's guessing/search idea for a better approach:
Initialize a struct tm object tm0 to the calendar time of the epoch.
Call mktime on tm0. This will result in its being interpreted as local time, however, so the result will not be the desired answer. Call this time_t value t0.
Apply gmtime to t0 to convert it to a broken-down universal time. It should differ from the desired epoch by less than 24 hours (actually, at most 12).
Adjust tm0 by the difference and return to step 2. If step 3 gives the right broken-down universal time epoch, we are finished. Otherwise, repeat steps 2-4 (should not be necessary).
In code,
time_t get_epoch()
{
struct tm tm0 = { .tm_year = 70, .tm_mday = 1 }, gmt;
time_t t0;
for (;;) {
t0 = mktime(&tm0);
gmt = *gmtime(&t0);
if (!gmt.tm_sec && !gmt.tm_min && !gmt.tm_hour &&
!gmt.tm_yday && gmt.tm_year==70) return t0;
tm0.tm_sec -= gmt.tm_sec;
tm0.tm_min -= gmt.tm_min;
tm0.tm_hour -= gmt.tm_hour;
tm0.tm_mday -= gmt.tm_mday-1;
tm0.tm_mon -= gmt.tm_mon;
tm0.tm_year -= gmt.tm_year-70;
}
}

How to decompose unix time in C

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.
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".
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.
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.

Resources