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

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s