2004-10-27  Derek R. Price  <derek@ximbiot.com>
	[BZ #487] This change is imported from gnulib.
	* time/mktime.c (not_equal_tm) [DEBUG]: Remove redundant check.

2004-10-24  Paul Eggert  <eggert@cs.ucla.edu>

	[BZ #473]
	* time/tst-mktime.c (main): Don't assume that mktime fails
	when given time stamps before 1970.  It returns negative
	time_t values instead, for compatibility with BSD.

	* time/tst-mktime2.c: New file.
	* time/Makefile (tests): Add it.

	[BZ #473] Import from gnulib.  Revamp to avoid several problems near
	time_t extrema, and on hosts with 64-bit time_t and 32-bit int.
	This fixes Debian bug 177940.
	* time/mktime.c (TIME_T_MIDPOINT): New macro.
	(ydhms_diff): Renamed from ydhms_tm_diff, with a new signature,
	which avoids overflow problems on hosts with 64-bit time_t and
	32-bit int.  All callers changed.  Now an inline function.
	Verify at compile-time that long int is wide enough to avoid
	these overflow problems.
	(guess_time_tm): New function.
	(__mktime_internal): Use it.  Avoid overflow when computing yday on
	hosts with 64-bit long and 32-bit int.  Remove tests for 69;
	no longer needed.  Use if rather than #ifdef for LEAP_SECONDS_POSSIBLE
	so that the code is checked by more compilers.
	Do not rely on floating point to probe: stick to integer arithmetic,
	to avoid potential porting problems.
	Repair potential overflow correctly in the Southern Hemisphere.
	(localtime_offset): Add a FIXME for the case where time_t is unsigned.
This commit is contained in:
Roland McGrath 2004-11-01 00:21:39 +00:00
parent 27b1a5c235
commit e507cc5673
5 changed files with 391 additions and 129 deletions

View File

@ -1,3 +1,37 @@
2004-10-27 Derek R. Price <derek@ximbiot.com>
[BZ #487] This change is imported from gnulib.
* time/mktime.c (not_equal_tm) [DEBUG]: Remove redundant check.
2004-10-24 Paul Eggert <eggert@cs.ucla.edu>
[BZ #473]
* time/tst-mktime.c (main): Don't assume that mktime fails
when given time stamps before 1970. It returns negative
time_t values instead, for compatibility with BSD.
* time/tst-mktime2.c: New file.
* time/Makefile (tests): Add it.
[BZ #473] Import from gnulib. Revamp to avoid several problems near
time_t extrema, and on hosts with 64-bit time_t and 32-bit int.
This fixes Debian bug 177940.
* time/mktime.c (TIME_T_MIDPOINT): New macro.
(ydhms_diff): Renamed from ydhms_tm_diff, with a new signature,
which avoids overflow problems on hosts with 64-bit time_t and
32-bit int. All callers changed. Now an inline function.
Verify at compile-time that long int is wide enough to avoid
these overflow problems.
(guess_time_tm): New function.
(__mktime_internal): Use it. Avoid overflow when computing yday on
hosts with 64-bit long and 32-bit int. Remove tests for 69;
no longer needed. Use if rather than #ifdef for LEAP_SECONDS_POSSIBLE
so that the code is checked by more compilers.
Do not rely on floating point to probe: stick to integer arithmetic,
to avoid potential porting problems.
Repair potential overflow correctly in the Southern Hemisphere.
(localtime_offset): Add a FIXME for the case where time_t is unsigned.
2004-10-30 Andreas Schwab <schwab@suse.de> 2004-10-30 Andreas Schwab <schwab@suse.de>
* sysdeps/m68k/dl-machine.h (elf_machine_rela) * sysdeps/m68k/dl-machine.h (elf_machine_rela)

View File

@ -1,4 +1,4 @@
# Copyright (C) 1991-2002, 2003 Free Software Foundation, Inc. # Copyright (C) 1991-2002,2003,2004 Free Software Foundation, Inc.
# This file is part of the GNU C Library. # This file is part of the GNU C Library.
# The GNU C Library is free software; you can redistribute it and/or # The GNU C Library is free software; you can redistribute it and/or
@ -34,7 +34,7 @@ aux := era alt_digit lc-time-cleanup
distribute := datemsk distribute := datemsk
tests := test_time clocktest tst-posixtz tst-strptime tst_wcsftime \ tests := test_time clocktest tst-posixtz tst-strptime tst_wcsftime \
tst-getdate tst-mktime tst-ftime_l tst-strftime tst-getdate tst-mktime tst-mktime2 tst-ftime_l tst-strftime
include ../Rules include ../Rules

View File

@ -60,6 +60,7 @@
#ifndef TIME_T_MAX #ifndef TIME_T_MAX
# define TIME_T_MAX TYPE_MAXIMUM (time_t) # define TIME_T_MAX TYPE_MAXIMUM (time_t)
#endif #endif
#define TIME_T_MIDPOINT (((TIME_T_MIN + TIME_T_MAX) >> 1) + 1)
/* Verify a requirement at compile-time (unlike assert, which is runtime). */ /* Verify a requirement at compile-time (unlike assert, which is runtime). */
#define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } #define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; }
@ -111,42 +112,74 @@ const unsigned short int __mon_yday[2][13] =
# define __mktime_internal mktime_internal # define __mktime_internal mktime_internal
#endif #endif
/* Return an integer value measuring (YEAR1-YDAY1 HOUR1:MIN1:SEC1) -
(YEAR0-YDAY0 HOUR0:MIN0:SEC0) in seconds, assuming that the clocks
were not adjusted between the time stamps.
/* Yield the difference between (YEAR-YDAY HOUR:MIN:SEC) and (*TP), The YEAR values uses the same numbering as TP->tm_year. Values
measured in seconds, ignoring leap seconds. need not be in the usual range. However, YEAR1 must not be less
YEAR uses the same numbering as TM->tm_year. than 2 * INT_MIN or greater than 2 * INT_MAX.
All values are in range, except possibly YEAR.
If TP is null, return a nonzero value. The result may overflow. It is the caller's responsibility to
If overflow occurs, yield the low order bits of the correct answer. */ detect overflow. */
static time_t
ydhms_tm_diff (long int year, int yday, int hour, int min, int sec, static inline time_t
const struct tm *tp) ydhms_diff (long int year1, long int yday1, int hour1, int min1, int sec1,
int year0, int yday0, int hour0, int min0, int sec0)
{ {
if (!tp) verify (C99_integer_division, -1 / 2 == 0);
return 1; verify (long_int_year_and_yday_are_wide_enough,
else INT_MAX <= LONG_MAX / 2 || TIME_T_MAX <= UINT_MAX);
{
verify (C99_integer_division, -1 / 2 == 0);
/* Compute intervening leap days correctly even if year is negative. /* Compute intervening leap days correctly even if year is negative.
Take care to avoid int overflow. time_t overflow is OK, since Take care to avoid integer overflow here. */
only the low order bits of the correct time_t answer are needed. int a4 = (year1 >> 2) + (TM_YEAR_BASE >> 2) - ! (year1 & 3);
Don't convert to time_t until after all divisions are done, since int b4 = (year0 >> 2) + (TM_YEAR_BASE >> 2) - ! (year0 & 3);
time_t might be unsigned. */ int a100 = a4 / 25 - (a4 % 25 < 0);
int a4 = (year >> 2) + (TM_YEAR_BASE >> 2) - ! (year & 3); int b100 = b4 / 25 - (b4 % 25 < 0);
int b4 = (tp->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (tp->tm_year & 3); int a400 = a100 >> 2;
int a100 = a4 / 25 - (a4 % 25 < 0); int b400 = b100 >> 2;
int b100 = b4 / 25 - (b4 % 25 < 0); int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
int a400 = a100 >> 2;
int b400 = b100 >> 2; /* Compute the desired time in time_t precision. Overflow might
int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); occur here. */
time_t years = year - (time_t) tp->tm_year; time_t tyear1 = year1;
time_t days = (365 * years + intervening_leap_days time_t years = tyear1 - year0;
+ (yday - tp->tm_yday)); time_t days = 365 * years + yday1 - yday0 + intervening_leap_days;
return (60 * (60 * (24 * days + (hour - tp->tm_hour)) time_t hours = 24 * days + hour1 - hour0;
+ (min - tp->tm_min)) time_t minutes = 60 * hours + min1 - min0;
+ (sec - tp->tm_sec)); time_t seconds = 60 * minutes + sec1 - sec0;
return seconds;
}
/* Return a time_t value corresponding to (YEAR-YDAY HOUR:MIN:SEC),
assuming that *T corresponds to *TP and that no clock adjustments
occurred between *TP and the desired time.
If TP is null, return a value not equal to *T; this avoids false matches.
If overflow occurs, yield the minimal or maximal value, except do not
yield a value equal to *T. */
static time_t
guess_time_tm (long int year, long int yday, int hour, int min, int sec,
const time_t *t, const struct tm *tp)
{
if (tp)
{
time_t d = ydhms_diff (year, yday, hour, min, sec,
tp->tm_year, tp->tm_yday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
time_t t1 = *t + d;
if ((t1 < *t) == (TYPE_SIGNED (time_t) ? d < 0 : TIME_T_MAX / 2 < d))
return t1;
} }
/* Overflow occurred one way or another. Return the nearest result
that is actually in range, except don't report a zero difference
if the actual difference is nonzero, as that would cause a false
match. */
return (*t < TIME_T_MIDPOINT
? TIME_T_MIN + (*t == TIME_T_MIN)
: TIME_T_MAX - (*t == TIME_T_MAX));
} }
/* Use CONVERT to convert *T to a broken down time in *TP. /* Use CONVERT to convert *T to a broken down time in *TP.
@ -199,13 +232,14 @@ ranged_convert (struct tm *(*convert) (const time_t *, struct tm *),
the monotonic and mostly-unit-linear conversion function CONVERT. the monotonic and mostly-unit-linear conversion function CONVERT.
Use *OFFSET to keep track of a guess at the offset of the result, Use *OFFSET to keep track of a guess at the offset of the result,
compared to what the result would be for UTC without leap seconds. compared to what the result would be for UTC without leap seconds.
If *OFFSET's guess is correct, only one CONVERT call is needed. */ If *OFFSET's guess is correct, only one CONVERT call is needed.
This function is external because it is used also by timegm.c. */
time_t time_t
__mktime_internal (struct tm *tp, __mktime_internal (struct tm *tp,
struct tm *(*convert) (const time_t *, struct tm *), struct tm *(*convert) (const time_t *, struct tm *),
time_t *offset) time_t *offset)
{ {
time_t t, dt, t0, t1, t2; time_t t, gt, t0, t1, t2;
struct tm tm; struct tm tm;
/* The maximum number of probes (calls to CONVERT) should be enough /* The maximum number of probes (calls to CONVERT) should be enough
@ -241,38 +275,95 @@ __mktime_internal (struct tm *tp,
/* Calculate day of year from year, month, and day of month. /* Calculate day of year from year, month, and day of month.
The result need not be in range. */ The result need not be in range. */
int yday = ((__mon_yday[leapyear (year)] int mon_yday = ((__mon_yday[leapyear (year)]
[mon_remainder + 12 * negative_mon_remainder]) [mon_remainder + 12 * negative_mon_remainder])
+ mday - 1); - 1);
long int lmday = mday;
long int yday = mon_yday + lmday;
time_t guessed_offset = *offset;
int sec_requested = sec; int sec_requested = sec;
/* Only years after 1970 are defined. if (LEAP_SECONDS_POSSIBLE)
If year is 69, it might still be representable due to {
timezone differences. */ /* Handle out-of-range seconds specially,
if (year < 69) since ydhms_tm_diff assumes every minute has 60 seconds. */
return -1; if (sec < 0)
sec = 0;
if (59 < sec)
sec = 59;
}
#if LEAP_SECONDS_POSSIBLE /* Invert CONVERT by probing. First assume the same offset as last
/* Handle out-of-range seconds specially, time. */
since ydhms_tm_diff assumes every minute has 60 seconds. */
if (sec < 0)
sec = 0;
if (59 < sec)
sec = 59;
#endif
/* Invert CONVERT by probing. First assume the same offset as last time. t0 = ydhms_diff (year, yday, hour, min, sec,
Then repeatedly use the error to improve the guess. */ EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, - guessed_offset);
tm.tm_year = EPOCH_YEAR - TM_YEAR_BASE; if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3)
tm.tm_yday = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; {
t0 = ydhms_tm_diff (year, yday, hour, min, sec, &tm); /* time_t isn't large enough to rule out overflows, so check
for major overflows. A gross check suffices, since if t0
has overflowed, it is off by a multiple of TIME_T_MAX -
TIME_T_MIN + 1. So ignore any component of the difference
that is bounded by a small value. */
for (t = t1 = t2 = t0 + *offset, dst2 = 0; /* Approximate log base 2 of the number of time units per
(dt = ydhms_tm_diff (year, yday, hour, min, sec, biennium. A biennium is 2 years; use this unit instead of
ranged_convert (convert, &t, &tm))); years to avoid integer overflow. For example, 2 average
t1 = t2, t2 = t, t += dt, dst2 = tm.tm_isdst != 0) Gregorian years are 2 * 365.2425 * 24 * 60 * 60 seconds,
which is 63113904 seconds, and rint (log2 (63113904)) is
26. */
int ALOG2_SECONDS_PER_BIENNIUM = 26;
int ALOG2_MINUTES_PER_BIENNIUM = 20;
int ALOG2_HOURS_PER_BIENNIUM = 14;
int ALOG2_DAYS_PER_BIENNIUM = 10;
int LOG2_YEARS_PER_BIENNIUM = 1;
int approx_requested_biennia =
((year_requested >> LOG2_YEARS_PER_BIENNIUM)
- ((EPOCH_YEAR - TM_YEAR_BASE) >> LOG2_YEARS_PER_BIENNIUM)
+ (mday >> ALOG2_DAYS_PER_BIENNIUM)
+ (hour >> ALOG2_HOURS_PER_BIENNIUM)
+ (min >> ALOG2_MINUTES_PER_BIENNIUM)
+ (LEAP_SECONDS_POSSIBLE ? 0 : sec >> ALOG2_SECONDS_PER_BIENNIUM));
int approx_biennia = t0 >> ALOG2_SECONDS_PER_BIENNIUM;
int diff = approx_biennia - approx_requested_biennia;
int abs_diff = diff < 0 ? - diff : diff;
/* IRIX 4.0.5 cc miscaculates TIME_T_MIN / 3: it erroneously
gives a positive value of 715827882. Setting a variable
first then doing math on it seems to work.
(ghazi@caip.rutgers.edu) */
time_t time_t_max = TIME_T_MAX;
time_t time_t_min = TIME_T_MIN;
time_t overflow_threshold =
(time_t_max / 3 - time_t_min / 3) >> ALOG2_SECONDS_PER_BIENNIUM;
if (overflow_threshold < abs_diff)
{
/* Overflow occurred. Try repairing it; this might work if
the time zone offset is enough to undo the overflow. */
time_t repaired_t0 = -1 - t0;
approx_biennia = repaired_t0 >> ALOG2_SECONDS_PER_BIENNIUM;
diff = approx_biennia - approx_requested_biennia;
abs_diff = diff < 0 ? - diff : diff;
if (overflow_threshold < abs_diff)
return -1;
guessed_offset += repaired_t0 - t0;
t0 = repaired_t0;
}
}
/* Repeatedly use the error to improve the guess. */
for (t = t1 = t2 = t0, dst2 = 0;
(gt = guess_time_tm (year, yday, hour, min, sec, &t,
ranged_convert (convert, &t, &tm)),
t != gt);
t1 = t2, t2 = t, t = gt, dst2 = tm.tm_isdst != 0)
if (t == t1 && t != t2 if (t == t1 && t != t2
&& (tm.tm_isdst < 0 && (tm.tm_isdst < 0
|| (isdst < 0 || (isdst < 0
@ -280,91 +371,83 @@ __mktime_internal (struct tm *tp,
: (isdst != 0) != (tm.tm_isdst != 0)))) : (isdst != 0) != (tm.tm_isdst != 0))))
/* We can't possibly find a match, as we are oscillating /* We can't possibly find a match, as we are oscillating
between two values. The requested time probably falls between two values. The requested time probably falls
within a spring-forward gap of size DT. Follow the common within a spring-forward gap of size GT - T. Follow the common
practice in this case, which is to return a time that is DT practice in this case, which is to return a time that is GT - T
away from the requested time, preferring a time whose away from the requested time, preferring a time whose
tm_isdst differs from the requested value. (If no tm_isdst tm_isdst differs from the requested value. (If no tm_isdst
was requested and only one of the two values has a nonzero was requested and only one of the two values has a nonzero
tm_isdst, prefer that value.) In practice, this is more tm_isdst, prefer that value.) In practice, this is more
useful than returning -1. */ useful than returning -1. */
break; goto offset_found;
else if (--remaining_probes == 0) else if (--remaining_probes == 0)
return -1; return -1;
/* If we have a match, check whether tm.tm_isdst has the requested /* We have a match. Check whether tm.tm_isdst has the requested
value, if any. */ value, if any. */
if (dt == 0 && isdst != tm.tm_isdst && 0 <= isdst && 0 <= tm.tm_isdst) if (isdst != tm.tm_isdst && 0 <= isdst && 0 <= tm.tm_isdst)
{ {
/* tm.tm_isdst has the wrong value. Look for a neighboring /* tm.tm_isdst has the wrong value. Look for a neighboring
time with the right value, and use its UTC offset. time with the right value, and use its UTC offset.
Heuristic: probe the previous three calendar quarters (approximately),
looking for the desired isdst. This isn't perfect,
but it's good enough in practice. */
int quarter = 7889238; /* seconds per average 1/4 Gregorian year */
int i;
/* If we're too close to the time_t limit, look in future quarters. */ Heuristic: probe the adjacent timestamps in both directions,
if (t < TIME_T_MIN + 3 * quarter) looking for the desired isdst. This should work for all real
quarter = -quarter; time zone histories in the tz database. */
for (i = 1; i <= 3; i++) /* Distance between probes when looking for a DST boundary. In
{ tzdata2003a, the shortest period of DST is 601200 seconds
time_t ot = t - i * quarter; (e.g., America/Recife starting 2000-10-08 01:00), and the
struct tm otm; shortest period of non-DST surrounded by DST is 694800
ranged_convert (convert, &ot, &otm); seconds (Africa/Tunis starting 1943-04-17 01:00). Use the
if (otm.tm_isdst == isdst) minimum of these two values, so we don't miss these short
{ periods when probing. */
/* We found the desired tm_isdst. int stride = 601200;
Extrapolate back to the desired time. */
t = ot + ydhms_tm_diff (year, yday, hour, min, sec, &otm); /* The longest period of DST in tzdata2003a is 536454000 seconds
ranged_convert (convert, &t, &tm); (e.g., America/Jujuy starting 1946-10-01 01:00). The longest
break; period of non-DST is much longer, but it makes no real sense
} to search for more than a year of non-DST, so use the DST
} max. */
int duration_max = 536454000;
/* Search in both directions, so the maximum distance is half
the duration; add the stride to avoid off-by-1 problems. */
int delta_bound = duration_max / 2 + stride;
int delta, direction;
for (delta = stride; delta < delta_bound; delta += stride)
for (direction = -1; direction <= 1; direction += 2)
{
time_t ot = t + delta * direction;
if ((ot < t) == (direction < 0))
{
struct tm otm;
ranged_convert (convert, &ot, &otm);
if (otm.tm_isdst == isdst)
{
/* We found the desired tm_isdst.
Extrapolate back to the desired time. */
t = guess_time_tm (year, yday, hour, min, sec, &ot, &otm);
ranged_convert (convert, &t, &tm);
goto offset_found;
}
}
}
} }
*offset = t - t0; offset_found:
*offset = guessed_offset + t - t0;
#if LEAP_SECONDS_POSSIBLE if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec)
if (sec_requested != tm.tm_sec)
{ {
/* Adjust time to reflect the tm_sec requested, not the normalized value. /* Adjust time to reflect the tm_sec requested, not the normalized value.
Also, repair any damage from a false match due to a leap second. */ Also, repair any damage from a false match due to a leap second. */
t += sec_requested - sec + (sec == 0 && tm.tm_sec == 60); int sec_adjustment = (sec == 0 && tm.tm_sec == 60) - sec;
if (! (*convert) (&t, &tm)) t1 = t + sec_requested;
return -1; t2 = t1 + sec_adjustment;
} if (((t1 < t) != (sec_requested < 0))
#endif | ((t2 < t1) != (sec_adjustment < 0))
| ! (*convert) (&t, &tm))
if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3)
{
/* time_t isn't large enough to rule out overflows in ydhms_tm_diff,
so check for major overflows. A gross check suffices,
since if t has overflowed, it is off by a multiple of
TIME_T_MAX - TIME_T_MIN + 1. So ignore any component of
the difference that is bounded by a small value. */
double dyear = (double) year_requested + mon_years - tm.tm_year;
double dday = 366 * dyear + mday;
double dsec = 60 * (60 * (24 * dday + hour) + min) + sec_requested;
/* On Irix4.0.5 cc, dividing TIME_T_MIN by 3 does not produce
correct results, ie., it erroneously gives a positive value
of 715827882. Setting a variable first then doing math on it
seems to work. (ghazi@caip.rutgers.edu) */
const time_t time_t_max = TIME_T_MAX;
const time_t time_t_min = TIME_T_MIN;
if (time_t_max / 3 - time_t_min / 3 < (dsec < 0 ? - dsec : dsec))
return -1;
}
if (year == 69)
{
/* If year was 69, need to check whether the time was representable
or not. */
if (t < 0 || t > 2 * 24 * 60 * 60)
return -1; return -1;
} }
@ -373,6 +456,10 @@ __mktime_internal (struct tm *tp,
} }
/* FIXME: This should use a signed type wide enough to hold any UTC
offset in seconds. 'int' should be good enough for GNU code. We
can't fix this unilaterally though, as other modules invoke
__mktime_internal. */
static time_t localtime_offset; static time_t localtime_offset;
/* Convert *TP to a time_t value. */ /* Convert *TP to a time_t value. */
@ -409,7 +496,6 @@ not_equal_tm (const struct tm *a, const struct tm *b)
| (a->tm_mday ^ b->tm_mday) | (a->tm_mday ^ b->tm_mday)
| (a->tm_mon ^ b->tm_mon) | (a->tm_mon ^ b->tm_mon)
| (a->tm_year ^ b->tm_year) | (a->tm_year ^ b->tm_year)
| (a->tm_mday ^ b->tm_mday)
| (a->tm_yday ^ b->tm_yday) | (a->tm_yday ^ b->tm_yday)
| (a->tm_isdst ^ b->tm_isdst)); | (a->tm_isdst ^ b->tm_isdst));
} }

View File

@ -55,9 +55,11 @@ main (void)
setenv ("TZ", "CET-1", 1); setenv ("TZ", "CET-1", 1);
t = mktime (&time_str); t = mktime (&time_str);
if (t != (time_t) -1) #define EVENING69_CET (EVENING69 - (5 - -1) * 60 * 60)
if (t != EVENING69_CET)
{ {
printf ("mktime returned %ld, expected -1\n", (long) t); printf ("mktime returned %ld, expected %ld\n",
(long) t, (long) EVENING69_CET);
result = 1; result = 1;
} }
else else

140
time/tst-mktime2.c Normal file
View File

@ -0,0 +1,140 @@
/* Test program from Paul Eggert and Tony Leneis. */
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
static time_t time_t_max;
static time_t time_t_min;
/* Values we'll use to set the TZ environment variable. */
static const char *tz_strings[] =
{
(const char *) 0, "GMT0", "JST-9",
"EST+3EDT+2,M10.1.0/00:00:00,M2.3.0/00:00:00"
};
#define N_STRINGS ((int) (sizeof (tz_strings) / sizeof (tz_strings[0])))
/* Fail if mktime fails to convert a date in the spring-forward gap.
Based on a problem report from Andreas Jaeger. */
static void
spring_forward_gap (void)
{
/* glibc (up to about 1998-10-07) failed this test. */
struct tm tm;
/* Use the portable POSIX.1 specification "TZ=PST8PDT,M4.1.0,M10.5.0"
instead of "TZ=America/Vancouver" in order to detect the bug even
on systems that don't support the Olson extension, or don't have the
full zoneinfo tables installed. */
setenv ("TZ", "PST8PDT,M4.1.0,M10.5.0", 1);
tm.tm_year = 98;
tm.tm_mon = 3;
tm.tm_mday = 5;
tm.tm_hour = 2;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
if (mktime (&tm) == (time_t)-1)
exit (1);
}
static void
mktime_test1 (time_t now)
{
struct tm *lt = localtime (&now);
if (lt && mktime (lt) != now)
exit (2);
}
static void
mktime_test (time_t now)
{
mktime_test1 (now);
mktime_test1 ((time_t) (time_t_max - now));
mktime_test1 ((time_t) (time_t_min + now));
}
static void
irix_6_4_bug (void)
{
/* Based on code from Ariel Faigon. */
struct tm tm;
tm.tm_year = 96;
tm.tm_mon = 3;
tm.tm_mday = 0;
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
mktime (&tm);
if (tm.tm_mon != 2 || tm.tm_mday != 31)
exit (3);
}
static void
bigtime_test (int j)
{
struct tm tm;
time_t now;
tm.tm_year = tm.tm_mon = tm.tm_mday = tm.tm_hour = tm.tm_min = tm.tm_sec = j;
now = mktime (&tm);
if (now != (time_t) -1)
{
struct tm *lt = localtime (&now);
if (! (lt
&& lt->tm_year == tm.tm_year
&& lt->tm_mon == tm.tm_mon
&& lt->tm_mday == tm.tm_mday
&& lt->tm_hour == tm.tm_hour
&& lt->tm_min == tm.tm_min
&& lt->tm_sec == tm.tm_sec
&& lt->tm_yday == tm.tm_yday
&& lt->tm_wday == tm.tm_wday
&& ((lt->tm_isdst < 0 ? -1 : 0 < lt->tm_isdst)
== (tm.tm_isdst < 0 ? -1 : 0 < tm.tm_isdst))))
exit (4);
}
}
static int
do_test (void)
{
time_t t, delta;
int i, j;
setenv ("TZ", "America/Sao_Paulo", 1);
/* This test makes some buggy mktime implementations loop.
Give up after 60 seconds; a mktime slower than that
isn't worth using anyway. */
alarm (60);
for (time_t_max = 1; 0 < time_t_max; time_t_max *= 2)
continue;
time_t_max--;
if ((time_t) -1 < 0)
for (time_t_min = -1; (time_t) (time_t_min * 2) < 0; time_t_min *= 2)
continue;
delta = time_t_max / 997; /* a suitable prime number */
for (i = 0; i < N_STRINGS; i++)
{
if (tz_strings[i])
setenv ("TZ", tz_strings[i], 1);
for (t = 0; t <= time_t_max - delta; t += delta)
mktime_test (t);
mktime_test ((time_t) 1);
mktime_test ((time_t) (60 * 60));
mktime_test ((time_t) (60 * 60 * 24));
for (j = 1; 0 < j; j *= 2)
bigtime_test (j);
bigtime_test (j - 1);
}
irix_6_4_bug ();
spring_forward_gap ();
return 0;
}
#define TEST_FUNCTION do_test ()
#include "../test-skeleton.c"