From: Roger Gibson (rxg@uniplex.co.uk) I have found a bug in 'localtime.c' for which I am sending a proposed fix. The problem occurs when 'mktime' is called with tm_mday <= 0, tm_mon > 1 and tm_year is a leap year or the year after a leap year. For such a case, the normalization is performed incorrectly, and yields a date which is one day out. The following example program demonstrates this: -------------------------------- #include <stdio.h> #include <time.h> int main() { struct tm tm; char buf[256]; memset(&tm, 0, sizeof tm); tm.tm_year = 96; /* 1996 */ tm.tm_mon = 2; /* March */ tm.tm_mday = 0; tm.tm_hour = 12; mktime(&tm); /* Should give Feb 29 */ strftime(buf, sizeof buf, "%C", &tm); printf("%s\n", buf); return 0; } -------------------------------- To deal with cases like this, the 'time2' function decrements the year, and adds the corresponding number of days to 'tm_mday'. The problem is that the number of days is taken to be that in the year after it has been decremented, whereas it is actually that in the year which contains the end of February which is spanned by the interval. It is necessary to take 'tm_mon' into account in determining this. The same problem occurs in the part of the code which deals with values of 'tm_mday' which exceed the number of days in a year. The version of 'time2' below contains my attempt to fix these problems. It seems to work, but I'd like to know if you concur. -------------------------------- Version information: static char elsieid[] = "@(#)localtime.c 7.44"; (Still present in version 7.55.) -------------------------------- static time_t time2(tmp, funcp, offset, okayp) struct tm * const tmp; void (* const funcp) P((const time_t*, long, struct tm*)); const long offset; int * const okayp; { register const struct state * sp; register int dir; register int bits; register int i, j ; register int saved_seconds; time_t newt; time_t t; struct tm yourtm, mytm; *okayp = FALSE; yourtm = *tmp; if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) return WRONG; if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* ** Turn yourtm.tm_year into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ if (increment_overflow(&yourtm.tm_year, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { if (increment_overflow(&yourtm.tm_year, -1)) return WRONG; if (yourtm.tm_mon >= 2) yourtm.tm_mday += year_lengths[isleap(yourtm.tm_year + 1)]; else yourtm.tm_mday += year_lengths[isleap(yourtm.tm_year)]; } while (yourtm.tm_mday > DAYSPERLYEAR) { if (yourtm.tm_mon >= 2) yourtm.tm_mday -= year_lengths[isleap(yourtm.tm_year + 1)]; else yourtm.tm_mday -= year_lengths[isleap(yourtm.tm_year)]; if (increment_overflow(&yourtm.tm_year, 1)) return WRONG; } for ( ; ; ) { i = mon_lengths[isleap(yourtm.tm_year)][yourtm.tm_mon]; if (yourtm.tm_mday <= i) break; yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; if (increment_overflow(&yourtm.tm_year, 1)) return WRONG; } } if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE)) return WRONG; if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) { /* ** We can't set tm_sec to 0, because that might push the ** time below the minimum representable time. ** Set tm_sec to 59 instead. ** This assumes that the minimum representable time is ** not in the same minute that a leap second was deleted from, ** which is a safer assumption than using 58 would be. */ if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN)) return WRONG; saved_seconds = yourtm.tm_sec; yourtm.tm_sec = SECSPERMIN - 1; } else { saved_seconds = yourtm.tm_sec; yourtm.tm_sec = 0; } /* ** Calculate the number of magnitude bits in a time_t ** (this works regardless of whether time_t is ** signed or unsigned, though lint complains if unsigned). */ for (bits = 0, t = 1; t > 0; ++bits, t <<= 1) continue; /* ** If time_t is signed, then 0 is the median value, ** if time_t is unsigned, then 1 << bits is median. */ t = (t < 0) ? 0 : ((time_t) 1 << bits); for ( ; ; ) { (*funcp)(&t, offset, &mytm); dir = tmcomp(&mytm, &yourtm); if (dir != 0) { if (bits-- < 0) return WRONG; if (bits < 0) --t; else if (dir > 0) t -= (time_t) 1 << bits; else t += (time_t) 1 << bits; continue; } if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) break; /* ** Right time, wrong type. ** Hunt for right time, right type. ** It's okay to guess wrong since the guess ** gets checked. */ /* ** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's. */ sp = (const struct state *) (((void *) funcp == (void *) localsub) ? lclptr : gmtptr); #ifdef ALL_STATE if (sp == NULL) return WRONG; #endif /* defined ALL_STATE */ for (i = 0; i < sp->typecnt; ++i) { if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) continue; for (j = 0; j < sp->typecnt; ++j) { if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) continue; newt = t + sp->ttis[j].tt_gmtoff - sp->ttis[i].tt_gmtoff; (*funcp)(&newt, offset, &mytm); if (tmcomp(&mytm, &yourtm) != 0) continue; if (mytm.tm_isdst != yourtm.tm_isdst) continue; /* ** We have a match. */ t = newt; goto label; } } return WRONG; } label: newt = t + saved_seconds; if ((newt < t) != (saved_seconds < 0)) return WRONG; t = newt; (*funcp)(&t, offset, tmp); *okayp = TRUE; return t; }