How to calculate time_t for an era in pure ISO C?

Using only the standard C library (regular ISO C, not POSIX, and therefore, it is not assumed that time_t is represented in "seconds from the era"), which is the easiest way to get the time_t value that is valid until January 01, 1970 00:00:00 UTC ?

The UTC part is the key; otherwise, just using mktime for a properly initialized struct tm will trivially solve the problem.

As an alternative (in fact, this is the "point" of the question), how one portable determines the number of POSIX seconds between a given value of time_t , for example. current time received through time(0) , and era? By “POSIX seconds” I mean the definition used in POSIX “Seconds from an Age” that does not use leap seconds. If this sounds too complicated, just go back to the question, as originally indicated in the first paragraph, and assume that the era is representable in time_t .

+7
source share
5 answers

Here's the entry for the way to do this, "simplest" if no one hits her:

  • mktime call on struct tm for 02 Jan 1970 00:00:00
  • calling mktime on struct tm for 31 Dec 1969 00:00:00 . This could reasonably return -1, and in this case treat it as 0.
  • A binary search between them for the value of time_t , which, when transmitted in gmtime leads to 01 Jan 1970 00:00:00

It is assumed that no local time exceeds 24 hours, other than UTC, which, I am sure, is a fact. If necessary, we could expand the boundaries, in extreme cases, we could search between 0 and time(0) .

Binary search can be improved, for example, by interpolation. But who knows, maybe some crazy time zone (or tzinfo broken data) can cause a change in daily savings in December / January. I doubt it happened for any real time zone. But this is not prohibited by the C standard, only common sense.

If not for this, I think we could calculate based on gmtime(mktime(01 Jan)) (to get the time zone) and comparison 01 Jan 1970 00:00:00 vs 01 Jan 1970 00:00:01 (to get precision time_t ).

+4
source

Your problem is pretty fundamental: ISO C accurately reflects time zones, just providing mktime() and localtime() and gmtime() conversions with a daylight saving time hook. (They implement, you decide.)

So there are only two things you can do:

  • suppose time_t is a second from the UTC era and use gmtime() to check for this and panic or alert if it ever fails; or
  • rely on a more complete standard than ISO C
0
source

Step 1: select any time_t (the current time will work fine) as a control point; name it t0 .

Step 2: Call gmtime on t0 and calculate the difference between the result and the era in the broken form struct tm .

Step 3: Call localtime on t0 and apply the broken difference from step 2 to the result of struct tm . Then call mktime on it to return a time_t .

The result should be time_t representing the era.

My first attempt to realize this had problems when local time shifts were not constant over time, for example, in places where daylight saving time was added or left, or which switched from observing one zone to another. This seems to be due to the fact that the data in struct tm , which the time zone information is based on, is changing. 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 calculate seconds from an era for a given struct tm using POSIX formulas (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html # tag_04_15), with extra work for processing years to 1970, etc., if necessary:

 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 compatibility with C89, long long need to be dropped, and the number of required calls mktime increases dramatically; offset cannot be calculated as a single value, but a loop will be needed to call mktime several times a year.

0
source

Edit: Perhaps the following non-standard timegm implementation will meet requirements other than the POSIX 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 that mktime will, according to the Linux manual page, act in such a way that structure members .. outside their valid interval ... will be normalized , or at least provide a return for something reasonable. I do not know if this guarantees the ISO C standard.

My initial version of my_timegm was from http://lists.samba.org/archive/samba-technical/2002-November/025571.html and enrolled there by 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 am still thinking about the need for code: tm->tm_hour-- etc.

0
source

My previous approach had too many ambiguity issues regarding how mktime resolves denormalized representations of time for me to be comfortable with it, so I'm going to try to combine this idea with guessing / searching idea by Steve Jessop for a better approach:

  • Initialize the struct tm tm0 for the calendar time of an era.

  • Call mktime on tm0 . This will cause it to be interpreted as local time, but the result will not be the desired answer. Name this value time_t t0 .

  • Apply gmtime to t0 to convert it to broken universal time. It should differ from the desired era in less than 24 hours (in fact, at most 12).

  • Adjust tm0 to the difference and return to step 2. If step 3 gives the correct broken universal time era, we are done. Otherwise, repeat steps 2-4 (optional).

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; } } 
0
source

All Articles