Pivotal ANSI like time functions

Pivotal ANSI-like time functions
OVERVIEW
The ANSI C library has several core time functions that do not give correct results past the year 2038. The functions below are replacements of two kinds, 1) drop-in replacements, and 2) replacement functions that require re-factoring your code by changing all use of time_t to time64_t. Please note that on Windows you should use the _gmtime64() and _localtime64() functions.

Also note that a certain consistency of the use of daylight savings in different countries is assumed with the _localtime64_r function. You are advised to look closely at the implementation.

The function prototypes are:

1)

  • struct tm *pivotal_localtime_r (const time_t * now, const time_t * t, struct tm *p);
  • struct tm *pivotal_gmtime_r (const time_t * now, const time_t * t, struct tm *p);

2)

  • typedef long long time64_t;
  • time64_t pivot_time_t (const time_t * now, time64_t *t);
  • time64_t mktime64 (struct tm *t);
  • struct tm *localtime64_r (const time64_t *t, struct tm *p);
  • struct tm *gmtime64_r (const time64_t *t, struct tm *p);p);
  • struct tm *gmtime64_r (const time64_t *t, struct tm *p);

API DOCUMENTATION
struct tm *pivotal_localtime_r (const time_t * now, const time_t * t, struct tm *p);
Calculates broken-down time representation for the local timezone: year/month/day/hour/minute/second.

Arguments:

now: Current time, or if passed as NULL does a system call to get the current time.
t: Requested time in UTC seconds.
p: User-suppled storage area.

Return value:

p is populated and returned back to the caller.

struct tm *pivotal_gmtime_r (const time_t * now, const time_t * t, struct tm *p);
Calculates broken down time representation for the timezone GMT, year/month/day/hour/minute/second.

Arguments:

now: Current time, or if passed as NULL does a system call to get the current time.
t: Requested time in UTC seconds.
p: User-suppled storage area.

Return value:

p is populated and returned back to the caller.

time64_t pivot_time_t (const time_t * now, time64_t *t);
Takes a 64-bit time that may have had its top 32-bits set to zero, and adjusts it so that it is in the range explained in the notes below. You can use pivot_time_t() to convert any time that may be incorrect. pivot_time_t() returns its argument unchanged if either now is NULL or sizeof(time_t) is not 4.

Arguments:

now: Current time, or if passed as NULL does a system call to get the current time.
t: Requested time in UTC seconds.

Return value:

Corrected 64-bit time.

time64_t mktime64 (struct tm *t);
Converts broken down time representation to 64-bit time in UTC, seconds since 1970-01-01 00:00:00.

Arguments:

t: Broken-down time.

Return value:

64-bit UTC time in seconds.

struct tm *localtime64_r (const time64_t *t, struct tm *p);
64-bit version; calculates broken down time representation for the local timezone: year/month/day/hour/minute/second.

Arguments:

now: Current time, or if passed as NULL does a system call to get the current time.
t: Requested time in UTC seconds.
p: User-suppled storage area.

Return value:

p is populated and returned back to the caller.

struct tm *gmtime64_r (const time64_t *t, struct tm *p);
64-bit version; calculates broken down time representation for the timezone GMT, year/month/day/hour/minute/second.

Arguments:

now: Current time, or if passed as NULL does a system call to get the current time.
t: Requested time in UTC seconds.
p: User-suppled storage area.

Return value:

p is populated and returned back to the caller.

HOW THIS WORKS

Although 31-bits (32 less the sign bit) cannot represent the full range of dates from 1970 past 2038, it can easily represent the full range of days between the present time and 231 seconds in the past or future. If we assume that a program is only interested in dates 68 years in the past or future, then it easy to calculate that a date that could be either 1917 or 2053 (due to the missing high bits) should be the latter value and not the former.
table
In order to support this, the only change to the standard ANSI C time functions is the inclusion of the current time as an additional parameter: “now” in the above prototypes. This is called the pivot point – hence the name of this library.

This is most useful where one is unable to re-factor all one’s code to use time64_t instead of a 32-bit time value.

It is intended that you pass ‘now’ as the current time (as previously retrieved with a call such as time(&now)). In this case, pivotal_gmtime_r() returns the correct broken down time in the range of
now – 2147483648 seconds
through to
now + 2147483647 seconds

For example, on 10-Jan-2008, pivotal_gmtime_r() will return the correct broken down time format for any time in the range 23-Dec-1939 through 29-Jan-2076.

For all values of ‘now’ before Jan-10-2009 and after Jan-19-2038, pivotal_gmtime_r() will return the correct broken down time format for any time 01-Jan-1970 through to 07-Feb-2106.

In other words, if, for example, pivotal_gmtime_r() is used in a program that needs to convert time values of 25 years into the future and 68 years in the past, the program will operate as expected until the year 2106-25=2081. This will be true even on 32-bit systems.

Note that “Jan-10-2009” is the date of the authoring of this code.

Programmers who have available to them 64-bit integer values (long long type) can use gmtime64_r() instead, which correctly converts the time even on 32-bit systems. Whether you have 64-bit integer values will depend on the operating system.

Both functions are 64-bit clean and should work as expected on 64-bit systems.

The localtime() equivalent functions do both a POSIX localtime_r() and gmtime_r() and work out the time zone offset from their difference. This is inefficient but gives the correct timezone offset and daylight savings time adjustments.

Note that none of these functions handle leap seconds.
Changes:
10-Jan-2009 v4.0: Make localtime work in 28-year cycle.
06-Feb-2005 v3.0: Some optimizations.
mktime() no-longer zeros tm struct.

Source : 2038bug.com

Even range of dates showing results for pivotal_localtime_r()

Even range of dates showing results for pivotal_localtime_r()

Even range of dates showing results for localtime(),
pivotal_localtime_r(), and localtime64_r().

Values in red show invalid dates, green shows valid dates.

The useful range of dates of pivotal_localtime_r()
versus localtime() can be seen.

localtime() pivotal_localtime_r() localtime64_r()
--------------------------------------------------------------------------------------------
UTC = -4000000000 seconds
08-May-1979 00:21 08-May-1979 00:21 31-Mar-1843 17:53
01-Jun-1984 20:48 01-Jun-1984 20:48 25-Apr-1848 14:20
27-Jun-1989 17:14 27-Jun-1989 17:14 21-May-1853 10:46
23-Jul-1994 13:41 23-Jul-1994 13:41 16-Jun-1858 07:13
18-Aug-1999 10:08 18-Aug-1999 10:08 12-Jul-1863 03:40
12-Sep-2004 06:34 12-Sep-2004 06:34 06-Aug-1868 00:06
08-Oct-2009 03:01 08-Oct-2009 03:01 31-Aug-1873 20:33
02-Nov-2014 22:28 02-Nov-2014 22:28 26-Sep-1878 16:00
28-Nov-2019 18:54 28-Nov-2019 18:54 22-Oct-1883 12:26
23-Dec-2024 15:21 23-Dec-2024 15:21 16-Nov-1888 08:53
18-Jan-2030 11:48 18-Jan-2030 11:48 12-Dec-1893 05:20
13-Feb-2035 08:14 13-Feb-2035 08:14 07-Jan-1899 01:46
02-Feb-1904 22:13 10-Mar-2040 04:41 02-Feb-1904 22:13
27-Feb-1909 18:40 05-Apr-2045 02:08 27-Feb-1909 18:40
25-Mar-1914 15:06 30-Apr-2050 22:34 25-Mar-1914 15:06
20-Apr-1919 12:33 26-May-2055 19:01 20-Apr-1919 12:33
15-May-1924 09:00 20-Jun-2060 15:28 15-May-1924 09:00
10-Jun-1929 05:26 16-Jul-2065 11:54 10-Jun-1929 05:26
06-Jul-1934 01:53 11-Aug-2070 08:21 06-Jul-1934 01:53
31-Jul-1939 22:20 31-Jul-1939 22:20 31-Jul-1939 22:20
25-Aug-1944 19:46 25-Aug-1944 19:46 25-Aug-1944 19:46
20-Sep-1949 15:13 20-Sep-1949 15:13 20-Sep-1949 15:13
16-Oct-1954 10:40 16-Oct-1954 10:40 16-Oct-1954 10:40
11-Nov-1959 07:06 11-Nov-1959 07:06 11-Nov-1959 07:06
06-Dec-1964 03:33 06-Dec-1964 03:33 06-Dec-1964 03:33
UTC = 0 seconds
01-Jan-1970 01:00 01-Jan-1970 01:00 01-Jan-1970 01:00
26-Jan-1975 20:26 26-Jan-1975 20:26 26-Jan-1975 20:26
21-Feb-1980 16:53 21-Feb-1980 16:53 21-Feb-1980 16:53
18-Mar-1985 13:20 18-Mar-1985 13:20 18-Mar-1985 13:20
13-Apr-1990 10:46 13-Apr-1990 10:46 13-Apr-1990 10:46
09-May-1995 07:13 09-May-1995 07:13 09-May-1995 07:13
03-Jun-2000 03:40 03-Jun-2000 03:40 03-Jun-2000 03:40
UTC = time of writing
29-Jun-2005 00:06 29-Jun-2005 00:06 29-Jun-2005 00:06
24-Jul-2010 20:33 24-Jul-2010 20:33 24-Jul-2010 20:33
19-Aug-2015 17:00 19-Aug-2015 17:00 19-Aug-2015 17:00
13-Sep-2020 13:26 13-Sep-2020 13:26 13-Sep-2020 13:26
09-Oct-2025 09:53 09-Oct-2025 09:53 09-Oct-2025 09:53
04-Nov-2030 05:20 04-Nov-2030 05:20 04-Nov-2030 05:20
30-Nov-2035 01:46 30-Nov-2035 01:46 30-Nov-2035 01:46
UTC = 231 - 1
18-Nov-1904 15:45 24-Dec-2040 22:13 24-Dec-2040 22:13
14-Dec-1909 12:11 19-Jan-2046 18:40 19-Jan-2046 18:40
09-Jan-1915 08:38 14-Feb-2051 15:06 14-Feb-2051 15:06
04-Feb-1920 05:05 11-Mar-2056 11:33 11-Mar-2056 11:33
01-Mar-1925 01:31 06-Apr-2061 09:00 06-Apr-2061 09:00
26-Mar-1930 21:58 02-May-2066 05:26 02-May-2066 05:26
21-Apr-1935 19:25 28-May-2071 01:53 28-May-2071 01:53
16-May-1940 15:51 16-May-1940 15:51 21-Jun-2076 22:20
11-Jun-1945 13:18 11-Jun-1945 13:18 17-Jul-2081 18:46
07-Jul-1950 08:45 07-Jul-1950 08:45 12-Aug-2086 15:13
02-Aug-1955 05:11 02-Aug-1955 05:11 07-Sep-2091 11:40
UTC = +4000000000 seconds

A version of gmtime() that works around the 2038 bug

A version of gmtime() that works around the 2038 bug.

/* pivotal_gmtime_r - a replacement for gmtime/localtime/mktime
that works around the 2038 bug on 32-bit
systems. (Version 4)

Copyright (C) 2009 Paul Sheer

Redistribution and use in source form, with or without modification,
is permitted provided that the above copyright notice, this list of
conditions, the following disclaimer, and the following char array
are retained.

Redistribution and use in binary form must reproduce an
acknowledgment: 'With software provided by http://2038bug.com/' in
the documentation and/or other materials provided with the
distribution, and wherever such acknowledgments are usually
accessible in Your program.

This software is provided "AS IS" and WITHOUT WARRANTY, either
express or implied, including, without limitation, the warranties of
NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THIS SOFTWARE IS WITH
YOU. Under no circumstances and under no legal theory, whether in
tort (including negligence), contract, or otherwise, shall the
copyright owners be liable for any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of the use of this software including, without limitation,
damages for loss of goodwill, work stoppage, computer failure or
malfunction, or any and all other commercial damages or losses. This
limitation of liability shall not apply to liability for death or
personal injury resulting from copyright owners' negligence to the
extent applicable law prohibits such limitation. Some jurisdictions
do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply
to You.

*/

const char pivotal_gmtime_r_stamp[] =
"pivotal_gmtime_r. Copyright (C) 2009 Paul Sheer. Terms and "
"conditions apply. Visit http://2038bug.com/ for more info.";

/* DOCUMENTATION: See http://2038bug.com/pivotal_gmtime_doc.html */

#include
#include
#include

typedef long long time64_t;

static const int days[4][13] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366},
};

#define LEAP_CHECK(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))

time64_t pivot_time_t (const time_t * now, time64_t * _t)
{
time64_t t;
t = *_t;
if (now && sizeof (time_t) == 4) {
time_t _now;
_now = *now;
if (_now < 1231500000 /* Jan 2009 - date of writing */ )
_now = 2147483647;
if ((time64_t) t + ((time64_t) 1 << 31) < (time64_t) _now)
t += (time64_t) 1 <tm_year = 70;
leap = LEAP_CHECK (p->tm_year);
while (m >= (long) days[leap + 2][12]) {
m -= (long) days[leap + 2][12];
p->tm_year++;
leap = LEAP_CHECK (p->tm_year);
}
v_tm_mon = 0;
while (m >= (long) days[leap][v_tm_mon]) {
m -= (long) days[leap][v_tm_mon];
v_tm_mon++;
}
} else {
p->tm_year = 69;
leap = LEAP_CHECK (p->tm_year);
while (m tm_year--;
leap = LEAP_CHECK (p->tm_year);
}
v_tm_mon = 11;
while (m tm_mday = (int) m + 1;
p->tm_yday = days[leap + 2][v_tm_mon] + m;
p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour, p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
return p;
}

struct tm *gmtime64_r (const time64_t * _t, struct tm *p)
{
time64_t t;
t = *_t;
return _gmtime64_r (NULL, &t, p);
}

struct tm *pivotal_gmtime_r (const time_t * now, const time_t * _t, struct tm *p)
{
time64_t t;
t = *_t;
return _gmtime64_r (now, &t, p);
}

time64_t mktime64 (struct tm * t)
{
int i, y;
long day = 0;
time64_t r;
if (t->tm_year = t->tm_year);
} else {
y = 70;
while (y tm_year) {
day += 365 + LEAP_CHECK (y);
y++;
}
}
for (i = 0; i tm_mon; i++)
day += days[LEAP_CHECK (t->tm_year)][i];
day += t->tm_mday - 1;
t->tm_wday = (int) ((day + 4) % 7);
r = (time64_t) day *86400;
r += t->tm_hour * 3600;
r += t->tm_min * 60;
r += t->tm_sec;
return r;
}

static struct tm *_localtime64_r (const time_t * now, time64_t * _t, struct tm *p)
{
time64_t tl;
time_t t;
struct tm tm, tm_localtime, tm_gmtime;
_gmtime64_r (now, _t, &tm);
while (tm.tm_year > (2037 - 1900))
tm.tm_year -= 28;
t = mktime64 (&tm);
localtime_r (&t, &tm_localtime);
gmtime_r (&t, &tm_gmtime);
tl = *_t;
tl += (mktime64 (&tm_localtime) - mktime64 (&tm_gmtime));
_gmtime64_r (now, &tl, p);
p->tm_isdst = tm_localtime.tm_isdst;
return p;
}

struct tm *pivotal_localtime_r (const time_t * now, const time_t * _t, struct tm *p)
{
time64_t tl;
tl = *_t;
return _localtime64_r (now, &tl, p);
}

struct tm *localtime64_r (const time64_t * _t, struct tm *p)
{
time64_t tl;
tl = *_t;
return _localtime64_r (NULL, &tl, p);
}

Year 2038 Bug Program C Example

An example C program
The following C program demonstrates this effect. It is strict ANSI C so it should compile on all systems that support an ANSI C compiler.

#include
#include
#include
#include

int main (int argc, char **argv)
{
time_t t;
t = (time_t) 1000000000;
printf ("%d, %s", (int) t, asctime (gmtime (&t)));
t = (time_t) (0x7FFFFFFF);
printf ("%d, %s", (int) t, asctime (gmtime (&t)));
t++;
printf ("%d, %s", (int) t, asctime (gmtime (&t)));
return 0;
}

The program produces the output:
1000000000, Sun Sep 9 01:46:40 2001
2147483647, Tue Jan 19 03:14:07 2038
-2147483648, Fri Dec 13 20:45:52 1901

Source : 2038bug.com

Year 2038 Bug What Can I Do As A Developer?

What can I do as a developer?

The first thing is to start campaigning for solutions to this problem. Petition major software vendors to start planning now. Try wherever possible to use large types for storing dates in databases: 64-bits is sufficient – a long long type in GNU C and POSIX/SuS.

Check where the time is encoded – many programmers encode the time in seconds into 4 bytes into databases and within network protocols. They then decode them into a signed integer. Use five bytes instead.

Another thing to do is to use GMT time whenever storing or processing the time as a text string. This is because it is easy to write a GMT time converter function that is 2038-bug clean (see source code below). By this I mean, if you have to convert your time to and from text, and if the users are not going to see that text, then never use local time, which has daylight savings and GMT offset complexities. (Note that some countries do not have daylight savings. Some sites use GMT and local time virtually synonymously.)

The result will be that internally, all your code will work with GMT time and work correctly through the year 2038. If it so happens that on January 20 2038 users find dates are displayed incorrectly within the user-interface, this will merely be a cosmetic problem.

Make sure your use of time_t is consistent throughout your code. Always use the Operating System vendor’s standard functions to convert times and use them exactly as directed by their documentation. Never cast time values to any type except the standard types provided by your system libraries. This way, when your code gets recompiled on newer systems, the bugs will naturally go away.

A version of gmtime() that works around the 2038 bug can be found here, with useful ranges of various functions shown in this table. See also documentation for this code.

Other solutions for 32-bit systems

On a 32-bit systems, time_t has a signed 32-bit integer C/C++ type. A common way to get the time is:


time_t now;
time (&now);

You want a situation where the 32-bit code will continue to work past the year 2038, but if re-compiled on a 64-bit system, then the same code ought to use a 64-bit time type. This requires being quite crafty. It should also be strict ANSI compliant.

Consider a 32-bit operating systems that works through the year 2038. At the time of the cross-over, the 32-bit time will continue to tick over and become a negative number. If we simply cast this signed 32-bit number to an unsigned value, we should have the correct time, even up to the year 2106.

A solution that works on all 32-bit and 64-bit systems (including Windows) is as follows. This assumes that we are not interested in dates before 1970.

First we define a time type that is a double float. A double float will be a clean integer for time values up to 143 million years from now, because double values are exact integers unless the size of their mantissa is exceeded. (We could use long long – this is your choice. The C type long long tends to throw compiler warnings on some systems however.)

Our function looks like this:

typedef double timef_t;

timef_t timef (void)
{
time_t now;
time (&now);
if (sizeof (now) == 4) {
unsigned int v;
v = (unsigned int) now;
return (double) v;
} else {
return (double) now;
}
}

If you need the UTC time in milliseconds. You can use the following function, which works both on Unix as well as Windows:


typedef double timef_t;

timef_t millitimef (void)
{
struct timeb t;
ftime (&t);
if (sizeof (t.time) == 4) {
unsigned int v;
v = (unsigned int) t.time;
return (double) v * 1000.0 + (double) t.millitm;
} else {
return (double) t.time * 1000.0 + (double) t.millitm;
}
}

Programs that use these functions will now do time calculations correctly in seconds. Now all they need is to display time correctly. For this, look at the pivot time code. However if you just need to display the GMT time in text, the solution is relatively simple as follows.

Most programs use one of the functions gmtime(), gmtime_r(), localtime() or localtime_r() to get the “broken-down” time format. These four functions convert the integer number of seconds since 1970 into a “struct tm” structure whose members contain the year, month, day, hour, minute, second, and week day. The function strftime() can then convert broken down time to a string. gmtime() and gmtime_r() convert to Greenwich time, and localtime() and localtime_r() convert to time offset by the number of hours left or right of Greenwich, inclusive of any daylight-savings offsets. The _r variations are the “re-entrant” (thread-safe) versions of the same function.

You may be using the functions asctime(), and ctime(), but these functions can easily be constructed from the functions I mention.

The problem with implementing localtime() is that, besides the fact that no one knows what future governments will decide about day-light savings, every country has different daylight savings policies. So a universal localtime() function requires maintenance, consensus, and a large number of daylight savings parameters for each country on earth.

The function gmtime_r() however is simple to implement if you are only interested in dates after 1970, and are not interested in leap second adjustments. The function strftime() is 2038 bug clean (test this on your system) because it does not work with the Unix epoch.

A compact gmtime/gmtime_r implementation is,

#define LEAP_CHECK(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))

struct tm *simple_gmtime_r (time_t *_t, struct tm *p)
{
static const int days[4][13] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366},
};
int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday, v_tm_tday;
int leap;
long long t;
long m;
if (sizeof (time_t) == 4) {
unsigned int __t;
__t = (unsigned int) *_t;
t = (long long) __t;
} else {
t = *_t;
}
v_tm_sec = (int) ((long long) t % (long long) 60);
t /= 60;
v_tm_min = (int) ((long long) t % (long long) 60);
t /= 60;
v_tm_hour = (int) ((long long) t % (long long) 24);
t /= 24;
v_tm_tday = (int) t;
WRAP (v_tm_sec, v_tm_min, 60);
WRAP (v_tm_min, v_tm_hour, 60);
WRAP (v_tm_hour, v_tm_tday, 24);
if ((v_tm_wday = (v_tm_tday + 4) % 7) tm_year = 70;
leap = LEAP_CHECK (p->tm_year);
while (m >= (long) days[leap + 2][12]) {
m -= (long) days[leap + 2][12];
p->tm_year++;
leap = LEAP_CHECK (p->tm_year);
}
v_tm_mon = 0;
while (m >= (long) days[leap][v_tm_mon]) {
m -= (long) days[leap][v_tm_mon];
v_tm_mon++;
}
p->tm_mday = (int) m + 1;
p->tm_yday = days[leap + 2][v_tm_mon] + m;
p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
return p;
}

static struct tm *simple_gmtime (time_t *_t)
{
static struct tm p;
return u32_s64_gmtime (_t, &p);
}

For many types of programs, the code above is all you need to get correct dates after 2038. You then need to change your code to drop the other time functions and use only simple_gmtime_r() and strftime() and be happy with Greenwich time.

Source : 2038bug.com