Date: Sun, 24 Jan 1999 10:34:49 +0200 (IST) From: Eli Zaretskii X-Sender: eliz AT is To: DJ Delorie cc: djgpp-workers AT delorie DOT com Subject: ctime.c problems Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Reply-To: djgpp-workers AT delorie DOT com While building the latest pretest of GNU Textutils, I found quite a few problems with ctime.c. These problems caused the configure script to decide that our mktime isn't ``working'' (ha!), and use the GNU version instead. Here are the problems I found when looking into this: 1) If the value of "TZ" in the environment changes during the program run, ctime.c doesn't pay attention to the change and continues to use the old timezone. 2) mktime will loop for a very long time (several minutes!) when its argument includes some extremely large values (e.g. try tm.tm_day = 2^30 - 1). 3) mktime fails for arguments close to the epoch returned by localtime in non-GMT timezones. For example, the value of the following expression is false (now and lt are both time_t) in the PST8 timezone: ((now = (time_t)0), (lt = localtime (&now)), (mktime (lt) == now)) Here's a patch that fixes all these problems, and then some: *** src/libc/ansi/time/ctime.c~0 Thu Jan 1 17:17:30 1998 --- src/libc/ansi/time/ctime.c Sat Jan 23 15:07:52 1999 *************** static char sccsid[] = "@(#)ctime.c 5.23 *** 50,55 **** --- 50,56 ---- #include #include + #include #include "posixrul.h" *************** static void timesub P((const time_t * t *** 152,157 **** --- 153,159 ---- const struct state * sp, struct tm * tmp)); static int tmcomp P((const struct tm * atmp, const struct tm * btmp)); + static void tmnormalize P((struct tm * tmp)); static time_t transtime P((time_t janfirst, int year, const struct rule * rulep, long offset)); static int tzload P((const char * name, struct state * sp)); *************** static struct state gmtmem; *** 171,178 **** #define gmtptr (&gmtmem) #endif /* State Farm */ ! static int lcl_is_set; static int gmt_is_set; char * tzname[2] = { WILDABBR, --- 173,181 ---- #define gmtptr (&gmtmem) #endif /* State Farm */ ! static int lcl_is_set; /* 0: no, 1: set by tzset, -1: set by tzsetwall */ static int gmt_is_set; + static char lcl_tzstr[512]; char * tzname[2] = { WILDABBR, *************** static const int year_lengths[2] = { *** 401,406 **** --- 404,415 ---- DAYSPERNYEAR, DAYSPERLYEAR }; + static const long n_year_lengths[3] = { + 3*DAYSPERNYEAR + DAYSPERLYEAR, /* 4-year cycle length */ + 25*(3*DAYSPERNYEAR + DAYSPERLYEAR) - 1, /* 100-year cycle length */ + 4*(25*(3*DAYSPERNYEAR + DAYSPERLYEAR) - 1) + 1 /* 400-year cycle length */ + }; + /* ** Given a pointer into a time zone string, scan until a character that is not ** a valid character in a zone name is found. Return a pointer to that *************** void *** 908,921 **** tzset(void) { const char * name; name = getenv("TZ"); if (name == NULL) { tzsetwall(); return; } ! lcl_is_set = TRUE; #ifdef ALL_STATE if (lclptr == NULL) { --- 917,951 ---- tzset(void) { const char * name; + static unsigned last_env_changed = 0; + /* If environ didn't changed since last time, don't waste time + looking at $TZ. */ + if (lcl_is_set > 0 && __environ_changed == last_env_changed) + return; + + /* If environ did change, but $TZ wasn't changed since last time we + were called, we are all done here. */ + last_env_changed = __environ_changed; name = getenv("TZ"); + /* Use stricmp, since if TZ points to a file name, we need to be + case-insensitive. */ + if (lcl_is_set > 0 && (name == NULL || stricmp(name, lcl_tzstr) == 0)) + return; + + /* On to some *real* work... */ if (name == NULL) { tzsetwall(); return; } ! if (strlen(name) < sizeof(lcl_tzstr)) ! { ! lcl_is_set = 1; ! strcpy(lcl_tzstr, name); ! } ! else ! lcl_is_set = 0; #ifdef ALL_STATE if (lclptr == NULL) { *************** tzset(void) *** 947,953 **** void tzsetwall(void) { ! lcl_is_set = TRUE; #ifdef ALL_STATE if (lclptr == NULL) { --- 977,985 ---- void tzsetwall(void) { ! if (lcl_is_set == -1) ! return; ! lcl_is_set = -1; #ifdef ALL_STATE if (lclptr == NULL) { *************** localsub(const time_t * const timep, con *** 982,989 **** int i; const time_t t = *timep; ! if (!lcl_is_set) ! tzset(); sp = lclptr; #ifdef ALL_STATE if (sp == NULL) --- 1014,1020 ---- int i; const time_t t = *timep; ! tzset(); sp = lclptr; #ifdef ALL_STATE if (sp == NULL) *************** gmtime(const time_t * const timep) *** 1078,1083 **** --- 1109,1159 ---- return &tm; } + /* Return the year which is DAYS away from the year Y0. */ + static int + days_to_years(int y0, long *days) + { + int y, dir, yleap; + + y = y0; + dir = *days >= 0 ? 1 : -1; + + /* We move by 400, 100, and 4 years at a time, to quickly reduce + DAYS to a reasonable value. */ + while (*days*dir > n_year_lengths[2]) + { + y += dir*400; + *days -= dir*n_year_lengths[2]; + } + while (*days*dir > n_year_lengths[1]) + { + y += dir*100; + *days -= dir*n_year_lengths[1]; + } + while (*days*dir > n_year_lengths[0]) + { + y += dir*4; + *days -= dir*n_year_lengths[0]; + } + if (dir == 1) + for ( ; ; ) + { + yleap = isleap(y); + if (*days < (long) year_lengths[yleap]) + break; + ++y; + *days = *days - (long) year_lengths[yleap]; + } + else + do { + --y; + yleap = isleap(y); + *days = *days + (long) year_lengths[yleap]; + } while (*days < 0); + + return y; + } + static void timesub(const time_t * const timep, const long offset, const struct state * const sp, struct tm * const tmp) { *************** timesub(const time_t * const timep, cons *** 1147,1168 **** tmp->tm_wday = (int) ((EPOCH_WDAY + days) % DAYSPERWEEK); if (tmp->tm_wday < 0) tmp->tm_wday += DAYSPERWEEK; ! y = EPOCH_YEAR; ! if (days >= 0) ! for ( ; ; ) ! { ! yleap = isleap(y); ! if (days < (long) year_lengths[yleap]) ! break; ! ++y; ! days = days - (long) year_lengths[yleap]; ! } ! else ! do { ! --y; ! yleap = isleap(y); ! days = days + (long) year_lengths[yleap]; ! } while (days < 0); tmp->tm_year = y - TM_YEAR_BASE; tmp->tm_yday = (int) days; ip = mon_lengths[yleap]; --- 1223,1230 ---- tmp->tm_wday = (int) ((EPOCH_WDAY + days) % DAYSPERWEEK); if (tmp->tm_wday < 0) tmp->tm_wday += DAYSPERWEEK; ! y = days_to_years(EPOCH_YEAR, &days); ! yleap = isleap(y); tmp->tm_year = y - TM_YEAR_BASE; tmp->tm_yday = (int) days; ip = mon_lengths[yleap]; *************** tmcomp(const struct tm * const atmp, con *** 1251,1256 **** --- 1313,1358 ---- return result; } + static void + tmnormalize(struct tm *tmp) + { + if (tmp->tm_sec >= SECSPERMIN + 2 || tmp->tm_sec < 0) + normalize(&tmp->tm_min, &tmp->tm_sec, SECSPERMIN); + normalize(&tmp->tm_hour, &tmp->tm_min, MINSPERHOUR); + normalize(&tmp->tm_mday, &tmp->tm_hour, HOURSPERDAY); + normalize(&tmp->tm_year, &tmp->tm_mon, MONSPERYEAR); + + /* If tm_mday is negative, or positive and too large, bring it back + to the reasonable range [1..366]. */ + if (tmp->tm_mday <= 0 || tmp->tm_mday > DAYSPERLYEAR) + { + long days = tmp->tm_mday; + int yleap = isleap(tmp->tm_year + TM_YEAR_BASE); + + while (tmp->tm_mon--) + days += mon_lengths[yleap][tmp->tm_mon]; + tmp->tm_year = + days_to_years(tmp->tm_year + TM_YEAR_BASE, &days) - TM_YEAR_BASE; + tmp->tm_mday = days; + tmp->tm_mon = 0; + } + + /* Now correct tm_mon and tm_mday so that they are within their + normal ranges [0..11] and [1..mon_length[tm_mon]], respectively. */ + for ( ; ; ) + { + int i = mon_lengths[isleap(tmp->tm_year + TM_YEAR_BASE)][tmp->tm_mon]; + if (tmp->tm_mday <= i) + break; + tmp->tm_mday -= i; + if (++tmp->tm_mon >= MONSPERYEAR) + { + tmp->tm_mon = 0; + ++tmp->tm_year; + } + } + } + static time_t time2(struct tm *tmp, void (*const funcp)(const time_t *const,const long,struct tm *), const long offset, int * const okayp) { *************** time2(struct tm *tmp, void (*const funcp *** 1264,1294 **** struct tm yourtm, mytm; *okayp = FALSE; yourtm = *tmp; - if (yourtm.tm_sec >= SECSPERMIN + 2 || yourtm.tm_sec < 0) - normalize(&yourtm.tm_min, &yourtm.tm_sec, SECSPERMIN); - normalize(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR); - normalize(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY); - normalize(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR); - while (yourtm.tm_mday <= 0) - { - --yourtm.tm_year; - yourtm.tm_mday += - year_lengths[isleap(yourtm.tm_year + TM_YEAR_BASE)]; - } - for ( ; ; ) - { - i = mon_lengths[isleap(yourtm.tm_year + - TM_YEAR_BASE)][yourtm.tm_mon]; - if (yourtm.tm_mday <= i) - break; - yourtm.tm_mday -= i; - if (++yourtm.tm_mon >= MONSPERYEAR) - { - yourtm.tm_mon = 0; - ++yourtm.tm_year; - } - } saved_seconds = yourtm.tm_sec; yourtm.tm_sec = 0; /* --- 1366,1373 ---- struct tm yourtm, mytm; *okayp = FALSE; + tmnormalize(tmp); yourtm = *tmp; saved_seconds = yourtm.tm_sec; yourtm.tm_sec = 0; /* *************** time1(struct tm * const tmp, void (*cons *** 1412,1417 **** --- 1491,1497 ---- time_t mktime(struct tm * tmp) { + struct tm save_tm = *tmp; int rv = time1(tmp, localsub, 0L); if (rv == -1) { *************** mktime(struct tm * tmp) *** 1427,1432 **** --- 1507,1530 ---- tmp->tm_hour -= delta; if (rv != -1) rv -= delta*60*60; + else + { + /* tmp might point to a time structure that's before 1/1/1970. + This can happen if tmp is a result of a call to localtime(0) + issued in a timezone with a negative offset, like PST8. + Adding the timezone offset will bring the time structure into + the valid range. */ + delta = 24 - tmp->tm_hour; + tmp->tm_hour += delta; + rv = time1(tmp, localsub, 0L); + tmp->tm_hour -= delta; + if (rv != -1) + rv -= delta*60*60; + } + if (rv == -1) + *tmp = save_tm; + else + tmnormalize(tmp); } return rv; }