Interesting HP-UX time bug
Thanks go to my good friend Mark Shoulson for help in discovering this.
So we're looking at some very old legacy code that does some date calculations in a particularly ... stunning ... fashion and we wanted to replace it with native Unix time functions for calculations.
So Mark and I wrote a test driver program to make sure that his functions would be bug-for-bug compatible with our current codebase, and instead uncovered the following interesting fact on HP-UX 11.3: one day after January 1, 2100 is January 1, 2100. (I guess Groundhog Day got moved that year.)
So we tried with this code:
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
int
main(argc, argv)
int argc;
char **argv;
{
struct tm thedate;
time_t timet;
int n;
char outdate[11];
char conv[11];
if (argc<3) {
printf("Usage: %s MM-DD-YYYY N\n", argv[0]);
exit(1);
}
if (NULL==strptime(argv[1], "%m-%d-%Y", &thedate)) {
printf("strptime(%s, \"%%m-%%d-%%Y\",...) failed.\n", argv[1]);
exit(1);
}
n=atoi(argv[2]);
thedate.tm_mday+=n;
thedate.tm_hour=12; thedate.tm_min=thedate.tm_sec=0;
timet=mktime(&thedate);
printf("(mktime returned %ld)\n",(long)timet);
strftime(outdate,11,"%m-%d-%Y",&thedate);
strcpy(conv,argv[1]);
printf("%s + %d days:\n\tstrptime computes:\t%s\n",
argv[1], n, outdate);
}
(We set the hour to be 12n as opposed to midnight to avoid a timezone issue, we're in EST5EDT not GMT.)
Well, we get, on Linux:
[jbaltz@linux ~]$ ./FDATE 08-23-2012 1
(mktime returned 1345827600)
08-23-2012 + 1 days:
strptime computes: 08-24-2012
and on HP-UX
jbaltz@hp-ux [~]> ./FDATE 08-23-2012 1
(mktime returned 1345827600)
08-23-2012 + 1 days:
strptime computes: 08-24-2012
Not too shabby: tomorrow is indeed the twenty-fourth of August, 2012.
Let's try around the troublesome 32-bit overflow:
[jbaltz@linux ~]$ ./FDATE 01-18-2038 2
(mktime returned 2147619600)
01-18-2038 + 2 days:
strptime computes: 01-20-2038
and
jbaltz@hp-ux [~]> ./FDATE 01-18-2038 2
(mktime returned 2147619600)
01-18-2038 + 2 days:
strptime computes: 01-20-2038
OK, let's go for the gold: the year 2100:
[jbaltz@linux ~]$ ./FDATE 01-01-2100 1
(mktime returned 4102592400)
01-01-2100 + 1 days:
strptime computes: 01-02-2100
but on HP-UX:
jbaltz@hp-ux [~]> ./FDATE 01-01-2100 1
(mktime returned 4102506000)
01-01-2100 + 1 days:
strptime computes: 01-01-2100
jbaltz@hp-ux [~]> ./FDATE 12-31-2099 2
(mktime returned 4102506000)
12-31-2099 + 2 days:
strptime computes: 01-01-2100
jbaltz@hp-ux[~]> ./FDATE 12-31-2099 3
(mktime returned 4102592400)
12-31-2099 + 3 days:
strptime computes: 01-02-2100
jbaltz@hp-ux [~]> ./FDATE 01-01-2100 2
(mktime returned 4102592400)
01-01-2100 + 2 days:
strptime computes: 01-02-2100
oopsie!
Yes, we're filing a bug report. Hopefully, if this hasn't been patched already, it will be before 2100.
Comments
Just to be specific, we've pinned it down to mktime(). If you have a struct tm in which tm_year=200, tm_month=0, tm_mday=2, i.e. 1 January 2100 (remember, months are 0-indexed, years are 1900-indexed, and days are 1-indexed), and innocuous values for time, and you run it through mktime, the struct you get back has the tm_mday set back to 1. In fact, for any positive tm_mday throughout the whole year 2100, mktime will always subtract one from the mday (in addition to renormalizing, if necessary). I haven't checked negatives.
In 2200, January 1 is okay, but February 1 isn't. Only dates later than January 2 are bad. In 2300, we're good all the way up through January 4. 2400 is a leap year, and is all Just Fine. Non-century years look okay, though we haven't tested them all.
This is 64-bit HPUX, so it is supposed to be good through 31 December 9999 (no Y2.37k bug here).
Once again, for tl;dr crowd: mktime subtracts one from the tm_mday.
Posted by: Mark | August 23, 2012 11:19 AM