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.
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.
Use the TAI library by Dan
Bernstein. This library copes with the time problem properly.
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) < 0)
v_tm_wday += 7;
m = (long) v_tm_tday;
p->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.